1、
int a[3] = {2,5,8}; int* p = a; 把數(shù)組名賦值給指針表示將數(shù)組的首元素的地址賦予此指針。 2、 int a[3] = {2,5,8}; int* p = &a[0]; a[0]是a數(shù)組的首元素,而&則是取地址運(yùn)算符,所以“ &a[0]”取得的同樣是a數(shù)組的首元素的地址,因此這段代碼的含義和代碼段是一致的。 3、 char * c1 = "Hello"; char c2[6] = "World"; 這兩句不都是聲明一個(gè)字符串嗎?有什么區(qū)別嗎? "Hello"是一個(gè)字符串,然后讓char指針c1指向這個(gè)字符串的首元素的地址。 第二句是聲明一個(gè)長(zhǎng)度為6的char類型數(shù)組,并且設(shè)置數(shù)組的初始值為"World"。注意末尾還有一個(gè)隱藏的“\0”,所以長(zhǎng)度是6,而不是5。 4、數(shù)組指針的加減運(yùn)算 對(duì)于指向數(shù)組的指針變量可以進(jìn)行加減運(yùn)算,比如對(duì)于指向數(shù)組的指針p,p++、p--、p+2等都是合法的。這里的加減運(yùn)算并不是針對(duì)數(shù)組元素的,而是針對(duì)指針位置的,比如p當(dāng)前指在首元素上,那么p++后就指在第二個(gè)元素上;比如p當(dāng)前指在第5個(gè)元素上,那么p=p-2后,p就指在第3個(gè)元素上。 例子: char * c1 = "Hello"; printf("%c\n",*c1);//輸出H c1++; printf("%c\n",*c1);//輸出e 5、指針之間的運(yùn)算 兩個(gè)指針之間可以進(jìn)行減法運(yùn)算,只有指向同一個(gè)數(shù)組的兩個(gè)指針之間進(jìn)行減法運(yùn)算才有意義,而指向不同數(shù)組的兩個(gè)指針之間進(jìn)行減法運(yùn)算則沒(méi)有意義。為什么呢? 指針其實(shí)就是內(nèi)存中的地址,兩個(gè)指針的減法運(yùn)算計(jì)算的就是兩個(gè)內(nèi)存地址之間的元素的個(gè)數(shù),比如整數(shù)指針p1指向內(nèi)存地址2008H,整數(shù)指針p2內(nèi)存地址2000H,而整數(shù)占4個(gè)字節(jié),所以p1-p2的結(jié)果就是(2008H-2000H)/4=2,也就是 p1和p2 之間相差 2 個(gè)元素。很顯然兩個(gè)指針進(jìn)行加法運(yùn)算或者兩個(gè)指向不同變量的指針進(jìn)行減法運(yùn)算都是沒(méi)有意義的。 例子: int a1[5]={3,5,6,8,2}; int* p1 = a1;//p1是指向第0個(gè)元素的地址 int* p2 = &a1[3];//p2指向的是第3個(gè)元素的地址 p1++;//p1指向了第1個(gè)元素 printf("%d",p2-p1);//輸出2 6、指針之間的大小比較 指向同一個(gè)數(shù)組的兩個(gè)指針之間進(jìn)行大小的比較是有意義的。比較結(jié)果為地址高低的比較: 例子: int a1[5]={3,5,6,8,2}; int* p1 = a1; int* p2 = &a1[3]; p1++; printf("%d\n",p2<p1);//輸出0,因?yàn)閜2的地址比p1高2位 p1=p1+2;//p1在數(shù)組內(nèi)向高位移動(dòng)兩位 printf("%d\n",p2==p1);//輸出1 ,因?yàn)閜1和p2的位相等 7、數(shù)組做為參數(shù)傳遞給函數(shù) 可以將數(shù)組做為傳遞給函數(shù),比如下面的代碼就是將傳入輸入的每個(gè)元素乘以2: void makeDoule(int arr[],int len) { int i=0; for(i=0;i<len;i++) { arr[i]= arr[i]*2; } } int main(int argc, char *argv[]) { int a1[5]={3,5,6,8,2}; int i = 0; int len=sizeof(a1)/sizeof(int); makeDoule(a1,len); for(i=0;i<len;i++) { printf("%d ",a1[i]); } } 運(yùn)行結(jié)果: 6 10 12 16 4傳遞給makeDoule函數(shù)的是a1,我們知道,一個(gè)數(shù)組的名字其實(shí)就是指向數(shù)組首元素的地址。所以makeDoule函數(shù)得到的arr就是數(shù)組a1的指針,那么在函數(shù)內(nèi)部對(duì)arr進(jìn)行操作的話就會(huì)改變a1數(shù)組。 所以makeDoule函數(shù)也可以寫(xiě)成下面的樣子,這兩個(gè)函數(shù)是等價(jià)的: void makeDoule(int* arr,int len) { int i=0; for(i=0;i<len;i++) { arr[i]= arr[i]*2; } } 當(dāng)然寫(xiě)成下面的形式更符合指針的使用習(xí)慣,因此推薦這種用法: void makeDoule(int* arr) { int i=0; for(i=0;i<5;i++) { //arr+i指向的值設(shè)置為arr+i指向的值的二倍。 *(arr+i)= *(arr+i)*2; } } 有的同學(xué)可能會(huì)問(wèn),為什么makeDoule函數(shù)還需要傳遞數(shù)組的長(zhǎng)度len呢?makeDoule函數(shù)這樣寫(xiě)不就行了嗎? void makeDoule(int* arr) { int i=0; for(i=0;i<5;i++) { arr[i]= arr[i]*2; } } 這樣寫(xiě)在這個(gè)函數(shù)中是沒(méi)有問(wèn)題的。但是這個(gè)makeDoule函數(shù)不一定為只為a1服務(wù),函數(shù)的最重要的特征是可以復(fù)用,也就是可以被其他地方調(diào)用,如果我們想用makeDoule函數(shù)為另外一個(gè)長(zhǎng)度為20的數(shù)組進(jìn)行“每個(gè)元素乘以2”的操作,那么將數(shù)組長(zhǎng)度5寫(xiě)死在函數(shù)中就會(huì)有問(wèn)題了。 那么為什么不在函數(shù)內(nèi)部計(jì)算數(shù)組的長(zhǎng)度呢?省得還傳遞一個(gè)len參數(shù) void makeDoule(int arr[]) { int i=0; int len = sizeof(arr)/sizeof(int); for(i=0;i<len;i++) { arr[i]= arr[i]*2; } } int main(int argc, char *argv[]) { int a1[5]={3,5,6,8,2}; int i = 0; int len=sizeof(a1)/sizeof(int); makeDoule(a1); for(i=0;i<len;i++) { printf("%d ",a1[i]); } } 運(yùn)行以后程序竟然輸出了:6 5 6 8 2 只有第一個(gè)元素被“乘以2”。為什么呢? 運(yùn)行下面的程序試一試: void test(int arr[]) { printf("%d\n",sizeof(arr)/sizeof(int)); } int main(int argc, char *argv[]) { int a1[5]={3,5,6,8,2}; printf("%d\n",sizeof(a1)/sizeof(int)); test(a1); } 運(yùn)行結(jié)果竟然是: 5 1為什么在main函數(shù)中計(jì)算數(shù)組的大小是5,在test函數(shù)中計(jì)算數(shù)組arr的大小就變成了1了呢?在C語(yǔ)言中可以通過(guò)sizeof的方式取得一個(gè)數(shù)組的尺寸,這是我們已經(jīng)知道的。但是一旦把這個(gè)數(shù)組傳遞給函數(shù)的時(shí)候,到了函數(shù)內(nèi)部使用的就是指向這個(gè)數(shù)組的指針了,雖然在test函數(shù)中arr聲明的是數(shù)組,但是這里的arr只是一個(gè)指針而已了,arr本質(zhì)上只是一個(gè)int類型的指針,而int類型的指針的大小是4,所以sizeof(arr)/sizeof(int)的結(jié)果就是1。這點(diǎn)是經(jīng)常容易犯錯(cuò)的,需要特別注意。如果對(duì)指針還有什么不清楚的,可以到這個(gè)網(wǎng)址查詢指針的學(xué)習(xí)資料:[url]http://www./forum/forumdisplay.php?fid=4[/url] ,指針是比較不好理解的,因此學(xué)習(xí)過(guò)程中要多試驗(yàn),多思考,不要?dú)怵H。8、多維數(shù)組的指針 設(shè)有一個(gè)二維數(shù)組int a[3][4]={{0,1,2,3},{4,5,6,7},{8,9,10,11}} (1)C語(yǔ)言允許把一個(gè)二維數(shù)組分解為多個(gè)一維數(shù)組來(lái)處理。因此數(shù)組 a 可分解為三個(gè)一維數(shù)組,即 a[0],a[1],a[2]。每一個(gè)一維數(shù)組又含有四個(gè)元素。例如 a[0]數(shù)組,含有 a[0][0],a[0][1],a[0][2],a[0][3]四個(gè)元素。 (2)從二維數(shù)組的角度來(lái)看,a 是二維數(shù)組名,a 代表整個(gè)二維數(shù)組的首地址,也是二維數(shù)組 0 行的首地址。 特別注意a+1表示第1行的首地址,而不是第0行第第1列的地址,這是初學(xué)者最容易犯錯(cuò)的地方。 同樣a[1]也是第1行一維數(shù)組的數(shù)組名和首地址。 所以a+1、*(a+1)、[1]是等價(jià)的。 (4)在二維數(shù)組中不能把&a[i]理解為元素 a[i]的地址,因?yàn)閍[i]不是一個(gè)數(shù)組元素,a[i]是一種地址計(jì)算方法,它本身就表示數(shù)組 a 第 i 行首地址。所以&a[i]和 a[i]是等價(jià)的。這一點(diǎn)也是初學(xué)者最容易犯錯(cuò)的地方。 (5)從上邊的分析我們得知a[0]+1是 a[0]的 1 號(hào)元素首地址,由此可得出 a[i]+j 則是一維數(shù)組 a[i]的 j 號(hào)元素首地址,它等于&a[i][j]。 由 a[i]=*(a+i)得 a[i]+j=*(a+i)+j。由于*(a+i)+j 是二維數(shù)組 a 的 i 行 j 列元素的首地址,所以,該元素的值等于*(*(a+i)+j)。 理解了下面的算法也就理解了多維數(shù)組的指針問(wèn)題: int main(int argc, char *argv[]) { int a[3][4]={{0,1,2,3},{4,5,6,7},{8,9,10,11}}; printf("%d\n",*(*(a+1)+2)); printf("%d\n",*(a[1]+2)); } 程序輸出如下: 6 6兩個(gè)表達(dá)式都輸出a[1][2]的值。*(a+1)則表示二維數(shù)組a的第1行,也就是等價(jià)于a[1]。a[1]、*(a+1)都表示數(shù)組的第1行。所以*(a[1]+2))和*(*(a+1)+2)都表示數(shù)組的第1行的第2個(gè)元素的值。 有的同學(xué)會(huì)問(wèn)了,既然“*(a+1)”和“a+1”是等價(jià)的,為什么“printf("%d\n",*((a+1)+2)))”輸出結(jié)果是錯(cuò)誤的呢? 編譯器在編譯“*((a+1)+2)))”的時(shí)候會(huì)把“(a+1)+2”優(yōu)化成“a+3”,因此“*((a+1)+2)))”就變成了“*(a+3)”,也就是a數(shù)組第3個(gè)一維數(shù)組的首地址,顯然這個(gè)只是一個(gè)地址,并不是我們想像中的a[1][2]的值,所以輸出了一個(gè)非常大的數(shù)。為了避免編譯器的這種誤解,建議大家表示“二維數(shù)組a的第1行”的時(shí)候用a[1]或者*(a+1),而盡量不要用(a+1)因?yàn)楹苋菀壮鲥e(cuò)。 9、函數(shù)指針 在C語(yǔ)言中,一個(gè)函數(shù)總是占用一段連續(xù)的內(nèi)存區(qū),而函數(shù)名就是該函數(shù)所占內(nèi)存區(qū)的首地址。我們可以把函數(shù)的這個(gè)首地址(或稱入口地址)賦予一個(gè)指針變量,使該指針變量指向該函數(shù)。然后通過(guò)指針變量就可以找到并調(diào)用這個(gè)函數(shù)。我們把這種指向函數(shù)的指針變量稱為“函數(shù)指針變量”。 函數(shù)指針變量定義的一般形式為: 函數(shù)的返回值的類型 (*指針變量名)(參數(shù)列表); 其中“參數(shù)列表”可以省略,不過(guò)建議明確標(biāo)明“參數(shù)列表”。 例子: void PrintIt(int i) { printf("%d\n",i); } int main(int argc, char *argv[]) { int i = 0; int arr[5] = {3,5,8,2,1}; void (*myAction)(int); myAction = PrintIt; for(i=0;i<sizeof(arr)/sizeof(int);i++) { myAction(arr); } } 上面的程序遍歷數(shù)組arr的所有元素,并且打印每個(gè)元素。 有的同學(xué)會(huì)說(shuō),這樣做有什么意義嗎?把“myAction(arr)”直接換成“PrintIt(arr)”不就得了嗎? 這么替換在這里是非常合理,也是非常正確的,但是有一天我發(fā)現(xiàn)很多地方都要遍歷數(shù)組做不同的事情,為了避免每次都寫(xiě)for循環(huán),我將遍歷數(shù)組的功能抽取到一個(gè)單獨(dú)的公共函數(shù)中完成 void eachItem(int* pArray,int len,void (*action)(int)) { int i = 0; for(i=0;i<len;i++) { //調(diào)用函數(shù)指針 action(*(pArray+i)); } } 注意函數(shù)eachItem的最后一個(gè)參數(shù)為函數(shù)指針類型。 這樣main函數(shù)就可以簡(jiǎn)化成如下的樣子了: void PrintIt(int i) { printf("%d\n",i); } int main(int argc, char *argv[]) { int arr[5] = {3,5,8,2,1}; int len = sizeof(arr)/sizeof(int); eachItem(arr,len,PrintIt); } 以后在其他的地方想對(duì)int數(shù)組做其他處理,那么只要寫(xiě)不同的函數(shù)就可以了,比如說(shuō)要將數(shù)組中的奇數(shù)輸出: void PrintOdd(int i) { if((i%2)==1) { printf("%d是奇數(shù)\n",i); } } 在main函數(shù)中如下調(diào)用即可: eachItem(arr,len,PrintOdd); 可以看到通過(guò)函數(shù)指針,將循環(huán)遍歷算法和具體的處理算法隔離了,實(shí)現(xiàn)了代碼的復(fù)用。 函數(shù)指針在編程中有非常多的應(yīng)用。函數(shù)指針有時(shí)候又被稱為回調(diào),在事件機(jī)制、模板算法等場(chǎng)合有著廣泛應(yīng)用,因此一定要掌握好。MFC、STL等流行的框架中都大量的應(yīng)用了函數(shù)指針,在的《C語(yǔ)言也能干大事》系列在線教學(xué)中也將進(jìn)一步講解函數(shù)指針的更生動(dòng)的應(yīng)用。 附錄:本小節(jié)用到的代碼: void PrintIt(int i) { printf("%d\n",i); } void PrintOdd(int i) { if((i%2)==1) { printf("%d是奇數(shù)\n",i); } } void eachItem(int* pArray,int len,void (*action)(int)) { int i = 0; for(i=0;i<len;i++) { action(*(pArray+i)); } } int main(int argc, char *argv[]) { int arr[5] = {3,5,8,2,1}; int len = sizeof(arr)/sizeof(int); eachItem(arr,len,PrintIt); eachItem(arr,len,PrintOdd); } 10、指針數(shù)組、指針的指針。 指針數(shù)組的經(jīng)典用途就是聲明字符串?dāng)?shù)組: static char *name[]={"Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"}; 這兩部分請(qǐng)同學(xué)們參考教材。 11、結(jié)構(gòu)指針 對(duì)結(jié)構(gòu)的訪問(wèn)一般形式為: 結(jié)構(gòu)變量.成員名 (*結(jié)構(gòu)指針變量).成員名 或?yàn)椋? 結(jié)構(gòu)指針變量->成員名 應(yīng)該注意(*pstu)兩側(cè)的括號(hào)不可少,因?yàn)槌蓡T符“.”的優(yōu)先級(jí)高于“*”。 12、結(jié)構(gòu)指針變量作函數(shù)參數(shù) 允許用結(jié)構(gòu)變量作函數(shù)參數(shù)進(jìn)行整體傳送。但是這種傳送要將全部成員逐個(gè)傳送,特別是成員為數(shù)組時(shí)將會(huì)使傳送的時(shí)間和空間開(kāi)銷很大,嚴(yán)重地降低了程序的效率。因此最好的辦法就是使用指針,即用指針變量作函數(shù)參數(shù)進(jìn)行傳送。 13、動(dòng)態(tài)存儲(chǔ)分配 注:“C語(yǔ)言中不允許動(dòng)態(tài)數(shù)組”是C89標(biāo)準(zhǔn)中的規(guī)定,所以TC、VC6等C89等老的編譯器會(huì)有這個(gè)問(wèn)題,新的C99中已經(jīng)不存在這個(gè)問(wèn)題。 C語(yǔ)言中不允許動(dòng)態(tài)數(shù)組類型。例如下面的代碼是錯(cuò)誤的: int n; scanf("%d",&n); int a[n]; 但是在實(shí)際的編程中,往往會(huì)發(fā)生這種情況,即所需的內(nèi)存空間取決于實(shí)際輸入的數(shù)據(jù)。對(duì)于這種問(wèn)題,用數(shù)組的辦法很難解決。為了解決上述問(wèn)題,C語(yǔ)言提供了一些內(nèi)存管理函數(shù),這些內(nèi)存管理函數(shù)可以按需要?jiǎng)討B(tài)地分配內(nèi)存空間,也可把不再使用的空間回收。 (1)分配內(nèi)存空間函數(shù) malloc 調(diào)用形式: (類型說(shuō)明符*)malloc(size) 功能:在內(nèi)存的動(dòng)態(tài)存儲(chǔ)區(qū)中分配一塊長(zhǎng)度為"size"字節(jié)的連續(xù)區(qū)域。函數(shù)的返回值為該區(qū)域的首地址。 (2)釋放內(nèi)存空間函數(shù) free 調(diào)用形式: free(void*ptr); |
|