最近在看《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 <stddef.h>
#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 <stddef.h>
#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
|