【C语言进阶深度学习记录】三十二 函数指针与使用函数指针实现回调函数

回调函数是非常重要的概念

1 函数的类型

跟以前学数组的时候是一样的,C语言中的数组是有自己的类型的。函数也是有自己的类型的。

  • 函数的类型由返回值、参数的类型、参数的个数共同决定

比如函数int add(int i, int j); 的函数类型是int (int, int)

  • 在C语言中可以使用typedef为函数重命名,如下
   typedef int f(int, int); // 定义f为函数类型int(int, int)
   typedef void p(int);   // 定义p为函数类型void(int)

上面定义函数类型一会再后面写具体的代码就会明白。

2 函数指针

与数组指针很相似

  • 函数指针用于指向一个函数
  • 函数名执行函数体的入口地址(这里与数组指针一样,数组名代表数组的入口地址)
  • 可以通过函数的类型定义函数指针:FuncType* pointer(例如上面定义的f,那么f* p; p指向函数类型为f的函数。)
  • 也可以直接定义(这种我们见到的比较多):type (*pointer)(parameter list)

上面:

  1. FuncType 是一种函数类型(例如int (int, int))
  2. type是函数的返回类型
  3. pointer是函数指针
  4. parameter list 是函数的参数列表

2.1 函数指针的使用

下面代码是演示定义函数类型与如何使用函数指针的代码:

  • 36-1.c:
#include <stdio.h>

typedef int FUNC(int);  //定义函数类型

int t(int i){
    return i*i;
}

void f(){
    printf("Call f()....\n");
}

int main(){

    FUNC* pt = t;  //定义函数指针pt
    void(*pf)() = &f;  //定义函数指针pf
    
    printf("pf = %p\n", pf);
    printf("f = %p\n", f);
    printf("&f = %p\n", &f);
    
    pf();   //一般这么用
    
    (*pf)();  //一般不这么用
    
    f();
    
    int k = pt(3);
    printf("k = %d\n", k);

    return 0;
}
  • 运行结果为:

上面代码比较简单,我们注意两点:

  • typedef int FUNC(int); //定义函数类型
  • FUNC* pt = t; //定义函数指针pt
  • void(*pf)() = &f; //定义函数指针pf
  • 函数名f代表函数的入口地址,&f代表函数的整个地址(类似于数组),所以在数值上f与&f是相等的。但是意义不一样。
  • 函数指针的使用:pf(); 或者 (*pf)(); 一般像前者那么使用

2.2 使用函数指针实现回调函数

如何使用C语言直接跳转到某个固定的地址开始执行?(不是goto)

使用回调函数,回调函数是一种很重要的思想。那么什么是回调机制呢?

  • 回调机制原理:
  1. 调用者不知道具体事件发生时需要调用哪一个具体的函数 (是不是与C++中的多态很像,是的C++中的多态原理就是这样)
  2. 具体的事件发生的时候 调用者通过函数指针,调用具体的函数。(是不是很像C++中的虚函数指针)

回调机制中的调用者和被调用者没有依赖关系

  • 上面的理论,看起来并不是很容易让人理解,下面直接上手写代码就知道什么是回调函数了。

  • 代码 36-2.c

#include <stdio.h>

typedef int (Fruit)(int); //定义Fruit为int(int)类型的函数 参数:吃多少克,返回值:获得多少能量

void Eat(Fruit* fruit, int n){ //函数指针fruit:指向吃什么水果的函数 参数n:吃多少克
    int ret = 0;
    
    printf("Eat...\n");
    
    ret = fruit(n);    //获得多少能量
    
    printf("Increase : %d\n", ret);
}

int Apple(int n){    //吃n克苹果获得ret克能量
    int ret = 0;
    int i = 0;
    
    for(i=0; i<n; i++){
        printf("Eat apple get energy : %d\n", 1);
        ret++;
    }
    return ret;
}

int Banana(int n){   //吃n克香蕉获得ret克能量
    int ret = 0;
    int i =0;
    
    for(i=0; i<n; i++){
        printf("Eat banana get energy : %d\n",3);
        ret+=3;
    }
    
    return ret;
}

int Pear(int n){     //吃n克梨子获得ret克能量
    int ret = 0;
    int i = 0;
    
    for(i=0; i<n; i++){
        printf("Eat pear get energy : %d\n",5);
        ret+=5;
    }
    return ret;
}
int main(){
    
    Eat(Apple, 5);  //get 5 
    printf("\n");
    Eat(Banana, 2);  // get 6
    printf("\n");
    Eat(Pear, 3);   //get 15
    printf("\n");
    
    return 0;
}
  • 上述代码的意思是:吃水果获得能量。但是吃什么水果,只有在程序运行起来之后才知道。
  • 所以吃这个动作Eat函数的参数无法指定吃哪种水果
  • 只能使用函数指针作为Eat函数的参数,当程序运行起来时根据传进来的参数确定吃什么水果,以及获得多少能量

上述程序编译运行结果为:

分析:

  • 上面代码主要的核心就在于函数指针的使用。一定要学会函数指针的定义使用。
  • 学会使用函数指针来实现回调函数

3 总结

  • 学会定义函数类型
  • 学会使用函数类型定义函数指针
  • 学会使用函数指针实现回调机制
全部评论

相关推荐

想干测开的tomca...:让我来压力你!!!: 这份简历看着“技术词堆得满”,实则是“虚胖没干货”,槽点一抓一大把: 1. **项目描述是“技术名词报菜名”,没半分自己的实际价值** 不管是IntelliDoc还是人人探店,全是堆Redis、Elasticsearch、RAG这些时髦词,但你到底干了啥?“基于Redis Bitmap管理分片”是你写了核心逻辑还是只调用了API?“QPS提升至1500”是你独立压测优化的,还是团队成果你蹭着写?全程没“我负责XX模块”“解决了XX具体问题”,纯把技术文档里的术语扒下来凑字数,看着像“知道名词但没实际动手”的实习生抄的。 2. **短项目塞满超纲技术点,可信度直接***** IntelliDoc就干了5个月,又是RAG又是大模型流式响应又是RBAC权限,这堆活儿正经团队分工干都得小半年,你一个后端开发5个月能吃透这么多?明显是把能想到的技术全往里面塞,生怕别人知道你实际只做了个文件上传——这种“技术堆砌式造假”,面试官一眼就能看出水分。 3. **技能栏是“模糊词混子集合”,没半点硬核度** “熟悉HashMap底层”“了解JVM内存模型”——“熟悉”是能手写扩容逻辑?“了解”是能排查GC问题?全是模棱两可的词,既没对应项目里的实践,也没体现深度,等于白写;项目里用了Elasticsearch的KNN检索,技能栏里提都没提具体掌握程度,明显是“用过但不懂”的硬凑。 4. **教育背景和自我评价全是“无效信息垃圾”** GPA前10%这么好的牌,只列“Java程序设计”这种基础课,分布式、微服务这些后端核心课提都不提,白瞎了专业优势;自我评价那堆“积极认真、细心负责”,是从招聘网站抄的模板吧?没有任何和项目挂钩的具体事例,比如“解决过XX bug”“优化过XX性能”,纯废话,看完等于没看。 总结:这简历是“技术名词缝合怪+自我感动式凑数”,看着像“背了后端技术栈名词的应届生”,实则没干货、没重点、没可信度——面试官扫30秒就会丢一边,因为连“你能干嘛”都没说清楚。
点赞 评论 收藏
分享
12-14 11:43
黑龙江大学 Java
用微笑面对困难:确实比较烂,可以这么修改:加上大学的qs排名,然后大学简介要写一些,然后硕士大学加大加粗,科研经历第一句话都写上在复旦大学时,主要负责xxxx,简历左上角把学校logo写上,建议用复旦大学的简历模板
点赞 评论 收藏
分享
评论
1
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务