指针声明的基本规则:
T *p; // p is a pointer to T
T *ap[N]; // ap is an N-element array of pointers to T
T *fp(); // fp is a function returning a pointer to T
T (*pa)[N]; // pa is a pointer to an array of T
T (*pf)(); // pf is a pointer to a function returning T
在表达式和声明符中,下标 [] 和函数调用 () 运算符的优先级高于一元解引用 * 运算符,因此像 *f() 这样的表达式或声明符被解析为 *(f()) - 你正在取消引用 f 的结果。如果你想在调用它之前取消引用f ,你必须明确地将* 运算符与它组合在一起 - (*f)()。
阅读毛茸茸的声明的方法是从最左边的标识符开始,按照上面的规则,递归地应用到任何函数参数。
如果你有一个像这样在参数列表中使用抽象声明符的函数,请记住:
T * -> T *λ
T *[N] -> T *λ[N]
T (*)[N] -> T (*λ)[N]
T *() -> T *λ()
T (*)() -> T (*λ)()
其中λ 表示标识符通常的位置。
所以,阅读这个声明的方式是
PFI -- PFI
typedef PFI -- is a typedef name for
typedef *PFI -- pointer to
typedef (*PFI) ( ) -- a function taking
typedef (*PFI) ( ) -- unnamed parameter
typedef (*PFI) ( * ) -- is a pointer to
typedef (*PFI) (char * ) -- char
typedef (*PFI) (char *, ) -- unnamed parameter
typedef (*PFI) (char *, *) -- is a pointer to
typedef (*PFI) (char *,char *) -- char
typedef int (*PFI) (char *,char *); -- returning int
在不太正式的术语中,PFI 是“指向带有两个 char * 参数并返回 int 的函数的指针”类型的别名。
那么我们将如何使用它呢?
一个常见的用途是在函数参数列表中声明回调:
void foo( char *foo, char *bar, PFI callback )
{
int x = callback( foo, bar );
...
}
这比读起来容易一点
void foo( char *foo, char *bar, int (*callback)(char *, char *) )
{
int x = callback( foo, bar );
...
}
它对于声明数组或struct 成员也很有用:
int func1( char *x, char *y ) { ... }
int func2( char *a, char *b ) { ... }
...
struct s {
char *x;
char *y;
PFI f;
} blah = {"foo", "bar", func1};
PFI callbacks[] = { func1, func2, ... };
再一次,这比写作要干净一些
struct s {
char *x;
char *y;
int (*f)(char *, char *);
};
或
int (*callbacks[])(char *, char *) = { func1, func2, ... };
但是……
“稍微干净一点”本身并不是将类型隐藏在typedef 后面的充分理由。您使用typedef 抽象出类型的用户不需要知道才能使用它的类型的详细信息。
例如,考虑stdio.h 中的FILE 类型。有一个完整的 API 支持 FILE 类型,因此您永远不需要知道它的底层是什么样子。
然而,在我们的例子中,我们必须知道PFI 是一个指向函数的指针,该函数需要两个char * 参数并返回一个int 才能使用它。使用 typedef 名称节省了一些击键,但它也创建了一个“泄漏”的抽象,因为有关类型的详细信息必须“泄漏”给程序员。在这种情况下,老实说,最好完全放弃 typedef 并使用“裸”声明。