C语言初学者笔记【预处理】
文章目录
- 前言
- 一、预定义符号
- 二、#define定义常量
- 三、#define定义宏
- 四、带有副作用的宏参数
- 五、宏替换规则
- 六、宏 vs 函数
- 七、#和##运算符
- 1. #运算符(字符串化)
- 2.##运算符(记号粘合)
- 八、命名约定
- 九、#undef
- 十、命令行定义
- 十一、条件编译
- 1.基本形式:
- 2.多分支:
- 3.判断是否定义:
- 十二、头文件包含
- 1. 包含方式
- 2. 防止重复包含
- 3. 其他预处理指令
- 总结
前言
我们用C语言直接写出来的代码是不能被计算机进行识别的,这其中需要进行一系列过程使源码转换成计算机所能识别的二进制语言,这一系列过程就叫做翻译。源码翻译过程主要有四步:
预处理:头文件展开,去注释,宏替换,条件编译等
编译:将C语言翻译成汇编语言
汇编:将汇编语言转化为可重定向目标文件(可被链接,已经是二进制,但不是可执行文件)
链接:自身程序+库文件进行关联,形成可执行程序
本篇文章我们就来谈谈预处理这个环节,并了解如何定义宏,如何进行条件编译。
一、预定义符号
C语言提供了一些内置的预定义符号,可用于获取编译环境信息:
· FILE:当前源文件名
· LINE:当前行号
· DATE:编译日期
· TIME:编译时间
· STDC:如果编译器遵循ANSI C,值为1,否则未定义
二、#define定义常量
语法:#define name stuff
示例:
#define MAX 1000
#define reg register
#define do_forever for(;;)
#define CASE break;case
注意:不要在#define结尾加分号,否则可能导致语法错误。
三、#define定义宏
语法:#define name(parameter-list) stuff
示例:
#define SQUARE(x) x*x // 有问题:SQUARE(a+1) → a+1*a+1
#define SQUARE(x) (x)*(x) // 正确:加上括号
重要提示:宏定义中的每个参数和整个表达式都应该用括号括起来,避免运算符优先级问题。
四、带有副作用的宏参数
副作用:表达式求值时产生的永久性效果(如x++)
示例:
#define MAX(a, b) ((a) > (b) ? (a) : (b))
x = 5; y = 8;
z = MAX(x++, y++); // 展开后:((x++) > (y++) ? (x++) : (y++))
// 结果:x=6, y=10, z=9
五、宏替换规则
- 调用宏时,先检查参数是否包含#define定义的符号,如有则先替换
- 替换文本插入到程序中原位置
- 再次扫描结果,查看是否包含其他#define定义的符号
注意:
· 宏不能递归
· 字符串常量中的内容不会被搜索替换
六、宏 vs 函数
属性 宏 函数
代码长度 每次使用都插入代码,增加程序长度 代码只出现一次
执行速度 更快(无调用开销) 有调用返回开销
操作符优先级 可能产生问题,需加括号 参数求值结果明确
副作用参数 参数可能被多次计算 参数只求值一次
参数类型 类型无关 类型相关
调试 不方便 可逐语句调试
递归 不能 可以
宏的优势:可处理类型参数
#define MALLOC(num, type) (type*)malloc(num * sizeof(type))
MALLOC(10, int); // 展开为:(int*)malloc(10 * sizeof(int))
七、#和##运算符
1. #运算符(字符串化)
将宏参数转换为字符串字面量
#define PRINT(n) printf("the value of "#n" is %d", n)
PRINT(a); // 输出:the value of a is [a的值]
2.##运算符(记号粘合)
将两边的符号合并为一个标识符
#define GENERIC_MAX(type) \
type type##_max(type x, type y) { return x > y ? x : y; }GENERIC_MAX(float) // 生成函数:float float_max(float x, float y)
八、命名约定
· 宏名:全部大写(如#define MAX_SIZE 100)
· 函数名:不要全部大写
九、#undef
用于移除已定义的宏:
#undef NAME
十、命令行定义
许多编译器允许在命令行中定义符号,用于根据不同需求编译不同版本的程序。
十一、条件编译
根据条件决定是否编译某段代码:
1.基本形式:
#if 常量表达式//...
#endif
2.多分支:
#if 常量表达式//...
#elif 常量表达式//...
#else//...
#endif
3.判断是否定义:
#ifdef SYMBOL // 或 #if defined(SYMBOL)//...
#endif#ifndef SYMBOL // 或 #if !defined(SYMBOL)//...
#endif
十二、头文件包含
1. 包含方式
· 本地文件:#include “filename.h”(先当前目录,后系统目录)
· 库文件:#include <filename.h>(直接系统目录)
2. 防止重复包含
方法1(传统方式):
#ifndef __HEADER_H__
#define __HEADER_H__
// 头文件内容
#endif
方法2(编译器扩展):
#pragma once
// 头文件内容
3. 其他预处理指令
· #error:生成编译错误信息
· #pragma:编译器特定指令
· #line:改变当前行号和文件名
· #pragma pack():在结构体部分介绍(用于内存对齐)
总结
预处理是C语言编译过程中的重要阶段,合理使用预处理指令可以:
- 提高代码可读性和可维护性
- 实现条件编译和跨平台支持
- 提高代码复用性
- 优化程序性能(通过宏替代小函数)
但需要注意宏可能带来的副作用和优先级问题,建议:
· 宏参数和整个表达式都用括号括起来
· 避免使用带有副作用的参数
· 遵循命名约定区分宏和函数