C語言指標與陣列易混淆知識點(一)
一、指標與陣列
指標:指標本身也是一個變數,它的內容是指向的內容的地址。指標同樣有型別的區分,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 上執行出錯