一個存在已久的謠言
源碼
#include <stdio.h> int main() { int ar[10] = { 1,2,3,4,5,6,7,8,9,10 }; printf("&ar[0]=%p\n",&ar[0]); printf("ar=%p\n", ar); printf("&ar=%p\n", ar); getchar(); return 0; }
運行結果:
根據運行結果,很多人就會得出“數組名就是首元素的地址”這樣錯誤的結論。見代碼
#include <stdio.h> int main() { int ar[10] = { 1,2,3,4,5,6,7,8,9,10 }; printf("&ar[0]=%p\n",&ar[0]); printf("ar=%p\n", ar); printf("&ar=%p\n", ar); printf("sizeof(ar)=%d\n", sizeof(ar)); printf("sizeof(&ar[0])=%d\n", sizeof(&ar[0])); getchar(); return 0; }
運行結果:
如果 “數組名就是首元素的地址” 結論屬實,那么數組名的大小就是一個指針的大小。事實上,數組名代表整個數組空間。
數組名(ar)本身的確是個地址,在數值上等於數組首元素取地址(&ar[0]),等於對數組名取地址(&ar)。數值上這三個數相等,那只是表象。其實質是地址背后指向內存空間的能力是不同的。
這里面還有個有趣的問題,就是數組作為形參會退化為指針。參考C++——數組形參退化為指針。這也就是為啥,數組作為形參的時候還要再多給一個數組長度參數。
數組指針、指針數組
數組名是地址,與數組首元素地址僅代表自己類型那么大內存不同,數組名內存指向能力非常強。數組名指向整個數組空間。進一步講,對數組名取地址,即就是在對整個數組取地址,則數組的地址自然要用指向數組的指針才能接收,所以,必須定義指向數組的指針類型,即為數組指針。見代碼
#include <stdio.h> int main() { int ar[10] = { 1,2,3,4,5,6,7,8,9,10 }; int *p = ar; /*下面是錯誤代碼 int **p = &ar;*/ //正確寫法 int(*pp)[10] = &ar; getchar(); return 0; }
int *p = ar; p的類型int *,p指向的類型int
int **p = &ar; p的類型int** ,p指向的類型int*
int ar[10]的類型,int[10]
int(*pp)[10] = &ar;pp的類型是 int (*)[10],pp指向int [10]類型
數組指針、指針數組重點在於后兩個字。即數組指針本質上是指針,指針數組本質上是數組。這是字面上理解,代碼角度怎么區分呢?
int ar[10] = { 1,2,3,4,5,6,7,8,9,10 }; int(*p1)[10] = &ar; //數組指針 int* p2[3]; //指針數組
變量(這里是p1,p2)與[ ]優先結合,所以int* p2[3];是指針數組,是一個數組。 要想變成指針,需要使用 ()強制優先結合指針。
總結:
數組指針
首先它是一個指針,它指向一個數組,在32位系統下永遠是占4個字節,至於它所指向的數組占多少字節是不知道的,它是“指向數組的指針”簡稱
對於數組指針,強調的是指針的概念,只不過,指針的能力是用來指向數組類型的,並且其方括號中的數字一定,例如:int (*p)[10],p就是指向數組的指針,其中p指針規定了只能指向整形的數組,並且數組大小只能是10個整形空間,不能多也不能少,多之少之都會認為其指針的能力與指向的實體不符。
指針數組
指針數組,首先它是一個數組,數組的元素都是指針,數組占多少字節由數組本身決定,它是“儲存指針的數組”的簡稱。
對於指針數組,強調的是數組的概念,只不過,數組所保存的類型是指針罷了,其地位跟普通的數組沒有什么區別,都是數組,只不過是大家保存的類型不同而已,因此,我們美名其曰:保存指針的數組就稱其為指針數組, 例如:int *p1[10]
指針數組最典型的例子就是main函數:int
main(
int
argc,
char
*argv[]) 第二個參數是一個字符串指針數組
#include <stdio.h> int main() { int ar[3] = { 1,2,3 }; //數組指針指向ar int (*p)[3] = &ar; //指針數組 int* q[3] = {&ar[0],&ar[1],&ar[2]}; char* pstr[] = { "Hello","halo","nihao" }; getchar(); return 0; }
函數指針、指針函數
函數指針
函數指針,首先它是一個指針,只不過,指針所指向的類型是函數,它是“指向函數的指針”的簡稱
#include <stdio.h> int Max(int a, int b) { printf("%d\n", a > b ? a : b); return a > b ? a : b; } //函數指針 int(*pfun)(int, int); void main() { //情形1 Max(1, 2); //情形2 int(*pfun)(int, int); pfun = &Max; (*pfun)(1, 2); //情形3 pfun = Max; pfun(1, 2); getchar(); }
一般來說,我們調動函數往往是通過函數名來進行調動,例如情形1,由於指針強悍與無所不能,只要你能表示出來的,指針都可以想辦法指向,因此,通過指針來調動函數就顯得很自然了,我們把能夠指向函數的指針稱為函數指針,例如情形2,把Max函數的地址賦給了pfun函數指針,在調動時先取值,然后再調動函數,這是一種標准的做法,事實上,由於函數名就是函數的入口地址,本身也充當了地址,因此,我們可以簡化程序,例如情形3,由於指針所指之物為函數,因此它的調動就行如直接運行函數類似了,但是,心里的清楚,情形3的做法實際是情形2的簡寫過程。
注意,一般指針都有其加1的能力,但是,函數指針不允許做這樣的運算。即pfun+1是一個非法的操作。
指針函數
指針函數,首先它是一個函數,只不過,函數所返回的類型是指針類型,它是“返回指針類型的函數”的簡稱。 我們把返回指針類型的函數稱其為指針函數,那就意味着只要返回值為指針,無論是什么類型的指針,都有資格稱為指針函數
//指針函數,返回整形指針 int* fun(int a, int b) { return 0; }
像這種,函數fun,參數是(int a, int b),返回值是int* 。這種比較明顯
返回函數指針的指針函數
先看一種錯誤的寫法。對於VS2012以后的IDE,這種代碼寫得時候直接顯示紅色〰,根本編譯不過
#include <stdio.h> int Max(int a, int b) { printf("%d\n", a > b ? a : b); return a > b ? a : b; } //指針函數,返回函數指針。但是這種寫法是錯誤的 int(*) (int,int) func(int a, int b, int(*FUN)(int, int)) { printf("max value=%d\n", FUN(a, b)); return FUN; } void main() { func(1, 2, Max); getchar(); }
正確寫法
#include <stdio.h> int Max(int a, int b) { printf("%d\n", a > b ? a : b); return a > b ? a : b; } //函數指針 int(*pfun)(int, int); //func這個函數參數是(int a, int b, int(*FUN)(int, int)) //返回值是個指針,這個指針是int (*) (int, int)型函數指針 int(*func(int a, int b, int(*FUN)(int, int))) (int, int) { printf("max value=%d\n", FUN(a, b)); return FUN; } void main() { func(1, 2, Max); getchar(); }
但是這種代碼寫出來太難理解了,可以使用typedef簡化代碼
#include <stdio.h> int Max(int a, int b) { printf("%d\n", a > b ? a : b); return a > b ? a : b; } //將函數指針定義成類型 typedef int(*pfun)(int, int); //func這個函數參數是(int a, int b, int(*FUN)(int, int)) //返回值是個指針,這個指針是int (*) (int, int)型函數指針 //int(*func(int a, int b, int(*FUN)(int, int))) (int, int) pfun func(int a, int b, pfun FUN) { printf("max value=%d\n", FUN(a, b)); return FUN; } void main() { func(1, 2, Max); getchar(); }