天天看點

對指針的詳細認識(三)—— 函數指針+函數指針數組+回調函數

文章目錄

  • ​​函數指針​​
  • ​​函數指針的定義​​
  • ​​函數指針的使用​​
  • ​​函數指針數組​​
  • ​​函數指針數組的定義​​
  • ​​函數指針數組的使用 - 模拟電腦​​
  • ​​指向函數指針數組的指針​​
  • ​​回調函數​​
  • ​​回調函數的定義​​
  • ​​回調函數的使用 - qsort函數​​

函數指針

函數指針的定義

函數指針和我們在​​對指針的詳細認識(二)​​​中學習的數組指針非常相似。

我們知道,整型指針是指向整型的指針,數組指針是指向數組的指針,其實,函數指針就是指向函數的指針。

和學習數組指針一樣,學習函數指針我們也需要知道三點:

  1. ( )的優先級要高于 * 。
  2. 一個變量除去了變量名,便是它的變量類型。
  3. 一個指針變量除去了變量名和 * ,便是指針指向的内容的類型。

舉個例子:

#include<stdio.h>
int Add(int x, int y)
{
  return x + y;
}
int main()
{
  int(*p)(int, int) = &Add;//取出函數的位址放在函數指針p中
  return 0;
}      

那麼,函數指針p的類型我們是如何建立的呢?

首先,p是一個指針,是以必須先與 * 結合,而( )的優先級高于 * ,是以我們要把 * 和p用括号括起來,讓它們先結合。

指針p指向的内容,即函數Add的類型是int (int,int),是以函數指針p就變成了int(*p)(int,int)。

去掉變量名p後,便是該函數指針的變量類型int( * )(int,int)。

對指針的詳細認識(三)—— 函數指針+函數指針數組+回調函數

函數指針的使用

知道了如何建立函數指針,那麼函數指針應該如何使用呢?

1.函數指針的指派

對于數組來說,數組名和&數組名它們代表的意義不同,數組名代表的是數組首元素位址,而&數組名代表的是整個數組的位址。

但是對于函數來說,函數名和&函數名它們代表的意義卻是相同的,它們都代表函數的位址(畢竟你也沒有聽說過函數有首元素這個說法吧)。

是以,當我們對函數指針指派時可以指派為&函數名,也可以指派為函數名。

int(*p)(int, int) = &Add;
  int(*p)(int, int) = Add;      

2.通過函數指針調用函數

方法一:我們知道,函數指針存放的是函數的位址,那麼我們将函數指針進行解引用操作,便能找到該函數了,于是就可以通過函數指針調用該函數。

#include<stdio.h>
int Add(int x, int y)
{
  return x + y;
}
int main()
{
  int a = 10;
  int b = 20;
  int(*p)(int, int) = &Add;
  int ret = (*p)(a, b);//解引用找到該函數
  printf("%d\n", ret);
  return 0;
}      

我們可以了解為, * 和&是兩個相反的操作符,像正号(+)和負号(-)一樣,一個 * 操作符可以抵消一個&操作符。

對指針的詳細認識(三)—— 函數指針+函數指針數組+回調函數

方法二:我們在函數指針指派中說到,函數名和&函數名都代表函數的位址,我們可以指派時直接指派函數名,那麼通過函數指針調用函數的時候我們就可以不用解引用操作符就能找到函數了。

#include<stdio.h>
int Add(int x, int y)
{
  return x + y;
}
int main()
{
  int a = 10;
  int b = 20;
  int(*p)(int, int) = Add;
  int ret = p(a, b);//不用解引用
  printf("%d\n", ret);
  return 0;
}      
對指針的詳細認識(三)—— 函數指針+函數指針數組+回調函數

函數指針數組

函數指針數組的定義

我們知道,數組是一個存放相同類型資料的空間,我們已經認識了指針數組,比如:

int* arr[10];//數組arr有10個元素,每個元素的類型是int*      

那如果要将一系列相同類型的函數指針存放到一個數組中,那麼這個數組就叫做函數指針數組,比如:

int(*pArr[10])(int, int);
  //數組pArr有10個元素,每個元素的類型是int(*)(int,int)      

函數指針數組的建立隻需在函數指針建立的基礎上加上[ ]即可。

比如,你要建立一個函數指針數組,這個數組中存放的函數指針的類型均為int(*)(int,int),如果你要建立一個函數指針為該類型,那麼該函數指針的寫法為int(*p)(int,int),現在你要建立一個存放該指針類型的數組,隻需在變量名的後面加上[ ]即可,int(*pArr[10])(int,int)。

函數指針數組的使用 - 模拟電腦

函數指針數組一個很好的運用場景,就是計算機的模拟實作:

#include<stdio.h>
void menu()
{
  printf("|-----------------------|\n");
  printf("|     1.Add   2.Sub     |\n");
  printf("|     3.Mul   4.Div     |\n");
  printf("|        0.exit         |\n");
  printf("|-----------------------|\n");
}//菜單
double Add(double x, double y)
{
  return x + y;
}//加法函數
double Sub(double x, double y)
{
  return x - y;
}//減法函數
double Mul(double x, double y)
{
  return x*y;
}//乘法函數
double Div(double x, double y)
{
  return x / y;
}//除法函數
int main()
{
  int input = 0;
  double x = 0;//第一個操作數
  double y = 0;//第二個操作數
  double ret = 0;//運算結果
  double(*pArr[])(double, double) = { 0, Add, Sub, Mul, Div };
  //函數指針數組-轉移表
  int sz = sizeof(pArr) / sizeof(pArr[0]);//計算數組的大小
  do
  {
    menu();
    printf("請輸入:>");
    scanf("%d", &input);
    if (input == 0)
      printf("退出程式\n");
    else if (input > 0 && input < sz)
    {
      printf("請輸入兩個操作數:>");
      scanf("%lf %lf", &x, &y);
      ret = pArr[input](x, y);
      printf("ret=%lf\n", ret);
    }
    else
      printf("選擇錯誤,請重新選擇!\n");
  } while (input);//當input不為0時循環繼續
  return 0;
}      

代碼中,函數指針數組存放的是一系列參數和傳回類型相同的函數名,即函數指針。将0放在該函數指針數組的第一位是為了讓使用者輸入的數字input與對應的函數指針下标相對應。

該代碼若不使用函數指針數組,而選擇使用一系列的switch分支語句當然也能達到想要的效果,但會使代碼出現許多重複内容,而且當以後需要增加該計算機功能時又需要增加一個case語句,而使用函數指針數組,當你想要增加計算機功能時隻需在數組中加入一個函數名即可。

指向函數指針數組的指針

既然存在函數指針數組,那麼必然存在指向函數指針數組的指針。

int(*p)(int, int);
  //函數指針
  int(*pArr[5])(int, int);
  //函數指針數組
  int(*(*pa)[5])(int, int) = &pArr;
  //指向函數指針數組的指針      

那指向函數指針數組的指針的類型是如何寫的呢?

對指針的詳細認識(三)—— 函數指針+函數指針數組+回調函數

是以pa就是一個指向函數指針數組的指針,該函數指針數組中每個元素類型是int(*)(int, int)。

回調函數

回調函數的定義

回調函數就是一個通過函數指針調用的函數。如果你把函數的指針(位址)作為參數傳遞給另一個函數,當這個指針被用來調用其所指向的函數時,我們就說這是回調函數。

舉個簡單的例子:

#include<stdio.h>
void test1()
{
  printf("hello\n");
}
void test2(void(*p)())
{
  p(); //指針p被用來調用其所指向的函數
}
int main()
{
  test2(test1);//将test1函數的位址傳遞給test2
  return 0;
}      

在該代碼中test1函數不是由該函數的實作方直接調用,而是将其位址傳遞給test2函數,在test2函數中通過函數指針間接調用了test1函數,那麼函數test1就被稱為回調函數。

回調函數的使用 - qsort函數

其實回調函數并不是很難見到,在用于快速排序的庫函數qsort中便運用了回調函數。

void qsort(void*base,size_t num,size_t width,int(*compare)(const void*e1,const void*e2));      

qsort函數的第一個參數是待排序的内容的起始位置;第二個參數是從起始位置開始,待排序的元素個數;第三個參數是待排序的每個元素的大小,機關是位元組;第四個參數是一個函數指針。qsort函數的傳回類型為void。

qsort函數的第四個參數是一個函數指針,該函數指針指向的函數的兩個參數的參數類型均為const void*,傳回類型為int。當參數e1小于參數e2時傳回小于0的數;當參數e1大于參數e2時傳回大于0的數;當參數e1等于參數e2時傳回0。

列如,我們要排一個整型數組:

#include<stdio.h>
int compare(const void* e1, const void* e2)
{
  return *((int*)e1) - *((int*)e2);
}//自定義的比較函數
int main()
{
  int arr[] = { 2, 5, 1, 8, 6, 10, 9, 3, 5, 4 };
  int sz = sizeof(arr) / sizeof(arr[0]);//元素個數
  qsort(arr, sz, 4, compare);//用qsort函數将arr數組排序
  return 0;
}      

最終arr數組将被排為升序。

繼續閱讀