【总结】C语言中typedef 的用法与陷阱

7 篇文章 0 订阅
4 篇文章 0 订阅

在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 p2const 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。
简单来说,记住当 consttypedef 一起出现时,typedef不会是简单的字符串替换就行

陷阱二

typedef在语法上是一个存储类的关键字(如auto、extern、mutable、static、register等一样)

例如有如下定义:

typedef static int INT2;

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

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值