最近在看《C语言接口与实现》一书. 第二章提到, 一个ADT(抽象数据类型)就是一个借口, 其标准范例是栈. 其接口定义如下所示:
//stack.h #ifndef STACK_INCLUDED #define STACK_INCLUDED #define T Stack_T typedef struct T *T; extern T Stack_new (void); extern int Stack_empty (T stk); extern void Stack_push (T stk, void* x); extern void*Stack_top (T stk); extern void*Stack_pop (T stk); extern void Stack_free (T* stk); #undef T #endif
众所周知宏定义的作用仅仅是一个替换而已, 因此typedef这句话实际为:
typedef struct Stack_T *Stack_T;
按照书上说法, Stack_T对外接口表现为一个“不透明指针”. 我起初理解是, 以后凡是看到Stack_T的地方实际指代的都是指针Stack_T*. 乍看起来这种理解没错, 但看一下栈的实现后, 便发生了理解不能的事情:
//stack.c #include#include "assert.h" #include "mem.h" #include "stack.h" #define T Stack_T struct T { int count; struct elem { void* x; struct elem* link; }* head; }; T Stack_new(void) { T stk; NEW(stk); stk->count = 0; stk->head = NULL; return stk; } int Stack_empty(T stk) { assert(stk); return stk->count == 0; } void Stack_push(T stk, void* x) { struct elem* t; assert(stk); NEW(t); t->x = x; t->link = stk->head; stk->head = t; stk->count++; } ...
这里#define 指令又把T定义为Stack_T的缩写. 问题来了, 以下结构体的定义和分配并初始化一个结构体函数中的Stack_T分别指什么呢:
struct Stack_T {
...
};
Stack_T Stack_new(void) {
Stack_T stk;
NEW(stk);
...
return stk;
}
如果按照上述理解 Stack_T 指代的就是指针 Stack_T*, 显然没有这种定义式无法通过编译: struct Stack_T*. 函数返回类型和结构体名称的重名令人困惑, 但这确实是合法而正规的.
究其根本原因是, struct后面的 Stack_T 和 函数的返回类型 Stack_T 不是同一个东西. 这就牵扯到C语言的命名空间约定(不同于C++的namespace, 是个全局的区分C语言标识符的约定):
1) 预处理器宏名
2) 语句标签(goto类型)
3) 结构、枚举、联合结构的标签(结构,联合和枚举的名称)
4) 成员名
5) 其他名称 包括变量名、函数名、typedef名称和枚举常量
因此struct后面的 Stack_T 属于 3), 而函数返回类型的 Stack_T 属于 5), 两者并不冲突. 即前者是个名为Stack_T 结构体的标签, 后者是个typedef的名称, 实际类型式 Stack_T* . 这里我对typedef的Stack_T 重新用 Stack_P 命名下, 上述代码就看的比较清楚了:
//stack.h #ifndef STACK_INCLUDED #define STACK_INCLUDED typedef struct Stack_T* Stack_P; extern Stack_P Stack_new (void); extern int Stack_empty (Stack_P stk); extern void Stack_push (Stack_P stk, void* x); extern void*Stack_top (Stack_P stk); extern void*Stack_pop (Stack_P stk); extern void Stack_free (Stack_P* stk); //#undef T #endif
//stack.c #include#include "assert.h" #include "mem.h" #include "stack.h" struct Stack_T { int count; struct elem { void* x; struct elem* link; }* head; }; Stack_P Stack_new(void) { Stack_P stk; NEW(stk); stk->count = 0; stk->head = NULL; return stk; } int Stack_empty(Stack_P stk) { assert(stk); return stk->count == 0; } void Stack_push(Stack_P stk, void* x) { struct elem* t; assert(stk); NEW(t); t->x = x; t->link = stk->head; stk->head = t; stk->count++; } ...
这种 typedef struct T *T 其实C语言接口库中比较标准的写法, 值得引起借鉴和学习.
参考:
C Namespaceshttps://www.spinellis.gr/cscout/doc/name.html
也谈C语言标识符的NAMESPACE | Tony Baihttps://tonybai.com/2008/05/15/also-talk-about-namespace-in-c



