| 
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //指針與數(shù)組 
 //指針與地址 一元運(yùn)算符&可用于取一個(gè)對象的地址。如: p=&c; 將把c的地址賦值給變量p,則稱p為指向c的指針。地址運(yùn)算符&只能應(yīng)用于內(nèi)存中的對象,即變量與數(shù)組元素。它不能作用于表達(dá)式,常量或register類型的變量。 一元運(yùn)算符*是間接尋址或間接引用運(yùn)算符。當(dāng)它作用于指針時(shí),將訪問指針?biāo)赶虻膶ο?。假定x與y是整數(shù),而ip是指向int類型的指針,下面的代碼說明了如何在程序中聲明指針以及如何使用運(yùn)算符&和*: int x=1 , y=2 ,z[10]; int *ip; // ip is a pointer to int ip=&x; // ip now points to x y=*ip; // y is now 1 *ip=0; // x is now 0 ip=&z[0]; // ip now points to z[0] 注意:指針只能指向某種特定類型的對象,即每個(gè)指針都必須指向某種特定的數(shù)據(jù)類型。 一個(gè)例外情況是指向void類型的指針可以存放指向任何類型的指針,但它不能間接引用其自身。 //指針與函數(shù)參數(shù) 由于C語言是以傳值的方式將參數(shù)值傳遞給被調(diào)用函數(shù)。因此,被調(diào)用函數(shù)不能直接修改主調(diào)函數(shù)中變量的值。 指針參數(shù)使得被調(diào)用函數(shù)能夠訪問和修改主調(diào)函數(shù)中對象的值。 //指針與數(shù)組 在C語言中,通過數(shù)組下標(biāo)所能完成的任何操作都可以通過指針來實(shí)現(xiàn)。一般來講,用指針編寫的程序比用數(shù)組下標(biāo)編寫的程序執(zhí)行速度快,但另一方面,用指針實(shí)現(xiàn)的程序理解比較困難。 聲明 int a[10]; 定義了一個(gè)長度為10的數(shù)組a。即定義了一個(gè)由10個(gè)對象組成的集合,這10個(gè)對象存儲在相鄰的內(nèi)存區(qū)域中,名字分別為a[0],a[1],a[2],...,a[9]。 a:|____|____|____|____|____|____|____|____|____|____| a[0] a[1] a[2] a[9] a[i]表示該數(shù)組的第i個(gè)元素。如果pa的聲明為 int *pa; 則說明它是一個(gè)指向整型對象的指針,那么,賦值語句 pa=&a[0]; 則可以將指針pa指向數(shù)組a的第0個(gè)元素,也就是說,pa的值為數(shù)組元素a[0]的地址 那么,賦值語句 x=*pa; 將把數(shù)組元素a[0]中的內(nèi)容復(fù)制到變量x中。 如果pa指向數(shù)組中的某個(gè)特定元素,那么,根據(jù)指針運(yùn)算的定義,pa+1將指向下一個(gè)元素,pa+i將指向pa所指向元素之后的第i個(gè)元素,而pa-i將指向pa所指向數(shù)組元素之前的第i個(gè)元素。因此,如果指針pa指向a[0],那么*(pa+1)引用的是數(shù)組元素a[1]的內(nèi)容,pa+i是數(shù)組元素a[i]的地址,*(pa+i)引用的是數(shù)組元素a[i]的內(nèi)容。 
 pa=&a[0]; 后,pa和a具有相同的值。因?yàn)閿?shù)組名所代表的就是該數(shù)組最開始的一個(gè)元素的地址,所以,賦值語句pa=&a[0]也可以簡寫為: pa=a; 對于數(shù)組元素a[i]的引用也可以寫成*(a+i)的形式。在計(jì)算數(shù)組元素a[i]的值時(shí),C語言實(shí)際上先將其轉(zhuǎn)換為*(a+1)的形式,然后再進(jìn)行求值,因此在程序中這兩種形式是等價(jià)的。如果對這兩種等價(jià)的表示形式分別施加地址運(yùn)算符&,便可得出這樣的結(jié)論:&a[i]和a+i的含義也是相同的。a+i是a之后第i個(gè)元素的地址。相應(yīng)的,如果pa是個(gè)指針,那么,在表達(dá)式中也可以在它的后面加下標(biāo)。pa[i]與*(pa+i)是等價(jià)的。簡言之,一個(gè)通過數(shù)組和下標(biāo)實(shí)現(xiàn)的表達(dá)式可等價(jià)地通過指針和偏移量實(shí)現(xiàn)。 
 當(dāng)數(shù)組名傳遞給一個(gè)函數(shù)時(shí),實(shí)際上傳遞的是該數(shù)組第一個(gè)元素的地址。在被調(diào)用函數(shù)中,該參數(shù)是一個(gè)局部變量,因此,數(shù)組名參數(shù)必須是一個(gè)指針,也就是一個(gè)存儲地址值的變量??梢岳迷撎匦员嫖鰏trlen函數(shù)的另一個(gè)版本,該函數(shù)用于計(jì)算一個(gè)字符串的長度。 // strlen: return length of string s int strlen(char *s) { int n; for(n=0; *s!='\0';s++) n++; return n; } 因?yàn)閟是一個(gè)指針,所以對其執(zhí)行自增運(yùn)算是合法的。執(zhí)行s++運(yùn)算不會(huì)影響到strlen函數(shù)的調(diào)用者中的字符串,它僅對該指針在strlen函數(shù)中的私有副本進(jìn)行自增運(yùn)算。因此,類似于下面這樣的函數(shù)調(diào)用: strlen("hello world"); // string constant strlen(array); // char array[100] strlen(ptr); // char *ptr 都是正確的。 在函數(shù)定義中,形式參數(shù) char s[]; 和 char *s; 是等價(jià)的。通常習(xí)慣使用后一種形式,因?yàn)樗惹罢吒庇^地表明了該參數(shù)是一個(gè)指針。如果將數(shù)組名傳遞給函數(shù),函數(shù)可以根據(jù)情況判定是按照數(shù)組處理還是按照指針處理,隨后根據(jù)相應(yīng)的方式操作該參數(shù)。為了直觀且恰當(dāng)?shù)孛枋龊瘮?shù),在函數(shù)中甚至可以同時(shí)使用數(shù)組和指針這兩種表示方法。 
 f(&a[2]); 與 f(a+2); 都將把起始于a[2]的子數(shù)組的地址傳遞給函數(shù)f。在函數(shù)f中,參數(shù)的聲明形式可以為 f(int arr[]){ ... } 或 f(int *arr){ ... } 對于函數(shù)f來講,它并不關(guān)心所引用的是否只是一個(gè)更大數(shù)組的部分元素。 如果確信相應(yīng)的元素存在,也可以通過下標(biāo)訪問數(shù)組第一個(gè)元素之前的元素。類似于p[-1],p[-2]這樣的表達(dá)式在語法上都是合法的,它們分別引用位于p[0]之前的兩個(gè)元素。當(dāng)然,引用數(shù)組邊界之外的對象是非法的。 //地址算術(shù)運(yùn)算 如果p是一個(gè)指向數(shù)組中某個(gè)元素的指針,那么p++將對p進(jìn)行自增運(yùn)算并指向下一個(gè)元素,而p+=i將對p進(jìn)行加i的增量運(yùn)算,使其指向指針p當(dāng)前所指向的元素之后的第i個(gè)元素。這類運(yùn)算是指針或地址算術(shù)運(yùn)算中最簡單的形式。 一般情況下,同其他類型的變量一樣,指針也可以初始化。通常,對指針有意義的初始化值只能是0或者是表示地址的表達(dá)式,對后者來說,表達(dá)式所代表的地址必須是在此前已定義的具有適當(dāng)類型的數(shù)據(jù)的地址。例如,聲明 static char* allocp=allocbuf; 將allocp定義為字符類型指針,并將它初始化為allocbuf的起始地址,該起始地址是程序執(zhí)行時(shí)的下一個(gè)空閑位置。上述語句也可以寫成下列形式: static char* allocp=&allocbuf[0]; 這是因?yàn)樵摂?shù)組名實(shí)際上就是數(shù)組第0個(gè)元素的地址。 
 首先,在某些情況下對指針可以進(jìn)行比較運(yùn)算。例如,如果指針p和q指向同一個(gè)數(shù)組的成員,那么它們之間就可以進(jìn)行類似于==,!=,<,>=的關(guān)系比較運(yùn)算。如果p指向的數(shù)組元素的位置q指向的數(shù)組元素位置之前,那么關(guān)系表達(dá)式 p<q 的值為真。任何指針與0進(jìn)行相等或不等的比較運(yùn)算都有意義。但是,指向不同數(shù)組的元素的指針之間的算術(shù)或比較運(yùn)算沒有意義。(特例:指針的算術(shù)運(yùn)算中可使用數(shù)組最后一個(gè)元素的下一個(gè)元素的地址) 其次,指針可以和整數(shù)進(jìn)行相加或相減運(yùn)算。例如,結(jié)構(gòu) p+n 表示指針p當(dāng)前指向的對象之后第n個(gè)對象的地址。無論指針p指向的對象是何種類型,上述結(jié)論都成立。在計(jì)算p+n時(shí),n將根據(jù)p指向的對象的長度按比例縮放,而p指向的對象的長度則取決于p的聲明。例如,如果int類型占4字節(jié)的存儲空間,那么在int類型的計(jì)算中,對應(yīng)的n將按4的倍數(shù)來計(jì)算。 指針的減法運(yùn)算也是有意義的,如果p和q指向相同的數(shù)組元素,且p<q,那么q-p+1就是位于p和q指向的元素之間的元素的數(shù)目。由此可以編寫出函數(shù)strlen的另一個(gè)版本: // strlen: return length of string s int strlen(char *s) { char *p=s; while(*p!='\0') p++; return p-s; } 指針p被初始化為指向s,即指向該字符串的第一個(gè)字符。while循環(huán)語句將一次檢查字符串中的每個(gè)字符,直到遇到標(biāo)識字符數(shù)組結(jié)尾的字符‘\0’為止。由于p是指向字符的指針,所以沒執(zhí)行一次p++,p就將指向下一個(gè)字符的地址,p-s則表示已經(jīng)檢查過的字符數(shù),即字符串長度。 注意:字符串中的字符數(shù)有可能超過int類型所能表示的最大范圍。頭文件<stddef.h>中定義的類型ptrdiff_t足以表示兩個(gè)指針之間的帶符號差值。但是,在這里使用size_t作為函數(shù)strlen的返回值類型,這樣可以與標(biāo)準(zhǔn)庫中的函數(shù)版本相匹配。size_t是由運(yùn)算符sizeof返回的無符號整型。 //字符指針與函數(shù) 字符串常量是一個(gè)字符數(shù)組,例如: “I am a string" 在字符串的內(nèi)部表示中,字符數(shù)組以空字符‘\0’結(jié)尾,所以,程序可以通過檢查空字符找到字符數(shù)組的結(jié)尾。字符串常量占據(jù)的存儲單元數(shù)也因此比雙引號內(nèi)的字符數(shù)大1。 
 printf("hello, world\n"); 當(dāng)類似于這樣的一個(gè)字符串出現(xiàn)在程序中時(shí),實(shí)際上是通過字符指針訪問該字符串的。在上述語句中,printf接受的是一個(gè)指向字符數(shù)組第一個(gè)字符的指針。也就是說,字符串常量可通過一個(gè)指向其第一個(gè)元素的指針訪問。 除了作為函數(shù)參數(shù)外,字符串常量還有其他用法。假定指針pmessage的聲明如下: char *pmessage; 那么,語句 pmessage="now is the time"; 將把一個(gè)指向該字符數(shù)組的指針賦值給pmessage。該過程并沒有進(jìn)行字符串的復(fù)制,而只是涉及到指針的操作。C語言沒有提供將整個(gè)字符串作為一個(gè)整體進(jìn)行處理的運(yùn)算符。 下面兩種定義之間有很大的差別: char amessage[]="now is the time"; //定義一個(gè)數(shù)組 char *pmessage="now is the time"; //定義一個(gè)指針 其中,amessage是一個(gè)僅僅足以存放初始化字符串以及空字符'\0'的一維數(shù)組。數(shù)組中的單個(gè)字符可以進(jìn)行修改,但是amessage始終指向同一個(gè)存儲位置。另一方面,pmessage是一個(gè)指針,其初始值指向一個(gè)字符串常量,之后它可以被修改以指向其他地址,但如果試圖修改字符串的內(nèi)容,結(jié)果是不可確定的。 第一個(gè)函數(shù)strcpy(s,t),把指針t指向的字符串復(fù)制到指針s指向的位置。如果使用語句s=t實(shí)現(xiàn)該功能,其實(shí)質(zhì)上只是拷貝了指針,而并沒有賦值字符。為了進(jìn)行字符的復(fù)制,這里使用了一個(gè)循環(huán)語句。strcpy函數(shù)的第一個(gè)版本是通過數(shù)組方法實(shí)現(xiàn)的,如下: // strcpy :copy t to s; array subscript version void strcpy(char *s, char *t) { int i; i=0; while(( s[i] = t[i] ) !='\0') i++; } 下面使用指針方法實(shí)現(xiàn)strpy函數(shù) //strcpy : copy t to s; pointer version void strcpy(char *s, char *t) { int i; i=0; while( ( *s = *t ) != '\0' ) { s++; t++; } } 因?yàn)閰?shù)是通過值傳遞的,所以在strcpy函數(shù)中可以以任何方式使用參數(shù)s和t。在此,s和t是方便地進(jìn)行了初始化的指針,循環(huán)每執(zhí)行一次,他們就沿著相應(yīng)的數(shù)組前進(jìn)一個(gè)字符,直到將t中的結(jié)束符'\0'復(fù)制到s為止。 實(shí)際上,strcpy函數(shù)并不會(huì)按照上面的這些方式編寫。經(jīng)驗(yàn)豐富的程序員更喜歡對它編寫成下列形式: // strcpy : copy t to s; pointer version 2 void strcpy(char *s, char *t) { while( (*s++ = *t++) != ‘\0’ ) ; } 在該版本中,s和t的自增運(yùn)算放到了循環(huán)的測試部分中。表達(dá)式*t++的值是執(zhí)行自增運(yùn)算之前t所指向的字符。后綴運(yùn)算符++表示在讀取該字符之后才改變t的值。同理,在s執(zhí)行自增運(yùn)算前,字符就被存儲到指針s指向的舊位置。該字符值同時(shí)也用來和空字符'\0'進(jìn)行比較運(yùn)算,以控制循環(huán)的執(zhí)行。最后的結(jié)果是依次將t指向的字符復(fù)制到s指向的位置,知道遇到結(jié)束符位置(同時(shí)也復(fù)制該結(jié)束符)。 為了更進(jìn)一步的精煉程序,我們注意到,表達(dá)式同'\0'的比較是多余的,因?yàn)橹恍枰卸ū磉_(dá)式的值是否為0即可。因此,該函數(shù)可進(jìn)一步寫成下列形式: // strcpy : copy t to s; pointer version 3 void strcpy(char *s , char *t) { while(*s++=*t++) ; } 該函數(shù)初看起來不太容易理解,但這種表示方法很有好處,應(yīng)該掌握此種方法,C語言程序中經(jīng)常會(huì)采用這種寫法。 標(biāo)準(zhǔn)庫<string.h>中提供的函數(shù)strcpy把目標(biāo)字符串作為函數(shù)值返回。 第二個(gè)函數(shù)是strcmp(s,t)。該函數(shù)比較字符串s和t,并且根據(jù)s按照字典順序小于、等于或大于t的結(jié)果分別返回負(fù)整數(shù)、0或正整數(shù)。該返回值是s和t由前向后逐字符比較時(shí)遇到的第一個(gè)不相等字符處的字符的差值。 // strcmp : return <0 is s<t, 0 if s==t, >0 if s>t int strcmp(char *s, char *t) { int i; for(i=0; s[i]==t[i];i++) if(s[i]=='\0') return 0; return s[i]-t[i]; } 下面是用指針方式實(shí)現(xiàn) // strcmp return <0 if s<t, 0 if s==t, >0 if s>t int strcmp(char *s, char *t) { for(; *s==*t; s++, t++) if(*s=='\0') return 0; return *s-*t; } 由于++和--既可以作為前綴,也可以作為后綴運(yùn)算符,所以還可以將運(yùn)算符*與運(yùn)算符++和--按照其他方式組合使用,但這些用法不多見。例如, *--p; 在讀取制作p指向的字符之前先對p指向自減運(yùn)算。事實(shí)上,下面的兩個(gè)表達(dá)式: *p++=val; //將val壓入棧 val=*--p; //將棧頂元素彈出到val中 是進(jìn)棧和出棧的標(biāo)準(zhǔn)用法。 //指針數(shù)組以及指向指針的指針 指針數(shù)組,其定義方式如下: char lineptr[MAXLINES]; // getline函數(shù) 它表示lineptr是一個(gè)具有MAXLINES個(gè)元素的一維數(shù)組,其中數(shù)組的每個(gè)元素是一個(gè)指向字符類型對象的指針。也就是說,lineptr[i]是一個(gè)字符指針,而*lineptr[i]是該指針指向的第i個(gè)文本行的首字符。 //多維數(shù)組 在C語言里,二位數(shù)組實(shí)際是一種特殊的一維數(shù)組,它的每個(gè)元素也是一個(gè)一維數(shù)組。 數(shù)組可以用花括號括起來的初值表進(jìn)行初始化,二維數(shù)組的每一行由相應(yīng)的子列表進(jìn)行初始化。 注意: int daytab[3][12]; 如果將二維數(shù)組作為參數(shù)傳遞給函數(shù),那么在函數(shù)的參數(shù)聲明中必須指名數(shù)組的列數(shù)。數(shù)組的行數(shù)沒有太大關(guān)系,函數(shù)調(diào)用時(shí)傳遞的是一個(gè)指針,它指向由行向量構(gòu)成的一維數(shù)組,其中每個(gè)行向量是具有13個(gè)整型元素的一維數(shù)組。在該例子中,傳遞給函數(shù)的是一個(gè)指向很多對象的指針,其中每個(gè)對象是由13個(gè)整型元素構(gòu)成的一維數(shù)組。因此,如果將數(shù)組daytab作為參數(shù)傳遞給函數(shù)f,那么f的格式應(yīng)寫作: f( int daytab[3][13] ){...} 或 f( int daytab[][13] ){...} 因?yàn)閿?shù)組的函數(shù)無關(guān)緊要,所以,該聲明還可寫成 f( int (*daytab)[13]) 這種聲明形式表明參數(shù)是一個(gè)指針,它指向具有13個(gè)整型元素的一維數(shù)組。因此方括號[]的優(yōu)先級高于*的優(yōu)先級,所以上述聲明中必須使用圓括號。如果去掉括號,則聲明變成 int *daytab[13] 這相當(dāng)于聲明了一個(gè)數(shù)組,概述組有13個(gè)元素,其中每個(gè)元素都是一個(gè)指向整型對象的指針。一般來說,除數(shù)組的第一維下標(biāo)可以不指定大小外,其余各維都必須明確指定大小。 //指針數(shù)組的初始化 
 //指針與多維數(shù)組 二位數(shù)組與指針數(shù)組之間的區(qū)別。假如有下面兩個(gè)定義: int a[10][21]; int *b[10]; 那么,從語法角度講,a[3][4]和b[3][4]都是對一個(gè)int對象的合法引用。但a是一個(gè)真正的二維數(shù)組,它分配了200個(gè)int類型長度的存儲空間,并且通過常規(guī)的矩陣下標(biāo)計(jì)算公式20*row+col(row行,col列)計(jì)算得到a[row][col]的位置。但是,對b來說,該定義僅僅分配了10個(gè)指針,并且沒有對它們初始化,它們的初始化必須以顯式的方式進(jìn)行,比如靜態(tài)初始化或通過代碼初始化。假定b的每個(gè)元素都指向一個(gè)具有20個(gè)元素的數(shù)組,那么編譯器就要為它分配200個(gè)int類型長度的存儲空間以及10個(gè)指針的存儲空間。指針數(shù)組的一個(gè)重要優(yōu)點(diǎn)在于,數(shù)組的每一行長度可以不同,也就是說,b的每個(gè)元素不必都指向一個(gè)具有20個(gè)元素的向量,某些元素可以指向具有2個(gè)元素的向量,某些元素可以指向有50個(gè)元素的向量,而某些元素可以不指向任何向量。 指針數(shù)組最頻繁的用處是存放具有不同長度的字符串,如下定義: char *name[]={"Illegal month","Jan","Feb","Mar"}; 下面是二維數(shù)組的聲明: char aname[][15]={"Illegal month", "Jan", "Feb", "Mar"}; 下面是指針數(shù)組和二維數(shù)組的圖形化描述: 
 //命令行參數(shù) 在支持C語言的環(huán)境中,可以在程序開始執(zhí)行時(shí)將命令行參數(shù)傳遞給程序。調(diào)用主函數(shù)main時(shí),它帶有兩個(gè)參數(shù)。第一個(gè)參數(shù)(習(xí)慣上成為argc,用與參數(shù)計(jì)數(shù))的值表示運(yùn)行程序時(shí)命令行中參數(shù)的數(shù)目;第二個(gè)參數(shù)(稱為argv,用于參數(shù)向量)是一個(gè)指向字符串?dāng)?shù)組的指針,其中每個(gè)字符串對應(yīng)一個(gè)參數(shù)。通常使用多級指針處理這些字符串。 按照C語言的約定,argv[0]的值是啟動(dòng)該程序的程序名,因此argc的值至少為1。另外,ANSI標(biāo)準(zhǔn)要求argv[argc]的值必須為一空指針。 ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //在C語言中,指針和數(shù)組名通常都可以混用。 如: char *p; // *p=p[0],*(p+1)=p[1] char b[5]; // b[0]=*b,b[2]=*(b+2) 
 float x; SBUF=((char*)&x)[0]; SBUF=((char*)&x)[1]; SBUF=((char*)&x)[2]; SBUF=((char*)&x)[3]; 接收時(shí),剛好倒過來。更有趣的是,對于數(shù)組形式,數(shù)組名和后面的偏移量可以隨便換。 char buff[10]; //或者用char *buff=&buffer; buff[3]=0xaa; 3[buff]=0xaa; //兩者是一樣的 因此,可以認(rèn)為編譯器是這么認(rèn)為的,對于形如xxx[yyy]這樣的表達(dá)式,會(huì)轉(zhuǎn)化為*(xxx+yyy),因此兩種形式都是一樣的。 
 注意:雖然數(shù)組名可以轉(zhuǎn)換為指向其指代實(shí)體的指針,但是它只能被看作一個(gè)指針常量,不能被修改,如下: int intArray[10];                 intArray++;            //錯(cuò)誤 1.數(shù)組名不是指針  示例程序1 示例程序1執(zhí)行結(jié)果: str len=10 pStr len=4 執(zhí)行示例程序可知:sizeof(str)=10,sizeof(pStr)=4,可見數(shù)組名和指針是不一樣的 2.數(shù)組名神似指針  示例程序2 示例程序2執(zhí)行結(jié)果:         str is:I miss u 標(biāo)準(zhǔn)C庫函數(shù)strcpy的函數(shù)原型中能接納的兩個(gè)參數(shù)都為char型指針,而示例程序中傳給它的確實(shí)兩個(gè)數(shù)組名。 3.數(shù)組名可能失去其數(shù)據(jù)結(jié)構(gòu)內(nèi)涵  示例程序3 示例程序3執(zhí)行結(jié)果:str len=4 分析: 數(shù)組名作為函數(shù)形參時(shí),在函數(shù)體內(nèi),其失去了本身的內(nèi)涵,僅僅只是一個(gè)指針; 在失去其內(nèi)涵的同時(shí),它還失去了其常量特性,可以作自增,自減等操作,可以被修改; 結(jié)果: 所以數(shù)組名作為函數(shù)形參時(shí),就是一個(gè)普通的指針,4字節(jié) //(*(a+1))和(&a+1)的區(qū)別  *(a+1)和(&a+1) 程序執(zhí)行結(jié)果:         *(a+1)=2,*(ptr-1)=5 | 
|  |