做淘宝客需要网站吗,男生学平面设计好就业吗,修改wordpress用户名密码忘记,长沙网站建设哪家最好目录 一、学习目标
二、宏定义
预处理
宏的概念
带参宏
无值宏定义
三、条件编译
条件编译
条件编译的使用场景
四、头文件
头文件的作用
头文件的内容
头文件的基础语句#xff1a;
GCC编译器的4个编译步骤#xff1a;
总结 一、学习目标
掌握宏定义含义和用…目录 一、学习目标
二、宏定义
预处理
宏的概念
带参宏
无值宏定义
三、条件编译
条件编译
条件编译的使用场景
四、头文件
头文件的作用
头文件的内容
头文件的基础语句
GCC编译器的4个编译步骤
总结 一、学习目标
掌握宏定义含义和用法理解条件编译的场景和用法清楚头文件的概念和用法
二、宏定义
预处理 在C语言程序源码中凡是以井号#开头的语句被称为预处理语句这些语句严格意义上并不属于C语言语法的范畴它们在编译的第一个阶段统一由所谓预处理器cc1来处理。所谓预处理顾名思义指的是真正的C程序编译之前预先进行的一些处理步骤这些预处理指令包括 头文件#include定义宏#define取消宏#undef条件编译#if、#ifdef、#ifndef、#else、#elif、#endif显示错误#error修改当前文件名和行号#line向编译器传送特定指令#progma 基本语法 一个逻辑行只能出现一条预处理指令多个物理行需要用反斜杠 \ 转义字符连接成一个逻辑行预处理是整个编译全过程的第一步预处理 - 编译 - 汇编 - 链接可以通过如下编译选项来指定来限定编译器只进行预处理操作 -E
gcc example.c -o example.i -E
宏的概念
宏macro实际上就是一段特定的字串在源码中用以替换为指定的表达式。例如
#define PI 3.14 此处PI 就是宏宏一般习惯用大写字母表达以区分于变量和函数但这并不是语法规定只是一种习惯是一段特定的字串这个字串在源码中出现时将被替换为3.14。例如
int main()
{printf(圆周率: %f\n, PI); // 此语句将被替换为printf(圆周率: %f\n, 3.14);
}宏的作用 使得程序更具可读性字串单词一般比纯数字更容易让人理解其含义。使得程序修改更易行修改宏定义即修改了所有该宏替换的表达式。提高程序的运行效率宏函数程序的执行不再需要函数切换开销而是就地展开。
无参宏
无参宏意味着使用宏的时候无需指定任何参数比如
#define PI 3.14
#define SCREEN_SIZE 800*480*4
int main()
{// 在代码中可以随时使用以上无参宏来替代其所代表的表达式printf(圆周率: %f\n, PI); mmap(NULL, SCREEN_SIZE, PROT_READ|PROT_WRITE, MAP_SHARED, ...);
}
注意到上述代码中除了有自定义的宏还有系统预定义的宏
// 自定义宏:
#define PI 3.14
#define SCREEN_SIZE 800*480*4 // 系统预定义宏
#define NULL ((void *)0)
#define PROT_READ 0x1 /* Page can be read. */
#define PROT_WRITE 0x2 /* Page can be written. */
#define MAP_SHARED 0x01 /* Share changes. */
#define NULL ((void *)0)宏的最基本特征是进行直接文本替换(不做任何的运算以及判断)以上代码被替换之后的结果是
int main()
{printf(圆周率: %f\n, 3.14); mmap(((void *)0), 800*480*4, 0x1|0x2, 0x01, ...);
}
带参宏 带参宏意味着宏定义可以携带“参数”从形式上看跟函数很像因此带参宏被称为宏函数例如 #define MAX(a, b) ab ? a : b
#define MIN(a, b) ab ? a : b以上的MAX(a,b) 和 MIN(a,b) 都是带参宏不管是否带参宏都遵循最初的规则即宏是一段待替换的文本例如在以下代码中宏在预处理阶段都将被替换掉 int main()
{int x 100, y 200;printf(最大值:%d\n, MAX(x, y));printf(最小值:%d\n, MIN(x, y));// 以上代码等价于:// printf(最大值:%d\n, xy ? x : y);// printf(最小值:%d\n, xy ? x : y);
}
带参宏的特点 直接文本替换不做任何语法判断更不做任何中间运算。宏在编译的第一个阶段就被替换掉运行中不存在宏。宏将在所有出现它的地方展开这一方面浪费了内存空间另一方面有节约了切换时间。
带参宏的副作用 由于宏仅仅做文本替换中间不涉及任何语法检查、类型匹配、数值运算因此用起来相对函数要麻烦很多。例如 #define MAX(a, b) ab ? a : bint main()
{int x 100, y 200;printf(最大值:%d\n, MAX(x, y200?888:999));
} 直观上看无论 y 的取值是多少表达式 y200?888:999 的值一定比 x 要大但由于宏定义仅仅是文本替换中间不涉及任何运算因此等价于 printf(最大值:%d\n, xy200?888:999 ? x : y200?888:999);可见带参宏的参数不能像函数参数那样视为一个整体整个宏定义也不能视为一个单一的数据事实上不管是宏参数还是宏本身都应被视为一个字串或者一个表达式或者一段文本因此最基本的原则是 将宏定义中所有能用括号括起来的部分都括起来比如
#define MAX(a, b) ((a)(b) ? (a) : (b))
无值宏定义
定义无参宏的时候不一定需要带值无值的宏定义经常在条件编译中作为判断条件出现实现条件编译例如
#define BIG_ENDIAN
#define __cplusplus三、条件编译
条件编译
概念有条件的编译通过控制某些宏的值来决定编译哪段代码。形式 形式1判断表达式 MACRO 是否为真据此决定其所包含的代码段是否要编译注意#if形式条件编译需要有值宏
#define A 0
#define B 1
#define C 2#if A... // 如果 MACRO 为真那么该段代码将被编译否则被丢弃
#endif
// 二路分支
#if A...
#elif B...
#endif
示例 #define DEBUG 0
#define DEMO 1
#define TEST 0 int main(int argc, char const *argv[])
{printf(%d__%s\n , __LINE__ , __FUNCTION__ );printf(%d__%s\n , __LINE__ , __FUNCTION__ );// 判断宏 DEBUG是否为真
#if DEBUG printf(%d__%s\n , __LINE__ , __FUNCTION__ );
#endif// 判断宏 DEMO 是否为真 来决定使用的是 21行或23行
#if DEMOprintf(%d__%s\n , __LINE__ , __FUNCTION__ );
#elseprintf(%d__%s\n , __LINE__ , __FUNCTION__ );
#endif// 判断宏 DEMO 是否为真 如果为真则启用地29行否则判断TEST是否为真来决定启用 31或33行
#if DEMOprintf(%d__%s\n , __LINE__ , __FUNCTION__ );
#elif TESTprintf(%d__%s\n , __LINE__ , __FUNCTION__ );
#elseprintf(%d__%s\n , __LINE__ , __FUNCTION__ );
#endifreturn 0;
}
如何在编译时定义或指定宏的值
gcc xxx.c -DDEBUG1 # 编译xxx.c时设置宏DEBUG的值为1 形式 形式2判断宏 MACRO 是否已被定义据此决定其所包含的代码段是否要编译
// 单独判断
#ifdef MACRO...
#endif// 二路分支
#ifdef MACRO...
#else...
#endif
形式 形式3判断宏MACRO是否未被定义据此决定其所包含的代码段是否要编译
// 单独判断
#ifndef MACRO...
#endif// 二路分支
#ifndef MACRO...
#else...
#endif
总结 #ifdef 此种形式判定的是宏是否已被定义这不要求宏有值。#if 、#elif 这些形式判定的是宏的值是否为真这要求宏必须有值。在命令行中进行编译程序时可以直接使用-D选项来定义宏此时宏的值如果没有特意给那么该宏的值默认为真。
gcc xxx.c -o xxx -DDEBUG # 当前命令行中定义了一个宏 DEBUG并该宏没有赋值那么他的值默认为真
条件编译的使用场景 控制调试语句在程序中用条件编译将调试语句包裹起来通过gcc编译选项随意控制调试代码的启停状态。例如 gcc example.c -o example -DMACRO 以上语句中-D意味着 DefineMACRO 是程序中用来控制调试语句的一个宏如此一来就可以在完全不需要修改源代码的情况下通过外部编译指令选项非常方便地控制调试信息的启停。 选择代码片段在一些大型项目中例如 Linux 内核某个相同功能的模块往往有不同的实现需要用户根据具体的情况来“配置”这个所谓的配置的过程就是对代码中不同的宏的选择的过程。 #define A 0 // 网卡1
#define B 1 // 网卡2 √
#define C 0 // 网卡3// 多路分支
#if A...
#elif B...
#elif C...
#endif
四、头文件 头文件的作用 通常一个常规的C语言程序会包含多个源码文件.c当某些公共资源需要在各个源码文件中使用时为了避免多次编写相同的代码一般的做法是将这些大家都需要用到的公共资源放入头文件.h当中然后在各个源码文件中直接包含即可。 头文件的内容
头文件中所存放的内容就是各个源码文件xxx.c的彼此可见的公共资源包括
全局变量的声明。普通函数的声明。静态函数的定义。 --- 使用static 修饰的函数宏定义。结构体、联合体的声明。枚举常量列表的声明。其他头文件。 --- 嵌套包含其他 的头文件
头文件的基础语句
#ifndef MY_HEAD_H //这是一个条件编译判断MY_HEAD_H是否没有被定义 如果没定义则条件成立
#define MY_HEAD_H //定义一个宏 MY_HEAD_H// 错误示范 如果该头文件被多个.C所包含则有可能会导致重复定义的问题
int a 123 ;
char b 45 ;#endif //ifndef 的结束标记
注意 头文件中的基础格式 #ifndef #define #endif 这3条语句用于防止同一个头文件被一个.c源文件多次重复包含。错误示范中 a与b的定义在多个原文件中被定义因此在链接阶段会出现多重定义的问题。 特别说明 全局变量、普通函数的定义一般出现在某个源文件*.c *.cpp中其他的源文件想要使用都需要进行声明因此声明语句一般放在头文件中更方便。 静态函数、宏定义、结构体、联合体的声明都只能在其所在的文件可见因此如果多个源文件都需要使用的话放到头文件中定义是最方便也是最安全的选择。 GCC编译器的4个编译步骤 预处理 编译 汇编 该操作直接使用汇编器把上一个步骤产生的.s汇编文件直接转换为对应的二进制的指令。 链接 总结 本文细讲了C语进阶路上的终极BOSS关卡这次的BOSS可以结合之前的所有技能和特点C语的第一阶段到现在就结束了将各关卡的小怪全部斩杀后可获得大量经验值。而下一阶段来细讲数据结构的小怪、BOSS听说通过后有神秘奖励哦~最后祝各位都可爬上C语巅峰斩尽拦路小妖。 本文参考 粤嵌文哥 的部分课件经过整理和修改后发布在C站。如有转载请联系本人