多应用+插件架构,代码干净,二开方便,首家独创一键云编译技术,文档视频完善,免费商用码云13.8K 广告
[TOC] # 基本概念 c语言对源程序处理的四个步骤:预处理,编译,汇编,链接 预处理是在程序源代码被编译之前,由预处理器(Preprocessor)对程序源代码进行的处理.这个过程并不对程序的源代码语法进行解析,但它会把源代码分割或处理成为特定的符号为下一步的编译做准备工作 # 文件包含指令 文件包含处理是指一个源文件可以将另外一个文件的全部内容包含进来.c语言提供了`#include`命令用来实现"文件包含"的操作 ![](https://box.kancloud.cn/5f03c0347d40a290ac6d805306e1bad3_724x312.png) ## `#include<>`和`#include ""`区别 * ""表示系统先在file1.c所在的当前目录找file1.h,如果找不到,再按系统指定的目录检索 * `<>`表示系统直接按系统指定的目录检索 注意: 1. `#include<>`常用于包含库函数的头文件 2. `#include ""`常用于包含自定义的头文件 3. 理论上`#include`可以包含任意格式的文件(`.c .h`等),但一般用于头文件的包含 # 宏参数的字符串化 `#` 的用法 **#用来将宏参数转换为字符串**,也就是在宏参数的开头和末尾添加引号。例如有如下宏定义: `#define STR(s) #s` 那么: ~~~ printf("%s", STR(c.biancheng.net)); printf("%s", STR("c.biancheng.net")); ~~~ 分别被展开为: ~~~ printf("%s", "c.biancheng.net"); printf("%s", "\"c.biancheng.net\""); ~~~ 可以发现,即使给宏参数“传递”的数据中包含引号,使用#仍然会在两头添加新的引号,而原来的引号会被转义。 将上面的例子补充完整: ~~~ #include <stdio.h> #define STR(s) #s int main() { printf("%s\n", STR(c.biancheng.net)); printf("%s\n", STR("c.biancheng.net")); return 0; } 运行结果: c.biancheng.net "c.biancheng.net" ~~~ 注意: 对空格处理 当传入参数名间存在空格时,编译器将会自动连接各个子字符串,用每个子字符串中只以一个空格连接,忽略其中多余一个的空格 ~~~ printf("%s\n", STR(abc 123)); ~~~ 输出 ~~~ abc 123 ~~~ # 宏参数的连接 `##`的用法 `##`称为连接符,用来将宏参数或其他的串连接起来。例如有如下的宏定义: ~~~ #define CON1(a, b) a##e##b #define CON2(a, b) a##b##00 ~~~ 那么: ~~~ printf("%f\n", CON1(8.5, 2)); printf("%d\n", CON2(12, 34)); ~~~ 将被展开为: ~~~ printf("%f\n", 8.5e2); printf("%d\n", 123400); ~~~ 将上面的例子补充完整: ~~~ #include <stdio.h>#define CON1(a, b) a##e##b#define CON2(a, b) a##b##00int main() { printf("%f\n", CON1(8.5, 2)); printf("%d\n", CON2(12, 34)); return 0;} ~~~ 运行结果: ~~~ 850.000000 123400 ~~~ 注意: 当用`##`连接形参时,`##`前后的空格可有可无 另外,如果`##`后的参数本身也是一个宏的话,`##`会阻止这个宏的展开 # 宏定义 ## 无参数的宏定义(宏常量) 如果在程序中大量用到了100这个值,那么为了方便管理,我们可以将其定义为: `const int num = 100;` 但是如果我们使用num定义一个数组,在不支持c99标准的编译器上是不支持的,因为num不是一个编译器常量,如果想得到一个编译器常量,那么可以使用: ~~~ #define num 100 ~~~ 在编译预处理时,将程序中在该语句以后出现的所有的num都用100代替.这种写法使用户能以一个简单的名字代替一个长的字符串,在预处理时将宏名替换成字符串的过程称为**宏展开**.宏定义,只在宏定义的文件中起作用 说明: 1. 宏名一般用于大写,以便于与变量区别 2. 宏定义可以是常数,表达式等 3. 宏定义不作语法检查,只有在编译被宏展开后的源程序才会报错 4. 宏定义不是c语言,不在行末加分号 5. 宏名有效范围为从定义到本源文件结束 6. 可以用`#undef`命令终止宏定义的作用域 ## 带参数的宏定义(宏函数) 在项目中,经常把一些短小又频繁使用的函数写成宏函数,这是由于宏函数没有普通函数参数压栈,跳转,返回等开销,可以调高程序的效率 宏通过使用参数,可以创建外形和作用都与函数类似的类函数宏(function-like macro) 宏的参数也用园括号括起来 ~~~ #define SUM(x, y) ((x) + (y)) void test() { //仅仅只是做文本替换,下边替换为int ret = ((10) + (20)); //不进行计算 int ret = SUM(10, 20); printf("ret:%d\n", ret); } ~~~ 注意: 1. 宏的名字中不能有空格,但是在替换的字符串中可以有空格.ANSIC允许在参数列表中使用空格 2. 用括号括住每一个参数,并括住宏的整体定义 3. 用大写字母表示宏的函数名 4. 如果打算宏代替函数来加快程序运行速度.假如在程序中只使用一次宏对程序的运行时间没有太大提高 # 一些特殊的预定义宏 c编译器,提供了几个特定形式的预定义宏,在实际编程中可以直接使用,很方便 ~~~ //__FILE__ 宏所在文件的源文件名 //__LINE__ 宏所在行的行号 //__DATE__ 代码编译的日期 //__TIME__ 代码编译的时间 int main() { printf("%s\n", __FILE__); printf("%d\n", __LINE__); printf("%s\n", __DATE__); printf("%s\n", __TIME__); getchar(); return 0; } ~~~ 输出 ~~~ /Users/jdxia/Desktop/study/studyc/main.c 7 Oct 19 2019 22:35:14 ~~~ # 条件编译 一般情况下,源程序中所有的行都参加编译.但是有时希望对部分源程序行只在满足一定条件下才编译,即对这部分源程序行指定编译条件 ~~~ //#define FLAG #define FLAG #ifdef FLAG void func(long a) { } #else void func(int a) { } #endif ~~~ ![](https://box.kancloud.cn/302ee46a51c50bbf21d8e4cc986e1642_685x234.png) ## `#if`的用法 ~~~ #include <stdio.h> int main(){ #if _WIN32 printf("This is Windows!\n"); #else printf("Unknown platform!\n"); #endif #if __linux__ printf("This is Linux!\n"); #endif return 0; } ~~~ ## `#ifdef` 的用法 #ifdef 用法的一般格式为: ~~~ #ifdef  宏名     程序段1 #else     程序段2 #endif ~~~ 它的意思是,如果当前的宏已被定义过,则对“程序段1”进行编译,否则对“程序段2”进行编译。 也可以省略 `#else`: ~~~ #ifdef  宏名     程序段 #endif ~~~ VS/VC 有两种编译模式,Debug 和 Release。在学习过程中,我们通常使用 Debug 模式,这样便于程序的调试;而最终发布的程序,要使用 Release 模式,这样编译器会进行很多优化,提高程序运行效率,删除冗余信息。 为了能够清楚地看到当前程序的编译模式,我们不妨在程序中增加提示,请看下面的代码: ~~~ #include <stdio.h> #include <stdlib.h> int main(){ #ifdef _DEBUG printf("正在使用 Debug 模式编译程序...\n"); #else printf("正在使用 Release 模式编译程序...\n"); #endif system("pause"); return 0; } ~~~ 当以 Debug 模式编译程序时,宏 \_DEBUG 会被定义,预处器会保留第 5 行代码,删除第 7 行代码。反之会删除第 5 行,保留第 7 行 ## `#ifndef`的用法 `#ifndef` 用法的一般格式为: ~~~ #ifndef 宏名     程序段1  #else      程序段2  #endif ~~~ 与 `#ifdef` 相比,仅仅是将 `#ifdef` 改为了 `#ifndef`。它的意思是,如果当前的宏未被定义,则对“程序段1”进行编译,否则对“程序段2”进行编译,这与 `#ifdef` 的功能正好相反。 ## 三者之间的区别 最后需要注意的是,#if 后面跟的是“整型常量表达式”,而 #ifdef 和 #ifndef 后面跟的只能是一个宏名,不能是其他的。 例如,下面的形式只能用于 #if: ~~~ #include <stdio.h> #define NUM 10 int main(){ #if NUM == 10 || NUM == 20 printf("NUM: %d\n", NUM); #else printf("NUM Error\n"); #endif return 0; } ~~~ 运行结果: NUM: 10 再如,两个宏都存在时编译代码A,否则编译代码B: ~~~ #include <stdio.h> #define NUM1 10 #define NUM2 20 int main(){ #if (defined NUM1 && defined NUM2) //代码A printf("NUM1: %d, NUM2: %d\n", NUM1, NUM2); #else //代码B printf("Error\n"); #endif return 0; } ~~~ 运行结果: NUM1: 10, NUM2: 20 `#ifdef` 可以认为是 `#if defined` 的缩写 # `#error`命令 #error 指令用于在编译期间产生错误信息,并阻止程序的编译,其形式如下: ~~~ #error error_message ~~~ 例如,我们的程序针对 Linux 编写,不保证兼容 Windows,那么可以这样做: ~~~ #ifdef WIN32 #error This programme cannot compile at Windows Platform #endif ~~~ WIN32 是 Windows 下的预定义宏。当用户在 Windows 下编译该程序时,由于定义了 WIN32 这个宏,所以会执行 `#error `命令,提示用户发生了编译错误,错误信息是: ~~~ This programme cannot compile at Windows Platform ~~~ 这和发生语法错误的效果是一样的,程序编译失败。请看下面的截图: ![](https://box.kancloud.cn/45ac33fefca77be28bad3134b393774b_847x159.png) 需要注意的是:报错信息不需要加引号`" "`,如果加上,引号会被一起输出。例如将上面的 #error 命令改为: ~~~ #error "This programme cannot compile at Windows Platform" ~~~ 那么错误信息如下: ![](https://box.kancloud.cn/d1f360116a2f857810a2e15087d26a4b_768x221.png) 再如,当我们希望以 C++ 的方式来编译程序时,可以这样做: ~~~ #ifndef __cplusplus #error 当前程序必须以C++方式编译 #endif ~~~