8种机械键盘轴体对比
本人程序员,要买一个写代码的键盘,请问红轴和茶轴怎么选?
指针是C语言的一大特色,也是其学习过程中的一大难点,在一般的编程中,我们见得最多的就是常见数据类型的指针变量,包括基本数据类型和复合数据类型,而针对于函数指针的编程则显得少之又少,不过,少见并不代表其不重要,利用好它将有助于我们在复杂的项目中大大提高代码的复用率,下面我们来一起学习函数指针的最基本使用方法。
Hello World 与函数指针
和学习其他编程语言一样,我们将从最简单的 Hello World 程序来学习函数指针的使用方法。在 C 语言中,我们通常会使用一下的方式来输出一个 Hello World :1
2
3
4
5
6
7
8
9
10
11void (){
printf("Hello World!n");
}
int main(){
sayHello();
return 0;
}
这里我们创建了一个返回值为 void 类型、参数为空的函数 sayHello,使用C语言标准输出函数 printf 打印出 Hello World!,在主函数中,我们直接通过函数名调用该函数打印出 Hello World!。下面,我们将创建一个最简单的函数指针来实现同样的功能:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16void (){
printf("Hello World!n");
}
int main(){
sayHello();
void (* ptr)() = sayHello;
ptr();
(*ptr)();
return 0;
}
我们先来看一看程序执行结果:1
2
3
4
5lihuideMacBook-Pro:Learn lihui$ gcc -o hello hello.c
lihuideMacBook-Pro:Learn lihui$ ./hello
Hello World!
Hello World!
Hello World!
可以看到,和通过函数名调用函数一样,通过函数指针调用函数的两种方式同样打印出了 Hello World! ,我们来分析一下这个过程是如何实现的:首先,我们通过 void (* ptr)() = sayHello 的方式定义了一个函数指针,并将函数 sayHello 的地址赋给它,这里,我们需要注意的是,void 代表函数指针所指向函数的返回值类型,函数名代表的是函数地址,这和数组名代表数组地址是一样的;
(* ptr) 表示一个指向函数的指针,一定要用括号括起来,否则就变成了 void * ptr(),这表示一个返回值类型为 void 的函数,而非函数指针;
(* ptr) 后的 () 则是函数指针所指向函数的参数列表,这里 sayHello 函数的参数列表为空;
在定义完函数指针后,我们通过两种方式来调用函数指针所指向的函数,分别是: ptr() 和 (* ptr)(),这两种调用方式的结果是一样的;
由以上分析可以,通过 void (* ptr)(),我们实际上定义了一个指向参数列表为空,返回值类型为 void 的函数指针 ptr。通过 ptr() 或者是 (* ptr)(),我们可以调用函数指针 ptr 所指向的函数。
带参数的函数指针
我们不妨改进一下上面的 Hello World 程序,使其可以动态的输出调用者的名字:1
2
3
4
5
6
7
8
9
10
11void (char *author){
printf("Hello World! By %s.n", author);
}
int main(){
sayHello("Hui Li");
return 0;
}
同样的,我们将上面带参数的 Hello World 程序改为通过函数指针的方式执行:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16void (char *author){
printf("Hello World! By %s.n", author);
}
int main(){
sayHello("Hui Li");
void (* ptr)(char *) = sayHello;
ptr("Hui Li");
(*ptr)("Hui Li");
return 0;
}
看一下程序执行结果:1
2
3
4
5lihuideMacBook-Pro:Learn lihui$ gcc -o hello hello.c
lihuideMacBook-Pro:Learn lihui$ ./hello
Hello World! By Hui Li.
Hello World! By Hui Li.
Hello World! By Hui Li.
和预期一样,函数依然正确执行。和不带参数列表的函数指针不同,指向带参函数的指针在定义时必须定义与所指函数参数列表一致的参数列表,这样才能保证我们可以通过函数指针来正确调用其所指的函数。
带参数列表和返回值类型的函数指针
现在,我们来写一个既带参数,又有返回值的程序:1
2
3
4
5
6
7
8
9
10
11
12int add(int a, int b){
return a + b;
}
int main(){
int sum = add(3, 5);
printf("%d + %d = %dn", 3, 5, sum);
return 0;
}
我们将其修改为函数指针来执行:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20#include
int add(int a, int b){
return a + b;
}
int main(){
int sum = add(3, 5);
printf("%d + %d = %dn", 3, 5, sum);
int (* ptr)(int, int) = add;
int sum_1 = ptr(3, 5);
printf("%d + %d = %dn", 3, 5, sum_1);
int sum_2 = (*ptr)(3, 5);
printf("%d + %d = %dn", 3, 5, sum_2);
return 0;
}
看一下执行结果:1
2
3
4
5lihuideMacBook-Pro:Learn lihui$ gcc -o add add.c
lihuideMacBook-Pro:Learn lihui$ ./add
3 + 5 = 8
3 + 5 = 8
3 + 5 = 8
和前面部分一样,函数执行结果依然没有问题。所以,我们只要理解函数指针的基本使用方式,无论再复杂的函数指针调用问题,我们都可以轻松解决。
函数指针基本定义形式:函数返回值类型 (* 指针变量)(函数参数列表) = 函数地址
函数指针作为参数调用不同的函数
接下来,我们将通过一个例子来学习如何通过函数指针来调用不同的函数:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23#include
int add(int a, int b){
return a + b;
}
int subtract(int a, int b){
return a - b;
}
int domath(int (* fun_name)(int, int), int x, int y) {
return fun_name(x, y);
}
int main(){
int a = 10, b = 5;
printf("a + b = %dn", domath(add, a, b));
printf("a - b = %dn", domath(subtract, a, b));
return 0;
}
在这里,我们首先定义了两个函数 int add(int, int) 、 int subtract(int, int),分别执行两个整数的加法与减法操作,接着定义了一个 int domath(int (* fun_name)(int, int), int, int) 函数,根据函数指针所指向的函数执行相关的操作。可以看到,domath 函数参数中,有一个指向带两个整形参数并且返回值为整形的函数的指针,而函数 int add(int, int) 和函数 int subtract(int, int) 恰好满足此特征,故通过函数名来分别调用这两种不同的方法是再好不过的了,domath 函数刚好实现了这一功能。
函数指针的这种灵活性在Linux内核的代码中得到充分体现,这种编码方式在Linux内核中比较常见,下面一段截取于 include/linux/netlink.h1
2
3
4
5
6
7
8
9
10
11...
struct netlink_dump_control {
int (*dump)(struct sk_buff *skb, struct netlink_callback *);
int (*done)(struct netlink_callback *);
void *data;
struct module *module;
u16 min_dump_alloc;
};
...
到此,我们对函数指针的学习就告一段落了。