刚接触vc6.0这个IDE的时候,发现MFC里面定义了很多新的变量类型,其实应该说是定义了很多已有类型的别名,刚开始学习的时候确实有点儿不习惯,后来想想为什么要重新定义一些新的类型名称呢?我自认为的好处是:(1)简化代码,使用方便;(2)方便移植。当我们要查找某个新定义的类型具体是什么的时候,一般在调试程序的时候,直接将鼠标放在我们所要查找的新类型的名称上,然后按F12即可以跳转到新类型定义的地方。
所以,我们在写程序的时候,也可以使用这一机制,即,用typedef来定义新的类型名称。下面我总结了一些关于typedef的用法和注意事项(也参考了一些网上的资料):
Part1:关于typedef的用法
我们根据用途来分别介绍一下typedef的相关用法。
(1)typedef用于定义一种类型的别名,而不只是简单的宏替换。可以使用这个别名同时声明某一类型的多个对象。
可以看出,在需要大量声明某些类型对象的时候(比如char*),使用typedef的方式更省事。
(2)用typedef帮助我们简化代码。
我们知道,在C语言中,一般我们定义一个struct结构体,以及定义一个其对象的方法如下:
而在C++中,我们定义一个结构体对象一般是直接写:结构名 对象名; 即,POINT p1; 为什么可以这样呢?是因为:
简化代码的好处,不只是上面这一点,typedef还可以将一个复杂的声明另起(定义)一个简单的别名。见第(3)点。
(3)typedef为复杂的声明定义一个新的简单的别名。
方法是:在原来的声明里逐步用别名替换一部分复杂的声明,如此循环,把带变量名的部分留到最后替换,得到的就是原声明的最简化版。
例1:
原声明:int*(*a[5])(int,char*);
//a是一个变量,后面接[],说明是一个数组([]的优先级高于*),前面有一个*,说明是一个指针,即,指针类型的数组。再看,(*a[5])后面有一对()括号,说明这是个函数,即,数组里面存放的是函数指针。这个函数有两个参数,分别是int类型和char*类型,且返回值为int*类型。
按照上面的方法,变量名为a,直接用一个新别名pFun替换a就可以了:
typedef int*(*pFun)(int,char*);
此时我们再想定义一个可以存放5个函数指针的数组,就可以简单地写为:
pFun a[5]; //原声明的最简化版本
例2:
原声明:void(*b[10])(void(*)());
//b是一个变量,后面接[],说明是一个数组([]的优先级高于*),前面有一个*,说明是一个指针,即,指针类型的数组。再看,(*b[10])后面有一对括号,说明这是一个函数,即,数组里面存放的是函数指针。然后我们发现函数的参数列表中,仅有一个参数,且这个参数是一个函数指针,此函数的参数为空,且返回值为void类型。而前面的那个函数的返回值为void类型。
按照上面的方法,变量名为b,先替换右边部分括号里的,pFunParam为别名一:
typedef void (*pFunParam)();
再替换左边的变量b,pFunx为别名二:
typedef void (*pFunx)(pFunParam);
原声明的最简化版:
pFunx b[10];
例3:
原声明:doubl(*)()(*e)[9];
//e是一个变量,前面有一个*,说明它是一个指针。再看(*e)的后面是一个[],说明它指向一个数组,这个数组是什么类型呢?前面是一个double(*)(),是一个函数指针,参数为空,返回值为double类型。即,声明了指针e,它指向一个数组,这个数组存放的是double(*)()类型的函数指针。
按照上面的方法,变量名为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。
自己感觉分析复杂声明的方法,有点儿像我们在英语中分析一个so long的定语从句,有很多嵌套,一个定语从句中嵌套若干个定语从句,所以我们一般是,先找到主句,然后再分析从句,一步一步解剖,直至分析完毕。
我们也可以记住2个模式:
type (*)(...) //函数指针(指向一个函数的指针)
type (*)[] //数组指针(指向一个数组的指针)
(4)用typedef来定义与平台无关的类型。(方便移植)
比如我们定义一个叫REAL的浮点类型,在目标平台一上,让它表示最高精度的类型为:
在不支持long double的平台二上,改为:
在连double都不支持的平台三上,改为:
也就是说,当跨平台时,只要改下typedef本身就行,不用对其他源码做任何修改。
标准库就广泛使用了这个技巧,比如size_t类型。
另外,因为typedef是定义了一种类型的新别名,不是简单的字符串替换,所以它比宏来得稳健(虽然用宏有时也可以完成以上的用途)。
Some Pitfalls of Using typedef
[陷阱一]
以前我发过一篇关于const限定符用法的文章,当时特别要求注意,当const和typedef一起使用的时候,很多人会出错!
记住,typedef是为一种类型定义了一个新名字(别名),不同于宏,它不是简单的字符串替换。
typedef定义的别名不能做简单的字符替换,我们应该把它看成是一个很好的封装,即,const修饰的是PSTR,也就是说,修饰的是char*这个整体。而const char* cstr;指明的是,const限定符修饰的是char,而不是char*!所以,const PSTR cstr; 定义的是一个指向char类型的const指针(即,常量指针,必须初始化,且以后指针的方向不能再被改变)。
注意:
const char * cstr;//等价于char const * cstr; (即,const限定符可以放在所修饰类型的前面或者后面,建议,放在后面好理解。)
[陷阱二]
typedef在语法上是一个存储类的关键字(如auto、extern、mutable、static、register等一样),虽然它并不真正影响对象的存储特性。
typedef static int SINT; //不可行
编译将失败,会提示“指定了一个以上的存储类”。因为不能在声明中有多个存储类关键字,typedef已经占据了存储类关键字的位置,在
typedef声明中不能用register(或任何其它存储类关键字)。
Part2:关于#define的用法
#define为一宏定义语句,通常用它来定义常量(包括无参量与带参量),以及用来实现那些“表面似和善、背后一长串”的宏,它本身并不在编译过程中进行,而是在这之前(预处理过程)就已经完成了,但也因此难以发现潜在的错误及其它代码维护问题。
在C语言中,#define主要用于定义一些常量,而在C++中,则建议用const定义常量,因为可以定义自己的类型。(更详细的原因可以查阅《Essential C++》)
下面给出一个用#define写的一个有趣的程序例子,帮助我们理解它的用法:
最后,欢迎对本篇文章内容有不同见解的朋友留言讨论!:)