數組名可以當做常量指針使用,那麼指針是否也可以當做數組名來使用呢?
數組的訪問方式:
以下標的形式訪問數組中的元素;
以指針的形式訪問數組中的元素;
指針以固定增量在數組中移動時,效率高於下標形式,指針增量爲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;
}
注意:
現代編譯期的生成代碼優化率已經大大提高,在固定增量時,下標形式的效率已經和指針形式的效率相當,但從可讀性和代碼維護的角度來看,下標的形式更優。
數組和指針的不同之處:
在ext.c中:
int a[] = {1, 2, 3, 4, 5}; //定義數組
在test.c中:
#include <stdio.h>
int main()
{
extern int* a; //聲明指針
printf("&a = %p\n", &a); //a定義時的地址值
printf("a = %p\n", a); //在這個地址值上取第一個數作爲地址
printf("*a = %d\n", *a); //ox1,這個內存地址是預留給操作系統的,所以無法訪問
return 0;
}
編譯爲報錯,運行:
./a.out
&a = 0x804a014
a = 0x1
段錯誤
但如果這樣聲明的時候:
#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
爲什麼會出現這種情況呢?數組名是不是指針呢?
首先必須明確,數組名並不是指針。
在外部.c文件中,定義了一個包含五個int類型的數組,所以編譯器會在內存中分配20個字節的空間,其地址如上所示。換句話說,標識符a在編譯後得到一個新的意義,代表了一個地址:0x804a014,接下來,當編譯text.c時,發現聲明瞭的extern int* a,編譯器在此時將默認爲a爲一個外部定義的指針,代表一個地址。
所以第一行打印就打印了a的地址值,第二行打印呢?a的值是什麼呢?a是一個指針變量,變量有四個字節,所以編譯器會在a的地址處讀取四個字節的地址,因爲此時a被認爲是一個指針,所以此時讀取的是在這個地址(0x804a014)上的0001這個數,即0x1。但是0x1這個地址是預留給操作系統的,但凡用戶態的程序要訪問這個地址(第三行打印),就會出現段錯誤。
a與&a的區別:
a爲數組首元素的地址
&a爲整個數組的地址
a和&a的區別在於指針運算:
#include <stdio.h>
int main()
{
int a[5] = {1, 2, 3, 4, 5};
int* p1 = (int*)(&a + 1);
int* p2 = (int*)((int)a + 1);
int* p3 = (int*)(a + 1);
printf("%d, %d, %d\n", p1[-1], p2[0], p3[1]);
return 0;
}
上述代碼編譯運行輸出什麼呢?
// A. 數組下標不能是負數,程序無法運行
// B. p1[-1]將輸出隨機數,p2[0]輸出2, p3[1]輸出3
// C. p1[-1]將輸出亂碼, p2[0]和p3[1]輸出2
值得注意的是p2,在對a進行運算時進行了強制類型轉換,這樣進行運算時,就不是地址值了。將代碼修改如下,更方便理解:
#include <stdio.h>
int main()
{
int a[5] = {1, 2, 3, 4, 5};
int* p1 = (int*)(&a + 1);
int* p2 = (int*)((int)a + 1);
printf("a = %p\n", a);
printf("*(int*)((int)a) = %d\n", *(int*)((int)a));
printf("*(int*)((int)a + 1) = %d\n", *(int*)((int)a + 1));
int* p3 = (int*)(a + 1);
printf("%d, %d, %d\n", p1[-1], p2[0], p3[1]);
return 0;
}
編譯運行:
~/will$ ./a.out
a = 0xbffe3780
*(int*)((int)a) = 1
*(int*)((int)a + 1) = 33554432
5, 33554432, 3
p2 = 0xbffe3780 + 1 = 0xbffe3781; p2已經不是指針運算了,即在原地址處加1,
p2[0] = *p2 ; 即在0xbffe3781處的數據:
0002
1000 2000 3000 4000 5000//小端系統
*p2 ---> 0x0200 0000
0x02000000十六進制轉化爲十進制爲33554432
數組參數:
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 = NULL; //此處的a是指針,不表示數組
}
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;
}
編譯運行:
~/will$ gcc test.c
~/will$ ./a.out
In func1: sizeof(a) = 4
array[0] = a
In func2: sizeof(b) = 4
array[0] = b
數組參數並不是數組了,而是一個指針。
小結:
數組名和指針僅使用方式相同
數組名的本質不是指針
指針的本質不是數組
數組名並不是數組的地址,而是數組元素的地址(體現在指針運算)
函數的數組參數退化爲指針。