C语言笔记之宏定义

(一)符号常量

宏定义是C语言中的一种替换策略,即使用预处理命令 #define 将一串(冗长的)文本与某个名字(称为宏)等同起来,然后就可以在源代码中批量使用宏。在预处理阶段再将源代码中的宏替换为原来的文本。例如,在源代码中:

#define  PI   3.14

那么在接下来的代码中,需要写3.14的地方可以直接用PI代替。预处理的时候,PI又全部变回3.14。

这样换来换去的有啥好处呢?万一代码的中的3.14需要全部改为3.1415926,那么如果没有刚才的宏定义,就只能挨个去修改;但是有了宏定义的话,只需修改宏定义就好了:

#define  PI   3.1415926

可以看出,每个#define命令由三部分组成,分别是:#define命令本身;缩略语(宏);替换文本或主体。各个部分之间用空格隔开,所以宏名称中不能存在空格,且必须遵循C变量命名规则。宏名称一般使用大写字母。

预处理中,从宏变回主体的过程成为宏展开。有些宏定义的主体比较长,可以在行尾使用反斜线“\”将剩余的部分延伸到下一行(但是注意第二行如果没有左对齐,那么开头的那些空白也会被当成主体的一部分)。

主体部分可以是常量,也可以是C表达式,甚至是一条完整的语句(即带有分号),总之可以是任意字符串。这里需要注意的是,如果主体中仍含有宏名称,则该宏也会被替换;但是如果这个宏名在主体中被双引号括起来,那么就不会发生宏替换了,只会被按照字面意思理解。

(二)类函数宏

宏定义分为两种,上面提到的都是不带参数的,称为类对象宏,这种宏也被称为符号常量;另外还有一种带参数的,称为类函数宏。例如:

#define FUN(X)  X * X

类函数宏的外形与函数十分相似,也是在名称后面紧跟一对圆括号,然后参数列表置于括号中。然后其主体就是这些参数的某种运算规则。在使用这个宏时,X可以被其他字符替换,就是函数的参数一样,X起到的也是参数的作用。

代码中这样的代码:

x = FUN(4);

会被替换成:

x= 4 * 4;

可能有点绕,但是我个人把这个宏展开的过程分为两步理解:首先,把FUN(4)直接替换成X * X;然后再用“实参”4代替“形参”X(不知道机器是不是这样执行的,会不会是偶的创新?)。

但是对类函数宏要有足够的警惕:预处理器在宏展开时,仅仅进行文字替换操作,而不是真的像函数传参那样。比如刚才那个宏这样使用:

x = FUN(3 + 4);

我们期望会替换成:

x = 7 * 7;

可实际上是:

x = 3 + 4 * 3 + 4;

由于结合顺序的原因,我们不会得到想要的结果。避免出现这种结果的办法就是:定义类函数宏时,最好给每个出现在主体中的参数加上括号,同时给每个运算规则也用括号保护起来。如,FUN应该这样定义:

#define FUN(X)  ((X) * (X))

那么替换之后就是这样:

x = ((3 + 4) * (3 + 4))

这样看似麻烦啰嗦,但却是避免意外的好办法。

下面是搬运时间(人家写的太精彩了,我就可耻的偷把懒~,摘自《Linux C一站式编程》)

函数式宏定义经常写成这样的形式(取自内核代码 include/linux/pm.h ):
#define device_init_wakeup(dev,val) do { device_can_wakeup(dev) = !!(val); device_set_wakeup_enable(dev,val); } while(0)
为什么要用 do { ... } while(0) 括起来呢?不括起来会有什么问题呢?
#define device_init_wakeup(dev,val) device_can_wakeup(dev) = !!(val); device_set_wakeup_enable(dev,val);
if (n > 0)
device_init_wakeup(d, v);
这样宏展开之后,函数体的第二条语句不在 if 条件中。那么简单地用 { ... } 括起来组成一个语
句块不行吗?
#define device_init_wakeup(dev,val) { device_can_wakeup(dev) = !!(val); device_set_wakeup_enable(dev,val); }
if (n > 0)
device_init_wakeup(d, v);
else
continue;
问题出在 device_init_wakeup(d, v); 末尾的 ; 号,如果不允许写这个 ; 号,看起来不像个函数调用,可如果写了这个 ; 号,宏展开之后就有语法错误, if 语句被这个 ; 号结束掉了,没法
跟 else 配对。因此, do { ... } while(0) 是一种比较好的解决办法。

然后我做点补充,do { ... } while(0) 这种形式到底怎么运行?我们知道,这种do...while()循环称为退出条件循环,判断条件在执行循环之后进行检查,这样就可以保证循环体中的语句至少被执行一次,而且这里的while条件被置为0,也就是说循环体恰好只执行一次,真是聪明的办法!!

(三)#与##运算符

写到这里真的有点撑不住,妹的C语言也真是博大精深,一个宏定义都要搞得这么复杂。。。

上面提到过,主体中双引号内的宏名称不会发生替换而是被当做普通文本,那如果非得要它发生替换呢?(话说怎么会有这么二的需求?)办法就是在这个宏名称前面加一个#符号,术语叫做:字符串化 ( stringizing )。

注意:这个符号仅仅用于类函数宏中的参数身上,即类对象宏中这种用法不起作用(我试过了,是真的)。

##运算符把两个语言符号组合成单个语言符号,但是它还可用于类对象宏的替换部分,被称作预处理器的粘合剂。在我看来,它的作用就是给对象(变量或函数)起名时用的,比如我们经常会给变量起x1  x2  x3之类的名字,这类名字的特征就是一部分不变而另一部分变。栗子:

#define XNAME(n)   x ## n

然后

<span style="font-size:18px;">int XNAME (1) = 14;</span>

将被替换成:

int  x1 = 14:

就酱,全篇完。

时间: 2024-10-01 07:04:42

C语言笔记之宏定义的相关文章

C语言学习笔记--C语言中的宏定义

1. C 语言中的宏定义 (1)#define 是预处理器处理的单元实体之一(因此,预处理器只是简单的进行替换,并不(2)#define 定义的宏可以出现在程序的任意位置(包括函数体的内部)(3)#define 定义之后的代码都可以使用这个宏 2. 定义宏常量 (1)#define 定义的宏常量可以直接使用(2)#define 定义的宏常量本质为字面量 3. 宏定义表达式 (1)#define 表达式的使用类似函数调用(2)#define 表达式可以比函数更强大(3)#define 表达式比函数

黑马程序员------C 语言学习笔记---C语言中的宏定义

1.5    C语言程序的运行过程 01 源程序:由高级语言或汇编语言编写,C语言源程序的扩展名为.C 02 目标程序:源程序经“编译程序”翻译所得的二进制代码为目标程序,其扩展名为.obj 03 可执行程序:目标程序与库函数连接,形成可执行程序,.out #include <stdio.h> int main() { #define PI 3.14 double r,len,area; printf("请输入半径:\n"); // 提示用户输入半径 scanf("

《linux 内核完全剖析》 笔记 CODE_SPACE 宏定义分析

在memory.c里面,遇到一个宏定义,如下: #define CODE_SPACE(addr) ((((addr)+4095)&~4095) < current->start_code + current->end_code) 看的第一眼,不知道,第二眼,还是不知道,纠结了半天还是不知道. 睡了一晚,今天早上再看,嘿嘿,居然看懂了... 这个宏定义用于判断给定的addr线性地址是否位于当前进程的代码段中. 4095 = 0xFFF; addr+4095的作用是将位于0~4095

C 语言 之 预处理-------- 宏定义

1 概述 使用过以"#"号开头的预处理命令.如包含命令# include,宏定义命令# define等.在源程序中这些命令都放在函数之外, 而且一般都放在源文件的前面,它们称为预处理部分. 所谓预处理是指在进行编译的第一遍扫描(词法扫描和语法分析)之前所作的工作.预处理是C语言的一个重要功能, 它由预处理程序负责完成.当对一个源文件进行编译时, 系统将自动引用预处理程序对源程序中的预处理部分作处理, 处理完毕自动进入对源程序的编译. C语言提供了多种预处理功能,如宏定义.文件包含. 条

c语言中的宏定义的学习体会

在学习stm32的过程中遇到关于宏定义的问题,所以,写出来大家一起学习一下 问题出处: 其中\是语言中的转义字符,用来连接上下文,因为宏定义只能是一个串,而当你的串过长(超过一行的时候)时,就需要换行了,此时就需要\来连接上下文. 下面是成熟软件中常用到的宏定义: 1,防止一个头文件被重复包含 #ifndef COMDEF_H #define COMDEF_H //头文件内容 #endif 2,重新定义一些类型,防止由于各种平台和编译器的不同,而产生的类型字节数差异,方便移植. typedef 

《linux 内核全然剖析》 笔记 CODE_SPACE 宏定义分析

在memory.c里面.遇到一个宏定义,例如以下: #define CODE_SPACE(addr) ((((addr)+4095)&~4095) < current->start_code + current->end_code) 看的第一眼,不知道.第二眼.还是不知道.纠结了半天还是不知道. 睡了一晚,今天早上再看,嘿嘿,竟然看懂了... 这个宏定义用于推断给定的addr线性地址是否位于当前进程的代码段中. 4095 = 0xFFF; addr+4095的作用是将位于0~40

【C语言总结】宏定义,预处理

宏定义 简单宏 格式:#define 标示符 替换列表 #define N 100 int a[N];//N就是100 带参数的宏 格式:#define 标示符(x1, x2, x3,--xn) 替换列表 #define MAX(x, y) ((x) > (y) ? (x) : (y)) 注意:程序编译的时候会把大写的标示符替换成原来的表达式,所以编译时候程序会增大. 优点或者缺点 优点 ①程序可能会稍微快些 ②宏会更通用 缺点 ①编译后代码通常会变大 ②无法用指针指向一个宏 ③宏可能会不止一次

20个C语言中常用宏定义总结

01: 防止一个头文件被重复包含 #ifndef COMDEF_H#define COMDEF_H//头文件内容#endif 02: 重新定义一些类型防止由于各种平台和编译器的不同,而产生的类型字节数差异,方便移植. typedef  unsigned char      boolean;     /* Boolean value type. */typedef  unsigned long int  uint32;      /* Unsigned 32 bit value */typedef

define宏定义中的#,##,@#及\符号

define宏定义中的#,##,@#及\符号 在#define中,标准只定义了#和##两种操作.#用来把参数转换成字符串,##则用来连接两个前后两个参数,把它们变成一个字符串. 1.# (stringizing)字符串化操作符.其作用是:将宏定义中的传入参数名转换成用一对双引号括起来参数名字符串.其只能用于有传入参数的宏定义中,且必须置于宏定义体中的参数名前. 如: #define example(instr) printf("the input string is:\t%s\n",#