C语言中的函数指针

  • 函数指针和一个简单的函数
  • 先来看最简单的Hello World程序:

    #include <stdio.h>

    //函数实现
    void sayHello(){
      printf("hello world\n");
    }

    // main函数调用
    int main() {
      sayHello();
    }

    如果要把上面的直接调用改为用函数指针调用,只要把上面的main函数改为:

    int main() {
      void (*sayHelloPtr)() = sayHello;
      (*sayHelloPtr)();
    }

    首先来理解代码中的void (*sayHelloPtr)()=sayHello;:

    1. void 说明指针指向的函数的返回值类型。

    2. sayHelloPtr 是函数指针的名字,我们用 * 符号表示这是一个指针,就像声明其他类型的指针一样。

    3. *sayHelloPtr 两端的括号是必需的,否则 void * sayHelloPtr() 表示的是一个返回 void * 的函数。

    4. 参数列表紧跟在指针名后面,本例中没有参数,所以括号中什么都没有。

    5. 综上所述,这行代码的意义就是,sayHelloPtr 是一个指向名为 sayHello 的返回值为 void 的函数指针。

    对上述代码中的(*sayHelloPtr)();的理解应该注意下面几点:

    1. 对 sayHelloPtr 的解引用方式也和其他指针的解引用一样,即在指针之前使用解引用符 * 。

    2. 同样的,为了使其能被识别为函数指针,需要在两端加上括号,即(*sayHelloPtr)。所以应该记住声明和解引用时都要在两端加上括号。

    3. 括号操作符用于C语言中的函数调用,如果有参数参与,将其置于括号中。

    除了上面的调用方式,我们还可以用其他方法调用函数指针,就像对待一个普通函数那样:

    int main() {
      void (*sayHelloPtr)() = sayHello;
      sayHelloPtr();
    }

    可以认为 sayHelloPtr(); 和 (*sayHelloPtr)(); 具有相同的意义。

  • 带参数的函数指针
  • 实例代码是最好的阐述方式:

    #include <stdio.h>

    //函数实现
    void subtractAndPrint(int x, int y) {
      int z = x - y;
      printf("Simon says, the answer is: %d\n", z);
    }

    //main函数调用
    int main() {
      void (*sapPtr)(int, int) = subtractAndPrint;
      (*sapPtr)(10, 2);
      sapPtr(10, 2);
    }

  • 带参数且有返回值得函数指针
  • 道理很简单,只要把上面代码中的关键字 void 改为相应的其他关键字即可。

    #include <stdio.h>

    // 函数实现
    int subtract(int x, int y) {
      return x - y;
    }

    // main函数调用
    int main() {
      int (*subtractPtr)(int, int) = subtract;

      int y = (*subtractPtr)(10, 2);
      printf("Subtract gives: %d\n", y);

      int z = subtractPtr(10, 2);
      printf("Subtract gives: %d\n", z);
    }

    对于返回值的使用也与普通函数调用无异。

  • 把函数指针作为参数来传递
  • #include <stdio.h>

    // 加法 x+ y
    int add(int x, init y) {
      return x + y;
    }

    // 减法 x - y
    int subtract(int x, int y) {
      return x - y;
    }

    // 根据输入执行函数指针
    int domath(int (*mathop)(int, int), int x, int y) {
      return (*mathop)(x, y);
    }

    // main函数调用
    int main() {

      // 用加法调用domath
      int a = domath(add, 10, 2);
      printf("Add gives: %d\n", a);

      // 用减法调用domath
      int b = domath(subtract, 10, 2);
      printf("Subtract gives: %d\n", b);
    }

  • 函数名和地址
  • 一个函数名(或称标签),被转换成了一个函数指针本身。这表明在函数指针被要求当作输入的地方,就能够使用函数名。这也导致了一些看起来很糟糕的代码却能够正常运行。比如:

    #include <stdio.h>

    // 加法 x + y
    void add(char *name, int x, int y) {
      printf("%s gives: %d\n", name, x + y);
    }

    // main函数调用
    int main() {

      // 一些糟糕的函数指针赋值
      void (*add1Ptr)(char*, int, int) = add;
      void (*add2Ptr)(char*, int, int) = *add;
      void (*add3Ptr)(char*, int, int) = &add;
      void (*add4Ptr)(char*, int, int) = **add;
      void (*add5Ptr)(char*, int, int) = ***add;

      // 仍然能够正常运行
      (*add1Ptr)("add1Ptr", 10, 2);
      (*add2Ptr)("add2Ptr", 10, 2);
      (*add3Ptr)("add3Ptr", 10, 2);
      (*add4Ptr)("add4Ptr", 10, 2);
      (*add5Ptr)("add5Ptr", 10, 2);

      // 当然,这也能运行
      add1Ptr("add1PtrFunc", 10, 2);
      add2Ptr("add2PtrFunc", 10, 2);
      add3Ptr("add3PtrFunc", 10, 2);
      add4Ptr("add4PtrFunc", 10, 2);
      add5Ptr("add5PtrFunc", 10, 2);
    }

    这是一个简单的例子,运行这段代码,你会看到每个函数指针都回执行,只是会收到一些关于字符转换的警告。但是,这些函数指针都能正常工作。我们讨论一下上述代码中的函数指针赋值部分。

    1. 指针赋值的第1行, add 作为函数名,返回这个函数的地址,它被隐式地转换为一个函数指针。如前所述,在函数指针被要求当作输入的地方,就能够使用函数名。

    2. 指针赋值的第2行,解引用符 * 作用于 add 之前,即返回在这个地址的函数。之后跟函数名一样,被隐式地转换为一个函数指针。

    3. 指针赋值的第3行,取地址符作用于 add 之前,返回这个函数的地址,之后又得到一个函数指针。

    4. 再后面的两行,add 不断解引用自身,不断返回函数名,并转换为函数指针。到最后,它们的结果都和函数名没有区别。

    显然,这段代码不是优秀的实例代码。我们从中得到如下知识。第一,函数名会被隐式地转换为函数指针,就像作为参数传递的时候,数组名被隐式地转换为指针一样。在函数指针被要求当作输入的任何地方,都能够使用函数名。第二,解引用符*和取地址符&用在函数名之前基本都是多余的。

    本文主要参考C语言函数指针基础

    发表评论

    电子邮件地址不会被公开。 必填项已用*标注