[C语言]函数指针

本篇是《C指针:基本概念、核心技术及最佳实践》(Pointers In C : A hands on Approach) 第七章“函数指针”的读书笔记。

1 定义方式

函数指针可以实现动态调用函数,适用与在程序运行时才决定调用哪个函数的情形,可以把函数指针看成动态调用所需函数的钩子。
函数指针变量指向内存中存储程序的位置。

函数返回类型  (*  指针变量名)(函数参数表)

实例:

int add(int x, int y);
int (*ptradd)(int , int);

char* stradd(char* a, char* b);
char* (*ptradd)(char*, char*);

2 初始化及赋值方式

有两种方式对函数指针变量初始化,这两种方式等价。

  1. 使用 &取址运算符
  2. 直接赋值 (常用这种方式)
void func1(int num)
{
    //函数实现....
}
int* func2(int x, int y))
{
    //函数实现....
}

//初始化
void (*pfunc1)(int ) = &func1;
void (*pfunc1)(int ) = func1; // 两种写法等价

int* (*pfunc2)(int, int) = &func2;
int* (*pfunc2)(int, int) = func2; // 两种写法等价    

//赋值
void (*pfunc1)(int );
int* (*pfucn2)(int , int);
pfunc1 = &func1;
pfunc1 = func1; // 两种写法等价
pfunc2 = &func2;
pfucn2 = func2;  // 两种写法等价    

不管哪种赋值方法,pfunc1 pfunc2 都保存的是函数func1 和 func2 的地址。

3 调用函数指针

调用方法也有两种:

  1. 直接指针变量名加上参数列表就可以了。指针变量名是函数的入口也是函数的地址。
  2. 取值 * 运算
指针变量名(参数列表)     // 第一种形式
(*指针变量名)(参数列表)  // 第二种形式

void func1(int m)
{
    printf("This is func1\n");
}
int *func2(int x, int y)
{
    int *z = (int *)malloc(sizeof(int));
    *z = x + y;
    return z;
}
int main()
{
    void (*pfunc1)(int) = func1;
    int *(*pfunc2)(int, int) = func2;
    printf("pfunc1 = %p\n", pfunc1);
    printf("pfunc2 = %p\n", pfunc2);
    pfunc1(10);  //打印一句话;
    (*pfunc1)(10);  // 和上面等价
    pfunc1(10,20);  //返回相加结果的地址
    printf("20 + 10 = %d", *pfunc2(20, 10)); //pfunc2返回地址,所以要加*
    printf("20 + 10 = %d", *(*pfunc2)(20, 10)); // 和上面等价
    return 0;
}

4 typedef 函数指针

typedef用于定义一个类型的别名,比如typedef int _int_t_int_t 定义成int的别名。

那么typedef作用于函数/函数指针时又是怎么样的呢?

形式:

typdef 返回类型(*新类型)(参数列表)

用typedef给函数/函数指针定义别名时,相当于直接在函数/函数指针的声明前面直接加上typedef。

实例1:

typedef int func1(int, int);
int add(int x, int y)
{
    printf("%d\n", x + y);
    return x + y;
}
int main()
{
    add(1, 2);
    // func1 f1 = add;//error
    func1 *f1 = add;
    // func1 *f1 = &add; // equal to the previous line
    f1(2, 3);
    return 0;
}

这里定义了一个func1类型,func1是一个有两个int参数、返回int的函数类型。在main中使用func1类型时,只能定义func1* 的指针类型。

实例2:

int add(int x, int y)
{
    printf("%d\n", x + y);
    return x + y;
}

typedef int (*func2)(int, int);

int main()
{
    add(1, 2);
    func2 f2 = add;
    f2(4, 5);
    return 0;
}

这里定义了一个函数指针func2,func2指向有两个int参数、返回int的函数。、

5 函数指针数组

函数指针数组提供了一种利用数组索引的函数切换方式。

形式:

<所指向函数的返回类型> (* 函数指针变量 [ ]) (所指向函数的参数列表)

实例:

int add(int x, int y);
int sub(int x, int y);
int mul(int x, int y);
int div(int y, int y);
int (*operation[])(int x, int y) = { add, sub, mul, div);
// 调用函数
opeartion[0](10,20);

PS: 使用函数指针数组时需要注意,如果索引越界,程序可能跳到任何地方。

6 函数指针作为函数参数

假如我们要设计一个接口search,用来在数据结构中搜索某个数据是否存在,每种数据结构对应的搜索算法实现都不同,这时就可以让用户自行输入搜索算法的实现。
首先有以下几种算法:

bool arraysearch(int n);
bool binarysearch(int n);
bool linkedlistsearch(int n);

我们可以通过函数指针动态调用这些函数。

bool search(bool (*funcptr)(int), int n)
{
    return funcptr(n);
    // return (*funcptr)(n); // 等价
}

实例:

int linkedlistsearch(int n)
{
    printf("this is linkedlistsearch\n");
    return 1;
}
int arraysearch(int n)
{
    printf("this is arraysearch\n");
    return 1;
}
int search(int (*funcptr)(int), int n)
{
    // return (*funcptr)(n);
    return funcptr(n);
}
int main()
{
    search(arraysearch, 20);
    search(linkedlistsearch, 20);
    return 0;
}

运行结果:

this is arraysearch
this is linkedlistsearch

当然,对于search接口,我们也可以用typedef先定义一个函数指针类型,search参数列表里用函数指针类型表示。

typedef int (*funcptr)( int, int);
int search(funcptr f, int n)
{
    return f(n);
}

再比如标准C库函数<stdlib.h>中的快速排序算法qsort(),该算法用来对array排序,但是array中可能存储int,也可能存储double,也可能是char,所以就需要用户手动实现算法中两个数值的比较。

void qsort (void *array, size_t count, size_t size, __comparison_fn_t compare)

最后一个参数compare的类型是__comparison_fn_t,这个类型的定义就是函数指针:

typedef int (*__compar_fn_t) (const void *, const void *)

用户手动实现compare比较算法给qsort函数调用。

应用实例:

int compare_double(const void *x, const void *y)
{
    const double *x1 = (const double *)x;
    const double *x2 = (const double *)y;
    return (*x1 > *x2) - (*x2 - *x1);
}

int main()
{
    double data[6] = {1.1, 3.1, 1.4, 5.2, 1.2};
    qsort(data, 6, sizeof(double), compare_double);
    for (int i = 0; i < 6; i++)
    {
        printf("%lf ", data[i]);
    }
    return 0;
}

结果:

0.000000 1.100000 1.200000 1.400000 3.100000 5.200000

7 函数指针作为函数返回值

函数指针用为函数返回值有两种实现方式:一种是先定义函数指针的类型,返回这个类型,这种方法比较容易;另一种是直接返回,这种方法比较复杂。下面举例说明:

方法1:

先typedef一个函数指针类型,然后用函数返回这个类型

typedef int (*funcptr)(int , int);
funcptr foo (int x, int y))
{
    funcptr addptr = add;
    return addptr(x,y);
}

实例:

int add(int x, int y)
{
    return x + y;
}
int sub(int x, int y)
{
    return x - y;
}
int mul(int x, int y)
{
    return x * y;
}
int div(int x, int y)
{
    return x / y;
}

int (*funcptr[])(int x, int y) = {add, sub, mul, div};

// 定义一个接口,用户输入选择加减乘除运算
typedef int (*calc)(int, int);
calc operationfunc(int index)
{
    return funcptr[index];
}
int main()
{
    int choice, p1, p2, res;
    printf("type 0-add 1-sub 2-mul 3-div\n");
    scanf("%d", &choice);
    printf("param1:\n");
    scanf("%d", &p1);
    printf("param2:\n");
    scanf("%d", &p2);
    /*
    calc 是函数指针类型,指向int (*)(int,int)类型的函数
    calculator 是函数指针变量,其值是接口函数operationfunc的返回值
    operationfunc 是返回函数指针的函数, 返回int (*)(int,int)类型的函数指针,也就是calc类型
    */
    calc calculator = operationfunc(choice);
    res = calculator(p1, p2);
    printf("res = %d", res);
    return 0;
}

方法2:

方法2不用typedef定义一个函数指针类型的别名,而是通过一步步推导出一个复杂的函数声明,这与解读一个复杂的函数声明是一个相反的过程。

首先有以下两个声明:

int add(int, int);
int (*addptr)(int, int); //用来指向上面的函数,也是函数要返回的类型

现在我们想要定义一个无任何输入(函数参数列表是void),返回addptr函数指针类型的函数

  1. 编写函数名:funcptr
  2. 添加函数的参数列表:funcptr(void)
  3. funcptr(void)返回函数指针int (*)(int, int) ,那么添加取值操作“*”,*funcptr(void) 就是一个函数
  4. *funcptr(void) 是一个函数,就要有参数列表,funcptr返回的指针指向的函数有两个int参数,添加上去,就成了(*funcptr(void))(int , int) ,注意要给取值操作加上括号
  5. 最后把funcptr返回的指针指向的函数的返回值添加上就可以了:int (*funcptr(void))(int , int)

另一种观点是,只需要给 funcptr(void) 填上返回值类型就可以了,funcptr返回的是int (*)(int, int) 类型,也就是addptr类型,那么直接在addptr的声明 nt (*addptr)(int, int); 把addptr替换成 funcptr(void) ,或者是直接将 funcptr(void) 添加在int (*)(int, int) 里面的“*”后面,得到int (*funcptr(void))(int , int)

8 函数指针声明总结

下面举几个函数指针声明的例子(来源书中)用于理解。

int add(int x, int y);  //函数
int (* addfptr)(int x, int y); //函数指针
int* add(int x, int y);  //函数
int* (*addfptr)(int x, int y); //函数指针
int add(int x, int y);  //函数
int (*addfptr)(int x, int y); //函数指针
void callfunc(int (*addfptr)(int x, int y)); //函数,参数是函数指针
int (*(*addfptr))(int x, int y); //指针,指向返回值是函数指针的函数
 //addfptr是一个指针,指向另一个指针,另一个指针指向参数是(int,int)返回值是int的函数
int (*retfuncptr(void)(int x, int y); //函数,返回函数指针
int*(*retfuncptr(void)(int x, int y);//函数,返回函数指针,函数指针指向的函数返回int 指针

如何解读复杂的函数/数组/指针声明?这里引用知乎用户邱昊宇的回答。

• 看见 [N] 读作 an array of N
• 看见 () 读作 a function that returns
• 看见 T * 读作 a pointer to T
然后从变量开始,先读右边的东西,再读左边的东西,然后被括号包裹着的话,就跳出去重复这个动作。

比如 int *foo[3]
• 右边是个 [3] :an array of 3…
• 左边是 int * :pointer to int
• 所以完整的就是 an array of 3 pointers to int

加上括号 int (*foo)[3]
• 右边没有东西
• 左边是个 * :a pointer to…
• 括号外面右边是 [3] :an array of 3…
• 括号外面左边是 int :int
• 所以完整的就是 a pointer to an array of 3 int

再复杂一点 int (*(*vtable)[])()
• 首先是 *vtable
• 右边没东西 • 左边是 * :a pointer to…
• 去掉一层括号是 *X[]
• 右边是 [] :an array of…
• 左边是 * :a pointer to…
• 去掉一层括号是 int X()
• 右边是 () :a function that returns
• 左边是 int :an int
• 所以完整的就是 a pointer to an array of pointers to a function that returns int

所以 int *(*f[3])() 就是
• 首先看 *f[3]
• 右边是 [3] :an array of 3…
• 左边是 * :pointer to
• 去掉一层括号是 int *X()
• 右边是 () :a function that returns
• 左边是 int * :a pointer to int
• 所以完整的就是 an array of 3 pointers to a function that returns a pointer to int

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Visual Studio (VS) 提供了一种称为“调试器监视”的功能,它可以让你监视和分析程序执行过程中的变量值,包括C语言中的函数指针。当你需要检查函数指针是否被正确地指向了预期的函数,或者在函数调用过程中,函数指针的值如何变化时,监视器非常有用。 以下是如何使用VS监视C语言函数指针的基本步骤: 1. **设置断点**:在你想要监视的函数指针赋值或使用的地方设置一个断点。这通常是在函数声明或函数指针初始化的代码行上。 2. **进入调试模式**:当程序运行到断点处暂停时,切换到调试模式。 3. **查看内存**:在调试窗口(通常是“ Autos”或“ Locals”视图),你可以看到函数指针的当前值。在这里,你可以查看它的地址、类型以及指向的具体函数。 4. **监视变化**:如果你想要跟踪函数指针在整个函数调用过程中的行为,可以在“Watch”或“Expressions”窗格中创建一个新的监视表达式,指定你要监视的函数指针。 5. **单步执行**:通过单步执行(F10或Step Into),你可以查看函数指针值如何随函数调用而改变。 6. **检查函数调用**:如果函数指针指向的是另一个函数,你可以通过“Call Stack”查看调用堆栈,确认函数调用是否正确。 相关问题-- 1. 在VS中,如何在断点处检查函数指针的值? 2. 如何在VS的监视视图中设置长期跟踪函数指针的变化? 3. 如何通过VS的调试工具验证函数指针是否指向了正确的函数?

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值