建议14:小心typedef使用中的陷阱
typedef本来是很好理解的一个概念,但是因为与宏并存,理解起来就有点困难了。再加上部分教材以偏概全,更是助长了错误认识的产生。某些教材介绍typedef时会给出类似以下的形式,但是缺少进一步的解释:
typedef string NAME;
typedef int AGE;
这种形式让我不由地想起C语言中著名的宏定义:
#define MAC_NAME string
#define MAC_AGE int
因为二者的声明方式太相似了,所以很多人习惯用#define的思维方式来看待typedef,认为应当把int 与AGE看成独立的两部分。实际情况是怎样的呢?首先分析下
面的代码片段:
typedef int* PTR_INT1;
#define int* PTR_INT2
int main()
{
PTR_INT1 pNum1, pNum2;
PTR_INT2 pNum3, pNum4;
int year = 2011;
pNum1 = &year;
pNum2 = &year;
pNum3 = &year;
pNum4 = &year;
cout<<pNum1<<" "<<pNum2<<" "<<pNum3<<" "<<pNum4;
return 0;
}
输出为:2011 2011 2011 0E8951241。通过程序执行结果可以看出typedef与#define的不同:typedef后面是一个整体声明,是不能分割的部分,就像整型变量声明int i;,只不过typedef声明的是一个别名。宏定义只是简单的字符串替换,不过,typedef并不是原地扩展,它的新名称具有一定的封装性,更易于定义变量,它可以同时声明指针类型的多个对象,而宏则不能。使用typedef声明多个指针对象,形式直观,方便省事:
char *pa, *pb, *pc, *pd; //方式1
typedef char* PTR_CHAR;
PTR_CHAR pa, pb, pc, pd; //方式2,直观省事除此之外,typedef还有多种用途,下面来看看。
(1)在部分较老的C代码中,声明struct对象时,必须带上struct关键字,即采用“struct 结构体类型 结构体对象”的声明格式。例如:
struct tagRect
{
int width;
int length;
};
strcut tagRect rect;
为了在结构体使用过程中,少写声明头部的struct,于是就有人使用了typedef:
typedef struct tagRect
{
int width;
int length;
}RECT;
RECT rect;
在现在的C++代码中,这种方式已经不常见,因为对于结构体对象的声明已经不需要使用struct了,可以采用“结构体类型 结构体对象”的形式。
(2)用typedef定义一些与平台无关的类型。例如在标准库中广泛使用的size_t的定义:
#ifndef _SIZE_T_DEFINED
#ifdef _WIN64
typedef unsigned __int64 size_t;
#else
typedef _W64 unsigned int size_t;
#endif
#define _SIZE_T_DEFINED
#endif
(3)为复杂的声明定义一个简单的别名。这一点将在建议93中详细介绍。它可以增强程序的可读性和标识符的灵活性,这也是它最突出的作用。
typedef本来是很好理解的一个概念,但是因为与宏并存,理解起来就有点困难了。再加上部分教材以偏概全,更是助长了错误认识的产生。某些教材介绍typedef时会给出类似以下的形式,但是缺少进一步的解释:
typedef string NAME;
typedef int AGE;
这种形式让我不由地想起C语言中著名的宏定义:
#define MAC_NAME string
#define MAC_AGE int
因为二者的声明方式太相似了,所以很多人习惯用#define的思维方式来看待typedef,认为应当把int 与AGE看成独立的两部分。实际情况是怎样的呢?首先分析下
面的代码片段:
typedef int* PTR_INT1;
#define int* PTR_INT2
int main()
{
PTR_INT1 pNum1, pNum2;
PTR_INT2 pNum3, pNum4;
int year = 2011;
pNum1 = &year;
pNum2 = &year;
pNum3 = &year;
pNum4 = &year;
cout<<pNum1<<" "<<pNum2<<" "<<pNum3<<" "<<pNum4;
return 0;
}
输出为:2011 2011 2011 0E8951241。通过程序执行结果可以看出typedef与#define的不同:typedef后面是一个整体声明,是不能分割的部分,就像整型变量声明int i;,只不过typedef声明的是一个别名。宏定义只是简单的字符串替换,不过,typedef并不是原地扩展,它的新名称具有一定的封装性,更易于定义变量,它可以同时声明指针类型的多个对象,而宏则不能。使用typedef声明多个指针对象,形式直观,方便省事:
char *pa, *pb, *pc, *pd; //方式1
typedef char* PTR_CHAR;
PTR_CHAR pa, pb, pc, pd; //方式2,直观省事除此之外,typedef还有多种用途,下面来看看。
(1)在部分较老的C代码中,声明struct对象时,必须带上struct关键字,即采用“struct 结构体类型 结构体对象”的声明格式。例如:
struct tagRect
{
int width;
int length;
};
strcut tagRect rect;
为了在结构体使用过程中,少写声明头部的struct,于是就有人使用了typedef:
typedef struct tagRect
{
int width;
int length;
}RECT;
RECT rect;
在现在的C++代码中,这种方式已经不常见,因为对于结构体对象的声明已经不需要使用struct了,可以采用“结构体类型 结构体对象”的形式。
(2)用typedef定义一些与平台无关的类型。例如在标准库中广泛使用的size_t的定义:
#ifndef _SIZE_T_DEFINED
#ifdef _WIN64
typedef unsigned __int64 size_t;
#else
typedef _W64 unsigned int size_t;
#endif
#define _SIZE_T_DEFINED
#endif
(3)为复杂的声明定义一个简单的别名。这一点将在建议93中详细介绍。它可以增强程序的可读性和标识符的灵活性,这也是它最突出的作用。
在typedef的使用过程中,还必须记住:typedef在语法上是一个存储类的关键字,类似于auto、extern、mutable、static、register等,虽然它并不会真正影响对象的存储特性,如:typedef static int INT2; //不可行,编译将失败编译器会提示“指定了一个以上的存储类型”。
请记住:区分typedef与#define之间的不同;不要用理解宏的思维方式对待typedef,typedef声明的新名称具有一定的封装性,更易定义变量。同时还要注意它是一个无“现实意义”的存储类关键字。