C/C++知識分享: 函數指針與指針函數,看完這篇你還能不懂?


一、 什么是指針?

定義:指針是程序數據在內存中的地址,而指針變量是用來保存這些地址的變量;


 

上面一個 4GB 的內存可以存放 2^32 字節的數據。左側連續的十六進制編號就是內存地址,每個內存地址對應一個字節的內存空間。而指針變量保存的就是這個編號,也即內存地址。

指針的聲明:

指針其實就是一個變量,指針的聲明方式與一般的變量聲明類似,如下:

int *p; // 聲明一個 int 類型的指針 p,該指針指向一個int類型的對象

char *p        // 聲明一個 char 類型的指針 p,該指針指向一個int類型的對象

int *arr[10]    // 聲明一個指針數組,該數組有10個元素,其中每個元素都是一個指向 int 類型對象的指針

int (*arr)[10]  // 聲明一個數組指針,該指針指向一個 int 類型的一維數組

int **p;        // 聲明一個指針 p ,該指針指向一個 int 類型的指針

聲明一個指針變量並不會自動分配任何內存。在對指針進行間接訪問之前,指針必須進行初始化:或是使他指向現有的內存,或者給他動態分配內存,否則我們並不知道指針指向哪兒,這個問題需要特別關注。


 

二、什么是函數指針?

函數指針定義:函數指針是指向函數的指針變量。因此“函數指針”本身首先應是指針變量,只不過該指針變量指向函數。這正如用指針變量可指向整型變量、字符型、數組一樣,這里是指向函數。

其通用表達式為:類型說明符 (*函數名) (參數)

int (*fun)(int x) //函數指針的定義

int (*fun)(int x,int y) //函數指針的定義

函數指針在PC軟件開發中使用較少,在嵌入式行業使用較多,但是無論是PC軟件還是嵌入式軟件,理解函數指針的定義和使用,對於理解程序設計都是很有好處的。

函數指針的賦值

函數指針和其他指針一樣定義之后使用之前也是需要初始化。

函數指針有兩個用途:調用函數和做函數的參數

int (*fun)(int x,int y) //函數指針的定義

fun = &Function          //函數指針的賦值方式1

fun = Function          //函數指針的賦值方式2

x = (*fun)()            //函數指針的調用方式1

x = fun()                //函數指針的調用方式2

函數賦值的時候取地址運算符&不是必需的,因為一個函數標識符就表示了它的地址,並且賦值的時候函數不需要帶圓括號;

如果是函數調用,還必須包含一個圓括號括起來的參數表。

函數指針的用法

我們使用指針的時候,需要通過鑰匙 * 來取其指向的內存里面的值,函數指針使用也如此。通過用(*pf)取出存在這個地址上的函數,然后調用它。

char* fun(char* p1,char* p2)

{

  int i = 0;

  i = strcmp(p1,p2);

  if(0 == i)

  {

    return p1;

  }

  else

  {

    return p2;

  }

}

int main()

{

  char * (*pf)(char* p1,char* p2);

  pf = &fun;

  (*pf)("aa","bb");

  return 0;

}

這里需要注意到是,在Visual C++6.0里,給函數指針賦值時,可以用&fun或直接用函數名fun。這是因為函數名被編譯之后其實就是一個地址,所以這里兩種用法沒有本質的差別。


 

用法延申

當我們不滿足於函數指針上面如此簡單的用法時,這時候需要一個高級用法來擴展我們對於函數指針的認知邊界。

感興趣的同學可以看看下面這個用法,並嘗試理解該表達式是如何使用的函數指針。

(* (void(*)()) 0)(); //出自《C Trap and Pitfalls》這本經典的書

答案如下:   ``

第一步:通過void(*) (),可以明白這是一個函數指針類型。這個函數沒有參數,沒有返回值。

第二步:通過(void(*) ())0,可以明白這是將0強制轉換為函數指針類型,0是一個地址,也就是說一個函數存在首地址為0的一段區域內。

第三步:通過(*(void(*) ())0),可以明白這是取0地址開始的一段內存里面的內容。

第四步:最終理解(*(void(*) ())0)(),這是函數調用。

讓程序跳轉到絕對地址為0x0113F90C

方法一:

將0x0113F90C地址強制轉換為函數指針類型,即: (void (*)())0x0113F90C

然后調用:((void (*)())0x0113F90C)()

方法二:

typedef (void (*)()) VoidFuncPtr;

((VoidFuncPtr)0x0113F90C)();


 


面試題:指出程序的錯誤

#include<stdio.h>

void main(void)

{

  int max(x,y);

  int *p=max;

  int a,b,c,d;

  scanf("%d %d %d",a,b,c);

  d=p(p(a,b),c);

  printf("d:%d\n",d);

  return;

}

int max(int a,int y)

{

  return(x > y ?x:y);

}

答案:

int max(x ,y);函數聲明錯誤,改為:int max(int x,int y);

解析:max函數聲明只是寫出了函數的形參的名稱,這對參數的類型來說是毫無意義的,編譯器會把x和y當做數據類型來看,編譯時會出錯,max的調用肯定也會出錯。

int *p=max;指針定義錯誤,改為:int (*p)(int ,int)=max;

解析:函數的指針是不能直接賦值給int型指針.

scanf("%d %d %d",a,b,c);庫函數使用錯誤,改為scanf("%d %d%d",&a,&b,&c);

解析:庫函數使用錯誤,第二部分應該是接收數據的地址,這里卻寫成了變量。

d=p(p(a,b),c);函數指針調用函數錯誤,改為d=(*p)((*p)(a,b),c);`

解析:用函數指針調用函數的格式如下:(【*】【函數指針名稱】)(【參數列表】);不能直接用函數指針加上參數就直接調用。


 

三、什么是指針函數?

指針函數定義:指針函數的落腳點是一個函數,這個函數的返回值是一個指針,與普通函數int function(int,int)類似,只是返回的數據類型不一樣而已。

_type_ *function(int, int) //返回的是指針地址

int function(int,int)    //返回的是int型數據。

 

int * fun(int x,int y)  //指針函數的定義

int* fun(int x,int y)  //指針函數的定義

以上三種寫法均正確,但是*靠近返回值一點更容易理解。int *fun(int x) //指針函數的定義

指針函數的調用

在調用指針函數時,需要一個同類型的指針來接收其函數的返回值。

typedef struct _Data{

    int a;

    int b;

}Data;

//指針函數

Data* f(int a,int b){

    Data * data = new Data;

    data->a = a;

    data->b = b;

    return data;

}

int main(int argc, char *argv[])

{

    QApplication a(argc, argv);

    //調用指針函數

    Data * myData = f(4,5);

    qDebug() << "f(4,5) = " << myData->a << myData->b;

    return a.exec();

}

不過也可以將其返回值定義為 void* 類型,在調用的時候強制轉換返回值為自己想要的類型。

其輸出結果是一樣的,不過不建議這么使用,因為強制轉換可能會帶來風險。返回類型可以是任何基本類型和復合類型。返回指針的函數的用途十分廣泛。

事實上,每一個函數,即使它不帶有返回某種類型的指針,它本身都有一個入口地址,該地址相當於一個指針。

比如函數返回一個整型值,實際上也相當於返回一個指針變量的值,不過這時的變量是函數本身而已,而整個函數相當於一個“變量”。


 

四、函數指針與指針函數區別

通過以上的介紹,小伙伴應該都能理解二者的定義。那么簡單的總結下二者的區別:

1. 定義不同

指針函數本質是一個函數,其返回值為指針。

函數指針本質是一個指針,其指向一個函數。

2. 寫法不同

指針函數:int* fun(int x,int y);

函數指針:int (*fun)(int x,int y);

可以簡單粗暴的理解為,指針函數的*是屬於數據類型的,而函數指針的星號是屬於函數名的。

再簡單一點,可以這樣辨別兩者:函數名帶括號的就是函數指針,否則就是指針函數。

3. 用法不同

上面函數指針和指針函數的用法都有,但是函數指針的用法會更多,相對而言難度也更大,例如函數指針與回調函數,如果是C++非靜態成員函數指針,其用法也會有一些區別,感興趣的同學可以關注后續推文或自行查閱相關書籍。

總而言之,這兩個東西很容易搞混淆,一定要深入理解其兩者定義和區別,避免犯錯。

看到這里,你是不是對“C/C++”又有了一點新的認知呢~如果你喜歡這篇文章的話,動動小指,加個關注哦~


 

最后,如果你也想成為程序員,想要快速掌握編程,這里為你分享一個學習企鵝圈子!

里面有資深專業軟件開發工程師,在線解答你的所有疑惑~編程語言入門“so easy”

資料包含:編程入門、游戲編程、課程設計、黑客等。

編程學習書籍:


 

編程學習視頻:


 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM