自定义排序规则、事件处理函数、插件接口和状态机,背后都是这种能力。它也是 C 语言中最容易让读者停下来眯眼细看的语法,所以我们就从语法讲起。
声明语法
先看一个普通函数:
int add(int a, int b);
要声明一个能存放它地址的指针,把名字换成 (*name):
int (*op)(int, int);
从里往外读:op 是一个指针,指向一个函数,该函数接收 (int, int),返回 int。
括号不是装饰。没有它们,* 就会和返回类型结合:
int (*f)(int); // pointer to a function taking int, returning int
int *g(int); // FUNCTION taking int, returning int* - completely different
下面是一个完整可运行的例子:
#include <stdio.h>
int add(int a, int b) { return a + b; }
int subtract(int a, int b) { return a - b; }
int multiply(int a, int b) { return a * b; }
int main(void) {
int (*op)(int, int); // declare
op = add; // assign (no & needed)
printf("add: %d\n", op(10, 3));
op = subtract; // point it somewhere else
printf("subtract: %d\n", op(10, 3));
op = &multiply; // the & is allowed but redundant
printf("multiply: %d\n", (*op)(10, 3)); // so is this call form
printf("\nthe function itself lives at %p\n", (void *)(void (*)(void))add);
return 0;
}
#include <stdio.h>
typedef int (*BinaryOp)(int, int); // BinaryOp = pointer to int(int,int)
int add(int a, int b) { return a + b; }
int multiply(int a, int b) { return a * b; }
// now a function taking a function reads like any other declaration
int apply(BinaryOp op, int x, int y) {
return op(x, y);
}
int main(void) {
BinaryOp op = add;
printf("apply(add, 4, 5) = %d\n", apply(op, 4, 5));
printf("apply(multiply, 4, 5) = %d\n", apply(multiply, 4, 5));
return 0;
}
对比一下 int apply(BinaryOp op, int x, int y) 和原始写法 int apply(int (*op)(int, int), int x, int y)。含义相同,但其中一个一眼就能看懂。typedef 的一般规则请参见 typedef。
#include <stdio.h>
typedef int (*Predicate)(int);
int is_even(int n) { return n % 2 == 0; }
int is_positive(int n) { return n > 0; }
int is_big(int n) { return n > 100; }
// one function, any test
int count_if(const int *arr, int n, Predicate test) {
int count = 0;
for (int i = 0; i < n; i++) {
if (test(arr[i])) {
count++;
}
}
return count;
}
int main(void) {
int data[8] = {-4, 3, 120, 7, -9, 200, 0, 15};
int n = 8;
printf("even: %d\n", count_if(data, n, is_even));
printf("positive: %d\n", count_if(data, n, is_positive));
printf("big: %d\n", count_if(data, n, is_big));
return 0;
}
#include <stdio.h>
#include <stdlib.h>
int compare_ints(const void *a, const void *b) {
int x = *(const int *)a; // cast back to the real type, then read
int y = *(const int *)b;
if (x < y) return -1;
if (x > y) return 1;
return 0;
}
int compare_desc(const void *a, const void *b) {
return compare_ints(b, a); // swap the arguments to reverse the order
}
int main(void) {
int data[8] = {42, 7, 19, 3, 88, 1, 55, 23};
int n = sizeof data / sizeof data[0];
qsort(data, n, sizeof data[0], compare_ints);
printf("ascending: ");
for (int i = 0; i < n; i++) printf("%d ", data[i]);
qsort(data, n, sizeof data[0], compare_desc);
printf("\ndescending: ");
for (int i = 0; i < n; i++) printf("%d ", data[i]);
printf("\n");
return 0;
}
从这个比较函数中要养成两个习惯。永远不要写 return x - y;:它看起来很巧妙,但遇到很大的值或负值时会溢出,得到错误的符号,数组就排错了。先转换类型,再解引用,而不是反过来:*(const int *)a,因为对 void * 做 *a 没有东西可读。
同样的机制可以按任意字段对结构体排序:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct {
char name[20];
int score;
} Player;
int by_score(const void *a, const void *b) {
const Player *p = a, *q = b;
return (q->score > p->score) - (q->score < p->score); // highest first
}
int by_name(const void *a, const void *b) {
const Player *p = a, *q = b;
return strcmp(p->name, q->name);
}
int main(void) {
Player players[4] = {
{"Ada", 91},
{"Grace", 78},
{"Linus", 91},
{"Ken", 85}
};
int n = 4;
qsort(players, n, sizeof players[0], by_score);
printf("by score:\n");
for (int i = 0; i < n; i++) printf(" %-6s %d\n", players[i].name, players[i].score);
qsort(players, n, sizeof players[0], by_name);
printf("by name:\n");
for (int i = 0; i < n; i++) printf(" %-6s %d\n", players[i].name, players[i].score);
return 0;
}