C中函数指针的正确值类型

时间:2018-11-22 21:07:18

标签: c function-pointers

我试图理解C中的函数指针。在Internet上阅读它时(主要是堆栈溢出QA)-我遇到了两种可以为函数指针赋值的方法

#include <stdio.h>

double add(int a, double b) {
  return a + b;
}

double subtract(int a, double b) {
  return a - b;
}

int main() {
  int op_a = 23;
  double op_b = 2.9;

  // Case 1. Just pass the function (not its address)
  double (*math_op)(int a, double b) = add;
  printf("The output of Math Operation %f\n", math_op(op_a, op_b));

  // Case 2. Pass the function's address
  math_op = &subtract;
  printf("The output of Math Operation %f\n", math_op(op_a, op_b));

  printf("End of program\n");
  return 0;
}

我有几个问题

  1. 在上面的代码中-将值赋给函数指针的正确方法是什么?我看到了一些堆栈溢出的答案,它们遵循了情况1 中的约定,并且我也看到了一些答案遵循了情况2 中的约定。两者似乎都对我来说工作正常。哪个是正确的(或更可取的)?
  2. 此外,为了调用函数指针,我看到了两种调用它们的方法-math_op(op_a, op_b)(*math_op)(op_a, op_b)。再次,有没有一种更好的方法可以这样做-两者似乎都对我来说是正确的。

3 个答案:

答案 0 :(得分:3)

我认为

Function calls (§6.5.2.2/1)

  

表示被调用函数的表达式必须具有指向函数的类型指针,该函数返回void或返回除数组类型以外的其他对象类型。

足以回答您的两个问题。函数名称隐式转换为函数类型指针,因此在将函数的地址分配给函数指针时使用address-of运算符是多余的。对于通过函数指针调用函数,语法与“常规”函数调用没有什么不同。

答案 1 :(得分:1)

这实际上是C的怪癖。*和(单个)&在获取函数地址时会被忽略。

int main() {
    printf("%p %p %p", (void*)add, (void*)************add, (void*)&add);
}

“解引用”函数指针时,*被忽略

int main() { 
    double (*math_op)(int a, double b) = add;
     printf("%p %p %f", (void*)math_op, (void*)*************math_op, (***************math_op)(1, 1.0));
}

没关系。但是要保持一致。通常,在这种情况下,我看不到*&也没有使用。

答案 2 :(得分:0)

首选案例1。

&是可选的,用于获取函数的地址(就像:char buf[100]; char *bp = buf;

首选案例1的原因是,语法 same ,用于从函数 address 或其他函数分配给函数指针指针

typedef double (*math_func)(int a, double b);
math_func math_op = add;
math_func math_op2 = sub;
math_func math_op3 = math_op2;

类似地,这就是为什么您可以说:math_op(2,23)作为(*math_op)(2,23)

的简写的原因

更新:

  

指针typedef不是首选(无论如何对我来说就是这样)

它们肯定有助于函数指针[Jonathan Leffler例外]。

如果您有(例如)10个需要带参数作为函数指针的函数,您是否想在所有10个函数原型中都将其拼写出来,特别是如果函数指针本身带8个参数呢?

假设[在某些API中]您有一个函数指针:

typedef void (*funcptr)(int x,int y,int z);

请考虑您具有以下形式的功能:

void func(funcptr f,...);

而且,您具有以下函数的调用链:

a -> b -> c -> d -> apiabc

函数a-d不会调用f,而只能执行apiabc。只有apiabc知道如何正确调用f。其他指针只是沿 指针传递给它们,以便apiabc可以对其执行操作。

如果必须funcptr进行更改,则更改a-d会出现问题(即它们不是API的一部分-仅是API的用户)。

在一般情况下,对于IMO指针typedef 如果它们相当明显且一致,则它们很好。

这将是用法的一个示例:

typedef struct node *Node;              // poor
typedef struct node *pNode;             // MS (common but, IMO, poor)
typedef struct node *nodeP;             // okay
typedef struct node *nodeptr;           // better?
typedef const struct node *nodecptr;    // better?

IMO,MS版本很差,因为如果我们对所有名称pNode进行排序,它们将与所有[无关] pWhatever混合在一起,而不会吸引到node,而从左向右{{ 1}}首先是主要部分(即,它与节点有关)。

IMO,POSIX将nodeP声明为其类型名称的省份是 hubris 。但是,发生冲突的可能性很小(无论如何,对我来说,因为我总是使用可能不会冲突的名称)。如果基于未来 POSIX类型存在 冲突,我会迷失方向,必须更改自己的定义。

因此,我的个人习惯是:

whatever_t

只要一致地应用,任何此类约定都可以使用。在给定的代码集中,如果一个人第一次遇到typedef struct node node_t; typedef node_t *node_p; typedef const node_t *node_pc; ,是的,必须在foo_p文件中查找定义。此后,当人们看到.h时,就知道该约定。

对我来说,这通常不是问题,因为当我遇到不熟悉的代码库时,我总是先查看bar_p文件中的定义,然后再查看中的代码。 .h