c語言之指針-指針函數和函數指針與指針數組和數組指針

1.函數指針和指針函數的概念
(1)之前在上篇文章中說到int (*pArr)[10]爲數組指針,而函數指針和數組指針類似,形如int (*pFun)(int,int)這種就叫做函數指針。
函數指針和函數是有區別的,實質上它是一個指針,所以(*pFun)必須要有(*)。函數指針也叫做指向函數的指針。
(2)指針函數
int *fun(int a,int b)
{
    int* p=&a;
    *p += a+b;
    return p;    
}
形如int *fun(int a, int b);爲原型的就叫指針函數,毫無疑問,它是一個函數,而函數的返回值是一個指針(int *)類型。
所以我們把這種返回值是指針類型的函數叫做指針函數。

2.函數指針的使用
int Add(int a,int b)
{
    return a+b;
}
//int (*pfun)(int,int);    
typedef int (*pFun)(int,int); 

int main()
{
    int res;
    // 對於int (*pfun)(int,int)
    pfun=&Add;
    res = (*pfun)(10,20);
    printf("%d\n",res);

    // 對於typedef int (*pFun)(int,int)
    pFun p=&Add;
    res = (*p)(10,20);
    printf("%d\n",res);

    return 0;
}
輸出結果:
30
30
(1)對函數指針使用typedef
因爲函數指針在很多時候形式較爲複雜,一般來說我們用typedef來對函數指針的類型重定義。
如果我們不使用typedef,那麼int (*pfun)(int,int)相當於定義了一個變量pFun。每次定義變量時類型都要如此顯得很複雜。
而typedef int (*pFun)(int,int);相當於聲明瞭pFun是一個int (*)(int,int)類型,因此我們可以用pFun直接來定義一個函數指針的變量p。
(2)如何操作函數指針
函數指針和普通指針一樣,可以用來指向一個對象,不過函數指針是指向一個函數的,所以pfun=&Add相當於函數指針指向了函數。
和普通對象一樣,*pfun就是所指對象的值(常稱解引用),所以(*pfun)(10, 20)相當於調用了Add(10,20),*和pfun一體的。
(3)函數指針指向函數時的一些
其實上述pfun=&Add這個函數指針pfun指向函數Add,也可以換成pfun=Add或者直接*pfun=Add甚至是*pfun=&Add(一般編譯器會有警告)。
我們觀察內存中的地址,本機上通過debug找到Add的地址是0x00401005,&Add的地址也是0x00401005。就是說Add和&Add的內存地址相同。
其實pfun指向函數名,函數名和數組名類似,只是進入到這個函數的入口地址而已。所以我們用pfun指向了Add和&Add都相當於指向0x00401005空間的內容。
而函數指針的類型規定了指針從函數Add的入口地址開始爲該指針劃分內存空間的大小(就是int (*)(int,int)的大小),所以首地址和空間大小確定了。
這也就是爲什麼pfun指向Add和將Add直接賦值給pfun的效果相同。

3.函數指針和指針函數的結合使用
int Add(int a,int b)
{
    return a+b;
}

typedef int (*pFun)(int,int); 

pFun printSum(int a,int b, pFun pfun)
{
    printf("The sum is=%d\n",pfun(a,b));
    return pfun;    
}

int main()
{
    printSum(10,20,Add);
    return 0;
}
(1)再次理解typedef帶來的好處
前面說過,我們應該對函數指針使用typedef,這樣在定義變量的時候更加方便。而且在函數指針作爲函數的參數或返回值的時候這一點尤爲明顯。
typedef int (*pFun)(int,int); 
pFun printSum(int a,int b, pFun pfun);
對於這兩句代碼,如果我們不使用typedef的話後果如何:
定義一個int (*pfun)(int,int); 然而printSum函數聲明中的類型也要用函數指針。那好,讓我們看看printSum的函數原型結構。
printSum具有三個參數,分別是int,int,和int (*)(int,int)類型,返回值類型也是int (*)(int,int)。
那麼printSum函數原型應該是int (*)(int,int) printSum(int a,int b, int (*pfun)(int,int));這個時候build的時候編譯錯誤。
正確printSum的原型應該是int (*printSum(int a, int b, int (*pfun)(int,int)))(int,int);外層的函數指針先結合指針,再結合函數參數。
這種形式的函數並非返回值爲int,而是函數指針,看起來很奇怪。後面我們再來分析這種結合指針的複雜式。
(2)函數指針作爲函數參數
這是一種很常用的c/c++用法,一般稱作爲函數參數的函數指針爲回調函數,其他變成語言大多沒有指針,但是有回調函數。
一般函數在調用時只能傳入自己參數按照該函數的代碼順序執行,但是函數指針可以使得在調用函數的時候調用的函數再調用你自己傳入的函數。
這樣就保證了程序的更加靈活性,特別是調用庫函數和一些算法的時候,常常我們加入函數指針作爲參數來人爲規定函數的走向。
回調函數在事件系統和消息處理(通知)的時候也扮演重要角色。
(3)返回指針值的函數
指針函數即返回指針值的函數,pFun是一個函數指針,那麼函數printSum就是一個返回函數指針的函數(指針函數指針)。
同樣,我們在使用變量接收這個返回的地址的時候也必須要用指針值來接受,這裏是函數指針。關於返回函數指針,我暫時不瞭解它有什麼實際意義。

4.指針和數組的關係
int ar[10]={1,2,3,4,5,6,7,8,9,10};
int *p=ar;
int (*pp)[10]=&ar;            
//int **pp=&ar;                // 錯誤

printf("p[1]=%d\n",p[1]);
printf("*(p+1)=%d\n", *(p+1));
printf("(*pp)[1]=%d\n", (*pp)[1]);

printf("%p\n",&ar[0]);
printf("%p\n",ar);            
printf("%p\n",&ar);            
printf("%d\n",sizeof(ar));
printf("%d\n",sizeof(&ar[0]));
printf("%p\n",sizeof(&ar));

printf("p=%p\n", p);
printf("p+1=%p\n", p+1);
printf("pp=%p\n", pp);
printf("pp+1=%p\n", pp+1);
輸出結果:
p[1]=2
*(p+1)=2
(*pp)[1]=2
0019FF08
0019FF08
0019FF08
40
4
00000028
p=0019FF08
p+1=0019FF0C
pp=0019FF08
pp+1=0019FF30
(1)數組指針和二級指針
c語言一直流傳着數組就是指針的說法,這種說法是不正確的。指針是存在一個合法性的問題,而數組不同。
上章中說到可以用指針*p來操作ar,也可以用(*p)[10]來指向ar,這個時候p[1]和*(p+1)結果相同,同樣(*(pp+0))[1]和*(pp[0]+1)相同。
看似[]和*可以互相轉化,但是int **pp=&ar卻是錯誤的。因爲只有*p具有指針的功能,int *現在只是類型,第二級的指針已經不具有指向的功能了。
(2)數組名和數組地址
int *p=ar;p的值就是數組名ar的值,而*p的值對應ar[0]的值。而p是*p的地址,那麼ar是否是ar[0]的地址呢?
關於數組名就是數組首元素地址這種說法是錯誤的。&ar[0]和ar還有&ar三者的值都是一個地址值且相等,所以容易讓人產生錯覺。
我們可以用sizeof來分別測試&ar[0]和ar的大小,發現前者爲40,後者爲4。所以他們雖然同一地址,但是對於該值開闢的內存大小空間是不同的。
和指針類似,ar相當於是一個首地址,但是他的類型規定了他有10個int(40bytes)和內存空間。而ar[0]就是簡單的一個int類型,4字節。
(3)從數組名的角度再看指針
指針是帶有類型的,對於int *p和int (*pp)[10]都可以操作數組ar,而數組名ar的所佔空間是40bytes。
p=ar,p存儲ar的值,但是我們發現p+1只偏移了4bytes,所以p真正指向的是ar[0],而p的值就是&ar[0]並非數組名,所以p+1就是&p[1],*(p+1)就是p[1]。
int (*pp)[10]=&ar,這個時候pp+1偏移了40bytes,所以pp的類型應該是int (*)[10],(*(pp+1))[0]指向了數組ar之外的下一個空間單元。
正確的通過數組指針尋數組值ar[1]的方式是(*pp)[1],但是由於數組下標和指針尋值效果一樣。
所以(*pp)[1]也可以用*((pp[0])+1)或者*((*pp)+1)甚至pp[0][1]。他們效果一樣,而pp[0][1]也是二維數組的形式。這個特性也衍生出來更多問題。

4.指針數組
int main(int argc, char *argv[])                // argv就是指針數組
{
    int ar[3]={1,2,3};
//    int (*p)[4]=&ar;            // 錯誤

    int a=10;
    int b=20;
    int c=30;
    int *A[30]={&a,&b,&c};        

    char *B[3]={"C","C++","JavaScript"};                

    return 0;
}
(1)數組指針和指針數組的區別
從字面上來看數組指針就是指向數組的指針,指針數組就是數據成員爲指針的數組。一個是指針,另一個是指針的數組。
對於int ar[3],只能用int (*p)[3]來指向它,int (*p)[4]來指向它就是錯誤的。所以數組指針的下標號和數組的下標號必須對應相等。
而對於指針數組int *A[30],它的下標號大小並不需要和數據數據的大小對應(數組的特性決定的)。
因爲[]的優先級高於*,所以在定義一個數組指針和指針數組,只需要對*使用()即可,int (*A)[3]就是數組指針,int *A[3]就是指針數組。
(2)指針數組的使用和意義
int *A[30]={&a,&b,&c};可以將變量的地址(指針值)存儲到一個數組裏,數組A存放了三個指針數據。
而對於字符串char*來說,它同樣是一個char的指針,所以可以直接存放可變長度的字符串到char指針數組中,這樣更加方便靈活。
在有很多條記錄的時候,例如n個學生的學號和姓名,我們可以用指針數組單獨存放n個學號,另一個指針數組存放n個姓名。
(3)main函數的參數
每一個c程序中都有main函數,他是程序的入口函數,cpu從main開始執行c語言程序。通常我們的main函數的形式是void main(){ ... }。
其實main函數也是帶參數的,在傳統的單任務系統中使用命令行來運行c程序,main函數的原型是int main(int argc, char *argv[])。
如果通過命令行運行了c程序,我們可以在main.c後面加上想要傳入給main函數的參數,main就可以直接使用我們傳入的參數。
argc是傳入字符串的個數,argv是傳入字符串的內容。argv也可以是char **類型。程序運行結束時return 0返回給命令行界面,一般代表函數正常結束。
其實一般在傳入參數的時候,即使函數參數的數組規定了下標號,這個下標號也沒有意義,所以通常我們傳入一個int值來規定數組大小。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章