C07-typedef-preprocessor
本文最后更新于:1 小时前
C typedef
使用关键字 typedef 可以为类型起一个新的别名。typedef 的用法一般为:typedef oldName newName;
oldName 是类型原来的名字,newName 是类型新的名字。例如:
1 |
|
INTEGER a, b;等效于int a, b;。
typedef 是赋予现有类型一个新的名字,而不是创建新的类型,按照惯例,定义时会大写字母,并且尽量使用含义明确的标识符,以便提醒用户类型名称是一个象征性的缩写。
typedef 还可以给数组、指针、结构体等类型定义别名。先来看一个给数组类型定义别名的例子:
typedef char ARRAY20[20];
表示 ARRAY20 是类型char [20]的别名。它是一个长度为 20 的数组类型。接着可以用 ARRAY20 定义数组:ARRAY20 a1, a2, s1, s2;
它等价于:char a1[20], a2[20], s1[20], s2[20];
注意,数组也是有类型的。例如char a1[20];定义了一个数组 a1,它的类型就是 char [20]。
又如,为结构体类型定义别名:
1 |
|
STU 是 struct stu 的别名,可以用 STU 定义结构体变量:STU body1,body2;
它等价于:struct stu body1, body2;
再如,为指针类型定义别名:typedef int (*PTR_TO_ARR)[4];
表示 PTR_TO_ARR 是类型int * [4]的别名,它是一个二维数组指针类型。接着可以使用 PTR_TO_ARR 定义二维数组指针:PTR_TO_ARR p1, p2;
按照类似的写法,还可以为函数指针类型定义别名:typedef int (*PTR_TO_FUNC)(int, int);
PTR_TO_FUNC pfunc;
typedef 与 #define 比较
(1)typedef 仅限于为类型定义符号名称,#define 不仅可以为类型定义别名,也能为数值定义别名,比如可以定义 1 为 ONE。
(2)typedef 是由编译器执行解释的,#define 语句是由预编译器进行处理的,只是字面上的替换。
(3)#define可以使用其他类型说明符对宏类型名进行扩展,但对 typedef 所定义的类型名却不能这样做。例如:
1 |
|
(4)在连续定义几个变量的时候,typedef 能够保证定义的所有变量均为同一类型,而 #define 则无法保证。例如:
1 |
|
经过宏替换以后,第二行变为:int *p1, p2;
这使得 p1、p2 成为不同的类型:p1 是指向 int 类型的指针,p2 是 int 类型。
相反,在下面的代码中:
1 |
|
p1、p2 类型相同,它们都是指向 int 类型的指针。
typedef 还有一个作用,就是为复杂的声明定义一个新的简单的别名。用在回调函数中特别好用:
原声明:int *(*a[5])(int, char*);
在这里,变量名为 a,直接用一个新别名 pFun 替换 a 就可以了:
typedef int *(*pFun)(int, char*);
于是,原声明的最简化版:
pFun a[5];
另外也要注意,typedef 在语法上是一个存储类的关键字(如 auto、extern、mutable、static、register 等一样),虽然它并不真正影响对象的存储特性,如:
typedef static int INT2; // 不可行
编译将失败,会提示“指定了一个以上的存储类”。
C预处理
预处理指令是以#号开头的代码行,# 号必须是该行除了任何空白字符外的第一个字符,为了增强可读性,预处理器指令应从第一列开始。# 后是指令关键字,在关键字和 # 号之间允许存在任意个数的空白字符,整行语句构成了一条预处理指令,该指令将在编译器进行编译之前对源代码做某些转换。
下面列出了所有重要的预处理器指令:
指令 | 描述 |
---|---|
#define | 定义宏(宏定义命令) |
#include | 包含一个源代码文件(文件包含命令) |
#undef | 取消已定义的宏 |
#ifdef | 如果宏已经定义,则返回真,编译下面代码 |
#ifndef | 如果宏没有定义,则返回真,编译下面代码 |
#if | 如果给定条件为真,则编译下面代码 |
#else | #if 的替代方案 |
#elif | 如果前面的 #if 给定条件不为真,当前条件为真,则编译下面代码 |
#endif | 结束一个 #if……#else 条件编译块 |
#error | 当遇到标准错误时,输出错误消息 |
#pragma | 使用标准化方法,向编译器发布特殊的命令到编译器中 |
预处理器实例
分析下面的实例来理解不同的指令。
#define
#define MAX_ARRAY_LENGTH 20
这个指令告诉 CPP 把所有的 MAX_ARRAY_LENGTH 替换为 20。使用 #define 定义常量来增强可读性。
#include
1 |
|
这些指令告诉 CPP 从系统库中获取 stdio.h,并添加文本到当前的源文件中。下一行告诉 CPP 从本地目录中获取 myheader.h,并添加内容到当前的源文件中。
说明:
#include 的处理过程很简单,就是将头文件的内容插入到该命令所在的位置,从而把头文件和当前源文件连接成一个源文件,这与复制粘贴的效果相同。
#include 的用法有两种,如下所示:
1 |
|
使用尖括号< >和双引号” “的区别在于头文件的搜索路径不同:
- 使用尖括号< >,编译器会到系统路径下查找头文件;
- 而使用双引号” “,编译器首先在当前目录下查找头文件,如果没有找到,再到系统路径下查找。
像 stdio.h 和 stdlib.h 都是标准头文件,它们存放于系统路径下,所以使用尖括号和双引号都能够成功引入;而我们自己编写的头文件,一般存放于当前项目的路径下,所以不能使用尖括号,只能使用双引号。通常使用尖括号来引入标准头文件,使用双引号来引入自定义头文件(自己编写的头文件),这样一眼就能看出头文件的区别。
关于 #include 用法的注意事项:
- 一个 #include 命令只能包含一个头文件,多个头文件需要多个 #include 命令。
- 同一个头文件可以被多次引入,多次引入的效果和一次引入的效果相同,因为头文件在代码层面有防止重复引入的机制。
- 文件包含允许嵌套,也就是说在一个被包含的文件中又可以包含另一个文件。
#undef
1 |
|
这个指令告诉 CPP 取消已定义的 FILE_SIZE,并定义它为 42。
#ifndef
1 |
|
这个指令告诉 CPP 只有当 MESSAGE 未定义时,才定义 MESSAGE。
1 |
|
这个指令告诉 CPP 如果定义了 DEBUG,则执行处理语句。在编译时,如果您向 gcc 编译器传递了 -DDEBUG 开关量,这个指令就非常有用。它定义了 DEBUG,您可以在编译期间随时开启或关闭调试。
#define的用法,C语言宏定义
#define 叫做宏定义命令,它也是C语言预处理命令的一种。所谓宏定义,就是用一个标识符来表示一个字符串,如果在后面的代码中出现了该标识符,那么就全部替换成指定的字符串。在预处理阶段,对程序中所有出现的“宏名”,预处理器都会用宏定义中的字符串去代换,这称为“宏替换”或“宏展开”。
宏定义是由源程序中的宏定义命令#define完成的,宏替换是由预处理程序完成的。
宏定义的一般形式为:
#define 宏名 定义内容
#表示这是一条预处理命令,所有的预处理命令都以 # 开头。宏名是标识符的一种,命名规则和变量相同。定义内容可以是数字、表达式、if 语句、函数等。
对 #define 用法的几点说明
- 宏定义是用宏名来表示一个字符串,在宏展开时又以该字符串取代宏名,这只是一种简单粗暴的替换。字符串中可以含任何字符,它可以是常数、表达式、if 语句、函数等,预处理程序对它不作任何检查,如有错误,只能在编译已被宏展开后的源程序时发现。
- 宏定义不是说明或语句,在行末不必加分号,如加上分号则连分号也一起替换。
- 宏定义必须写在函数之外,其作用域为宏定义命令起到源程序结束。如要终止其作用域可使用#undef命令。
- 代码中的宏名如果被引号包围,那么预处理程序不对其作宏代替
- 宏定义允许嵌套,在宏定义的字符串中可以使用已经定义的宏名,在宏展开时由预处理程序层层代换。
- 习惯上宏名用大写字母表示,以便于与变量区别。但也允许用小写字母。
- 可用宏定义表示数据类型,使书写方便。
带参数的宏定义
C语言允许宏带有参数。在宏定义中的参数称为“形式参数”,在宏调用中的参数称为“实际参数”,这点和函数有些类似。对带参数的宏,在展开过程中不仅要进行字符串替换,还要用实参去替换形参。
带参宏定义的一般形式为:
#define 宏名(形参列表) 字符串
在字符串中可以含有各个形参。
带参宏调用的一般形式为:
宏名(实参列表);
例如:
1 |
|
在宏展开时,用实参 5 去代替形参 y,经预处理程序展开后的语句为k=5*5+3*5
。
对带参宏定义的说明
- 带参宏定义中,形参之间可以出现空格,但是宏名和形参列表之间不能有空格出现。
例如把:#define MAX(a,b) (a>b)?a:b
写为:#define MAX (a,b) (a>b)?a:b
将被认为是无参宏定义,宏名 MAX 代表字符串(a,b) (a>b)?a:b。宏展开时,宏调用语句:max = MAX(x,y);
将变为:max = (a,b)(a>b)?a:b(x,y);
这显然是错误的。
- 在带参宏定义中,不会为形式参数分配内存,因此不必指明数据类型。而在宏调用中,实参包含了具体的数据,要用它们去替换形参,因此实参必须要指明数据类型。
这一点和函数是不同的:在函数中,形参和实参是两个不同的变量,都有自己的作用域,调用时要把实参的值传递给形参;而在带参数的宏中,只是符号的替换,不存在值传递的问题。
- 在宏定义中,字符串内的形参通常要用括号括起来以避免出错。
预处理器运算符
C 预处理器提供了下列的运算符来帮助您创建宏:
宏延续运算符(\)
一个宏通常写在一个单行上。但是如果宏太长,一个单行容纳不下,则使用宏延续运算符(\)。例如:
1 |
|
字符串常量化运算符(#)
在宏定义中,当需要把一个宏的参数转换为字符串常量时,则使用字符串常量化运算符(#)。在宏中使用的该运算符有一个特定的参数或参数列表。例如:
1 |
|
当上面的代码被编译和执行时,它会产生下列结果:
Carole and Debra: We love you!
标记粘贴运算符(##)
宏定义内的标记粘贴运算符(##)会合并两个参数。它允许在宏定义中两个独立的标记被合并为一个标记。例如:
1 |
|
当上面的代码被编译和执行时,它会产生下列结果:
token34 = 40
这是怎么发生的,因为这个实例会从编译器产生下列的实际输出:
printf ("token34 = %d", token34);
这个实例演示了 token##n 会连接到 token34 中,在这里,我们使用了字符串常量化运算符(#)和标记粘贴运算符(##)。
defined() 运算符
预处理器 defined 运算符是用在常量表达式中的,用来确定一个标识符是否已经使用 #define 定义过。如果指定的标识符已定义,则值为真(非零)。如果指定的标识符未定义,则值为假(零)。下面的实例演示了 defined() 运算符的用法:
1 |
|
当上面的代码被编译和执行时,它会产生下列结果:
Here is the message: You wish!
预定义宏
ANSI C 定义了许多宏。在编程中您可以使用这些宏,但是不能直接修改这些预定义的宏。
宏 | 描述 |
---|---|
DATE | 当前日期,一个以 “MMM DD YYYY” 格式表示的字符常量。 |
TIME | 当前时间,一个以 “HH:MM:SS” 格式表示的字符常量。 |
FILE | 这会包含当前文件名,一个字符串常量。 |
LINE | 这会包含当前行号,一个十进制常量。 |
STDC | 当编译器以 ANSI 标准编译时,则定义为 1。 |
实例:
1 |
|
当上面的代码(在文件 test.c 中)被编译和执行时,它会产生下列结果:
1 |
|
#if、##ifdef、#ifndef的用法详解,C语言条件编译详解
能够根据不同情况编译不同代码、产生不同目标文件的机制,称为条件编译。条件编译是预处理程序的功能,不是编译器的功能。
#if 的用法
#if 用法的一般格式为:
1 |
|
它的意思是:如常“表达式1”的值为真(非0),就对“程序段1”进行编译,否则就计算“表达式2”,结果为真的话就对“程序段2”进行编译,为假的话就继续往下匹配,直到遇到值为真的表达式,或者遇到 #else。这一点和 if else 非常类似。
需要注意的是,#if 命令要求判断条件为“整型常量表达式”,也就是说,表达式中不能包含变量,而且结果必须是整数;而 if 后面的表达式没有限制,只要符合语法就行。这是 #if 和 if 的一个重要区别。
#ifdef 的用法
#ifdef 用法的一般格式为:
1 |
|
它的意思是,如果当前的宏已被定义过,则对“程序段1”进行编译,否则对“程序段2”进行编译。
#ifdef 可以认为是 #if defined 的缩写。
#ifndef 的用法
#ifndef 用法的一般格式为:
1 |
|
与 #ifdef 相比,仅仅是将 #ifdef 改为了 #ifndef。它的意思是,如果当前的宏未被定义,则对“程序段1”进行编译,否则对“程序段2”进行编译,这与 #ifdef 的功能正好相反。
三者之间的区别
最后需要注意的是,#if 后面跟的是“整型常量表达式”,而 #ifdef 和 #ifndef 后面跟的只能是一个宏名,不能是其他的。
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!