无论在 C 还是在 C++代码中,typedef 都是出现频率较多的一个关键字。typedef本身的功能是很容易理解的,其主要功能是定义一个已存在类型的别名,但是和宏并存,问题就变得复杂了。再加上国内一些教科书的问题,导致一些编程人员将宏和typedef 混为一谈.
typedef 有助于创建平台无关类型,甚至能隐藏复杂和难以理解的语法。使用typedef 可编写出更加美观和可读的代码。所谓美观,意指 typedef 能隐藏笨拙的语法构造以及平台相关的数据类型,从而增强可移植性以及未来的可维护性。本实用经验将竭尽全力来揭示typedef 的强大功能以及如何避免一些常见的使用陷阱。
首先来看typedef 和宏混用陷阱。为了说明这一问题,我们看下面这段代码:
#define PSTR MACRO char* //定义一个宏类型PSTRMACRO
typedef char *PSTR; //通过typedef定义一个新类型PSTR
int main(int argc, char* argv[])
{
//声明两个变量piVar1和 piVar2
PSTR piVar1, piVar2;
//声明两个变量 piVar3和 piVar4
PSTR_MACRO piVar3, piVar4;
Inti Var=100; //定义一个int变量iVar
//将iVar变量地址赋给piVar1、piVar2、piVar3、piVar4四个变量
piVar1 =&iVar;
piVar2=&iVar;
piVar3 =&iVar;
piVar4 = &iVar;
//输出 piVar1、piVar2,到 piVar3、piVar4 四个变量的值
printf("piVar1=%0X\r\n",piVar1);
printf("piVar2=%0X\r\n",piVar2);
printf("piVar3 =%0X \r\n",piVar3);
printf("piVar4=%0X \r\n",piVar4);
returm 0:
}
代码段的输出为:
piVar1 =19FC78
piVar2 =19FC78
piVar3 =19FC78
piVar4=78
通过代码片段的执行结果可以看出typedef 和#define 还是有很大区别的。我们先分析一下为什么上述代码中4个变量的输出值不同。
PSTRMACRO 为预处理宏,只是简单的字符串替换,piVar3 和 piVar4 经过预处理后,“PSTR_MACRO piVar3, piVar4;”声明转化为“char* piVar3, piVar4;”。到这里也许你已经看出问题来了:piVar4 是一个char 型变量,而不是 char*型变量。
PSTR为一个通过typedef 定义的类型别名,不进行原地扩展,新定义的别名有一定的封装性。“PSTRpiVar1,piVar2;”在编译过程中,由于 PSTR 为 int 的别名,编译器会把“PSTR piVar1, piVar2;”语句当作“char *piVar1,*piVar2;”处理,而不是简单的宏替换。
宏和typedef 的区别:
● 宏定义只是简单的字符串替换。
● typedef 定义的类型是类型的别名,typedef 后面是一个整体声明,是不能分割的一个整体,具有一定的封装性,不是简单的字符串替换。
通过typedef 声明多个指针对象,形式直观,方便省事。例如声明3 个指针变量:
char*pszA,*pszB,*pszC; //声明3个指针变量,方式1
typedef char*PSTR: //声明3个指针变量,方式2:直观省事
PSTR pszA, pszB, pszC;
小心陷阱
typedef 主要为复杂的声明定义简单的别名,它本身是一种存储类的关键字,与auto、extern、mutable、static、register 等关键字不能出现在同一个表达式中,如“typedefstatic int S_INT;”就是非法的。
讨论了 typedef 和#define 的区别,我们继续讨论 typedef 的其他用途。
(1) 用在旧的C代码中,声明struct新对象时,必须带上 struct,即形式为 struct结构名对象名,例如:
struct tagPOINT //点数据结构
{
Int x:
Int y;
};
struct tagPOINT p1:
为了实现在结构体使用过程中少写声明头部分的 struct,可采用如下实现方式:
typedef struct tagPOINT
{
Int x;
int y;
}POINT;
POINTp1;
这样就比原来的方式少写了一个 struct,比较省事,尤其是在大量使用的时候。而在C++中则可以直接写:结构名对象名,即:
tagPOINT1 p1;
在C++中,typedef 的这种用途就不是很大,但是理解了它,对掌握以前的旧代码还是有帮助的,毕竟我们在项目中有可能会遇到以前遗留下来的代码。
(2) typedef 另外一个重要的用途就是定义与机器无关的类型,保障代码具有较好的跨平台特性。例如,可定义一个名为 REAL_NUM 的浮点类型,在目标机器上它可以获得最高的精度:
typedef long double REAL NUM;//实数
在不支持 long double的机器上,通过typedef 可采用如下定义:
typedef double REAL_NUM;
对于连 double 都不支持的机器上,通过typedef 可采用如下定义:
typedef float REAL_NUM;
采用typedef 实现数据类型的定义,不用对源代码做任何修改,便可以在每一种平台上编译这个使用 REAL_NUM 类型的应用程序。
唯一需要修改的是typedef 本身。在大多数情况下,甚至这个微小的变动完全都可以通过奇妙的条件编译来自动实现。不是吗?STL 标准库广泛地使用typedef 来创建这样的平台无关类型,size_t、ptrdiff 和 fpos_t 就是这样的例子。
(3) 为复杂的声明定义一个简单的名称,简化代码。这一功能可增强代码的可读性和标识符的灵活性。我们看下面这个复杂的声明。在这个声明中,paFunc 为变量名称。
int *(*paFunc[6])(char *pszInput);
现在看通过typedef 简化后的声明形式。
Typedef int*(*pFunc)(char*pszInput);
pFunc paFunc[6]
最后,看一下 C++类经常使用的回调函数实现。假设有一个类叫隧道 CTunnel类,同时若此类接收到某一个数据时,回调一个预先设置好的回调函数。
//Tunnel.h 隧道类声明文件
//回调函数声明
typedef BOOL*CallBackFunc(const char*pszData, const int nDatalength);
//隧道类声明
class CTunnel
{
CTunnel();
virtual~CTunnel();
//设置回调函数
void SetCallBack(CallBackFunc *pCallBackFunc);
//隧道接收数据处理函数
int OnRcvData(const char *pszData, const int nDataLength);
private:
//回调函数存储指针
CallBackFunc m pCallBackFunc;
};
//Tunnel.cpp 隧道类实现文件
CTunnel::CTunnel()
{
m pCallBackFunc =NULL;
}
CTunnel::~CTunnel()
{
}
//设置回调函数
void CTunnel::SetCallBack(CallBackFunc *pCallBackFunc)
{
m pCallBackFunc = pCallBackFunc
}
//隧道接收数据处理函数
int CTunnel::OnRcvData(const char *pszData, const int nDataLength)
{
if((NULL=pszData)|l(0 =nDataLength))
{
retum-1;
}
...
if(NULL !=m_pCallBackFunc
{
retum m_pCallBackFunc(pszData, nDataLength);
}
retum 0;
}
请谨记
● 区分宏和 typedef 的差异,不要用宏的思维方式对待typedef,因为 typedef 声明的新名称具有一定的封装性,而#define 宏只是简单的字符替换。
● 尽量用typedef 实现那些复杂的声明形式,以保证代码清晰、易于阅读。