數組和指針的聯繫
數組名就是該數組首元素的地址
所以,下面的式子是正確的:
int data[] = {1, 2, 3}
data == &data[0]
可以驗證一下:
#include <stdio.h>
int main()
{
int data[] = { 1, 2, 3, 4, 5 };
int* p;
p = data;
printf("p = %p \n", p); // p = 008FFE04
if ( data == &data[0] ) // True
{
printf("True \n");
}
printf("data = %p \n", data); // data = 008FFE04
printf("&data[0] = %p \n", &data[0]); // &data[0] = 008FFE04
return 0;
}
從下面這個例子也能看出,其實數組名就是一個地址,因爲在 scanf 中並沒有用 &str,而是直接使用了 str:
#include <stdio.h>
int main()
{
char str[128];
printf("請輸入一串字符:");
scanf("%s", str); // ABCDE
printf("str = %s\n", str); // ABCDE
return 0;
}
那麼若要輸出數組中的全部元素,就需要使用循環操作。其中分爲三種方法:1)直接用數組元素下標循環獲取;2)利用數組名循環計算各元素的地址並獲取;3)利用指針的移動獲取數組元素。
#include <stdio.h>
int main()
{
int data[] = { 1, 2, 3, 4, 5 };
int* p;
p = data;
for (int i = 0; i < 5; i++) // 利用下標直接獲取數組元素
{
printf("i = %d, data = %d \n", i, data[i]);
}
for (int i = 0; i < 5; i++) // 利用數組名計算各數組元素的位置並獲取
{
printf("p = %p, data = %d \n", p + i, *(p + i));
}
for (int* pt = data; pt < (data + 5); pt++) // 利用指針間接獲取數組元素
{
printf("pt = %p, data = %d \n", pt, *pt);
}
return 0;
}
輸出:
i = 0, data = 1
i = 1, data = 2
i = 2, data = 3
i = 3, data = 4
i = 4, data = 5
p = 008FFE04, data = 1
p = 008FFE08, data = 2
p = 008FFE0C, data = 3
p = 008FFE10, data = 4
p = 008FFE14, data = 5
pt = 008FFE04, data = 1
pt = 008FFE08, data = 2
pt = 008FFE0C, data = 3
pt = 008FFE10, data = 4
pt = 008FFE14, data = 5
注意:對指針+1,等價於對指針的值加上它所指向的對象的字節大小。對於數組來說,地址會增加到下一個元素的地址,而不是下一個字節。這就是爲什麼在聲明指針變量時必須聲明它所指向對象的類型。所以下面的式子是成立的:
data + 2 == &data[2];
*(data + 2) == data[2];
數組和指針的區別
數組名非左值,不能對其值進行改變
比如下面這個例子:
int main()
{
char str[] = "Hello";
char* target = str;
int count = 0;
// while (*str++ != '\0') // 這樣寫是錯誤的
while (*target++ != '\0')
{
count++;
}
printf("共有%d個字符\n", count);
}
不能直接利用數組名(地址)進行自增運算,會報錯“error: lvalue required as increment operand”,這說明數組名非左值(左值:指用於識別或定位一個存儲位置的標識符,同時該標識符是可變的),由於數組名是不可改變的,所以數組名就不是左值。
如果不小心對 str 進行自增,會向你提示:表達式必須是可修改的左值:
但是下面這樣的運算是允許的,因爲並沒有對數組名(地址)進行修改:
int main()
{
int data[] = { 1, 2, 3, 4, 5 };
int* p;
printf("data[0] = %d, data[1] = %d, data[2] = %d \n", *data, *(data+1), *(data+2));
return 0;
}
>>>
data[0] = 1, data[1] = 2, data[2] = 3
指針數組和數組指針
指針數組是數組,每個數組元素存放一個指針變量;
數組指針是指針,它指向一個有n個元素的數組。
關於指針數組:
int main()
{}
char *p[3] = {"ABC", "DEF", "GHI"};
int i
for (i = 0; i < 3; i++)
{
printf("%s ", p[i]); // ABC DEF GHI
}
return 0;
}
需要注意的是,字符串常量和數組名一樣,被編譯器當作指針對待,字符串的值就是字符串的基地址。
關於字符串本身就是地址,可以驗證一下:
int main()
{
char str[] = "We have some apple.";
printf("%s \n", str);
printf("%p \n", str);
printf("%p \n", &str[0]);
printf("%c \n", *str);
return 0;
}
>>>
We have some apple.
006FFDB8
006FFDB8
W
關於數組指針:
重點是區分“常規的指向數組的指針”和“數組指針”之間的差別,看一個例子:
int main()
{
int str[] = { 1, 2, 3 };
int* p1 = str; // 這是指向數組第一個元素的指針
int(*p2)[3] = &str; // 這是數組指針,是一個整體
for (int i = 0; i < 3; i++)
{
// p1本身就是數組第一個元素的地址
printf("%d \n", *(p1 + i));
}
printf("%p \n", *p2);
for (int j = 0; j < 3; j++)
{
// 內部p2上的*是取數組第一個元素的地址,外部*是取值
printf("%p, %d \n", *p2 + j, *(*p2 + j));
}
return 0;
}
1
2
3
012FFE9C
012FFE9C, 1
012FFEA0, 2
012FFEA4, 3
可以發現二者的區別,對數組指針初始化,必須是一個“地址”。可以將二者理解爲,p1其實是指向數組第一個元素的指針,它存儲的是數組第一個元素的地址。而p2指向的是“整個數組”。
數組指針是指向整個數組的,這個概念怎麼理解呢?再看一個例子:
int main()
{
int str[] = { 1, 2, 3 };
int* p1 = str;
int(*p2)[3] = &str; // 數組指針
for (int i = 0; i < 3; i++)
{
printf("p1 + %d = %p, *(p1 + %d) = %d \n", i, p1 + i, i, *(p1 + i));
}
for (int i = 0; i < 3; i++)
{
printf("p2 + %d = %p, *(p2 + %d) = %p \n", i, p2 + i, i, *(p2 + i));
}
return 0;
}
>>>
p1 + 0 = 00D6FCDC, *(p1 + 0) = 1
p1 + 1 = 00D6FCE0, *(p1 + 1) = 2
p1 + 2 = 00D6FCE4, *(p1 + 2) = 3
p2 + 0 = 00D6FCDC, *(p2 + 0) = 00D6FCDC
p2 + 1 = 00D6FCE8, *(p2 + 1) = 00D6FCE8
p2 + 2 = 00D6FCF4, *(p2 + 2) = 00D6FCF4
第一個for循環很好理解,就是普通的指針不斷+1並取出對應位置上的元素,每個int型變量4個byte,所以p1+0,p1+1,p1+2相差4。
第二個for循環,p2+i 和 *(p2+i) 的輸出結果爲什麼一摸一樣?而且爲什麼每兩個值之間相差8?
因爲數組指針是“指向數組整體”,所以數組指針 p2+1,就相當於指針要跳過“整個數組的大小”,而非“一個int變量的大小”,我們上面定義數組str的大小剛好是12byte(3x4byte),所以每次p2加1,就是跳過12個byte的大小。p2+i是指向數組整體,那麼*(p2+i)就是指向當前數組的第一個元素,所以兩者值是相同的。
並且第二個for循環裏的循環變量i實際上和數組大小並沒有關係,你也可以指定爲更大的數:
for (int i = 0; i < 10; i++)
{
printf("p2 + %d = %p, *(p2 + %d) = %p \n", i, p2 + i, i, *(p2 + i));
}
>>>
p2 + 0 = 001EF748, *(p2 + 0) = 001EF748
p2 + 1 = 001EF754, *(p2 + 1) = 001EF754
p2 + 2 = 001EF760, *(p2 + 2) = 001EF760
p2 + 3 = 001EF76C, *(p2 + 3) = 001EF76C
p2 + 4 = 001EF778, *(p2 + 4) = 001EF778
p2 + 5 = 001EF784, *(p2 + 5) = 001EF784
p2 + 6 = 001EF790, *(p2 + 6) = 001EF790
p2 + 7 = 001EF79C, *(p2 + 7) = 001EF79C
p2 + 8 = 001EF7A8, *(p2 + 8) = 001EF7A8
p2 + 9 = 001EF7B4, *(p2 + 9) = 001EF7B4
現在知道了p2實際上是指向整個數組,就不難想到如何來取值了:先*p2取得數組第一個元素的地址,然後再*(*p2+i)取數組第i個元素的地址,這樣兩種方法得到的結果就是一樣的:
int main()
{
int str[] = { 1, 2, 3 };
int* p1 = str;
int(*p2)[3] = &str;
for (int i = 0; i < 3; i++)
{
printf("p1 + %d = %p, *(p1 + %d) = %d \n", i, p1 + i, i, *(p1 + i));
}
printf("p2 = %p, *p2 = %p \n", p2, *p2);
for (int i = 0; i < 3; i++)
{
printf("*p2 + %d = %p, *(*p2 + %d) = %d \n", i, *p2 + i, i, *(*p2 + i));
}
return 0;
}
>>>
p1 + 0 = 012FFC78, *(p1 + 0) = 1
p1 + 1 = 012FFC7C, *(p1 + 1) = 2
p1 + 2 = 012FFC80, *(p1 + 2) = 3
p2 = 012FFC78, *p2 = 012FFC78
*p2 + 0 = 012FFC78, *(*p2 + 0) = 1
*p2 + 1 = 012FFC7C, *(*p2 + 1) = 2
*p2 + 2 = 012FFC80, *(*p2 + 2) = 3