1. 概念
C语言中的宏定义是一种预处理指令,它允许开发者为一段代码或值定义一个名称,称为宏。在编译程序时,预处理器会在实际编译之前对源代码进行预处理,将宏名称替换为其定义的内容。宏定义通常使用 #define 指令来实现。
宏定义的本质是在预处理时对文本进行替换,可以减少栈空间的使用 ,在编译期间计算(0运行开销 : 编译时即确定大小)
#define 名称/宏名/别名 代码块/数据/原来的数据
#define macro_name replacement_text
//macro_name 是宏的名称,
//replacement_text 是宏替换时要插入的文本。
1.1 格式
- 简单的宏定义:
- #define <宏名> <字符串>
- 带参数的宏定义(宏函数)
- #define <宏名>(参数表) <宏体>
- #define A(a,b,c) ({a=1;b+=1;c=3;a+b+c;})
- #define xxx() ({})
#define MAX(a, b) ((a) > (b) ? (a) : (b))
//这是一个多个参数的宏
在上面的例子中,MAX 是一个带参数的宏,它接受两个参数 a 和 b,并返回两者中的较大值。在代码中使用 MAX(x, y) 时,预处理器会将其替换为 ((x) > (y) ? (x) : (y))。
1.2 注意
宏定义在C语言中非常有用,它们可以简化代码、提高代码的可读性和可维护性,并允许开发者在编译时进行一些特定的代码替换或操作。然而,由于宏定义只是简单的文本替换,没有类型检查或其他编译时的检查,因此在使用时需要特别小心,以避免一些常见的错误和陷阱。
- 解决方案
- 传参时检查类型是否符合要求
- 宏代码块要加()保证其优先级不会因为跟其他表达式结合导致,优先级混乱
2. 复杂的宏定义
在C语言中,宏定义可以非常复杂,并且可以用于生成相当复杂的代码片段。通过结合参数、操作符、条件和嵌套宏,可以创建出功能强大的宏
C语言中,若要使用多行代码块的宏,必须使用do...while(0)语句
复杂宏的每一行使用' \ '分隔换行,提高代码可读性
#define IS_EVEN(x) ((x) % 2 == 0)
#define PRINT_NUMBERS(n) do { \
int i; \
for (i = 0; i < (n); ++i) { \
if (IS_EVEN(i)) { \ //这里使用了宏的嵌套
printf("%d is even\n", i); \
} else { \
printf("%d is odd\n", i); \
} \
} \
} while (0)
在这个例子中,IS_EVEN 是一个简单的宏,用于检查一个数是否是偶数。
PRINT_NUMBERS 是一个更复杂的宏,它使用 do { ... } while (0) 结构来模拟一个语句块,并在其中使用了一个 for 循环来打印从0到 n-1 的所有整数,同时标记每个数是偶数还是奇数。
3. 预定义宏
预定义宏是C语言中标准编译器预先定义的宏(官方定义了,不需要我们定义,我们需要时直接使用这些宏就行)
- 预处理日期和时间
- __DATE__ 、__TIME__
- 函数名 和 当前行
- __FUNCTION__、 ___LINE__
- 文件名
- __FILE__
例如以下应用
#include <stdio.h>
int test01()
{
//............
int a;
printf("文件名:%s 当前函数:%s 当前行:%d a:%d\n",__FILE__,__FUNCTION__,__LINE__,a);
//当前时间和日期
printf("%s %s\n",__DATE__,__TIME__);
}
int main()
{
test01();
return 0;
}
4. #符号和##符号
使用 # 符号 ,把一个宏参数变成对应的字符串
如果对变量使用# ,会把变量名作为字符串打印出来
#include <stdio.h>
#define CHANGE(STR) #STR // #将文本STR转换成字符串
int main()
{
char *str = "hello" "world" "ikun";
printf("str = %s\n", str);
printf("CHANGE(STR)= %s\n", CHANGE(ikun i love you)); // #ikun i love you 转成"ikun i love you"
return 0;
}
##可以把位于它两边的符号合成一个符号。它允许宏定义从分离的文本片段创建标识符
链接产生在预处理时,必须链接产生一个合法的标识符
#include <stdio.h>
#define STRCAT(str1, str2) str1##str2 // 将str1和str2标识连在一起
int main()
{
int sum5 = 100;
printf("sum5 = %d\n", STRCAT(sum, 5));
return 0;
}
5.条件编译
5.1 概念
能够根据不同情况编译不同代码,产生不同目标文件的机制,称之为条件编译。说白了,条件编译的意思就是有条件地编译某些我们指定的代码,而不一定编译文件中所有的代码。
5.2 #if指令
- 检查内容为表达式的值是否为真 非0则编译
- 关心宏的具体值
- 必须跟常量表达式
#if 整型常量表达式1 //可以使用宏的形式作为常量表达式
程序段1 // 如果常量表达式为真(非0),则编译此代码
#elif 整型常量表达式2
程序段2
#elif 整型常量表达式3
程序段3
#else // 如果常量表达式为假(0),则编译此代码
程序段4
#endif
5.3 #ifdef指令
- 检查内容时宏是否被定义(与值无关
- 定义则编译
- 预定义宏 ifdef 一般根据系统提供的宏判断出当前程序运行的平台, 已经开发模式 DEBUG(调试),产品模式
#ifdef 宏名 //如果程序中存在这个宏,执行下面的程序段1
程序段1
#else //如果程序中不存在这个宏,执行程序段2
程序段2
#endif
#ifdef常用于跨平台编写代码
#ifdef _WIN32
// Windows 特定代码
system("cls");
#elif defined(__linux__)
// Linux 特定代码
system("clear");
#elif defined(__APPLE__)
// macOS 特定代码
system("clear");
#endif
#ifdef MACRO ≡ #if defined(MACRO),但后者可以在 #if 表达式中组合使用,更灵活。
5.5 C/C++ 中的头文件保护
- 主要作用:
- 防止重复包含
- 当同一个头文件被多个源文件包含,或者被嵌套包含时,这个保护机制可以防止:
- 重复定义(编译错误)
- 重复声明
- 宏重复定义
- 防止重复包含
#ifndef _TOOL_H_ // 第一次:如果 _TOOL_H_ 未定义
#define _TOOL_H_ // 第一次:定义 _TOOL_H_
// 头文件内容(只会在第一次包含时生效)
#endif // 结束条件编译
5.6 #if与#ifdef间的区别
| 特性 | #if | #ifdef | #ifndef |
|---|---|---|---|
| 检查内容 | 表达式值是否为真 | 宏是否已定义 | 宏是否已定义 |
| 语法 | #if 表达式 | #ifdef 宏名 | #ifndef 宏名 |
| 等价形式 | - | #if defined(宏名) | - |
| 未定义宏 | 当作 0 处理 | 条件为假 | 条件为真 |
| 空值宏 | 当作 0 处理 | 条件为真 | 条件为假 |
| 灵活性 | 高(可组合、运算) | 低(只能检查存在) | 低(只能检查存在) |
| 常见用途 | 版本控制、级别判断 | 头文件保护、平台检测 | 头文件保护、平台检测 |
编译过程
编译过程分为以下四个阶段
- 预处理:
- 处理预处理语句(#开头的语句),删除注释、头文件展开、宏替换...
- gcc hello.c -o hello.i -E
- 编译:
- 将C语言程序转化为汇编语言
- gcc hello.c -o hello.s -S
- 汇编:
- 将程序代码转化为二进制代码//大型项目中可以节省编译时间
- gcc hello.c -o hello.o -c
- 链接:
- 将所有二进制代码合并起来,根据应用规则生成一个专门针对某个平台执行的应用程序镜像
- gcc hello.c -o hello
Comments NOTHING