笨办法学C 练习18:函数指针

练习18:函数指针

原文:Exercise 18: Pointers To Functions

译者:飞龙

函数在C中实际上只是指向程序中某一个代码存在位置的指针。就像你创建过的结构体指针、字符串和数组那样,你也可以创建指向函数的指针。函数指针的主要用途是向其他函数传递“回调”,或者模拟类和对象。在这歌1练习中我们会创建一些回调,并且下一节我们会制作一个简单的对象系统。

函数指针的格式类似这样:

int (*POINTER_NAME)(int a, int b)

记住如何编写它的一个方法是:

  1. 编写一个普通的函数声明:int callme(int a, int b)

  2. 将函数用指针语法包装:int (*callme)(int a, int b)

  3. 将名称改成指针名称:int (*compare_cb)(int a, int b)

这个方法的关键是,当你完成这些之后,指针的变量名称为compare_cb,而你可以将它用作函数。这类似于指向数组的指针可以表示所指向的数组。指向函数的指针也可以用作表示所指向的函数,只不过是不同的名字。

int (*tester)(int a, int b) = sorted_order;printf("TEST: %d is same as %d\n", tester(2, 3), sorted_order(2, 3));

即使是对于返回指针的函数指针,上述方法依然有效:

  1. 编写:char *make_coolness(int awesome_levels)

  2. 包装:char (make_coolness)(int awesome_levels)

  3. 重命名:char (coolness_cb)(int awesome_levels)

需要解决的下一个问题是使用函数指针向其它函数提供参数比较困难,比如当你打算向其它函数传递回调函数的时候。解决方法是使用typedef,它是C的一个关键字,可以给其它更复杂的类型起个新的名字。你需要记住的事情是,将typedef添加到相同的指针语法之前,然后你就可以将那个名字用作类型了。我使用下面的代码来演示这一特性:

# include # include # include # include /  Our old friend die from ex17. */void die(const char *message){    if(errno) {        perror(message);    } else {        printf("ERROR: %s\n", message);    }    exit(1);}// a typedef creates a fake type, in this// case for a function pointertypedef int (*compare_cb)(int a, int b);/  * A classic bubble sort function that uses the  * compare_cb to do the sorting.  */int *bubble_sort(int *numbers, int count, compare_cb cmp){    int temp = 0;    int i = 0;    int j = 0;    int *target = malloc(count * sizeof(int));    if(!target) die("Memory error.");    memcpy(target, numbers, count * sizeof(int));    for(i = 0; i  0) {                temp = target[j+1];                target[j+1] = target[j];                target[j] = temp;            }        }    }    return target;}int sorted_order(int a, int b){    return a - b;}int reverse_order(int a, int b){    return b - a;}int strange_order(int a, int b){    if(a == 0 || b == 0) {        return 0;    } else {        return a % b;    }}/   * Used to test that we are sorting things correctly * by doing the sort and printing it out. */void test_sorting(int *numbers, int count, compare_cb cmp){    int i = 0;    int *sorted = bubble_sort(numbers, count, cmp);    if(!sorted) die("Failed to sort as requested.");    for(i = 0; i die函数,我将它用于错误检查。ex18.c:21这是使用typedef的地方,在后面我像int或char类型那样,在bubble_sort和test_sorting中使用了compare_cb。ex18.c:27~49一个冒泡排序的实现,它是整数排序的一种不高效的方法。这个函数包含了:ex18.c:27这里是将typedef用于 compare_cb作为cmp最后一个参数的地方。现在它是一个会返回两个整数比较结果用于排序的函数。ex18.c:29~34栈上变量的通常创建语句,前面是使用malloc创建的堆上整数数组。确保你理解了count * sizeof(int)做了什么。ex18.c:38冒泡排序的外循环。ex18.c:39冒泡排序的内循环。ex18.c:40现在我调用了cmp回调,就像一个普通函数那样,但是不通过预先定义好的函数名,而是一个指向它的指针。调用者可以像它传递任何参数,只要这些参数符合compare_cb typedef的签名。ex18.c:41-43冒泡排序所需的实际交换操作。ex18.c:48最后返回新创建和排序过的结果数据target。ex18.c:51-68compare_cb函数类型三个不同版本,它们需要和我们所创建的typedef具有相同的定义。否则C编辑器会报错说类型不匹配。ex18.c:74-87这是bubble_sort函数的测试。你可以看到我同时将compare_cb传给了bubble_sort来演示它是如何像其它指针一样传递的。ex18.c:90-103一个简单的主函数,基于你通过命令行传递进来的整数,创建了一个数组。然后调用了test_sorting函数。ex18.c:105-107最后,你会看到compare_cb函数指针的typedef是如何使用的。我仅仅传递了sorted_order、reverse_order和strange_order的名字作为函数来调用test_sorting。C编译器会找到这些函数的地址,并且生成指针用于test_sorting。如果你看一眼test_sorting你会发现它把这些函数传给了bubble_sort,并不关心它们是做了什么。只要符合compare_cb原型的东西都有效。ex18.c:109我们在最后释放了我们创建的整数数组。## 你会看到什么运行这个程序非常简单,但是你要尝试不同的数字组合,甚至要尝试输入非数字来看看它做了什么:

$ make ex18
cc -Wall -g ex18.c -o ex18
$ ./ex18 4 1 7 3 2 0 8
0 1 2 3 4 7 8
8 7 4 3 2 1 0
3 4 2 7 1 0 8
$

## 如何使它崩溃我打算让你做一些奇怪的事情来使它崩溃,这些函数指针都是类似于其它指针的指针,他们都指向内存的一块区域。C中可以将一种指针的指针转换为另一种,以便以不同方式处理数据。这些通常是不必要的,但是为了想你展示如何侵入你的电脑,我希望你把这段代码添加在test_sorting下面:

unsigned char data = (unsigned char )cmp;

for(i = 0; i

  1. 用十六进制编辑器打开ex18,接着找到函数起始处的十六进制代码序列,看看是否能在原始程序中找到函数。

  2. 在你的十六进制编辑器中找到更多随机出现的东西并修改它们。重新运行你的程序看看发生了什么。字符串是你最容易修改的东西。

  3. 将错误的函数传给compare_cb,并看看C编辑器会报告什么错误。

  4. 将NULL传给它,看看程序中会发生什么。然后运行Valgrind来看看它会报告什么。

  5. 编写另一个排序算法,修改test_sorting使它接收任意的排序函数和排序函数的比较回调。并使用它来测试两种排序算法。

关键字:c, lxthw

版权声明

本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处。如若内容有涉嫌抄袭侵权/违法违规/事实不符,请点击 举报 进行投诉反馈!

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部