聊聊C語言中使用頻率較高的指針與數(shù)組
定義
指針:C語言中某種數(shù)據(jù)類型的數(shù)據(jù)存儲的內(nèi)存地址,例如:指向各種整型的指針或者指向某個結(jié)構(gòu)體的指針。
數(shù)組:若干個相同C語言數(shù)據(jù)類型的元素在連續(xù)內(nèi)存中儲存的一種形態(tài)。
數(shù)組在編譯時就已經(jīng)被確定下來,而指針直到運行時才能被真正的確定到底指向何方。所以數(shù)組的這些身份(內(nèi)存)一旦確定下來就不能輕易的改變了,它們(內(nèi)存)會伴隨數(shù)組一生。
而指針則有很多的選擇,在其一生他可以選擇不同的生活方式,比如一個字符指針可以指向單個字符同時也可代表多個字符等。
指針和數(shù)組在C語言中使用頻率是很高的,在極個別情況下,數(shù)組和指針是“通用的”,比如數(shù)組名表示這個數(shù)組第一個數(shù)據(jù)的指針。
如下代碼:
#include <stdio.h>
char array[4] = {1, 2, 3, 4};
int main(void)
{
char * p;
int i = 0;
p = array;
for (; i < 4; i++)
{
printf("*array = %d\n", *p++);
}
return (0);
}
這里我們將數(shù)組名array作為數(shù)組第一個數(shù)據(jù)的指針賦值給p。但是不能寫成*array++。準確來說數(shù)組名可以作為右值,不能作為左值(左值和右值的概念這里不再展開講解)。
數(shù)組名的值其實是一個指針常量,這樣我想你就明白了數(shù)組名為什么不能做為左值了。如果想用指針p訪問array的下面2的數(shù)據(jù),以下寫法是合法的。
char data;
/*第一種寫法*/
p = array;
data = p[2];
/*第二種寫法*/
p = array;
data = *(p+2);
/*第三種寫法*/
p = array +
指針與二維數(shù)組
先說一下二維數(shù)組,二維數(shù)組在概念上是二維的,有行和列,但在內(nèi)存中所有的數(shù)組元素都是連續(xù)排列的,它們之間沒有“縫隙”。以下面的二維數(shù)組 a 為例:
int a[3][4] = { {0, 1, 2, 3}, {4, 5, 6, 7}, {8, 9, 10, 11} };
從概念上理解,a 的分布像一個矩陣:
- 0 1 2 3。
- 4 5 6 7。
- 8 9 10 11。
但是內(nèi)存是連續(xù)的,沒有這樣的“矩陣內(nèi)存”,所以二維數(shù)組a分布是連續(xù)的一塊內(nèi)存。
C語言允許把一個二維數(shù)組分解成多個一維數(shù)組來處理。對于數(shù)組 a,它可以分解成三個一維數(shù)組,即 a[0]、a[1]、a[2]。每一個一維數(shù)組又包含了 4 個元素,例如 a[0] 包含 a[0][0]、a[0][1]、a[0][2]、a[0][3]。那么定義如下指針如何理解呢?
int (*p)[4];
括號中的*表明 p 是一個指針,它指向一個數(shù)組,數(shù)組的類型為int [4],這正是 a 所包含的每個一維數(shù)組的類型。那么和下面定義有什么區(qū)別呢?
int *p[4];
這里就要先說明*和[]的優(yōu)先級了,[]的優(yōu)先級是高于*的,所以int *p[4];等同于int *(p[4]);。所以它是一個指針數(shù)組。這里很繞,總接下:
int (*p)[4];是數(shù)組指針,它指向二維數(shù)組中每個一維數(shù)組的類型,它指向的是一個數(shù)組。
int *p[4];是指針數(shù)組,它是一個數(shù)組,數(shù)組中每個數(shù)是指向int型的指針。
指針數(shù)組與數(shù)組指針
對于指針數(shù)組,說的已經(jīng)很明確了,不再詳細講解,下面說一下數(shù)組指針。舉例看一下:
#include <stdio.h>
int main()
{
int a[3][4] = {{0, 1, 2, 3}, {4, 5, 6, 7}, {8, 9, 10, 11}};
int(*p)[4];
p = a;
printf("%d\n", sizeof(*(p + 1)));
return (0);
}
對于數(shù)組指針p如下:
那么printf("%d\n", sizeof(*(p +
1)));的結(jié)果就是16。如果想打印a[1][0]的值,代碼如下:
printf("%d\n", *(*(p + 1)));
如果想打印a[1][1]的值,代碼如下:
printf("%d\n", *(*(p + 1)+1));
這個代價自行體會,p是數(shù)組指針,它指向的是一個數(shù)組,所以對獲取它指向的值,也就是*p,是指向一個數(shù)組還是一個值,指向a[0]。獲取獲取a[0][0],就需要寫成**p。
對指針進行加法(減法)運算時,它前進(后退)的步長與它指向的數(shù)據(jù)類型有關(guān),p 指向的數(shù)據(jù)類型是int [4],那么p+1就前進 4×4 = 16 個字節(jié),p-1就后退 16 個字節(jié),這正好是數(shù)組 a 所包含的每個一維數(shù)組的長度。也就是說,p+1會使得指針指向二維數(shù)組的下一行,p-1會使得指針指向數(shù)組的上一行。
最后再次捋一下數(shù)組指針和指針數(shù)組。
int *p1[4];是指針數(shù)組。
int (*p2)[4];是數(shù)組指針。
“[]”的優(yōu)先級比“*”要高。
對于指針數(shù)組,p1先和“[]”結(jié)合,構(gòu)成一個數(shù)組的定義,數(shù)組名為p1,int *修飾的是數(shù)組的內(nèi)容,即數(shù)組的每個元素。那么它本質(zhì)是一個數(shù)組,這個數(shù)組里有4個指向int類型數(shù)據(jù)的指針。
對于數(shù)組指針,“()”的優(yōu)先級比“[]”高,“*”號和p2 構(gòu)成一個指針的定義,指針變量名為p2,int 修飾的是數(shù)組的內(nèi)容,即數(shù)組的每個元素。數(shù)組在這里并沒有名字,是個匿名數(shù)組。那么它本質(zhì)是一個指針,它指向一個包含4個int 類型數(shù)據(jù)的數(shù)組。
既然深入談了指針數(shù)組和數(shù)組指針,就多聊一下。
#include <stdio.h>
int main()
{
char a[5] = {'A', 'B', 'C', 'D'};
char(*p3)[5] = &a;
char(*p4)[5] = a;
return 0;
}
上面代碼是編譯編譯是報了waring的,報警如下:
注意:不同的編譯器編譯結(jié)果可能不同,我的編譯方法請參考《使用vscode編譯C語言》。p3 這個定義的“=”號兩邊的數(shù)據(jù)類型完全一致,而p4 這個定義的“=”號兩邊的數(shù)據(jù)類型就不一致了。左邊的類型是指向整個數(shù)組的指針,右邊的數(shù)據(jù)類型是指向單個字符的指針。所以才有了上面的警告。
但由于&a 和a 的值一樣,而變量作為右值時編譯器只是取變量的值,所以運行并沒有什么問題。不過編譯器仍然警告你別這么用。
再舉一個栗子:
int vector[10];
int matrix[3][10];
int *vp,*vm;
vp = vector;
vm = matrix;
上面的代碼第5行是錯誤的,因為vm是指向整型的指針,但是matrix不是指向正向的指針,他是指向整型數(shù)組的指針。下面是正確的寫法:
int matrix[3][10];
int (*vm)[10];
vm = matrix;
數(shù)組指針的應(yīng)用
上面說了那么多,可能大部分開發(fā)者用不到,數(shù)組指針在很多時候都是可以代替二維數(shù)組的,有些程序員喜歡用指針數(shù)組來代替多維數(shù)組,一個常見的用法就是處理字符串。
#include <stdio.h>
char *Names[] =
{
"Bill",
"Sam",
"Jim",
"Paul",
"Charles",
0};
void main()
{
char **nm = Names;
while (*nm != 0)
printf("%s \n", *nm++);
}
具體運行我就不講解了,運行結(jié)果如下:
注意數(shù)組中的最后一個元素被初始化為0,while循環(huán)以次來判斷是否到了數(shù)組末尾。具有零值的指針常常被用做循環(huán)數(shù)組的終止符。
這種零值的指針稱為為空指針(NULL)。采用空指針作為終止符,在增刪元素時就不必改動遍歷數(shù)組的代碼,因為此時數(shù)組仍然以空指針作為結(jié)束。
操作
寫到這里想到一個“操作”,先看下面代碼是否正確。
p[-1]=0;
初看這句代碼,覺得奇怪,甚至覺得它就是錯誤,日常C語言開發(fā)基本有見到小標是負數(shù)的,但是仔細想想沒有哪一本書說過下標能為負數(shù)的。看下面代碼:
void main()
{
int data[4] = {0, 1, 2, 3};
int *p;
p = data +2;
printf("p[-1] is %d\n",p[-1]);
printf("*(p-1) is %d\n",*(p-1));
}
運行結(jié)果如下:
不僅可以編譯通過,還能正確的輸出結(jié)果為1。這表明,C的下標引用和間接訪問表達式是一樣的。當然不鼓勵這種操作,代碼需要很強的可讀性,而不是這樣的操作,這里只是演示下標引用和簡介表達式的關(guān)系。