程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> 關於C++ >> 詳解C說話編程中的函數指針和函數回調

詳解C說話編程中的函數指針和函數回調

編輯:關於C++

詳解C說話編程中的函數指針和函數回調。本站提示廣大學習愛好者:(詳解C說話編程中的函數指針和函數回調)文章只能為提供參考,不一定能成為您想要的結果。以下是詳解C說話編程中的函數指針和函數回調正文


函數指針:

就是存儲函數地址的指針,就是指向函數的指針,就是指針存儲的值是函數地址,我們可以經由過程指針可以挪用函數。

我們先來界說一個簡略的函數:

//界說如許一個函數
void easyFunc()
{
  printf("I'm a easy Function\n");
}
//聲明一個函數
void easyFunc();
//挪用函數
easyFunc();

//界說如許一個函數
void easyFunc()
{
  printf("I'm a easy Function\n");
}
//聲明一個函數
void easyFunc();
//挪用函數
easyFunc();

下面三個步調就是我們在進修函數的時刻必需要做的,只要經由過程以上三步我們才算界說了一個完全的函數。

若何界說一個函數指針呢?後面我們界說其他類型的指針的格局是 類型 * 指針名 = 一個地址,好比:

int *p = &a;//界說了一個存儲整形地址的指針p

也就是說假如我們要界說甚麼類型的指針就得曉得甚麼類型,那末函數的類型怎樣肯定呢?函數的類型就是函數的聲明把函數名去失落便可,好比下面的函數的類型就是:

void ()

我們再來聲明一個有參數和前往值的函數:

int add(int a, int b);

下面函數的類型照舊是把函數名去失落便可:

int (int a, int b)

既然我們曉得了函數的類型那末函數指針的類型就是在前面加個 * 便可,是否是如許呢?

int (int a, int b) * //這個是相對毛病的

下面這麼界說是毛病的,相對是毛病的,許多初學者都如許去做,總認為就應當如許,其實函數指針的類型的界說正比如較特別,它是如許的:

int (*) (int a, int b);//這裡的型號在中央,必定要用括號括起來

int (*) (int a, int b);//這裡的型號在中央,必定要用括號括起來

我們界說函數指針只需在 * 前面加個指針稱號便可,也就是上面如許:

int (*p)(int a, int b) = NULL;//初始化為 NULL

int (*p)(int a, int b) = NULL;//初始化為 NULL

假如我們要給 p 賦值的話,我們就應當界說一個前往值類型為 int ,兩個參數為 int 的函數:

int add(int a, int b)
{
  return a + b;
}
p = add;//給函數指針賦值

int add(int a, int b)
{
  return a + b;
}
p = add;//給函數指針賦值

經由下面的賦值,我們便可以應用 p 來代表函數:

p(5, 6);//等價於 add(5, 6);
printf("%d\n", p(5, b));

p(5, 6);//等價於 add(5, 6);
printf("%d\n", p(5, b));

輸入成果為:11

經由過程下面的指針函數來應用函數,普通不是函數的重要用法,我們應用函數指針重要是用來完成函數的回調,經由過程把函數作為參數來應用。

函數指針的值

函數指針跟通俗指針一樣,存的也是一個內存地址, 只是這個地址是一個函數的肇端地址, 上面這個法式打印出一個函數指針的值(func1.c):

#include <stdio.h>

typedef int (*Func)(int);

int Double(int a)
{
  return (a + a);
}

int main()
{
  Func p = Double;
  printf("%p\n", p);
  return 0;
}

編譯、運轉法式:

[lqy@localhost notlong]$ gcc -O2 -o func1 func1.c
[lqy@localhost notlong]$ ./func1
0x80483d0
[lqy@localhost notlong]$ 

然後我們用 nm 對象檢查一下 Double 的地址, 看是否是正好是 0x80483d0:

[lqy@localhost notlong]$ nm func1 | sort
08048294 T _init
08048310 T _start
08048340 t __do_global_dtors_aux
080483a0 t frame_dummy
080483d0 T Double
080483e0 T main
...

  不出料想,Double 的肇端地址果真是 0x080483d0。

函數回調

函數回調的實質就是讓函數指針作為函數參數,函數挪用時傳入函數地址,也就是函數名便可。

我們甚麼時刻應用回調函數呢?我們先舉個例子,好比如今小明如今功課有個題不會做,因而給小紅打德律風說:我如今功課有個題不會做,你能幫我做下嗎?然後把謎底告知我?小紅聽到後認為這個題也不是連忙能做出來的,所以跟小明說我做完以後告知你。這個做完以後告知小明就是函數的回調,若何告知小明,小紅必需有小明的接洽方法,這個接洽方法就是回調函數。接上去我們用代碼來完成:

小明須要把接洽方法留給小紅,並且還得獲得謎底,是以須要個參數來保留謎底:

void contactMethod(int answer)
{
  //把謎底輸入
  printf("謎底為:%d\n", answer);
}

void contactMethod(int answer)
{
  //把謎底輸入
  printf("謎底為:%d\n", answer);
}
小紅這邊得拿到小明的接洽方法,須要用函數指針來存儲這個辦法:


void tellXiaoMing(int xiaoHongAnswer, void (*p)(int))
{
  p(xiaoHongAnswer);
}
//當小紅把謎底做出來的時刻,小紅把謎底經由過程小明留下的接洽方法傳曩昔
tellXiaoMing(4, contactMethod);

void tellXiaoMing(int xiaoHongAnswer, void (*p)(int))
{
  p(xiaoHongAnswer);
}
//當小紅把謎底做出來的時刻,小紅把謎底經由過程小明留下的接洽方法傳曩昔
tellXiaoMing(4, contactMethod);


下面的回調有人會問為何我們不克不及直接 tellXiaoMing 辦法中直接挪用 contactMethod 函數呢?由於小紅假如用函數指針作為參數的時刻,不只可以存儲小明的接洽方法,還可以存儲小軍的接洽方法,如許的話我這邊的代碼就不消修正了,你只須要傳入分歧的參數就好了,是以如許的設計代碼重用性很高,靈巧性很年夜。

函數回調的全部進程就是下面如許,這裡有個重要特色就是當我們應用回調的時刻,普通用在一個辦法須要期待操作的時刻,好比下面的小紅要比及謎底做出來的時刻才告訴小明,不如當小明問小紅時,小紅直接能給出謎底,就沒需要有回調了,那履行次序就是:

回調的次序是:

下面的小紅做題是個期待操作,比擬耗時,小明也不克不及一向拿著德律風期待,所以只要小紅做出來以後,再把德律風打歸去能力告知小明謎底。

是以函數回調有兩個重要特點:

函數指針作為參數,可以傳入分歧的函數,是以可以回調分歧的函數
函數回調普通應用在須要期待或許耗時操作,或許得在必定時光或許事宜觸發後回調履行的情形下
我們應用函數回調來完成一個靜態排序,我們如今個先生的構造體,外面包括了姓名,年紀,成就,我們有個排序先生的辦法,然則詳細是依照姓名排?照樣年紀排?照樣成就排?這個是不肯定的,或許一會還會有新需求,是以經由過程靜態排序寫好以後,我們只需傳入分歧的函數便可。

界說先生構造體:

//界說個構造體 student,包括name,age 和 score
struct student {
  char name[255];
  int age;
  float score;
};
//typedef struct student 為 Student
typedef struct student Student;

界說比擬成果的列舉:

//界說比擬成果列舉
enum CompareResult {
  Student_Lager = 1, //1 代表年夜於
  Student_Same = 0,// 0 代表等於
  Student_Smaller = -1// -1 代表小於
};
//typedef enum CompareResult 為 StudentCompareResult
typedef enum CompareResult StudentCompareResult;

界說成就,年紀和成就比擬函數:

/*
  經由過程成就來比擬先生
*/
StudentCompareResult compareByScore(Student st1, Student st2)
{
  if (st1.score > st2.score) {//假如後面先生成就高於前面先生成就,前往 1
    return Student_Lager;
  }
  else if (st1.score == st2.score) {//假如後面先生成就等於前面先生成就,前往 0
    return Student_Same;
  }
  else { //假如後面先生成就低於前面先生成就,前往 -1
    return Student_Smaller;
  }
}
 
/*
  經由過程年紀來比擬先生
*/
StudentCompareResult compareByAge(Student st1, Student st2)
{
  if (st1.age > st2.age) {//假如後面先生年紀年夜於前面先生年紀,前往 1
    return Student_Lager;
  }
  else if (st1.age == st2.age) {//假如後面先生年紀等於前面先生年紀,前往 0
   return Student_Same;
  }
  else {//假如後面先生年紀小於前面先生年紀,前往 -1
    return Student_Smaller;
  } 
}
 
/*
   經由過程名字來比擬先生
*/
StudentCompareResult compareByName(Student st1, Student st2)
{
  if (strcmp(st1.name, st2.name) > 0) {//假如後面先生名字在字典中的排序年夜於前面先生名字在字典中的排序,前往 1
    return Student_Lager;
  }
  else if (strcmp(st1.name, st2.name) == 0) {//假如後面先生名字在字典中的排序等於前面先生名字在字典中的排序,前往 0
    return Student_Same;
  }
  else {//假如後面先生名字在字典中的排序小於前面先生名字在字典中的排序,前往 -1
    return Student_Smaller;
  }  
}

界說排序函數:

/*
  依據分歧的比擬方法停止先生排序
  stu1[]:先生數組
  count :先生個數
  p :函數指針,來傳遞分歧的比擬方法函數
*/
void sortStudent(Student stu[], int count, StudentCompareResult (*p)(Student st1, Student st2))
{
  for (int i = 0; i < count - 1; i++) {
    for (int j = 0; j < count - i - 1; j++) {
      if (p(stu[j], stu[j + 1]) > 0) {
        Student tempStu = stu[j];
     stu[j] = stu[j + 1];
       stu[j + 1] = tempStu;
      }
    }
  }
}

界說構造體數組:

//界說四個先生構造體
Student st1 = {"lingxi", 24, 60.0};
Student st2 = {"blogs", 25, 70.0};
Student st3 = {"hello", 15, 100};
Student st4 = {"world", 45, 40.0};
//界說一個構造體數組,寄存下面四個先生
Student sts[4] = {st1, st2, st3, st4};

輸入排序前的數組,排序和排序後的數組:

//輸入排序前數組中的先生名字
printf("排序前\n");
for (int i = 0; i < 4; i++) {
  printf("name = %s\n", sts[i].name);//輸入名字
}
//停止排序
sortStudent(sts, 4, compareByName);
//輸入排序後數組中的先生名字
printf("排序後\n");
for (int i = 0; i < 4; i++) {
  printf("name = %s\n", sts[i].name);
}

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved