目录
1.宏的定义
宏值是编程中一种常见的预处理指令,主要用于在代码中进行文本替换。它通过#define指令定义,允许开发者使用标识符(宏名)来表示特定的值或表达式,从而提高代码的可读性、可维护性和效率。宏值分为两种类型:无参宏定义和带参宏定义。
1.1无参宏定义
无参宏定义用于将标识符替换为固定的值或字符串。其基本格式为:
#define 宏名 替换值
例如:

甚至可以是一段语句(注意语句后不应加“;”)

1.2带参宏定义
带参宏定义类似于函数,但仅在预处理阶段进行文本替换,不进行类型检查。其格式为:
#define 宏名(参数1, 参数2, ...) 替换表达式
例如:

2.#define的替换规则
#define的替换规则总体分为以下几个步骤:
1.在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号。如果是,它们会被替换。
2.替换后被插入到程序中原来文本的位置,对于宏,参数名被他们替换。
3.最后,再次扫描,看看是否包含任何由#define定义的符号。如果是,重复上述过程,最终替换成没有宏的现象。
注意:
1.宏参数和#define定义中可以出现其他#define定义的变量。但是对于宏,不能出现递归。(宏是没有递归的概念)
2.当预处理搜索#define定义的符号时,字符串常量的内容不被搜索


3.宏与函数的对比
3.1代码长度
宏:每次调用时,宏代码都会被插入到程序中。除了非常小的宏之外,程序的长度将大幅增加。
函数:函数代码只出现在一个地方,每次调用这个函数时,调用的都是那个地方的同一份代码。
3.2执行速度
宏:更快。
函数:存在函数调用/返回的额外开销,更慢。
3.3操作符优先级
宏:宏在使用时只会简单地将宏名替换而不会进行计算,参数的求值是在所有周围下上文环境里,除非给它们加上括号,否则邻近操作符的优先级,可能会产生不可预测的结果。

函数:函数参数只在函数调用时求值一次,它的结果值传递给函数。表达式的求值更容易预测。
3.4参数求值
宏:参数每次用于宏定义时。它们都将重新求值,由于多次求值,具有副作用的参数可能会产生不可预料的结果。
函数:参数在函数被调用前只求值一次,在函数中多次使用参数,并不会导致多种求值过程,参数的副作用并不会造成任何特殊问题。
3.5参数类型
宏:宏与类型无关,只要参数的操作是合法的,它可以适用于任何参数类型。
函数:函数的参数是与类型有关的,如果参数的类型不同,就需要使用不同的函数,即使它们的任务是相同的。

3.6调试
宏:宏不方便调试。
函数:函数可以逐句调试。
3.7命名约定
一般来讲函数和宏的使用语法很相似,所以语言本身没法帮我们区分二者。
我们平时的习惯是:把宏名全大写,函数名不要全大写。
4.#undef
在C语言中,#undef指令用于取消之前定义的宏。这个指令的语法是#undef 标识符,它的作用是将前面定义的宏标识符取消定义。#undef的使用可以避免宏定义冲突,增强代码的可读性。

5.#和##
在C语言中,#和##是两个特殊的预处理运算符,主要用于宏定义中,分别实现字符串化和标记连接功能。
5.1字符串化运算符(#)
#运算符会将宏参数转换为字符串字面量。这在生成调试信息或打印变量名时非常有用。


在这里,#N将参数N转换为字符"a"。
5.2标记连接运算符(##)
##运算符将两个标记拼接成一个新的标记,常用于动态生成变量名或函数名。

此例中,CON(a, b)被展开为ab,并成功访问变量。
6.条件编译
在C语言中,程序员可以根据不同的条件,有选择性地编译不同的代码块(忽略其它的代码块),从而产生不同的目标文件,这种机制称为条件编译。
6.1#if、#elif、#else 和 #endif
#if、#elif、#else 和 #endif 允许我们根据特定的条件(一个表达式的值),来决定哪些代码段应该被编译,哪些应该被忽略。它们的基本用法如下:
#if 常量表达式1
//代码段1
//如果 常量表达式1 的值为真,则编译 代码段1,忽略其它代码
#elif 常量表达式2
//代码段2
//如果 常量表达式2 的值为真,则编译 代码段2,忽略其它代码
#else
//代码段n
//如果以上表达式的值都为假,则编译 代码段n,忽略其它代码
#endif
注意:
#if #else 命令和 if else 语句的用法非常类似,都是一种分支结构。但是,#if 命令要求判断条件为“常量表达式”,不能包含变量以及函数调用,而 if 语句后面的表达式没有限制,只要符合语法就行。这是 #if 和 if 的一个重要区别。(#if #else 是在预处理阶段进行替换,无法获取变量的值,所以表达式中不能包含变量)

写完可以在末尾加上注释,以后如果嵌套使用方便查找。
6.2#ifdef 和#ifndef
#ifdef用于判断某个宏是否定义,如果宏已定义,则编译对应的代码块。#ifndef功能正好相反,二者仅支持判断单个宏是否已经定义。
常见用途:头文件保护。防止头文件被多次包含。(也可用#pragma once代替)

7.预定义符号
在C语言中,预定义符号是由编译器提供的特殊符号,具有特定的含义和功能。
常见的预定义符号:
1. __LINE__: 表示当前代码所在的行号。
2. __FILE__: 表示当前源文件的文件名。
3. __DATE__: 表示当前编译的日期,格式为"MMM DD YYYY",例如"Jul 29 2023"。
4. __TIME__: 表示当前编译的时间,格式为"HH:MM:SS",例如"10:30:36"。
5. __STDC__: 表示当前编译器是否符合C语言标准。如果定义了该符号,则表示编译器符合C语言标准;否则,表示不符合。
6. NULL: 表示空指针常量。

8.小拓展:offsetof宏的实现
8.1offsetof宏
offsetof 宏是 C 语言中的一个重要工具,它用于计算结构体成员相对于结构体开头的字节偏移量。这个宏定义在 stddef.h 头文件中,其返回值是一个 size_t 类型的整数,表示成员在结构体中的位置。
size_t offsetof(type, member);
type 是结构体的类型,而 member 是结构体中的成员。

8.2MY_OFFSETOF

我们假设了一个地址为0的结构体,通过这个地址去找到我们需要的成员,该成员的地址便是相对于结构体开头的字节偏移量,将其强制转化为size_t类型。这样便实现了offsetof宏的功能。



8635

被折叠的 条评论
为什么被折叠?



