高级指针话题
本文为《C 与 指针读书笔记》,感兴趣的读者可以去看原书。
函数指针
你不会每天都使用函数指针。但是,它们确有用武之地,最常见的两个用途是转换表(jump table)和作为参数传递给另一个函数。
int f( int );
int (*pf)( int ) = &f;
初始化表达式中的&操作符是可选的,因为函数名 被使用时总是由编译器把它转换为函数指针 。&操作符只是显式地说明了编译器将隐式执行的任务。
命令行参数
处理命令行参数是指向指针的指针的另一个用武之地。有些操作系统,包括UNIX和MS-DOS,让用户在命令行中编写参数来启动一个程序的执行。这些参数被传递给程序,程序按照它认为合适的任何方式对它们进行处理。
总结
如果声明得当,一个指针变量可以指向另一个指针变量。和其他的指针变量一样,一个指向指针的指针在它使用之前必须进行初始化。为了取得目标对象,必须对指针的指针执行双重的间接访问操作。更多层的间接访问也是允许的(比如一个指向整型的指针的指针的指针),但它们与简单的指针相比用的较少。你也可以创建指向函数和数组的指针,还可以创建包含这类指针的数组。
你可以使用函数指针来实现回调函数。一个指向回调函数的指针作为参数传递给另一个函数,后者使用这个指针调用回调函数。使用这种技巧,你可以创建通用型函数,用于执行普通的操作如在一个链表中查找。任何特定问题的某个实例的工作,如在链表中进行值的比较,由客户提供的回调函数执行。
转移表也使用函数指针。转移表像switch语句一样执行选择。转移表由一个函数指针数组组成(这些函数必须具有相同的原型)。函数通过下标选择某个指针,再通过指针调用对应的函数。你必须始终保证下标值处于适当的范围之内,因为在转移表中调试错误是非常困难的。
如果某个执行环境实现了命令行参数,这些参数是通过两个形参传递给main函数的。这两个形参通常称为argc和argv。argc是一个整数,用于表示参数的数量。argv是一个指针,它指向一个序列的字符型指针。该序列中的每个指针指向一个命令行参数。该序列以一个NULL指针作为结束标志。其中第1个参数就是程序的名字。程序可以通过对argv使用间接访问操作来访问命令行参数。
出现在表达式中的字符串常量的值是一个常量指针,它指向字符串的第1个字符。和数组名一样,你既可以用指针表达式也可以用下标来使用字符串常量。
编程练习
练习 1
编写一个通用目的的函数,遍历一个单链表。它应该接受两个参数:一个指向链表第1个节点的指针和一个指向一个回调函数的指针。回调函数应该接受单个参数,也就是指向一个链表节点的指针。对于链表中的每个节点,都应该调用一次这个回调函数。这个函数需要知道链表节点的什么信息?
typedef void (*callback_fn)(NODE *);
void traverse_list(NODE *head, callback_fn callback) {
NODE *current = head; // 从链表头开始遍历
while (current != NULL) {
callback(current); // 对当前节点调用回调函数
current = current->next; // 移动到下一个节点
}
}
练习 2
转换下面的代码段,使它改用转移表而不是switch语句
void add_new_trans(Node *list, Transaction *transaction);
void delete_trans(Node *list, Transaction *transaction);
void forward_trans(Node *list, Transaction *transaction);
void backward_trans(Node *list, Transaction *transaction);
void search_trans(Node *list, Transaction *transaction);
void edit_trans(Node *list, Transaction *transaction);
void (*trans_table[])(Node *, Transaction *) = {
add_new_trans,
delete_trans,
forward_trans,
backward_trans,
search_trans,
edit_trans
};
// 假设transaction->type是Trans_type枚举中的一个有效值
trans_table[transaction->type](list, transaction);
练习 3
编写一个名叫sort的函数,它用于对一个任何类型的数组进行排序。为了使函数更为通用,它的其中一个参数必须是一个指向比较回调函数的指针,该回调函数由调用程序提供。比较函数接受两个参数,也就是两个指向需要进行比较的值的指针。如果两个值相等,函数返回零;如果第1个值小于第2个,函数返回一个小于零的整数;如果第1个值大于第2个,函数返回一个大于零的整数。
// 比较回调函数的类型定义
typedef int (*compare_fn)(const void *, const void *);
#include <stdlib.h>
// 通用的排序函数
void sort(void *array, size_t nitems, size_t size, compare_fn cmp) {
// 用一个临时变量来保存元素,以进行交换
void *temp = malloc(size);
if (temp == NULL) {
// 处理内存分配失败的情况
return;
}
for (size_t i = 0; i < nitems - 1; ++i) {
for (size_t j = 0; j < nitems - i - 1; ++j) {
// 计算数组中当前元素和下一个元素的指针
void *current = (char*)array + j * size;
void *next = (char*)array + (j + 1) * size;
// 使用提供的比较函数进行比较
if (cmp(current, next) > 0) {
// 如果current > next,则交换它们
memcpy(temp, current, size);
memcpy(current, next, size);
memcpy(next, temp, size);
}
}
}
free(temp); // 释放用于交换的临时内存
}
#include <stdio.h>
// 整数比较函数
int compare_ints(const void *a, const void *b) {
return (*(int*)a - *(int*)b);
}
int main() {
int arr[] = {5, 3, 2, 4, 1};
size_t n = sizeof(arr) / sizeof(arr[0]);
sort(arr, n, sizeof(arr[0]), compare_ints);
// 打印排序后的数组
for (size_t i = 0; i < n; i++) {
printf("%d ", arr[i]);
}
printf("\n");
return 0;
}