目录一预定义符号二#define定义常量三#define定义宏1运算优先级问题2带有副作用的宏参数3宏的替换规则4宏和函数的对比5#和##6移除宏定义四条件编译1单分支的条件编译2多个分支的条件编译3,判断是否被定义4嵌套指令五头文件的包含1头文件被包含的方式2嵌套文件包含头文件的多次包含六关于宏的例题1offsetof宏的实现2交换奇偶位一预定义符号C语言中设置了一些预定义符号可以直接使用预定义符号也在预处理阶段进行处理。如下__FILE__ //表示进行编译的源文件__DATE__ //表示文件被编译的日期__TIME__ //文件被编译的时间精确到秒__LINE__ //此代码在文件中的行号__STDC__ //在VS中无法使用不支持ANSIC如果编译器遵循ANSIC,其值为1否则就是未定义二#define定义常量语法#define name stuff常见使用方式#define MAX 100 //为MAX赋值为100#define regregister//为关键字register创建一个简短的名字#define do_forever for(;;) //用符号替换for循环由于没有终止条件会创造一个死循环#defineDEBUG_PRINTprintf(file:%s\tdate:%s\t \time:%s\tline:%d\n , \__FILE__,__DATE__, \__TIME__,__LINE__)如上图所示如果stuff的内容过长就可以使用续行符\来多行输出注意续行符后面不可加任何内容包括空格直接换行即可。注意在定义标识符时不要再结尾加上;例如#define MAX 100;(X)#define MAX 100 //正确原因由于#define是直接替换所以在下方代码使用标识符会连同;一起替换。例如#define MAX 100;int main(){int ret MAX; //int ret 100;; (X)return 0;}三#define定义宏宏的使用语法#define name(parament-list) stuffname为 宏名parament-list为宏的参数suff为宏体注意宏的参数左边的括号必须紧挨着name不然就会被当成宏体的一部分使用举例1运算优先级问题宏不加括号导致的运算顺序与实际预想不符原因宏内容是直接的替换所以会出现由于没有加括号而导致的结果不符。要想完成任务需要在x的周围加上括号在比如修改如下2带有副作用的宏参数x1 //不带副作用x //带有副作用使用a和b使得a和b的值发生改变3宏的替换规则1,在调用宏时首先检查宏的参数如果宏的参数中有#define定义的常量或者宏则先替换参数处的常数或先计算参数处的宏。2再将宏的宏体替换到代码调用位置处。例如先替换M,再计算MAX(aM),最后计算MAX(b,10)注意1宏中可以出现其他的由#define定义的数字但是宏不可以递归宏体中包含自身2当#define定义的常量或者宏出现在字符串常量中时不会被替换使用。举例printf中双引号中的M不会被替换成24宏和函数的对比宏一般用来执行简单的运算例如找两个数的最大值#define MAX(X,Y) ((X)(Y)?(X):(Y))为什么不是用函数来完成上述操作和函数相比宏的优点1由于调用函数以及从函数中返回代码也会花费时间。所以使用宏在程序的规模上更小速度上更快。2函数的参数必须声明为指定的类型但是宏的参数不用指定类型可以传入任意类型的数据。和函数相比宏的缺点1每次使用宏时都会把宏体插入到代码中去如果宏体的代码量庞大时每次调用就会大幅增加程序的长度2宏不能调试3宏不用传入参数类型就不够严谨4宏可能出现运算符优先级的问题又或者会产生一些副作用宏可以做到一些函数做不到的事情宏的参数可以是类型函数的参数不可以是类型例如宏和函数的对比5#和###运算符#可以将宏体中的不可改变的字符串常量替换成宏传入的参数的字符串#运算符所执行的操作可以称为字符串化例如#n替换为“n”字符串,不为数字##运算符##可以把位于两边的符号合成一个符号##称为记号粘合这样的连接必须产生一个合法的标识符。否则其结果就是未定义注意一般便于在代码部分便于区分要把宏名全部大写函数名不要大些6移除宏定义语法#undef NAMENAME为宏名举例四条件编译常见的编译指令1单分支的条件编译if 常量表达式//……#endif如果常量表达式中的值为真则其下面的内容执行如果下面的内容为假则其下面的内容不能执行。2多个分支的条件编译#if 常量表达式//……#elif 常量表达式//……#else//……#endif举例3,判断是否被定义判断一个符号是否已经被定义定义了就执行没定义就不执行一共四种写法4嵌套指令多层条件编译进行判断 //类似if/else嵌套指令五头文件的包含1头文件被包含的方式头文件的包含分为两种一种是本地文件包含一种是库文件包含。例如本地文件包含#include add.h库文件包含#include stdio.h这两种的区别在于查找策略不同本地文件的查找策略先在工程目录下找如果找不到就在库函数的标准位置下查找一共查找两次。如果找不到就提示编译错误。VS环境下的标准头文件的路径按照自己的安装路径找库文件的查找策略直接去库函数的标准路径下查找只查找一次如果找不到就提示编译错误。注意对于库函数也可以使用的形式查找两次但是多查找会增加时间同时不容易区分本地文件和库文件。2嵌套文件包含头文件的多次包含当文件互相嵌套时头文件就有可能被多次包含。如果被多次包含就会被实际编译多次最终造成代码量的骤增同时对编译造成压力。如何解决上述问题我们有两种方式解决1第一种条件编译定义2#pragma once六关于宏的例题1offsetof宏的实现头文件#include stddef.h核心功能计算结构体成员相对于首地址的偏移量实现#define offsetof(s,m) ((size_t)(((s*)0)-m))其中s为结构体类型m为结构体成员拆分解释(s*)0 //将0强制转化为s结构体类型的指针这样做相当于结构体的首地址是0同时表示结构体的首地址((s*)0)-m) //表示结构体成员 //用指针访问成员类似:ps-age ps为结构体指针首地址((size_t)(((s*)0)-m)) //对结构体成员取地址并将地址值强制转化为size_t类型由于首地址为0所以成员的地址转化一下类型就是偏移量。2交换奇偶位写一个宏用来交换一个整数二进制的奇偶位解析补充在创建头文件时自动会在第一行添加#pragma once为的就是防止头文件被多次包含。本节常见问题1头文件中的#ifndef/#define/#endif的作用是什么答防止头文件被多次包含。2#include filename.h和#include filename.h的区别是什么答查找策略不同。