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