徹底搞懂c語言數組與指針
部分引用
- c語言指針怎么理解 知乎
- 程序設計入門————c語言 (浙江大學翁愷)
- 《c primer plus》第六版
基礎知識
1. 指針基礎

- 關於電腦大小端的討論:大端是指是指數據的高字節,保存在內存的低地址中,而數據的低字節,保存在內存的高地址中。小端是指數據的高字節保存在內存的高地址中,而數據的低字節保存在內在的低地址中。例如下圖:

- 假設 int b=4; int *a = &b 則*a=4: 因為*a代表a變量中的地址所指的值。重復一下對比:&b是指針類型,值是地址;*b是實際指針b所指的變量的值。
- 如果打印地址則用%p,以16進制顯示指針的值,而不是用%x,如: printf("%p\n", &i)
- 32位和64位下指針的長處不同,32位下為4個字節,和int一樣,64位下8個字節。
2. 數組基礎
-
數組特點:
- 數組大小一旦定義不可改變
- 所有的元素具有相同的數據類型
- 數組中的元素在內存中是連續依次排列的
- 數組的集成初始化:int a[] = {1,2,3,4,25,6,5,4}; 即讓編譯器自己來數元素的數量
- 如果這樣賦值:int a[3] = {2}; 則結果是這個數組有三個元素,a[0]=2,a[1]=0,a[2]=0;編譯器自動補全后面的數字為0;
- 集成初始化的定位:int a[6] = {[1] = 2, [3] = 3, 6}; 結果是 a[0]=0, a[1]=2, a[2]=0, a[3]=3, a[4]=6, a[5]=0; 適合初始數據稀疏的數組。
- 如果想讓定義的數組變成只讀,即不可修改的類型,則可以在最前面加上一個const。如:const int a[2] = {2, 3, 4}; 當然此條也適用於二維數組。
- 數組只有在最開始即定義初始化的時候可以集成賦值,下列賦值方法錯誤:int a[3] = {}; a[3] = {1,2,3}; 這時會錯誤,因為這是一個單個元素賦值的方法,況且a[3]已經超出了范圍。
- 求數組的大小,穩定的方法是 sizeof(a)/sizeof(a[0]) ; 就算修改初始數組a中的數據,也不用修改遍歷時的代碼;
- 數組作為函數參數時,往往必須再用另一參數來傳入數組的大小。
- 不能在[]中給出數組的大小
- 不能在函數中再利用sizeof計算數組的元素的個數
- 定義數組a, b:int a[10]; b=[]; 則不能直接用b=a來給數組b賦值。
- 對於數組a,&a=a=&a[0]
-
二維數組:
- int a[2][3] 相當於一個2行3列的矩陣
- int a[0][0] 表示第一行第一列,意味着下標同樣也是從0開始
- 二維數組的遍歷需要嵌套for循環
- a[i][j]表示第i行第j列的元素,a[i,j]是一個表達式,相當於a[j],沒有意義,會報錯。
- 二維數組初始化的時候列數可以省略,行數可以由編譯器來數。例如:inta[][5] = {{0,1,2,3,4},{2,3,4,5,6}};
- 初始化二維數組的兩種方法:部分初始化則將剩下的那部分賦值為0
- int a[2][3] = {{5, 6},{7, 8}}; 則a[0][0]=5, a[0][1]=6, a[0][2]=0, a[1][0]=7, a[1][1]=8, a[1][2]=0;
- int a[2][3] = {5, 6, 7, 8}; 則a[0][0]=5, a[0][1]=6, a[0][2]=7, a[1][0]=8, a[1][1]=0, a[1][2]=0;
- 三位數組理解方法:比如int box[10][20][30]; 則可以理解成由10個二維數組(每個是20行30列)堆疊起來,這20個數組元素中的每個元素是內含30個元素的數組。
通過程序加深理解一些概念
1. 數組的名字就相當於這個數組第一個元素的內存地址:
#include <stdio.h>
int main(){
int a[10]={1,2,3,4,5,6,7,8,9,10}; //定義一個整型數組,這里a實質上是一個指向數組中第一個數據a[0]的指針
int *p=a;
printf("%d\n",*p);
printf("%d",*(p+1));
return 0;
}
返回結果為:
1
2
2. 利用指針對數組進行初始化
#include <stdio.h>
int main(){
int d[10];
int *e;
e=&d[0]; //e保存了數組d的第一個數據的地址
for (int i=0; i<10; i++){
*e = i; //把該地址中的數據依次賦值0,1,2,3,4,5,6,7,8,9
e++; //地址累加一次,也就是數組中下一個數據的地址
}
for (int i=0; i<10; i++){
printf("%d\n", d[i]); //打印數組d中的所有元素
}
return 0;
}
3. 數組作為函數參數時,往往必須再用另一參數來傳入數組的大小。
- 不能在[]中給出數組的大小
- 不能在函數中再利用sizeof計算數組的元素的個數
- 聲明數組形參時,下列方法等價:(函數原型可以省略參數名)切記:此為函數聲明時用法,不可直接引用於函數定義
- int sum(int *ar, int len);
- int sum(int *, int);
- int sum(int ar[], int n);
- int sum(int [], int);
- 函數定義中不能省略參數名,以下兩種方法可行且等價:
- int sum(int *ar, int len)
{
//省略其他代碼
} - int sum(int ar[], int len)
{
//省略其他代碼
}
- int sum(int *ar, int len)
#include <stdio.h>
//此方法為最簡單,最基礎的數組遍歷
int search(int key, int a[], int len)
int main()
{
int a[]= {1,3,5,2,9,4,12,23,15,32};
int r = search (12, a, sizeof(a)/sizeof(a[0])); //傳入參數的時候在main函數中計算好函數的個數傳入到search函數中;另外,此處a傳入的時a[0]元素的地址。
printf("%d\n", r);
return 0;
}
int search(int key, int a[], int len) //len變量必須要加,因為在search函數中無法用sizeof函數計算數組的大小
{
int ret = -1;
int i;
for (i=0; i<len; i++){
if (key == a[i]){
ret = i;
break;
}
}
return ret;
}
4. 二維數組中數組名的含義
#include<stdio.h>
int main(){
int a[2][3]={{1,2,3},{4,5,6}};
printf("%p\n",a); //輸出指針a數據,也就是指針a[0]的地址
printf("%p\n",a+1); //輸出a+1的數據 ,也就是a[1]的地址
printf("%p\n",&a[0]);
printf("%p\n",&a[1]); //驗證上述
printf("%p\n",(*a)+1); //輸出的是a[0][1]的地址
printf("%p\n",&a[0][1]); //驗證
printf("%d\n",*(a[0])); //輸出的是a[0]a[0]的值
printf("%d\n",*(*(a+1)+1)); //輸出的是a[1][1]的值
}
注意:a是一個2行3列的數值,a+1表示的a[1]值所在的地址,a[1]的值又代表a[1][0]的值所在的地址
5. int與char指針類型的區別
int i=2; int *a=&i 和 char j='m'; char *b=&j 區別在於:int占據四個字節,a中雖然記載的i的第一個字節的地址,但是由於a是int類型的指針,*a讀取的時候自動再往后讀3個字節;而b是char類型的指針,則只讀取當前記錄的這一個字節,自然不能用指針b來保存int i的值。
#include <stdio.h>
int main(){
int i = 2;
int j = 's';
int *a = &i;
char *b = &i;
int *m = &j;
char *n = &j;
printf("%d\n", *a);
printf("%d\n", *b); //會產生warning
/* 解釋為什么前兩行輸出為什么一樣:
* 在存儲中,2作為int存儲為 00000010 00000000 00000000 00000000 四個字節,用此種表示方法是因為我的電腦是個小端電腦(Little-endian)。詳述見下條。
* a 和 b 所記錄的都是四個字節中第一個字節的地址,*a讀取到的是4個完整的字節,而*b讀取到的是第一個字節 00000010,由於巧合,二者所代表的都是數字1。
*/
printf("%c\n", *m); //會產生warning
printf("%c\n", *n);
return 0;
}
6. 指針內存位置理解深入剖析(一定在自己的電腦上運行試下)
#include <stdio.h>
int main()
{
int a = 1, b = 2;
char c = 'c', d = 'd';
int *m, *n;
char *j, *k;
m = &a;
n = &b;
j = &c;
k = &d;
printf("int變量在內存中的存儲情況");
printf("a %p\n", m);
printf("b %p\n", n);
//由於棧自頂向下的存儲方法,內存位置上a與b兩個元素是緊鄰着的,a位置高,b低,相差四個字節。
printf("\n");
printf("對int指針變量+1會得到什么結果,實際改變幾個字節?\n");
printf("&b+1 %p\n", n+1);
printf("&a-&b %d\n", m-n);
//上兩個語句測試得到結果,a與b的地址m,n相差並不是4而是1。因為相差的是存儲單元數而不是字節數
printf("\n");
printf("char指針變量什么情況,本來就相差1個字節?\n");
printf("c %p\n", j);
printf("d %p\n", k);
//由於char變量只占1個字節,所以這兩個變量位置地址相差為1
printf("\n");
printf("\n");
printf("int型指針變量占據幾個字節?\n");
printf("&m %p\n", &m);
printf("&n %p\n", &n);
printf("char型指針變量占據幾個字節?\n");
printf("&j %p\n", &j);
printf("&k %p\n", &k);
//可以得到,在64位系統上,像m,n這種指針本身的存儲都是占據8個字節,不管是char類型還是int類型。
return 0;
}
一句話概括指針的加減:指針加1,指針的值遞增它所指向類型的大小(以字節位單位),或者說增加一個存儲單元。
short dates; // dates的類型占2個字節
dates + 2 == &dates[2] //相同的地址
*(dates + 2) == dates[2] //相同的值