typedef和#define的用法与区别


1. typedef的用法

在C/C++中,typedef常用来定义一个标识符或关键字的别名,它是语言编译的一部分,但它并不实际分配内存空间。比如:

typedef	int	INT;
typedef	int	ARRAY[10];
typedef	(int*)	pInt;

typedef可以增强程序的可读性,以及标识符的灵活性,但它也有“非直观性”等缺点。

2. #define的用法

#define是宏,通常用来定义常量(包括无参量和带参量),以及用来实现哪些“表面似和善,背后一长串”的宏,它本身并不在编译过程中进行,而是在预处理过程就已经完成了,但也因此难以发现潜在的错误及其它代码维护的问题。比如:

#define	INT	int
#define	TRUE	1
#define	Add(a,b)	((a)+(b))
#define	Loop_10		for(int i=0; i<10; i++)

更多关于#define的用法可以参见我的另一篇博客《C预处理器》。

3. typedef和#define的区别

从以上概念便能基本清楚,typedef只是为了增加可读性而为标识符另起的新名称(仅仅只是个别名),而#define原本在C中是为了定义常量,到了C++,const、enum、inline的出现使它也渐渐成为了起别名的工具。有时候很容易搞不清楚两个该用哪个好,比如#define INT int32 语句,用typedef一样可以完成,到底用哪个好?我推荐用typedef,因为在早期的许多C编译器中这条语句是非法的,只是现在的编译器又做了扩充。为了尽可能地兼容,一般都遵循#define定义“可读”的常量以及一些宏语句的任务,而typedef则常用来定义关键字、冗长类型的别名。

宏定义只是简单的字符串替换(原地扩展),而typedef则不是原地扩展,它的新名字具有一定的封装性,以致于新命名的标识符具有更易于定于变量的功能。比如:typedef  (int*)  pInt; 和#define pInt2  int* ,看起来效果似乎是一样的,但其实是不同的:pInt a,b; 的效果同int *a; int *b;  表示定义了两个整型指针变量。而pInt2 a,b; 的效果同int *a,b; 表示定义了一个整型指针变量a和整型变量b。

4. typedef的四个用途和两个陷阱

用途1:定义一种类型的别名,而不只是简单的宏替换。可以用作同时声明指针型的多个对象。比如注意一下三种定义的区别与联系:

char*	pa,pb;

typedef char*	PCHAR;
PCHAR	pa,pb;		// 等效于char	*pa,*pb;

用途2:在旧的C代码中,定义一个结构体类型变量的时候,需要写上struct,即struct 结构名 变量名 。而在C++或新的C中,可以省去struct关键字,直接定义变量。所以在旧的C标准中,我们可以用typedef来实现新的C中的功能:

struct tagPoint
{
	int	x;
	int	y;
};
struct tagPoint	p1;		// 旧C中

tagPoint p2;	// C++或新C中

typedef struct tagPoint
{
	int  x;
	int  y;
}Point;
Point	p3;		// 通用的方法

用途3:定义与平台无关的类型。比如定义一个叫   REAL   的浮点类型,在目标平台一上,让它表示最高精度的类型为:typedef long double REAL; 在不支持long  double的平台二上,改为:typedef   double   REAL;   在连double都不支持的平台三上,改为:typedef float REAL; 也就是说,当跨平台时,只要改下typedef本身就行,不用对其他源码做任何修改。
标准库就广泛使用了这个技巧,比如size_t。另外,因为typedef是定义了一种类型的新别名,不是简单的字符串替换,所以它比宏来得稳健(虽然用宏有时也可以完成以上的用途)。

用途4:为复杂的声明定义一个新的简单的别名。方法是:在原来的声明里逐步用别名替换一部分复杂声明,如此循环,把带变量名的部分留到最后替换,得到的就是原声明的最简化版。举例:
(1) 原声明:

int   *(*a[5])(int,   char*);

变量名为a,直接用一个新别名pFun替换a就可以了:

typedef   int   *(*pFun)(int,   char*);

原声明的最简化版:pFun   a[5]; 。

(2) 原声明:

void   (*b[10])   (void   (*)());

变量名为b,先替换右边部分括号里的,pFunParam为别名一:

typedef void (*pFunParam)();

再替换左边的变量b,pFunx为别名二:

typedef void (*pFunx)(pFunParam);

原声明的最简化版:pFunx   b[10]; 。

(3)原声明:

doube(*)() (*e)[9];

变量名为e,先替换左边部分,pFuny为别名一:

typedef double(*pFuny)();

再替换右边的变量e,pFunParamy为别名二:

typedef pFuny (*pFunParamy)[9];

原声明的最简化版:pFunParamy   e;  。

理解复杂声明可用的“右左法则”:从变量名看起,先往右,再往左,碰到一个圆括号就调转阅读的方向;括号内分析完就跳出括号,还是按先右后左的顺序,如此循环,直到整个声明分析完。举例:

int (*func)(int *p);

首先找到变量名func,外面有一对圆括号,而且左边是一个*号,这说明func是一个指针;然后跳出这个圆括号,先看右边,又遇到圆括号,这说明(*func)是一个函数,所以func是一个指向这类函数的指针,即函数指针,这类函数具有int*类型的形参,返回值类型是int。

int (*func[5])(int *);

func右边是一个[]运算符,说明func是具有5个元素的数组;func的左边有一个*,说明func的元素是指针(注意这里的*不是修饰func,而是修饰func[5]的,原因是[]运算符优先级比*高,func先跟[]结合)。跳出这个括号,看右边,又遇到圆括号,说明func数组的元素是函数类型的指针,它指向的函数具有int*类型的形参,返回值类型为int。

也可以记住2个模式:
type   (*)(....)函数指针
type   (*)[]数组指针

陷阱1:记住,typedef是定义了一种类型的新别名,不同于宏,它不是简单的字符串替换。比如:

先定义:typedef   char*   PSTR; 然后:int   mystrcmp(const   PSTR,   const   PSTR);
const   PSTR实际上相当于const   char*吗?不是的,它实际上相当于char*   const。原因在于const给予了整个指针本身以常量性,也就是形成了常量指针char*   const。简单来说,记住当const和typedef一起出现时,typedef不会是简单的字符串替换就行。

陷阱2:typedef在语法上是一个存储类的关键字(如auto、extern、mutable、static、register等一样),虽然它并不真正影响对象的存储特性,如:

typedef   static   int   INT2;   //不可行

编译将失败,会提示“指定了一个以上的存储类”。

本文转自:http://www.cnblogs.com/kerwinshaw/archive/2009/02/02/1382428.html


仅有一条评论

  1. jackMeK

    Hello! Quick question that's totally off topic. Do you know how to make your site mobile friendly? My website looks weird when browsing from my apple iphone. I'm trying to find a theme or plugin that might be able to correct this problem. If you have any recommendations, please share. Thanks!

    jackMeK 回复

添加新评论

选择表情 captcha

友情提醒:不填或错填验证码会引起页面刷新,导致已填的评论内容丢失。

|