• C语言函数指针和回调函数


     彻底搞定C指针-函数名与函数指针

    函数名&函数名取地址

    函数指针

    通常我们可以将指针指向某类型的变量,称为类型指针(如,整型指针)。若将一个指针指向函数,则称为函数指针。

    函数名的意义

    函数名代表函数的入口地址,同样的,我们可以通过根据该地址进行函数调用,而非直接调用函数名。

     1 void test001(){
     2     printf("hello, world");
     3 }
     4 
     5 int main(){
     6 
     7     printf("函数入口地址:%d", test001);//qt中的函数入口地址不会变,C中会变,这里仅为了说明问题
     8     //test001();
     9     int *testADD = (int *)20123883;//将地址转化为int型指针
    10     void(*myfunc)() = testADD;//将函数写成函数指针,有些书上会写&testADD
    11     myfunc(); //调用函数指针
    12     system("pause");
    13     return 0;
    14 }

    另外,还有以下结论:

    (1)test001的函数名与myfunc函数指针都是一样的,即都是函数指针。test001函数名是一个函数指针常量,而myfunc是一个函数指针变量,这是它们的关系。

     1 int test001(int a, char b){
     2     printf("hello, world
    ");
     3     return 0;
     4 }
     5 
     6 int main(){
     7 
     8     int(*myFun)(int, char) = test001;
     9     myFun = test001;
    10 
    11     //下面四种表达式的结果是相同的
    12     int a = 10;
    13     char b = 's';
    14     myFun(a, b);
    15     (*myFun)(a, b);
    16     test001(a, b);
    17     (*test001)(a, b);
    18 
    19     system("pause");
    20     return 0;
    21 }

    (2)testADD和&testADD的值一样,但表达的含义不同,与数组名和“&数组名”类似,详见指针和数组的关系

    定义函数指针

    定义函数指针最简单的是直接定义函数指针变量,另外还有定义函数类型和定义函数指针类型。

     1 int test001(int a, char b){
     2     printf("hello, world
    ");
     3     return 0;
     4 }
     5 
     6 void test002(){
     7 
     8     //定义函数类型
     9     typedef int(Fun)(int, char);
    10     Fun *funFir = test001;
    11 
    12     //定义函数指针类型
    13     typedef int(*FunP)(int, char);
    14     FunP funSec = test001;
    15 
    16     //定义函数指针变量
    17     int(*funThi)(int, char) = NULL;//若报错,在强制转型,(int(*)(int , char))NULL
    18     funThi = test001;
    19 }

    函数指针用于形参

    这种用法通常出现在回调函数中,一般回调函数用于定制操作,下面的例子将说明如何进行定制操作

     1 /*
     2 -----------------------
     3 函数指针用作另一个函数的参数
     4 -----------------------
     5 */
     6 int con1(int a, int b){
     7     return a + b;
     8 }
     9 
    10 int con2(int a, int b){
    11     return a - b;
    12 }
    13 
    14 int con3(int a, int b){
    15     return a + b + 10;
    16 }
    17 
    18 //在函数体中显式调用函数,将失去灵活性
    19 //尽管我可以用switch实现三种con的切换
    20 void doc(){
    21     int a = 10;
    22     int b = 20;
    23     int ret = con1(a, b);
    24 }
    25 
    26 //用如下的调用方式,调用者并不知道调用的哪个函数
    27 //因此根据函数指针的函数原型可以自己实现新函数,并进行调用
    28 int doc_p(int(*temp)(int ,char)){
    29     int a = 10;
    30     int b = 20;
    31     int ret = temp(a,b);
    32     return ret;
    33 }
    34 
    35 /*
    36 ---------------------
    37 函数指针数组
    38 ---------------------
    39 */
    40 void func1(){
    41     printf("a");
    42 }
    43 void func2(){
    44     printf("a");
    45 }
    46 void func3(){
    47     printf("a");
    48 }
    49 
    50 void test003(){
    51     int(*func[3])();
    52     func[0] = func1;
    53     func[1] = func2;
    54     func[2] = func3;
    55 
    56     for (int i = 0; i < 3; ++i)
    57     {
    58         func[i];
    59     }
    60 }

    为什么我们要把函数作为参数来调用呢,直接在函数体里面调用不好吗?

    在这个意义上,“把函数做成参数”和“把变量做成参数”目的是一致的,就是以不变应万变。形参是不变的,而实参是可以定制的。唯一不同的是,普通的实参可以由计算机程序自动产生,而函数这种参数计算机程序是无法自己写出来的,因为函数本身就是程序,它必须由人来写。所以对于回调函数这种参数而言,它的“变”在于人有变或者人的需求有变。

    回调函数

    回调函数和普通函数完成的功能是一样的,但回调函数更灵活,普通函数在函数体中调用,失去了变量的灵活性,有点类似于模板编程。

    (1)首先是通过内存偏移,访问数组的各元素地址,两种方法的结果相同

     1 void printAll(void *arr, int eleSize, int len){
     2     
     3     char *start = (char*)arr; //强制转型
     4     for (int i = 0; i < len; ++i){
     5         printf("%d
    ", start+i*eleSize);//内存偏移
     6     }
     7 }
     8 
     9 void test004(){
    10     int arr[5] = {1,2,3,4,5};
    11     printAll(arr, sizeof(int), 5);
    12     printf("-------------------
    ");
    13     for (int i = 0; i < 5; ++i){
    14         printf("%d
    ", &arr[i]);
    15     }
    16 }
    17 
    18 int main(){
    19     test004();
    20 
    21     system("pause");
    22     return 0;
    23 }

    (2)对上面的函数用函数指针进行改写

     1 //添加函数指针作形参,必须写明变量名
     2 void printAll(void *arr, int eleSize, int len, void(*print)(void *data)){
     3     
     4     char *start = (char*)arr; //强制转型
     5     for (int i = 0; i < len; ++i){
     6         char *eleAddr = start + i*eleSize;
     7         print(eleAddr);
     8         //print(start+i*eleSize);
     9     }
    10 }
    11 
    12 //自定义的被调用函数
    13 void Myprint(void * data){
    14     int *p = (int *)data;
    15     printf("%d
    ", *p);
    16 }
    17 
    18 void test004(){
    19     int arr[5] = {1,2,3,4,5};
    20     printAll(arr, sizeof(int), 5, Myprint);
    21 }
    22 
    23 int main(){
    24     test004();
    25 
    26     system("pause");
    27     return 0;
    28 }

    (3)对上面的函数指针添加自定义的数据类型

     1 //添加函数指针作形参,必须写明变量名
     2 void printAll(void *arr, int eleSize, int len, void(*print)(void *data)){
     3     
     4     char *start = (char*)arr; //强制转型
     5     for (int i = 0; i < len; ++i){
     6         char *eleAddr = start + i*eleSize;
     7         print(eleAddr);
     8         //print(start+i*eleSize);
     9     }
    10 }
    11 
    12 //自定义的被调用函数
    13 void Myprint(void * data){
    14     int *p = (int *)data;
    15     printf("%d
    ", *p);
    16 }
    17 
    18 struct Person{
    19     char name[64];
    20     int age;
    21 };
    22 
    23 //添加自定义的数据类型打印函数
    24 void MyprintStruct(void * data){
    25     struct Person *p = (struct Person  *)data;
    26     printf("%s,%d
    ", p->name, p->age);
    27 }
    28 
    29 
    30 
    31 
    32 void test004(){
    33     int arr[5] = {1,2,3,4,5};
    34     printAll(arr, sizeof(int), 5, Myprint);
    35 
    36     struct Person person[] = {
    37         {"aaa", 10},
    38         {"bbb", 20}
    39     };
    40     printAll(person, sizeof(struct Person), 2, MyprintStruct);
    41 
    42 }
    43 
    44 int main(){
    45     test004();
    46 
    47     system("pause");
    48     return 0;
    49 }

    回调函数最大的优势在于灵活操作,可以实现用户定制的函数,降低耦合性,实现多样性。

  • 相关阅读:
    Day4 0708
    Day2 0706
    两道递推公式题的解题报告
    博客还需优化
    飞行路线Luogu4568
    堆优化Dijkstra(Luogu 4779)
    2019四等奖的清明节征文
    2019四等奖的叶圣陶初稿
    Luogu P1072 Hankson的趣味题
    Loj10022 埃及分数(迭代加深搜索IDDFS)
  • 原文地址:https://www.cnblogs.com/qinguoyi/p/10198019.html
Copyright © 2020-2023  润新知