一、指針與數組
指針:指針本身也是一個變量,它的內容是指向的內容的地址。指針同樣有類型的區分,char 的指針只能指向char型數據,int 指針指向int型數據。但是指針所佔內存單元的大小(即其內容)是跟操作系統的地址位數有關,比如32位地址的系統,那麼指針所佔的內存單元就是4個字節,16位就是2個字節,因此,指針的類型只是限定它所指向的變量的類型,其內容所佔的單元大小是與操作系統的地址位數相關,與變量類型無關。
在32位地址系統中,比如:
int a = 10; //int型一般長度爲4字節,所以sizeof(a)的值爲4
int * p =a; // p中存放的是a的地址,所以sizeof(p)的長度是4字節
char b = 'c'; // char型一般長度爲1個字節,所以sizeof(b)值爲1
char * d = c; // d中存放的依然是a的地址,所以sizeof(d)值爲
// 4(而不是 1 !)
注意:void * 指針是未確定的指針,它可以指向任何類型的變量。
數組:數組是一個連續佔有一段內存單元的變量集合。
比如:
int a[10]; // a:數組名,代表的是當前數組所在的首地址。
a = 3; // 錯誤;數組名a不是一個變量,它是一個地址常量,因
// 此對於a的賦值或自增是會報錯的;
// a[0]:表示的是當前首地址下存儲的內容;
//&a[0]:表示第一個元素的地址,這時候與a是一樣,即首地址
指針與數組區別:
1、指針是一個單獨變量,只是指向了其他變量的地址(相當於彙編中的間接尋址,與地址寄存器類似);
數組是一串元素序列並且真實的存儲元素內容,它的數組名可以相當一個指針(與指針的用法“基本”一樣),代表數組的首地址;
比如:
int a[10]; //系統實實在在分配了10*4個字節的連續內存單元
int* p=a; //也可以寫成 int* p = &a[0];p指向數組a[10]的首地址a[0];
//p只是一個變量,只佔據4個內存單元,存儲的是數組a的首地址
其實:p=p[0]=a[0]=*a;(p+i)=p[i]=a[i]=*(a+i);指針和數
組的取值可以互換。
2、 sizeof對於指針變量和數組的處理是不一樣的。拿上面的指針p 和數組a[10]來說,對於一個32位地址的系統。
sizeof(p)的值爲 4 個字節;
sizeof(a)的值爲 40 個字節;
原因是因爲,p是一個指針變量其內容存儲的是4個字節的地址;而數組名a並不是一個變量,它是一個常量的地址,sizeof將其視爲整個數組的代表,因此計算的時候會計算整個數組的大小。
但是下面的情況又會不同:
void computesize(int *a,int b[], int c[10]);
int main(){
int a[10];
computesize(a,a,a);
}
void computesize(int *a,int b[],int c[10]){
printf("a = %d , b = %d , c = %d",sizeof(a), sizeof(b),sizeof(c));
}
這時執行strlen(a,a,a); 假設是32位地址系統。
輸出的值並不是4、40、40,而是4、4、4,那是不是有矛盾了呢?並不是的,因爲這牽扯到了另一個知識點——函數參數的傳遞。我們知道,傳遞參數有值傳遞和地址傳遞。上面的情況就屬於地址傳遞,無論形參是*a、b[]還是c[10],傳入的時候都是將首地址傳給指針變量a 、b、c。b與c的不同就是b沒有指定長度,而c指定了長度。這時候其實a、b、c相當於一個指針變量而不是之前的數組了,否則每一次傳遞都要重新弄一個副本,系統會吃不消的。
二、指針與函數
1、指針與函數參數:指針和數組作爲參數傳遞的時候,其實是傳遞的一個地址。
比如:
int main(){
int b[10];
int* a = b;
printf(" %d ",sizeof(b));//輸出40
printf(" %d ",sizeof(a));//輸出4
fun(a,b);
}
void fun(int *a,int b[]){
printf(" %d ",sizeof(b));//輸出4
printf(" %d ",sizeof(a));//輸出4
};
傳進去的都是一個地址變量,sizeof計算出來的大小是一樣的(但是在定義他們的地方sizeof的長度是不一樣的)。
2、字符指針與函數:
//定義一個數組,內存中只有數組,存放在堆棧中(注意,此時字符串不是
//常量字符串,不在靜態區而是在數組所在的堆棧內存中)。
char aMeg[] = "I am a boy.";
//定義一個指針變量,內存中具有一個指針變量和一個字符串常量,並且字
//符串常量存放在靜態區中,指針在堆棧中分配。
char * pMeg = "I am a boy.";
三、指針數組、數組指針、指向指針的指針
1、指針數組:
形如:int * p[10]; //一個指針數組,數組裏面有10個元素,每一個元素都是一個int型的指針
數組內的每一個元素都是一個指針變量(這時注意每個元素所佔的內存單元大小是地址的長度而不是類型長度)
2、數組指針:形如:int (* b)[10];//一個數組指針,指針指向一個列長度爲十的一個二維數組的第一行的行地址。
也稱作行指針,該指針指向了一個長度爲10的數組的行首地址;
如:b表示第一行的首地址;b+1表示第二行首地址
*(b) = b[0][0]; *(b+1) = b[1][0]; *(*(b+i)+j) = b[i][j]
3、指向指針的指針:形如: int **c = p;//二級指針c,指向了指針數組p的首地址&p[0],即指向了指針數組的第一個指針的地址
該指針大小也是取決於操作系統,它跟一級指針其實本質上是沒有差別的,只是說是有連環指向的這種感覺。
c是指向指針的指針,因此它使用間接取址符時需要兩次才能取出目標的內容。
比如:*c表示的是p[0]的地址,而**c表示的是p[0]地址中的內容
c == &p[0]; // 指針數組中第一個指針元素的地址
*c == p[0]; // 指針數組中第一個指針元素地址的內容,即目標變量的地址
**c == *p[0]; // 指針數組中第一個指針元素指向的目標變量值
四、程序陷阱
1、*與++運算符的優先級相同,而且都是右結合的(一元都是的)。
a = *p++ :表示先將指針p自增,然後再取指針的內容賦值給a;
a =(*p)++: 表示先取指針內容賦值給a,然後指針再自增
2、/、*、%等二元運算符具有相同的優先級,而且結合性是自左向右的。
a = 1/2*a: 因爲是自左向右結合的,故表示的是 0.5*a,然後再賦值給a。
a = 1/(2*a): 這才表示求2*a的倒數,然後賦值給a。
3、字符串數組和指針
當指針指向字符串常量時,通過指針是不能修改字符串常量的值的。如:
char * p1 = "Hello World";
p1[5] = ','; //錯誤,這是一種C語言標準未定義的操作
對於p1[5] = ‘,’;不同系統會給出不同結果,在Turbo C環境下,可能會完成賦值過程,但是對於像VC++、Dev-C++來說這是一個錯誤的操作,因爲“Hello World”是一個字符串常量,存儲在常量區,從C語言的概念和定義上講,是沒有這個標準的。
4、利用malloc分配內存
如果想將某一個字符串複製到某一個控件,一定要記得分配足夠的空間大小,不要忘記”\0”結束符。 比如:
char * strSrc = "Hello Boy.";
char * strDest;
//錯誤,strlen並未計算"\0"結束符,賦值後的指針末尾指向未知空間。
strDest = (char *) malloc(strlen(strSrc));
//正確,爲"\0"留出空間。
strDest = (char *) malloc(strlen(strSrc)+1);
strcpy(strDest,strSrc);
5、空指針和空字符串的差別
空指針是指向0(NULL)的指針,C語言保證對空指針的操作是安全的。如下:
char * p = NULL; // #define NULL 0 ,這是C語言定義的NULL
而空字符串則是一個只有一個’\0’結束符的字符串,它在內存中是有存儲空間的。比如:
char p[] ="";
char p1[] = "\0";//與上面不同,這裏佔據兩個字符空間
char a[10];
printf("%d , %d \n",sizeof(p),strlen(p)); //輸出爲1,0 說明佔有一個字節空間,注意此時strlen(p)爲0,
printf("%d , %d \n",sizeof(p1),strlen(p1)); //輸出爲2,0 說明佔有兩個字節空間,注意此時strlen(p1)爲0!
6、strlen與sizeof的區別
sizeof:
1、計算所有變量類型的佔用字節大小
2、在計算字符串的時候,會將字符串後面的’\0’的大小也計算上來。
3、計算的是字節內存的大小
4、計算數組名的時候特殊,會計算整個數組的長度。其他的均計算單個變量strlen:
1、計算的是字符串的長度大小
3、計算字符串長度時,將會忽略’\0’結束符,遇到’\0’字符就會結束。
7、使用未初始化的指針
int x,*p;
x=10;
*p = x; // 錯誤,p並未指向一個確定的地方,它並沒有被初始化。
8、NULL指針
NULL指針並不指向任何對象,在賦值和比較運算意外的其他運算符都是非法的。由於標準並未對NULL指針賦值運算、比較運算意外的運算進行定義,因此這些運算都將得到一個不確定的結果。有時候可能給系統帶來災難性的後果。 如:
int *p = NULL;
int x = 10;
int a = *p * x;
int b = *p * x;
printf("%d %d",a,b); //在 Dev 上運行出錯