20 陣列和指標
目錄
1 陣列的本質
-
陣列是一段連續的記憶體空間
-
陣列的空間大小為
sizeof(array_type)*array_size
-
陣列名可看作指向陣列第一個元素的常量指標,但陣列名絕不是指標
-
a + 1
的意義#include <stdio.h> int main() { int a[5] = {0}; int* p = NULL; printf("a = 0x%X\n", (unsigned int)(a)); //指標運算的結果是一個指標, printf("a + 1 = 0x%X\n", (unsigned int)(a + 1)); printf("p = 0x%X\n", (unsigned int)(p)); printf("p + 1 = 0x%X\n", (unsigned int)(p + 1)); return 0; } //輸出結果 a = 0xBFF51B48 a + 1 = 0xBFF51B4c p = 0x0 p + 1 = 0x4
2 指標的運算
-
指標是一種特殊的變數,與整數的運算規則為:
p + n; => (unsigned int)p + n * sizeof(*p);
-
當指標 p 指向一個同類型的陣列的元素時:
p + 1
將指向當前元素的下一個元素;p - 1
將指向當前元素的上一個元素 -
指標之間只支援減法運算,參與減法運算的指標型別必須相同:
p1 - p2; => ((unsigned int)p1 - (unsigned int)p2) / sizeof(type);
-
只有當兩個指標指向同一個陣列中的元素時,指標相減才有意義,其意義為指標所指元素的下標差
-
當兩個指標指向的元素不在同一個陣列中時,結果未定義
3 指標的比較
-
指標也可以進行關係運算(<,<=,>,>=)
-
指標關係運算的前提是同時指向同一個陣列中的元素
-
任意兩個指標之間的比較運算(==,!=)無限制
-
參與比較運算的指標型別必須相同
-
示例1
#include <stdio.h> int main() { char s1[] = {'H', 'e', 'l', 'l', 'o'}; int i = 0; char s2[] = {'W', 'o', 'r', 'l', 'd'}; char* p0 = s1; char* p1 = &s1[3]; char* p2 = s2; int* p = &i; printf("%d\n", p0 - p1); //-3 printf("%d\n", p0 + p2); //error printf("%d\n", p0 - p2); //error printf("%d\n", p0 - p); //error printf("%d\n", p0 * p2); //error printf("%d\n", p0 / p2); //error return 0; }
-
示例2
#include <stdio.h> #define DIM(a) (sizeof(a) / sizeof(*a)) int main() { char s[] = {'H', 'e', 'l', 'l', 'o'}; char* pBegin = s; char* pEnd = s + DIM(s); // Key point char* p = NULL; printf("pBegin = %p\n", pBegin); printf("pEnd = %p\n", pEnd); printf("Size: %d\n", pEnd - pBegin); for(p=pBegin; p<pEnd; p++) { printf("%c", *p); } printf("\n"); return 0; } //輸出結果 pBegin = 0xbfac155f pEnd = 0xbfac1564 size = 5 Hello
4 陣列的訪問方式
-
陣列名可以當作常量指標使用,那麼指標是否也可以當作陣列名來使用?
- 可以!
-
以下標的形式訪問陣列中的元素
int main() { int a[5] = {0}; a[1] = 3; a[2] = 5; return 0; }
-
以指標的形式訪問陣列中的元素
int main() { int a[5] = {0}; //陣列名看作常量指標 *(a + 1) = 3; *(a + 2) = 5; return 0; }
-
下標形式 VS 指標形式
- 指標以固定增量在陣列中移動時,效率高於下標形式
- 指標增量為 1 且硬體具有硬體增量模型時,效率更高
- 下標形式與指標形式的轉換:
a[n] <=> *(a + n) <=> *(n + a) <=> n[a]
- 現代編譯器的生成程式碼優化率已經大大提高,在固定增量時,下標形式的效率已經和指標形式相當;但從可讀性和程式碼維護的角度來看,下標形式更優
-
陣列的訪問方式
#include <stdio.h> int main() { int a[5] = {0}; int* p = a; int i = 0; for(i=0; i<5; i++){ //將指標當作陣列名使用 p[i] = i + 1; } for(i=0; i<5; i++){ printf("a[%d] = %d\n", i, *(a + i)); } printf("\n"); for(i=0; i<5; i++){ //等價代換公式 i[a] = i + 10; } for(i=0; i<5; i++){ printf("p[%d] = %d\n", i, p[i]); } return 0; } //輸出結果 a[0] = 1 a[1] = 2 a[2] = 3 a[3] = 4 a[4] = 5 p[0] = 10 p[1] = 11 p[2] = 12 p[3] = 13 p[4] = 14
-
陣列和指標不同
//ext.c int a[] = {1, 2, 3, 4, 5}; //test.c #include <stdio.h> int main() { extern int a[]; printf("&a = %p\n", &a);//陣列地址 printf("a = %p\n", a);//陣列第一個元素的地址 printf("*a = %d\n", *a);//取“陣列第一個元素的地址”上的值 return 0; }
-
執行結果
&a = 0x804a014 a = 0x804a014 *a = 1
-
修改:驗證陣列名與指標是否一樣。執行結果分析:
- ext.c 中定義了一個數組,在記憶體為:
1000 2000 ... 5000
(Linux為小端系統) ,一共 20 個位元組 - 該陣列在記憶體中的地址為:
0x804a014
,也就是說在編譯後,識別符號a
的意義為一個地址:0x804a014
- 在編譯 test.c 時,當編譯到
extern int* a;
時,發現識別符號a
在別處定義 - 當編譯到
printf("&a = %p\n", &a);
時,列印識別符號a
的地址,即為:0x804a014
- 當編譯到
printf("a = %p\n", a);
時,列印的是一個指標變數a
,其值儲存的是一個地址,那麼取 4 個位元組的值即為:0x1
- 當編譯到
printf("*a = %d\n", *a);
時,*a
是到地址0x1
取值,而此地址為作業系統所使用,產生段錯誤
//test.c #include <stdio.h> int main() { //陣列名改為指標 extern int* a; printf("&a = %p\n", &a); printf("a = %p\n", a); printf("*a = %d\n", *a); return 0; } //執行結果 &a = 0x804a014 a = 0x1 段錯誤
- ext.c 中定義了一個數組,在記憶體為:
-
4 a 和 &a 的區別
-
a 為陣列首元素的地址
-
&a 為整個陣列的地址
-
a 和 &a 的區別在於指標運算
a + 1 => (unsigned int)a + szieof(*a)
&a + 1 => (unsigned int)(&a) + sizeof(*&a) => (unsigned int)(&a) + sizeof(a)
- 二者加 1 增加的步長不一樣
-
指標運算示例
#include <stdio.h> int main() { int a[5] = {1, 2, 3, 4, 5}; printf("a = %p\n",a); int* p1 = (int*)(&a + 1); //p1指向陣列a最後一個元素5後的下一個位置 int* p2 = (int*)((int)a + 1); //整數加1是數學運算,結果為整數,p2指向一個地址:0xbfa71990+1=0xbfa71991,=> p2 = 0x0200 0000 =>十進位制數:33554432 int* p3 = (int*)(a + 1); //p3指向第2個元素 printf("%d, %d, %d\n", p1[-1], p2[0], p3[1]); return 0; } //執行結果 a = 0xbfa71990 5,33554432,3
5 陣列引數
-
陣列作為函式引數時,編譯器將其編譯成對應的指標
void f(int a[]); <=> void f(int* a);
void f(int a[5]); <=> void f(int* a);
-
一般情況下,當定義的函式中有陣列引數時,需要定義另一個引數來標示陣列的大小
#include <stdio.h> void func1(char a[5]) { printf("In func1: sizeof(a) = %d\n", sizeof(a)); *a = 'a'; //如果a是陣列名的話會報錯:陣列名不可以賦值 a = NULL; } void func2(char b[]) { printf("In func2: sizeof(b) = %d\n", sizeof(b)); *b = 'b'; b = NULL; } int main() { char array[10] = {0}; func1(array); printf("array[0] = %c\n", array[0]); func2(array); printf("array[0] = %c\n", array[0]); return 0; } // 執行結果 In func2: sizeof(a) = 4 array[0] = a In func2: sizeof(b) = 4 array[0] = b