前言
學習C語言,指針絕對是一道大坎,很多人談指針色變,使用起來小心翼翼的。“一切指針都是紙老虎” ,同時,對我們得“在戰略上藐視指針,戰術上重視指針”。
本文先剖析下一維數組和指針,多維的情況后序博客繼續更新。
文章流程:
1、辨析指針和數組的不同
2、辨析它們相同的時刻
3、總結
指針和數組為什么這么糾纏不清
首先說一點,指針的使用等同於數組的情況遠遠多於他們不同的情況,因此,在初學時,為了容易理解,很多人都說 “指針=數組”。 但是,這卻是錯誤的!
來個例子吧:
// file1.c 定義一個數組array int array[100];
以上是在文件1中的定義
// file2.c 聲明file1.c中的array extern int* array
以上是在文件2中的聲明
上面的例子對不對呢? 大家可以自己測試下,會很神奇的發現: 咔,怎么編譯出錯啦! 為什么呢?
繼續深入聲明和定義
在辨析數組和指針前,首先要說下聲明和定義的區別,之前博文“為何不精通C? 03 深入剖析聲明” 中的末尾已經提過這一點了。這里重新說下:
C語言中,對象有且僅有一個定義,而聲明卻可以有多個extern 聲明。
定義:只能出現在一個地方,確定同時分配內存,它是特殊的聲明
聲明:只是描述其他地方創建對象的屬性。有extern前綴,作用於變量
對於聲明,就像斷言一樣,說是什么就是什么,不容許有妥協的余地,因為在聲明的同時,也規定了指針/數組的移動方式及長度。
So, 對於上面的例子,我們可以知道,file2.c中,array被聲明成一個int指針。但是,file1.c 中的定義卻是數組。 他們不兼容! 為什么不兼容呢?
通過指針和數組訪問數據的方式不同
這里先補充一個知識點,sizeof(x) 這個運算符,它返回的是x在內存中的字節數。
1 #include "stdio.h" 0 main () 1 { 2 int array[20]; 3 printf("類型\t字節數\t指針字節數\n"); 4 printf("%s\t%d\t%d\n","char",sizeof(char), sizeof(char*)); 5 printf("%s\t%d\t%d\n","short",sizeof(short), sizeof(short*)); 6 printf("%s\t%d\t%d\n","int",sizeof(int), sizeof(int*)); 7 printf("%s\t%d\t%d\n","long",sizeof(long), sizeof(long*)); 8 printf("%s\t%d\t%d\n","float",sizeof(float), sizeof(float*)); 9 printf("%s\t%d\t%d\n","double",sizeof(double), sizeof(double*)); 10 printf("%s\t%d\t%d\n","array",sizeof(array), sizeof(&array[0])); 11 return 0; 12 }

這里,我們可以歸納出,所有的指針長度都是固定的, 都是4字節(因為我的電腦是32位=4*8bit。同理,在64位電腦這里顯示8)。
我們認真觀察下第10行,sizeof(array), sizeof(&array[0]),(因為我們都說數組名就是首地址指針,所以我這么表示。)
可以看出,sizeof(array)打印出了80, 即20*4,20個int的字節長度。但是sizeof(&array[0]),還是4,指針的固定長度。喏,一大區別就在這!
好,通過sizeof, 能夠很好認識到原來數組和指針還真存在着不同點。現在繼續說下通過指針和數組對數組元素訪問的不同策略。
由sizeof可以知道,在定義數組時,等同於在內存中連續分配了N長度的空間,數組的首地址(假設是5000)代表這段空間的起始點。比如通過array[i]訪問時,步驟如下:
- 移動到 5000+i*4 的地址
- 取存在這里的值
而通過指針呢? 我們都只是指針中保存的是地址值, 如 int*p = array; 這里,假設p的地址為6000, 它存的值為array的首地址5000, 通過 p[i]訪問是,步驟如下:
- 取指針p中的值5000,移動到該地址
- 移動到5000+i*4的地址
- 取存在這里的值
可以看出,多了一個步驟。這個多出來的步驟,其實大家都知道,這也說明了指針的靈活性。
關於字符串數組、指針的初始化
關於字符數組,我們可以這么來初始化:
char *p = "helloworld"; char a[] = "helloworld";
我們來辨析下內部隱含的區別吧:
對於指針p, 系統為我們分配了匿名一個字符串常量,這個常量是只讀的,把它的地址給了p, 因此不能通過p[i]修改字符串常量的值。
對於數組a, 系統定義了一個連續的內存塊來分配字符串,它的首地址是a, 我們可以通過 a[i]操作來修改某個字符的值。
關於字符串數組,指針的左值性
在C語言中,數組的首地址被定義為“不可修改的左值”, 即類似 int * const a 這樣的聲明, 我們可以通過它修改它指向的內容,卻不能把它賦個新值。即:允許a[i]=yyy,而對 a=xxx 卻是提示非法的!
指針呢,就看他怎么聲明咯, 一般來說,就是 int* p, 我們可以 p=a, p=xxx, p[i]=yyy ……,都是允許的。
好啦,大概的區別點就是上述表示的了,那我們繼續探討下他們什么時候相同。
指針等同於數組的時候
我們知道,指針充滿了靈活性,而數組卻是比較死板的東西,這么說來,就必須先理解好數組,才能更好的了解指針,我們先來說下數組。對於C語言來說,任何事物都必須聲明,再使用,我們也按這個順序,先說下聲明:
數組的聲明
- 1、外部數組的聲明
- 2、數組的定義
- 3、函數參數的聲明, 這時候,隨便你寫指針形式還是數組形式,他們都是等同的。
數組的使用
任何時候,都可以變成指針表達式
我們歸納下:
1、 “表達式中的數組名” 就是指針, 及你可以使用a[i]; *(a+i); p=a, p+i,*p;等形式表達同樣的意思。
2、C語言將數組下標作為指針的偏移量
3、作為函數形參的數組名,等同於指針,即:
my_func(int* p); my_func(int p[]); my_func(int p[200]);
都是一個意思,編譯器都把它翻譯成指針。
總結
采用《C專家編程》中的總結,我適當修改合並了一些。作者建議我們在自己真正理解數組、指針后,要先自我總結下,再看他的總結。
1、用 a[i]形式的表達式,編譯器都解釋成*(a+i);
2、指針永遠是指針,不可改寫成數組。你可以通過下標形式訪問指針,且必須是你知道指針指向了是一個數組。 畢竟,編譯器並不知道 int*指向了什么,一個int數組還是int變量地址。
3、作為函數參數時,數組都被改寫成指針的形式來表達,即類似於 p=&a[0];因此,對於編譯器而言,參數表中沒有數組,只有指針。
4、聲明和定義必須匹配!若是定義了數組,在其他文件聲明時也必須是數組,指針亦然。
