目錄
一、傳值調用和傳址調用
二、數組名的理解
三、通過指針訪問數組
四、一維數組傳參的本質
五、指針數組
六、指針數組模擬實現二維數組
一、傳值調用和傳址調用
指針可以用在哪里呢?我們看下面一段代碼:
#include <stdio.h>void Swap(int x, int y)
{int z = x;x = y;y = z;
}
int main()
{int a = 10;int b = 20;printf("交換前:a=%d b=%d\n", a, b);Swap(a, b);printf("交換后:a=%d b=%d\n", a, b);return 0;
}
運行結果如下:
我們發現,并沒有完成交換,調試發現:
出現這樣的原因:a和b是實參(是真實傳遞給函數的),x和y是形參,當實參ab傳給形參xy時候,形參把實參數據臨時拷貝了一份,x,y創建自己的獨立空間,因為有自己獨立的空間,跟a,b無關,所以修改形參不會影響實參。這種交換方式也叫傳值調用。
所以說:實參傳遞給形參的時候,形參會單獨創建?份臨時空間來接收實參,對形參的修改不影響實參。
措施:可以將a,b的地址傳給Swap函數,Swap函數里通過地址間接的操作main函數中的a和b,達到交換的效果。也就是傳址調用。
代碼如下:
#include <stdio.h>void Swap(int* pa, int* pb)//用指針變量來接收
{int z = *pa;*pa = *pb;*pb = z;
}
int main()
{int a = 10;int b = 20;printf("交換前:a=%d b=%d\n", a, b);Swap(&a, &b);//傳地址printf("交換后:a=%d b=%d\n", a, b);return 0;
}
傳址調用,可以讓函數和主調函數之間建立真正的聯系,在函數內部可以修改主調函數中的變量。
- 未來函數中只是需要主調函數中的變量值來實現計算,就可以采用傳值調用。
- 如果函數內部要修改主調函數中的變量的值,就需要傳址調用。
二、數組名的理解
數組名就是數組首元素(第一個元素)的地址。
代碼如下:
但有兩個例外:
- sizeof(數組名),sizeof中單獨放數組名,這里的數組名表示整個數組,計算的是整個數組的大小,單位是字節。
- &數組名,這里的數組名表示整個數組,所以&數組名取出的是整個數組的地址
除此之外,任何地方使用數組名,數組名都表示首元素的地址。
可能會有疑問,為什么首元素地址(arr)跟整個數組地址(&arr)相同呢?
代碼如下:
從值的角度來講,地址就是一個編號,它們打印出來的地址一模一樣,但是意義卻不相同,操作arr和&arr帶來的結果也是不一樣的。
如下代碼:
可以看出,&arr[0]和&arr[0]+1相差4個字節,arr和arr+1相差4個字節,是因為&arr[0]和arr都是首元素的地址,類型是int*,+1就是跳過一個整型(元素)。
但是&arr和&arr+1相差40個字節,這就是因為&arr是數組的地址,+1操作是跳過整個數組的。
也可以這么理解:
arr與&arr都是指針,指針有兩個要素:
- 第一個是地址值,也就指向的位置,打印出來的就是地址值,arr與&arr的地址值是一樣的。
- 第二個是類型(所指向的數據類型),arr指向數組第一個元素,&arr指向數組arr,arr+1后的地址值會偏移一個元素的長度,&arr+1后的地址值會偏移一整個數組的長度,所以arr與&arr類型是不一樣的。
三、通過指針訪問數組
#include <stdio.h>
int main()
{int arr[10] = { 0 };int i = 0;int sz = sizeof(arr) / sizeof(arr[0]);int* p = arr;//p存放的數組首元素的地址for (i = 0; i < sz; i++){scanf("%d", p + i);//scanf("%d", arr+i);//也可以這樣寫}//輸出for (i = 0; i < sz; i++){printf("%d ", *(p + i));//輸出可以寫成:// *(p+i) <==> arr[i] <==> *(arr+i) <==> *(i+arr) <==> i[arr]}return 0;
}
可以發現,數組的下標引用操作符([ ])效果相當于解引用操作符(*)
其實數組元素的訪問在編譯器處理的時候,也是轉換成首元素的地址+偏移量求出元素的地址,然后解引用來訪問的。
四、一維數組傳參的本質
之前我們都是在函數外部計算數組的元素個數,那可以把函數傳給一個函數后,在函數內部求數組的元素個數嗎?
我們來看一段代碼:
#include <stdio.h>void test(int arr[])
{int sz2 = sizeof(arr) / sizeof(arr[0]);printf("sz2 = %d\n", sz2);
}
int main()
{int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };int sz1 = sizeof(arr) / sizeof(arr[0]);printf("sz1 = %d\n", sz1);test(arr);return 0;
}
運行結果如下:
發現sz1計算錯誤,函數內部沒有正確獲得數組的元素個數。
原因:
數組名是數組首元素地址,傳遞的數組名,本質上數組傳參傳遞的是數組首元素地址。
所以函數的形參部分理論上應該用指針來接收首元素的地址。那么在函數內部寫sizeof(arr)計算的是一個地址的大小(單位字節),而不是數組的大小(單位字節)。
正因為函數的參數部分本質是指針,所以在函數內部是沒辦法求數組元素個數的。
代碼如下:
void test(int arr[10]) //==>arr[] 參數寫成數組形式,本質上還是指針
{
printf("%d\n", sizeof(arr)); //計算?個指針變量的??
}void test(int* arr) //參數寫成指針形式
{
printf("%d\n", sizeof(arr));
}
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
test(arr);
return 0;
}
上面數組傳參的本質是傳遞了數組首元素的地址,所以形參訪問的數組和實參的數組是同一個數組。
形參的數組是不會單獨再創建數組空間的,所以形參的數組是可以省略掉數組大小的。
總結:一維數組傳參,形參的部分可以寫成數組的形式,也可以寫成指針的形式。
五、指針數組
數組是一組相同類型元素的集合。數組有整形數組,字符數組.....
指針數組:存放指針(地址)的數組。
int* arr1[6];//存放整型指針的數組
char* arr2[10];//存放字符指針的數組
如下圖:
指針數組的每個元素是地址,又可以指向一塊區域。
六、指針數組模擬實現二維數組
代碼如下:
#include <stdio.h>
int main()
{int arr1[] = { 1,2,3,4,5 };int arr2[] = { 2,3,4,5,6 };int arr3[] = { 3,4,5,6,7 };//數組名是數組首元素的地址,類型是int*的,可以存放在parr數組中int* parr[3] = { arr1, arr2, arr3 };int i = 0;int j = 0;for (i = 0; i < 3; i++){for (j = 0; j < 5; j++){printf("%d ", parr[i][j]);//parr[i][j]<==>*(parr[i]+j)<==>*(*(parr+i)+j)}printf("\n");}return 0;
}
parr[i]是訪問parr數組的元素,parr[i]找到的數組元素指向了整型一維數組,parr[i][j]就是整型一維數組中的元素。
上述的代碼模擬出二維數組的效果,本質上其實不是二維數組。
因為二維數組在內存中是連續存放的,而這三個數組在內存可不一定連續存放。