在C/C++开发的日常研发过程中发现有很多地方用到 typedef
和 宏define(#define
)。现将程序中经常用到的和误区总结一下。
1. 使用 typedef
简化代码,提高程序的可读性
在程序中经常正确的使用 typedef 可以有效的增加代码的可读性、可维护性。试着比较如下的两段代码:
//代码1
int (* myFunc(int (*pf)(const char *, const char*)))(const char *, const char*);
//代码2
typedef int (*PF)(const char*, const char*);
PF myfunc(PF pf);
相信 代码2 比 代码1 更容易理解也更方便维护吧。原因何在?
相同点
:> typedef 行为有点像 define 宏,用其实际类型替代同义字;
不同点
:> typedef 在编译时会被解释,因此让编译器来应付超越预处理器能力的文本替换。
代码2的详细说明:
1.
这个声明引入了PF类型作为函数指针的别名,当中函数有两个const char*类型的参数以及一个int类型的返回值
2.
myFunc() 的参数是一个 PF 类型的回调函数,返回某个函数的地址。
2. 使用 typedef
定义易于记忆的类型名
typedef
使用最多的地方是 创建易于记忆的类型名称,用它来表示我们的真实意图。
也可以这么理解 在变量名字声明前面加上typedef,将变量提升为这个类型的别名(alias)
注意
:> typedef并不创建新的类型。它仅仅为现有类型添加一个别名.
例如:
typedef int size;
定义了一个int的同义字size,你可以在任何需要int的上下文中使用size
typedef
还可以掩饰复合类型,如指针和数组。例如下面的代码
//例如,你不用像下面这样重复定义有 20 个字符元素的数组:
char line[20];
char text[20];
//定义一个 typedef,每当要用到相同类型和大小的数组时,可以这样:
typedef char Line[81];
Line text, secondline;
getline(text);
3. 使用 typedef
隐藏指针
标准函数 strcmp()有两个 const char*
类型的参数。因此,它可能会误导人们像下面这样声明 mystrcmp():int mystrcmp(const pstr, const pstr);
typedef char * pstr;
int mystrcmp(pstr, pstr);
这样声明 mystrcmp
是错误的:
按照顺序,
const pstr
被解释为char * const
一个指向 char 的常量指针,而不是const char *
(指向常量 char 的指针)。
这个问题很容易解决:修改类型定义为
typedef const char * cpstr;
int mystrcmp(cpstr, cpstr); // 现在是OK的
强调一下
:
只要为指针声明typedef,都要在最终的 typedef 名称前面加一个 const
4. typedef
与 结构体
的问题
typedef struct Node
{
char *pData;
pNode pNext;
} *pNode;
这段代码可能你认为没问题,实际上编译器会报一个错误。上述代码的根本问题在于 typedef 的使用有误。
根据我们上面的阐述可以知道:
新结构建立的过程中遇到了pNext域的声明,类型是pNode
。但是此时在类型本身还没有建立完成的时候,这个类型的新名字也还不存在,也就是说这个时候编译器根本不认识pNode。
可以修改成下面的几种方式来解决此问题:
//方式1 -- 推荐使用
typedef struct Node {
char * pData;
struct Node *pNext;
} *pNode;
//方式2
typedef struct Node *pNode;
struct Node {
char * pData;
pNode pNext;
};
//方式3
struct Node {
char * pData;
struct Node *pNext;
};
typedef struct Node *pNode;
5. typedef
和 #define
的区别
typedef char *pStr;
define pStr char *;
上面两种定义 pStr
数据类型的方法,两者有什么不同?哪一种更好一点?
通常讲,typedef要比 define宏要好,特别是在有指针的场合。请看例子:
typedef char *PSTRING1;
#define PSTRING2 char *;
PSTRING1 p1, p2;
PSTRING2 p3, p4;
在上述的变量定义中,
p1、p2、sp3都被定义为char *;
p4则定义成了char,而不是我们所预期的指针变量
根本原因在于 #define
只是简单的字符串替换;而 typedef
则是为一个类型起新名字。
注意
:
如果这样用
#define f(x) x*x
是有问题的,应该这样#define f(x) ((x)*(x))
在许多C语言编程规范中提到使用#define
定义时,如果定义中包含表达式,必须使用括号.
再如:
typedef char * pStr;
char string[4] = "abc";
const char *p1 = string;
const pStr p2 = string;
p1++;
p2++;
上面的代码有错误吗? 其实 p2++
会出错。
这个问题再一次提醒我们:typedef
和 #define
不同,它不是简单的文本替换。
上述代码中
const pStr p2
并不等于const char * p2
。
const pStr p2
和const long x
本质上没有区别,都是对变量进行只读限制,只不过此处变量p2的数据类型是我们自己定义的而不是系统固有类型而已。
因此,const pStr p2的含义是:限定数据类型为char *的变量p2为只读,因此p2++错误
。
引申
:
#define
有一个特别的长处:可以使用#ifdef ,#ifndef等来进行逻辑判断,使用#undef来取消定义。
typedef
也有一个特别的长处:它受范围规则约束,使用typedef定义的变量类型其作用范围限制在所定义的函数或者文件内(取决于此变量定义的位置)`,而宏定义则没有这种约束。
6. typedef 为复杂的声明定义简单的别名时的理解规则
理解复杂声明可用的 “右左法则
”:
1.
从变量名看起,先往右,再往左,碰到一个圆括号就调转阅读的方向;
2.
括号内分析完就跳出括号,还是按先右后左的顺序,如此循环,直到整个声明分析完。
//示例1
int (*func)(int *p);
//声明解析:
1. 找到变量名func,外面有一对圆括号,而且左边是一个 * 号,这说明func是一个指针;
2. 跳出这个圆括号,先看右边,又遇到圆括号,这说明 (*func) 是一个函数,
==> func是函数指针,且这类函数具有int*类型的形参,返回值类型是int。
//示例2
int (*func[5])(int *);
//声明解析:
1. func右边是一个[]运算符,说明func是具有5个元素的数组;
2. func的左边有一个 * ,说明func的元素是指针
3. 跳出这个括号,看右边,又遇到圆括号,
===> func数组的 每一个元素是函数指针(指向的函数具有int*类型的形参,返回值类型为int)。
2处的特别说明
:
这里的
*
不是修饰func,而是修饰func[5]
的,原因是[]
运算符优先级比*
高,func先跟[]
结合
记住2个模式
:
//函数指针
typedef (*)(....);
//数组指针
typedef (*)[];
7. typedef 的两大陷阱
陷阱一
:
typedef是定义了一种类型的新别名,不同于宏,它不是简单的字符串替换.
typedef char* PSTR;
int mystrcmp(const PSTR, const PSTR);
const PSTR实际上相当于const char吗?不是的,它实际上相当于char const。
原因在于const给予了整个指针本身以常量性,也就是形成了常量指针char* const。
简单来说,记住当 const
和 typedef
一起出现时,typedef不会是简单的字符串替换就行。
陷阱二
:
typedef在语法上是一个存储类的关键字(如auto、extern、mutable、static、register等一样)
例如有如下定义:
typedef static int INT2;
进行编译将会失败,会提示指定了一个以上的存储类
。