c和指针
C和指針
第一章:
?? ?1,在邏輯上刪除代碼而不是注釋掉用:
?? ??? ?#if 0
?? ??? ??? ?statements
?? ??? ?#endif
?? ?2,#include和#define稱為預(yù)處理指令,由預(yù)處理器解釋,預(yù)處理器讀入源碼,根據(jù)預(yù)處理指令對其修改,然后將修改過的源代碼交給編譯器。
?? ??? ?列如:#include<stdio.h> ;預(yù)處理器用名叫stdio.h的庫函數(shù)頭文件的內(nèi)容替換#include指令語句,其結(jié)果是stdio.h的內(nèi)容被逐字寫到源文件的那個位置。
?? ?3,int func(int ,*)函數(shù)原型以一個類型名開頭,表示返回值的類型,跟在后面的是函數(shù)的名字,后面是參數(shù)。
?? ?4,在C語言中,數(shù)組參數(shù)是以引用形式進(jìn)行傳遞的,也就是傳址調(diào)用,而標(biāo)量和常量則是按值傳遞的。在函數(shù)中對標(biāo)量參數(shù)的任何修改都會在函數(shù)返回時丟失,因此被調(diào)用的
?? ??? ?函數(shù)無法修改調(diào)用函數(shù)以傳值形式傳遞給它的參數(shù)。當(dāng)被調(diào)用函數(shù)修改數(shù)組參數(shù)時,調(diào)用函數(shù)傳遞的數(shù)組會被修改。
?? ?5,所有傳遞給函數(shù)的參數(shù)都是按傳值傳遞的,但當(dāng)數(shù)組名作為參數(shù)時就會產(chǎn)生按引用傳遞的效果。
?? ?6,gets讀取一行,一行的輸入由一串字符組成,以換行符結(jié)尾。gets丟棄換行符在末尾存儲一個NULL(一個NULL字節(jié)是字節(jié)模式為全0的字節(jié),類似‘\0’,NULL是一個其值為0的指針)
?? ??? ?在c語言中,不存在string類型,但字符串就是以NULL結(jié)尾的字符。NULL是其終止符,本身不被看做字符串的一部分。
?? ??? ?字符串常量在源程序中被雙引號括起來的一串字符"hello",包含h e l l 0 NULL 在內(nèi)存中占6個字節(jié)的空間。
?? ?7,在scanf中,所有標(biāo)量都必須加上&,在輸入值前的空格、制表符、換行符等都被跳過。
?? ?8,puts函數(shù)時gets的輸出版本,它把指定的字符串寫到標(biāo)準(zhǔn)輸出并在末尾添加一個換行符。
?? ?9,int char=getchar() ;whil(char!=EOF&&CH!='\n');為什么ch被聲明為整型,而讀取的是字符,答案是EOF是一個整型值,它的位數(shù)比字符類型要多,聲明為整型可防止輸入讀取的字符意外被
?? ??? ?解釋為EOF.但同時意味著接收字符的ch必須足夠大,足以容納EOF.字符只是小整數(shù)數(shù)而已。putchar與其對應(yīng),接收一個整型參數(shù),并在標(biāo)準(zhǔn)輸出中打印該字符。
?? ?10,當(dāng)數(shù)組名作為參數(shù)傳遞時,實(shí)際傳遞的是一個指向數(shù)組起始位置的指針,即數(shù)組在內(nèi)存中的地址。
?? ?11,strcpy(dest,src):將原有字符覆蓋。strncpy(dest,src,nsize):將源中n個字符復(fù)制到dest;strcat(dest,src):將src追加到dest末尾。dest必須有足夠的空間,函數(shù)不對其進(jìn)行檢查。
?? ??? ?strstr(string,char||string):在字符串中搜索字符第一次出現(xiàn)的位置。
?? ?總結(jié):#include預(yù)處理指令可以使一個函數(shù)庫頭文件的內(nèi)容由編譯器進(jìn)行處理,#define指令允許你給字面值常量取個符號名。
第二章
?? ?1,在c的任何一種實(shí)現(xiàn)中,存在兩種不同的環(huán)境,第一種是翻譯環(huán)境,在這個環(huán)境里,源代碼被轉(zhuǎn)換為可執(zhí)行的機(jī)器指令。第二種是執(zhí)行環(huán)境,它用于實(shí)際代碼的執(zhí)行。?? ?
?? ?編譯的步驟:組成程序的每個源文件通過編譯過程分別轉(zhuǎn)換為目標(biāo)代碼(object code).然后,各個目標(biāo)文件由鏈接器捆綁在一起,形成一個單一而完整的可執(zhí)行程序。鏈接器同時也會引入
?? ?標(biāo)準(zhǔn)c函數(shù)庫中任何可被該程序用到的函數(shù),而且也可以搜索程序員個人的程序庫,將其中需要用的函數(shù)連接到程序中。
?? ?編譯過程本身也有幾個階段組成,首先是預(yù)處理器:預(yù)處理器在源代碼上執(zhí)行一些文本操作。列如,用實(shí)際值代替#define指令定義的符號以及讀入由#include指令包含的文件的內(nèi)容
?? ?然后源代碼經(jīng)過解析,判斷語句的意思。第二個階段是產(chǎn)生絕大多數(shù)錯誤和警告信息的地方。隨后產(chǎn)生目標(biāo)代碼。目標(biāo)代碼是機(jī)器指令的初步形式,用于程序的語句。如果在編譯程序的命令行中加入了
?? ?要求進(jìn)行優(yōu)化的選項(xiàng),優(yōu)化器會對目標(biāo)代碼進(jìn)一步處理,使他效率更高。
?? ?2,編譯并鏈接一個完全包含于一個源文件的c程序:cc program.c;這條命令產(chǎn)生一個a.out的可執(zhí)行程序。中間產(chǎn)生一個program.o的目標(biāo)文件,但它在鏈接過程完成后會被刪除。
?? ?編譯并鏈接幾個c源文件:cc main.c sort.c lookup.c 當(dāng)編譯的源文件超過一個時,目標(biāo)文件便不會被刪除,這就運(yùn)行你對程序修改后,只對改過的源文件重新編譯,如cc main.o lookup.o sort.c
?? ?3, 編譯單個c源文件,并產(chǎn)生一個目標(biāo)文件 cc -c program.c 產(chǎn)生一個目標(biāo)文件program.o ;編譯幾個c源文件,并為每個文件產(chǎn)生一個目標(biāo)文件:cc -c main.c sort.c lookup.c?
?? ?鏈接幾個目標(biāo)文件:cc main.o sort.o lookup.o ;可以加上-o name 這個選項(xiàng),它可以使鏈接器把可執(zhí)行程序保存在‘name’文件中,而不是a.out;如果在編譯時加上-lname標(biāo)志,鏈接器會在name的函數(shù)庫
?? ?中進(jìn)行查找,這個選項(xiàng)應(yīng)該出現(xiàn)在命令行的最后。
?? ?4,程序的執(zhí)行:首先載入到內(nèi)存中,在宿主環(huán)境中(操作系統(tǒng)環(huán)境),這個任務(wù)由操作系統(tǒng)完成,那些不是存儲在堆棧中的尚未初始化的變量在這個時候得到初始值。在獨(dú)立環(huán)境中需手工安排。
?? ?然后程序的執(zhí)行便開始。在宿主環(huán)境中,一個小型的啟動程序與程序鏈接在一起。它負(fù)責(zé)一系列日常事務(wù)如收集命令行參數(shù)以便使程序能夠訪問它們。接著調(diào)用main函數(shù)。
?? ?現(xiàn)在便開始執(zhí)行程序的代碼。在大多數(shù)機(jī)器里,程序?qū)⑹褂靡粋€運(yùn)行時堆棧,用于存儲函數(shù)的局部變量和返回地址。程序同時也可以使用靜態(tài)內(nèi)存,存儲于靜態(tài)內(nèi)存中的變量在程序的整個執(zhí)行過程中
?? ?將一直保留它們的值。程序的最后一個階段就是程序的終止。
?? ?5,c是一種自由形式的語言,不規(guī)定一行中,什么地方出現(xiàn)空白,唯一的規(guī)則是相鄰的標(biāo)記之間必須出現(xiàn)一致多個空白字符或注釋。
?? ?總結(jié):一個c程序的源代碼保存在一個或多個源文件中,但一個函數(shù)只能從出現(xiàn)在一個源文件中,把相關(guān)函數(shù)放在同一個文件內(nèi)是一種好策略。每個源文件都分別編譯,產(chǎn)生對應(yīng)的目標(biāo)文件
?? ?然后目標(biāo)文件被鏈接在一起,形成可執(zhí)行程序。編譯和最終運(yùn)行程序的機(jī)器有可能相同或不同。程序必須載入到內(nèi)存中才能執(zhí)行。在宿主環(huán)境中,這個任務(wù)由操作系統(tǒng)完成。在自由式環(huán)境中,程序常常永久
?? ?存儲于ROM中,經(jīng)過初始化的靜態(tài)bianlzai程序執(zhí)行前獲得他們的值。程序起點(diǎn)是main函數(shù),大多數(shù)環(huán)境使用堆棧來存儲局部變量和其他數(shù)據(jù)。
第三章
?? ?1,在C語言中僅有4中基本數(shù)據(jù)類型-整型、浮點(diǎn)型、指針、聚合類型(如數(shù)組和結(jié)構(gòu)等)。缺省的char是signed char 或unsigned char取決于編譯器,顯示聲明可能會出現(xiàn)兼容性問題
?? ?2,如果一個值被當(dāng)做字符使用,那么把這個值表示為字符常量可以使意思更清晰。value=value=‘0’;它用于表示把一個字符轉(zhuǎn)換為二進(jìn)制值,更重要的是不管采用何種字符集使用字符常量所產(chǎn)生的總是正確的值
?? ??? ?所以它能提高程序的可移植性。
?? ?3,在程序中使用字符串常量會生成一個“指向字符的常量指針”。當(dāng)一個字符串常量出現(xiàn)于一個表達(dá)式中時,表達(dá)式使用的值就是這些字符所存儲的地址,而不是字符本身。因此可以把字符串常量
?? ?賦值給一個指向字符的指針,后者指向這些字符所存儲的地址。但是你不能把字符串常量賦值給一個字符數(shù)組,因?yàn)樽址A康闹苯又凳且粋€指針,而不是這些字符本身。字符串的賦值、復(fù)制采用函數(shù)
?? ?4,c語言編譯器并不檢查程序?qū)?shù)組下標(biāo)的引用是否在數(shù)組的合法范圍之內(nèi)。
?? ?5,char *message=“hello”;看上去初始值是賦值給表達(dá)式*message;事實(shí)上是賦值給message本身。相當(dāng)于char *message; message=“hello”;因此聲明時*盡量靠近message;
?? ?6,typedef 機(jī)制允許為各種數(shù)據(jù)類型定義為新名字。
?? ?7,int const *pci;是一個指向整型常量的指針。可以修改指針的值,但不能修改它所指向的值。int *const cpi;pci為一個指向整型的常量指針。它的值無法修改,但可以修改它所指向的整型的值
?? ?8,名字常量非常有用,因?yàn)樗鼈兛梢越o數(shù)值起符號名,否則只能寫成字面值形式。用名字常量定義數(shù)組的長度或限制循環(huán)的計(jì)數(shù)器能夠提高程序的可維護(hù)性。如果一個值必須修改,只需修改聲明就好。
?? ?9,編譯器可以確認(rèn)4種不同類型的作用域:文件作用域(任何在所有代碼塊之外聲明的標(biāo)識符都具有文件作用域,從聲明到所在源文件結(jié)尾處都是可以訪問的。在文件中定義的函數(shù)名也具有文件作用域
?? ??? ?函數(shù)名本身不屬于任何代碼塊。在#include指令包含到其他文件中的聲明就好像是直接寫在那些文件中一樣,他們的作用域并不局限于頭文件的文件尾)
?? ??? ?、函數(shù)作用域、代碼塊作用域一對花括號之間的所有語句、原型作用域(只適用于函數(shù)原型聲明的參數(shù)名)。
?? ?10,當(dāng)組成一個程序的各個源文件分別被編譯之后,所有的目標(biāo)文件以及那些從一個或多個函數(shù)庫中引用的函數(shù)鏈接在一起,形成可執(zhí)行程序。如果相同的標(biāo)識符出現(xiàn)在幾個不同的源文件中時,
?? ?標(biāo)識符的鏈接屬性決定如何處理在不同文件中出現(xiàn)的標(biāo)識符。標(biāo)識符的作用域與它的鏈接屬性有關(guān),但這兩個屬性并不相同。鏈接屬性:external(外部)、internal(內(nèi)部)、none(無)
?? ?none:當(dāng)做單獨(dú)個體。internal:在同一個源文件中所有聲明都指向同一個實(shí)體,不同源文件的屬于不同實(shí)體。external鏈接屬性的標(biāo)識符不論聲明多少次,位于幾個源文件都標(biāo)識同一實(shí)體。
?? ?11,關(guān)鍵字extern和static用于在聲明中修改標(biāo)識符的鏈接屬性。如果正常情況下具有external鏈接屬性,在它前面加上static關(guān)鍵字則可一個變?yōu)閕nternal,;
?? ?缺省狀態(tài)下的代碼塊外參數(shù)聲明和函數(shù)定義為external;static int c(int b)可以防止被其他源文件引用。static只對缺省鏈接屬性為external的聲明才有改變鏈接屬性的效果。
?? ?12,變量的存儲類型是值存儲變量值的內(nèi)存類型。變量的存儲類型決定變量何時創(chuàng)建、銷毀以及它的值保持多久。有三個地方可以用于存儲變量:普通內(nèi)存、運(yùn)行時堆棧、硬件寄存器、在這三個地方
?? ?存儲的變量具有不同的特性。
?? ??? ?1,變量的缺省存儲類型取決于它的聲明位置。凡是在任何代碼塊之外聲明的變量總是存儲在靜態(tài)內(nèi)存中,也就是不屬于堆棧的內(nèi)存,稱為靜態(tài)變量。這類變量無法指定其它存儲類型。靜態(tài)變量在程序運(yùn)行
?? ?之前創(chuàng)建,在程序執(zhí)行期間始終存在。它始終保持原先的值,除非給它一個不同的值或程序結(jié)束。
?? ??? ?2,在代碼塊內(nèi)部聲明的變量的缺省存儲類型是自動的,存儲于堆棧中,稱為自動變量。有一個變量auto就是用于修飾這種存儲類型的。因缺省狀態(tài)下,是自動創(chuàng)建一般不用。在程序執(zhí)行到聲明自動變量
?? ??? ?的代碼塊時,自動變量才被創(chuàng)建,當(dāng)程序執(zhí)行流離開代碼塊時,自動銷毀。多次執(zhí)行則多次創(chuàng)建和銷毀,內(nèi)存位置可能不同。
?? ??? ?3,在代碼塊內(nèi)部聲明的變量如果加上static,則存儲類型從自動變?yōu)殪o態(tài)。具有靜態(tài)存儲類型的變量在整個程序執(zhí)行過程中一直存在。注意,修改變量的存儲類型并不表示修改變量的作用域,它仍然
?? ??? ?只能在代碼塊內(nèi)部按名字訪問。函數(shù)的形式參數(shù)不能為靜態(tài),因?yàn)閷?shí)參總是在堆棧中傳遞給函數(shù)用于支持遞歸。
?? ??? ?4,關(guān)鍵字register可用于自動變量的聲明,提示他們應(yīng)該存儲于機(jī)器的硬件寄存器而不是內(nèi)存中。這類變量稱為寄存器變量。通常寄存器變量比存儲于內(nèi)存的變量訪問效率更高。但編譯器一般只選
?? ??? ?前幾個存儲于寄存器中,其余的按普通自動變量處理。在典型情況下,把頻率最高的變量聲明為寄存器變量,在某些計(jì)算機(jī)中,把頻繁執(zhí)行間接訪問操作的指針聲明為寄存器,以提高效率。
?? ??? ?把函數(shù)的形參聲明為寄存器變量,編譯會在函數(shù)的起始位置生成指令,把值從堆棧復(fù)制到寄存器中,問題是這種優(yōu)化措施節(jié)省的時間和空間開銷抵不上復(fù)制這幾個值所用的開銷。
?? ??? ?寄存器變量的創(chuàng)建和銷毀時間和自動變量相同,但需要額外的工作,在使用寄存器變量的函數(shù)返回前,寄存器先前存儲的值恢復(fù)。當(dāng)函數(shù)執(zhí)行時,它需要把所用的所有寄存器的內(nèi)容保存到
?? ??? ?堆棧中,函數(shù)返回時,這些值再復(fù)制回寄存器。由于寄存器的保存和恢復(fù),某個特定的寄存器在不同時刻保存的值不同,所以機(jī)器并提供寄存器變量的地址。
?? ?13.自動變量和靜態(tài)變量的初始化存在一個重要差別:靜態(tài)變量的初始化,可以把程序需要初始化的值放在變量將會使用的位置,不需要額外的時間和指令,變量將會得到正確的值,如果不顯示
?? ?指定初始值,則默認(rèn)為0;
?? ?14,自動變量的初始化需要更多開銷,因?yàn)槌绦蜴溄訒r還無法判斷自動變量的存儲位置。事實(shí)上,函數(shù)的局部變量在函數(shù)的每次調(diào)用中可能占據(jù)不同的位置。因此自動變量沒有缺省的初始值
?? ??? ?。在聲明變量的同時進(jìn)行初始化和先聲明后賦值只有風(fēng)格之差,并無效率之別。
?? ?15,當(dāng)staitc用于函數(shù)定義時或代碼塊外的變量時,用于修改標(biāo)識符的連接屬性從external改為internal,但標(biāo)識符的存儲類型和作用域不受影響。用這種方式聲明的函數(shù)或變量只能在聲明它們的
?? ?源文件中訪問。當(dāng)用于代碼塊內(nèi)部的變量聲明時,static關(guān)鍵字用于修改變量的存儲類型,從自動變量修改為靜態(tài)變量,但變量的鏈接屬性和作用域不受影響。此聲明方式在程序執(zhí)行前創(chuàng)建,一直存在
?? ?直到執(zhí)行完畢后銷毀。
?? ?16,代碼塊外的變量和函數(shù)聲明默認(rèn)為external鏈接屬性及存儲類型為靜態(tài),并不存儲與堆棧中,因此在程序執(zhí)行前創(chuàng)建,一直保存其值直到程序結(jié)束。加static可改變鏈接屬性為internal。同名的局部變量hui
?? ?隱藏同名的靜態(tài)變量。
?? ?17,局部變量不具有鏈接屬性,是自動存儲類型即存儲于堆棧中。寄存器類型的變量初始值是垃圾。對于函數(shù)而言,存儲類型不是問題,因?yàn)榇a總是存儲于靜態(tài)內(nèi)存中。
?? ?局部變量加external全局變量可以在整個代碼塊內(nèi)訪問,具有external鏈接屬性,存儲于靜態(tài)內(nèi)存中
?? ?總結(jié):
?? ??? ?具有externa鏈接屬性的實(shí)體在其它語言里稱為全局(global)實(shí)體,所有源文件的所有函數(shù)可以訪問它。只要變量不是聲明在代碼塊或函數(shù)內(nèi)部,默認(rèn)鏈接屬性為external,在內(nèi)部加external可變?yōu)?br /> ?? ??? ?全局變量。具有external鏈接屬性的實(shí)體總是具有靜態(tài)存儲類型。全局變量在程序執(zhí)行前創(chuàng)建,在執(zhí)行過程中始終存在。函數(shù)的局部變量在函數(shù)開始執(zhí)行時創(chuàng)建,在函數(shù)執(zhí)行完畢后銷毀,但用于
?? ??? ?執(zhí)行函數(shù)的機(jī)器指令在程序的整個生命周期內(nèi)一直存在。局部變量由函數(shù)內(nèi)部使用,不被其它函數(shù)引用,默認(rèn)存儲類型為自動存儲即堆棧。原因:需要時才分配存儲,減少內(nèi)存的需求量;在堆棧
?? ??? ?上分配存儲可以有效實(shí)現(xiàn)遞歸。如果變量值多次調(diào)用可以修改它的存儲類型,把自動變量改為靜態(tài)存儲。
第四章 語句
?? ?1,c不存在專門的賦值語句,加分號就可把表達(dá)式轉(zhuǎn)換為語句。表達(dá)式語句只有加了賦值操作才會將結(jié)果值存儲到堆棧中。
?? ?2,c的if、while語句與其它語言的差別在于,c不具備布爾類型,而是用整型來代替即采用關(guān)系操作。
?? ?總結(jié):c并不具備任何輸入/輸出語句;I/O是通過調(diào)用庫函數(shù)實(shí)現(xiàn)的。C也不具備任何異常處理語句,也是通過調(diào)用庫函數(shù)來完成的。在每個switch語句都使用degfault語句。
第五章 操作符合表達(dá)式
?? ?1,位的操作:value=value|1<<bit_number;把指定的位置設(shè)置為1;value=value&~(1<<bit_number);把指定位清0;value&(1<<bit_number);如果指定位已經(jīng)被設(shè)置為1,則表達(dá)式結(jié)果為非0值;
?? ?2,a=x=y+3;如果x是一個字符型變量,則y+3的值就會被截去一段,以容納于字符類型的變量中
?? ?3,*操作符是間接訪問操作符,它與指針一起使用,用于訪問指針?biāo)赶虻闹怠?br /> ?? ?4,前綴和后綴形式的增值操作符都復(fù)制一份變量值的拷貝,用于周圍表達(dá)式的值正是這份拷貝,前綴在復(fù)制前增加值,后綴是在復(fù)制后增加值。
?? ?5,c用整數(shù)來表示布爾型值
?? ?6,左值就是那些能夠出現(xiàn)在賦值符號左邊的東西,需要存儲到特定位置,因此需要標(biāo)識特定位置的地址。右值就是那些出現(xiàn)在賦值符號右邊的東西。字面值常量都不是左值
第六章
?? ?1,在要求邊界對齊的機(jī)器上,整型值存儲的起始位置只能是某些特定的字節(jié)通常是4或2的整數(shù)倍。內(nèi)存中的每個位置由一個獨(dú)一無二的地址標(biāo)識,每個位置包含一個值。每個內(nèi)存地址存儲一個值
?? ?但記住地址太笨拙,所以高級語言通過名字而不是地址來訪問內(nèi)存的位置。名字與內(nèi)存位置之間的關(guān)聯(lián)是由編譯器為我們實(shí)現(xiàn)的。但硬件仍然通過地址訪問內(nèi)存位置。
?? ?2,不能簡單的通過檢查一個值的位來判斷它的類型,為了判斷值的類型必須觀察它的使用方式看是整數(shù)型算術(shù)指令還是浮點(diǎn)型指令。
?? ?3,指針的初始化是用&操作符來完成的,它用于產(chǎn)生操作數(shù)的內(nèi)存地址。
?? ?4,指針變量和其他變量并無區(qū)別,如果是靜態(tài)則初始化為0,如果是自動的則不會初始化,無論哪種情況,聲明一個指向整型的指針都不會創(chuàng)建用于存儲整型值的內(nèi)存空間。
?? ?5,指針變量可以作為左值,并不因?yàn)樗鼈兪侵羔樁且驗(yàn)槭亲兞俊χ羔樧兞窟M(jìn)行間接訪問表示我們應(yīng)該訪問指針?biāo)赶虻奈恢?#xff0c;間接訪問指定一個特定的內(nèi)存位置。
?? ?6,*&a=25:首先&操作符產(chǎn)生變量a的地址,它是一個指針常量(注意使用這個指針常量并不需要知道它的實(shí)際值)。接著,*操作符訪問其操作數(shù)所表示的地址。在這個表達(dá)式中,操作數(shù)是a的地址,所以
?? ?值25就存儲于a中。
?? ?7,int a=12;int *b=&a;c=&b;c的類型是一個指針,b是一個指向整型的指針,c是一個指針的指針。
?? ?int a=12;int *b=&a;int **c=&b;*操作符具有從右向左的結(jié)合性,因此*(*c):從里向外,*c訪問c所指向的位置,指向變量b;第二個間接操作符訪問這個位置指向的地址即變量a;
?? ?8,char *cp=a;*cp+1;*操作符優(yōu)先級高于+,所以執(zhí)行間接訪問操作,得到他的值即a;然后取得值的一份拷貝并+1;得到字符b;
?? ?9.指針加法運(yùn)算的結(jié)果是個右值,它位置并未清晰定義,如果沒有間接訪問操作,則表達(dá)式cp+1不是一個合法左值,
?? ?然而間接訪問跟隨指針訪問一個特定位置,*(cp+1)就可以作為左值使用,cp指向的位置就后移一位。
?? ?10,++cp;表達(dá)式的結(jié)果是增值后的指針的一份拷貝,因?yàn)榍熬Y++先增加它的操作數(shù)的值再返回這個結(jié)果。這個拷貝存儲位置并未清晰定義,因此不是一個合法的左值。
?? ?cp++后綴同樣增加cp的值,先返回cp值的一份拷貝,再增加cp的值。因此++cp和cp++都是非法的。如果增加間接訪問操作符*++cp,這樣間接訪問操作符作用于增值后的指針拷貝上,所以它的右值是
?? ?后面那個內(nèi)存地址的值,而左值就是那個位置本身。
?? ?//給定一個指向以NULL結(jié)尾的指針列表的指針,在列表中的字符串中查找一個特定的字符
?? ?11,int find_char(char **strings,char value)
?? ??? ?{
?? ??? ??? ?char *string;//當(dāng)前正在查找的字符串
?? ??? ??? ?//遍歷列表中的每個字符串
?? ??? ??? ?while((string=*strings++)!=NULL){
?? ??? ??? ??? ?//觀察字符串中的每個字符
?? ??? ??? ??? ?while(*string!='\0'){
?? ??? ??? ??? ??? ?if(*string++==value)
?? ??? ??? ??? ??? ??? ?return true;
?? ??? ??? ??? ?}
?? ??? ??? ?}
?? ??? ??? ?return false;
?? ??? ?}
?? ??? ?//這個函數(shù)將破壞這些指針,只適用于這組字符串使用一次的情況
?? ?12,int find_char(char **strings,char value)
?? ??? ?{
?? ??? ??? ?//char *string;//當(dāng)前正在查找的字符串
?? ??? ??? ?//遍歷列表中的每個字符串
?? ??? ??? ?while(*strings!=NULL){
?? ??? ??? ??? ?//觀察字符串中的每個字符
?? ??? ??? ??? ?while(**strings!='\0'){//**strings:第一個間接訪問操作訪問指針數(shù)組中的當(dāng)前指針,第二個間接訪問操作隨該指針訪問字符串中的當(dāng)前字符。
?? ??? ??? ??? ??? ?if(*(*strings)++==value)//*(*strings)++:括號是需要的,第一個間接訪問操作訪問列表中的當(dāng)前指針,增值操作把該指針?biāo)赶虻哪莻€位置的值+1,
?? ??? ??? ??? ??? ??? ?但第二個間接訪問操作作用于原先那個值的拷貝上,
?? ??? ??? ??? ??? ??? ?return true;
?? ??? ??? ??? ?}
?? ??? ??? ??? ?strings++//遍歷數(shù)組的下一行
?? ??? ??? ?}
?? ??? ??? ?return false;
?? ??? ?}
?? ?13,字符指針+1,運(yùn)算結(jié)果產(chǎn)生的指針指向內(nèi)存中的下一個字符。當(dāng)一個指針和一個整數(shù)量執(zhí)行算術(shù)運(yùn)算時,整數(shù)在執(zhí)行加法運(yùn)算前始終會根據(jù)合適的大小進(jìn)行調(diào)整。
?? ??? ?float *p+3;這個三將根據(jù)float類型的大小進(jìn)行調(diào)整(相乘),實(shí)際加到指針上的整型值為12;結(jié)果是增加了三個float的大小,而不是三個字節(jié)。
?? ?14,數(shù)組中的元素存儲于連續(xù)內(nèi)存位置中,后面元素的地址大于前面元素的地址。因此指針+1指向數(shù)組中下一個元素。&vlause[max_num]:表示數(shù)組最后一個元素后面那個內(nèi)存位置的地址。
?? ?15,兩個指針指向同一個數(shù)組的元素時才允許相減,得到的結(jié)果是兩個指針在內(nèi)存中的距離(數(shù)組元素長度為單位而不是字節(jié));p1指向a[i],p2指向a[j];p2-p1=j-i;
?? ??? ?總結(jié):指針指向的是內(nèi)存位置即地址,必須用間接訪問來獲得所指向位置存儲的值。聲明一個指針變量不會自動分配內(nèi)存,因此在間接訪問前必須初始化或指向現(xiàn)有內(nèi)存或分配動態(tài)內(nèi)存。
第七章
?? ?1,int *find_int(int key,int arr[],int len); //最后的分號區(qū)分了函數(shù)原型和函數(shù)定義的起始部分。原型告訴編譯器函數(shù)的參數(shù)數(shù)量和參數(shù)類型及返回值類型。
?? ?2,在頭文件中包含函數(shù)原型,原型具有文件作用域,作用于整個源文件,函數(shù)原型只寫一次,避免出現(xiàn)不匹配現(xiàn)象;
?? ?3,int *func(void),void表示沒有任何參數(shù);
?? ?4,c的函數(shù)所有參數(shù)都是傳值調(diào)用,對數(shù)組是傳址調(diào)用。
第八章 數(shù)組
?? ?1,數(shù)組是具有確定數(shù)量的常量值,而指針只是一個標(biāo)量。
?? ?2,只有在兩種場合下數(shù)組名不用指針常量來表示,當(dāng)數(shù)組名作為sizeof操作符或單目操作符&操作數(shù)時。
?? ?3,&a[0]是一個指向數(shù)組第一個元素的指針,即數(shù)組名本身的值,等價于char *c=a;
?? ?4,下標(biāo)引用:*(b+3):指向b指針后移3個的位置。
?? ?5,a[subscript]等價于*(a+subscript);
?? ?6,當(dāng)根據(jù)某個固定數(shù)目的增量在一個數(shù)組中移動時,使用指針變量將比使用下標(biāo)產(chǎn)生效率更高的代碼,聲明為寄存器變量的指針通常比靜態(tài)內(nèi)存和堆棧中的指針效率更高。
?? ??? ?那些必須在運(yùn)行時求值的表達(dá)式如&a[size]\a+size;的常量表達(dá)式往往代價更高。
?? ?7,指針和數(shù)組并不是相等的:聲明一個數(shù)組時,編譯器根據(jù)聲明所指定的數(shù)量為數(shù)組保留內(nèi)存空間,在創(chuàng)建數(shù)組名時,它的值是一個常量,指向這段內(nèi)存空間的起始位置。聲明一個指針變量時,
?? ?編譯器只為指針本身保留內(nèi)存空間,并不為任何整型值分配內(nèi)存空間。指針變量并未被初始化為指向任何現(xiàn)有的內(nèi)存空間。
?? ?8,當(dāng)一個數(shù)組名作為參數(shù)傳遞給一個函數(shù)時,是一份該指針的拷貝,函數(shù)如果執(zhí)行了下標(biāo)引用,實(shí)際是間接訪問。
?? ?9,形參被聲明Wie一個指向const字符的指針的好處:文檔習(xí)慣,編譯器可以捕捉到任何試圖修改數(shù)據(jù)的意外錯誤,這類聲明允許向函數(shù)傳遞const參數(shù)。
?? ?10,函數(shù)的參數(shù)和局部變量被聲明為register變量,產(chǎn)生的代碼比靜態(tài)內(nèi)存中的變量和堆棧中的變量所產(chǎn)生的代碼執(zhí)行速度更快。
?? ?11,函數(shù)原型中一維數(shù)組形參無需寫明它的元素?cái)?shù)目,因?yàn)楹瘮?shù)并不為數(shù)組參數(shù)分配內(nèi)存空間。形參只是一個指針,它指向的是已經(jīng)在其它地方分配好的內(nèi)存空間。
?? ?12,數(shù)組的初始化類似于標(biāo)量變量的初始化方式,取決于他們的存儲類型,存儲于靜態(tài)內(nèi)存的數(shù)組只初始化一次,即在程序開始執(zhí)行之前。執(zhí)行時,不需要把他們放在合適的位置,
?? ?由鏈接器將它們連接在一起。自動變量位于運(yùn)行時堆棧中,執(zhí)行流每次進(jìn)入它們所在的代碼塊時,變量每次所處的內(nèi)存位置并不相同,在程序開始前,編譯器沒有辦法對這些位置進(jìn)行初始化。
?? ?13,快速初始化字符數(shù)組的方法:char message[]="hello"
?? ?14,多維數(shù)組:int[3][10]:可以看做一個一維數(shù)組,包含三個元素,只是每個元素恰好包含10個整型元素的數(shù)組。數(shù)組名的值是一個指向第一個元素的指針,所以a是一個包含10個整型元素的數(shù)組的指針。
?? ?15,*(a+1)+5:是二維數(shù)組先從*(a+1)第一行移到第二行,*(a+1)+5是定位到第二行第五位置,*(*(a+1)+5):是間接訪問該位置的值。
?? ?16,指向數(shù)組的指針:int a[10],*vp=a;合法;
?? ??? ?int matrx[3][10],*mp=matrix;非法。正確的創(chuàng)建了matrix數(shù)組,并把mp聲明為一個指向整型的指針,但mp的初始化不正確,因?yàn)閙atrix并不是一個指向整型的指針,而是一個指向數(shù)組的指針。
?? ??? ?int (*p)[10]:指向數(shù)組的指針。*p是一個指針,所以指向某種類型的數(shù)組。其初始化為:int (*p)[10]=matrix;下標(biāo)不能省略,指向matrxi的第一行。
?? ?17,函數(shù)參數(shù)的多維數(shù)組名的傳遞方式和一維數(shù)組名相同,實(shí)際傳遞的是指向數(shù)組第一個元素的指針,區(qū)別在于多維數(shù)組的每個元素本身是另外一個數(shù)組,編譯器需要知道它的維數(shù)。
?? ?18,在多維數(shù)組中,只有第1維才能根據(jù)初始化列表缺省的提供,剩余幾個維必須顯示的寫出。
?? ?19,指針數(shù)組:int *api[10],下標(biāo)引用優(yōu)先級高于間接訪問;因此api是某種類型的數(shù)組,在取得一個數(shù)組元素后進(jìn)行間接訪問。
?? ?20,sizeof(a):計(jì)算結(jié)果是整個數(shù)組所占用的字節(jié)數(shù),而sizeof(a[0])是每個元素占用的字節(jié)數(shù),二者相除得到數(shù)組元素的個數(shù)。?? ?
?? ?21,在訪問多維數(shù)組的元素時,誤用逗號分隔下標(biāo);在一個指向未指定長度的數(shù)組的指針上執(zhí)行運(yùn)算。
第九章
?? ?1,c沒有顯示的字符串?dāng)?shù)據(jù)類型,字符串以字符常量的形式出現(xiàn)或存儲于字符數(shù)組中,字符串常量適用于那些程序不會對它們進(jìn)行修改的字符串;所有其他字符串都必須存儲于字符數(shù)組或動態(tài)內(nèi)存中。
?? ?2,字符串以null結(jié)尾,但字符串的長度并不包含null字節(jié)。
?? ?3,*strcpy(char *dst,char const *src):把參數(shù)src字符串復(fù)制到dst參數(shù)。如果參數(shù)src和dst在內(nèi)存中出現(xiàn)重疊,其結(jié)果是未定義。由于dst參數(shù)將進(jìn)行修改,所以它必須是字符數(shù)組或者是一個指向
?? ?動態(tài)內(nèi)存分配的數(shù)組指針,不能使用字符串常量。
?? ?4,程序員必須保證目標(biāo)字符數(shù)組的空間足以容納需要復(fù)制的字符串,如果字符串比數(shù)組長,多余的字符仍被復(fù)制,它將覆蓋原先存儲于數(shù)組后面的內(nèi)存空間值。strcpy無法解決這個問題。
?? ?5,strcat與strcpy類似,必須保證目標(biāo)字符數(shù)組剩余的空間足以保存整個源字符串。
?? ?6,strcat與strcpy返回一個指向目標(biāo)字符數(shù)組的指針。
?? ?7,strnlen,strncpy,strncat:將src字符串中的n個字符復(fù)制到dst,它的結(jié)果不會以null字節(jié)結(jié)尾。
?? ?8,strchr(char const *str,int ch);返回字符第一次出現(xiàn)的位置的指針;strrchr(char const *str,int ch),返回字符最后一次出現(xiàn)位置的指針;注意第二個參數(shù)是一個整數(shù)。
?? ?9,char *strpbrk(char const *char,char const *group):函數(shù)返回一個str中第一個匹配group中任何一個字符的字符位置。
?? ?10,char *str(char const *s1,char const *s2):查找整個s2第一次出現(xiàn)的起始位置返回一個指向該位置的指針。
?? ?11,*memcpy(void *dst,void const *src,size_t length);*memmove(void *dst,void const *src,size_t length);*memcmp(void *dst,void const *src,size_t length);
?? ?*memchr(void *dst,int ch,size_t length);*memset(void *dst,int ch,size_t length);任何類型的指針都可以轉(zhuǎn)換為void*;length為src數(shù)組中總的字節(jié)數(shù)
?? ??? ?*memcpy(void *dst,void const *src,size_t length);如果src和dst以任何形式出現(xiàn)重疊,則結(jié)果未定義;
?? ??? ?*memmove(void *dst,void const *src,size_t length);源和目標(biāo)可以重疊;
?? ?總結(jié):strcpy:把一個字符串復(fù)制到一個較短的數(shù)組中導(dǎo)致溢出;strcat函數(shù)把一個字符串添加到一個數(shù)組中,導(dǎo)致數(shù)組溢出;?? ?
第十章
?? ?1,和數(shù)組名不同,當(dāng)一個結(jié)構(gòu)變量在表達(dá)式中使用時,它并不被替換成一個指針。
?? ?2,typedeg struct {int a;char b;float c;}SIMPLE;SIMPLE是一個類型名而不是結(jié)構(gòu)標(biāo)簽;
?? ?3,void func(struct COMPLEX *cp);(*cp).f訪問結(jié)構(gòu)的成員f;對指針執(zhí)行間接訪問將訪問的結(jié)構(gòu),然后點(diǎn)操作符訪問一個成員;等價于->f;
?? ?4,結(jié)構(gòu)體的自引用;struct SELF_REF1{int a;struct SELF_REF1 *b;int c; };
?? ?在結(jié)構(gòu)體中,b是一個指針,而不是一個結(jié)構(gòu),編譯器在結(jié)構(gòu)的長度確定之前就知道指針的長度,這種類型的自引用是合法的。b事實(shí)上所指向的是同一種類型的不同結(jié)構(gòu)。更加高級的數(shù)據(jù)結(jié)構(gòu)
?? ?如鏈表和樹都用這種技巧實(shí)現(xiàn),每個結(jié)構(gòu)指向鏈表的下一個元素或樹的下一個分支;
?? ?5,struct {int a;struct SELF_REF1 *b;int c; }SELF_REF1;truct SELF_REF1 *b未定義,類型名直到聲明的末尾才定義,所以在結(jié)構(gòu)聲明的內(nèi)部尚未定義。
?? ?6,不完整的聲明:一個結(jié)構(gòu)體包含了另一個結(jié)構(gòu)體的一個或多個成員,和自引用結(jié)構(gòu)一樣,至少有一個結(jié)構(gòu)必須在另一個結(jié)構(gòu)內(nèi)部以指針的形式存在。
?? ?如:struct B; struct A{ struct B *partner}; struct B{struct A *partner;};在A的成員列表中需要標(biāo)簽B的不完整聲明。一旦A被聲明之后,B的成員列表也可以被聲明;
?? ?7,間接訪問操作隨箭頭訪問結(jié)構(gòu),結(jié)果是整個結(jié)構(gòu)。
?? ?8,結(jié)構(gòu)的存儲分配:struct ALIGN{char a;int b;char c;};//系統(tǒng)禁止編譯器在一個結(jié)構(gòu)的起始位置跳過幾個字節(jié)來滿足邊界對齊要求,因此所有結(jié)構(gòu)的起始存儲位置必須是結(jié)構(gòu)中邊界要求最嚴(yán)格的
?? ?數(shù)據(jù)類型所要求的位置。因此,成員a必須存儲于一個能夠被4整除的地址。
?? ?9,位段的聲明和結(jié)構(gòu)類似,但它的成員是一個或多個位的字段,這些不同長度的字段實(shí)際上存儲于一個或多個整型變量中,因此必須為int,signed int 或unsigned int 類型,其次成員名的后面
?? ?是一個冒號和一個整數(shù),這個整數(shù)指定該位段所占用的位的數(shù)目;任何可以用位段實(shí)現(xiàn)的任務(wù)都可以使用移位和屏蔽來實(shí)現(xiàn)。位段是不可移植的。
?? ?10,聯(lián)合union的所有成員引用的是內(nèi)存中相同的位置。在一個成員不同的聯(lián)合里,分配給聯(lián)合的內(nèi)存數(shù)量取決于它的最長成員的長度。為了避免內(nèi)存浪費(fèi),在聯(lián)合中存儲指向不同成員的指針而不是
?? ?直接存儲成員本身。所有指針的長度都是相同的,解決了內(nèi)存浪費(fèi)的問題。當(dāng)它決定需要使用哪個成員時,就分配正確的數(shù)量的內(nèi)存來存儲它。
?? ?11,聯(lián)合變量可以被初始化,但初始值必須是第一個成員的類型。
第十一章
?? ?1,數(shù)組的元素存儲于內(nèi)存中連續(xù)的位置上,當(dāng)一個數(shù)組被聲明時,它所需的內(nèi)存在編譯時就被分配,可以用動態(tài)內(nèi)存分配在運(yùn)行時為它分配內(nèi)存。
?? ?2,malloc從內(nèi)存池中提取一塊合適的內(nèi)存,并返回一個指向這塊內(nèi)存的指針。這塊內(nèi)存此時并沒有以任何方式進(jìn)行初始化。參數(shù)為字節(jié)數(shù)目。它分配的是一塊連續(xù)的內(nèi)存。
?? ?3,對于要求邊界對齊的機(jī)器,malloc所返回的內(nèi)存起始位置將始終能夠滿足對邊界對齊要求最嚴(yán)格的類型的要求。
?? ?4,calloc與malloc的主要區(qū)別是在返回前把它初始化為0.calloc:參數(shù)包含所需元素的數(shù)量和每個元素的字節(jié)數(shù)。
?? ?5,reallloc用于修改一個原先已經(jīng)分配的內(nèi)存塊的大小。如果原先內(nèi)存塊無法改變大小,則另外分配一塊,將原先的復(fù)制到新的內(nèi)存塊上。
?? ?6,動態(tài)內(nèi)存分配最常見的錯誤就是忘記檢查所請求的內(nèi)存釋放成功分配;另一個是操作內(nèi)存時超出了分配內(nèi)存的邊界。
?? ?7,free釋放一塊內(nèi)存的一部分是不允許的,動態(tài)分配的內(nèi)存必須整塊一起釋放。但realloc可以縮小一塊動態(tài)分配的內(nèi)存,有效釋放尾部的部分內(nèi)存。
?? ?8,分配內(nèi)存后不釋放導(dǎo)致內(nèi)存泄漏。
?? ?總結(jié):1,不檢查從malloc函數(shù)返回的指針是否為null,訪問動態(tài)分配的內(nèi)存之外的區(qū)域,向free函數(shù)傳遞一個并非由malloc函數(shù)返回的指針。在動態(tài)內(nèi)存被釋放后再訪問它。
?? ?動態(tài)內(nèi)存分配有助于消除程序內(nèi)部存在的限制。使用sizeof計(jì)算數(shù)據(jù)類型的長度,提高程序的可移植性。
第十二章:
?? ?1,可以使用結(jié)構(gòu)和指針創(chuàng)建強(qiáng)大的數(shù)據(jù)結(jié)構(gòu)。
?? ?2,root是一個指向node的指針,所以參數(shù)的類型應(yīng)該是Node**,即指向node指針的指針
第十三章
?? ?1,變量名在函數(shù)的作用域內(nèi)部是未知的,函數(shù)擁有的只是一個指向需要修改的內(nèi)存位置的指針,所以對該指針進(jìn)行間接訪問操作以訪問需要修改的變量。
?? ?2,int *f();函數(shù)調(diào)用操作符()優(yōu)先級高于間接訪問操作符,因此,f是一個函數(shù),它的返回值類型是一個指向整型的指針。
?? ?3,int (*f) ():第一對括號只起到聚組的作用,迫使間接訪問在函數(shù)調(diào)用前進(jìn)行,使f成為一個函數(shù)指針,它所指向的函數(shù)返回一個整型值,第二個是函數(shù)調(diào)用操作符
?? ?4,int * (*f) ():所指向的函數(shù)返回一個整型指針。
?? ?5,int ?*f[]:下標(biāo)的優(yōu)先級更高,所以f是一個數(shù)組,它的元素類型是指向整型的指針。
?? ?6,int ?(*f[]) ();*f[]是一個元素為某種類型的指針的數(shù)組,表達(dá)式的末尾()是函數(shù)調(diào)用 操作符,所以f肯定是一個數(shù)組,數(shù)組元素類型是一個函數(shù)指針。
?? ?7,函數(shù)指針和其它指針一樣,對函數(shù)指針執(zhí)行間接訪問前必須把它初始化為指向某個函數(shù)。
?? ?8,函數(shù)指針的初始化:int f(int);int (*pf)(int)=&f;函數(shù)指針指向f,表達(dá)式中&是可選的,因?yàn)楹瘮?shù)名使用時總是由編譯器把它轉(zhuǎn)換為函數(shù)指針,&操作符只是顯示說明了編譯器將隱士執(zhí)行的任務(wù)。
?? ?9,main(argc,**argv):argv數(shù)組中第一個參數(shù)為程序的名稱、
?? ?10,“xyz”+1;因字符串常量實(shí)際上是一個指針時,這個表達(dá)式就是計(jì)算“指針值+1”即結(jié)果是個指向y的指針。
?? ?11,*“xyz”:是對一個指針的間接訪問操作。
第十四章
?? ?1,c預(yù)處理器在源代碼編譯前對其進(jìn)行一些文本性質(zhì)的操作。它的主要任務(wù)是刪除注釋,插入被#include指令包含的文件內(nèi)容,定義和替換由#define指令定義的符號以及確定代碼的部分內(nèi)容是否應(yīng)該
?? ?根據(jù)一些條件編譯指令進(jìn)行編譯。
?? ?2,#define MAX(a,b) ((a)>(b)?(a):(b)):采用此種方法的好處是:使用宏比使用函數(shù)在程序的規(guī)模和速度方面好,更重要的是,函數(shù)的參數(shù)類型必須聲明為一種特定的類型,所以只能在類型合適的表達(dá)式
?? ?上使用。而宏基本數(shù)據(jù)類型及其他任何可以用>操作符的比較值大小的類型,即宏與類型無關(guān)。壞處是每次使用宏定義代碼的拷貝都插入到程序中大幅度增加程序的長度。宏定義結(jié)尾無分號;
?? ?3,#undef name;移除一個宏定義;
?? ?總結(jié):不要在宏定義末尾加上分號,使其成為一條完整的語句;在宏定義中使用參數(shù),但忘記了在他們周圍加上括號;忘了了在整個宏定義的兩邊加上括號。
第十五章
?? ?1,絕大多數(shù)流是完全緩沖的,意味著讀取和寫入實(shí)際上是從一塊被稱為緩沖區(qū)的內(nèi)存區(qū)域來回復(fù)制數(shù)據(jù)。用于輸出流的緩沖區(qū)只有當(dāng)它寫滿時才會別刷新(flush,物理寫入)到設(shè)備或文件中。
?? ?2,流分為文本流和二進(jìn)制流。文本流對于不同系統(tǒng)一個不同就是文本行的最大長度。以及文本行的結(jié)束方式。ms-dos中以一個回車和一個換行符結(jié)尾;linux只使用換行符結(jié)尾;
?? ?3,二進(jìn)制流中的字節(jié)完全根據(jù)程序編寫它們的形式寫入到文件或設(shè)備中,而且完全根據(jù)它們從文件或設(shè)備讀取的程序讀入到程序中,它們并未做任何改變。
?? ?
??
總結(jié)
- 上一篇: 网站漏洞检测
- 下一篇: 真无线蓝牙耳机,享受高品质杜比音效