一篇思维导图送给大家(总结思维导图真的提升非常大);
目录
预处理
一些预定义符号
#define定义标识符
#define定义宏
#define的替换规则
带副作用的宏参数
宏与函数对比
#undef
命令行定义
条件编译
文件包含
头文件重复包含问题
头文件重复包含解决方案
预处理 一些预定义符号 __FILE__ 进行编译的源文件 __LINE__ 文件当前的行号 __DATE__ 文件被编译的日期 __TIME__ 文件被编译的时间 __STDC__ 如果编译器遵循ANSI C,其值为1,否则未定义
这些预定义符号就可以打印对应的内容,比如我们来举个例子
int main(){printf("file:%sn", __FILE__);//进行编译的源文件printf("line:%dn", __LINE__);// 文件当前的行号printf("data:%sn", __DATE__);// 文件被编译的日期printf("time:%sn", __TIME__);// 文件被编译的时间return 0;}
#define定义标识符 语法: #define name (名字) stuff(这个名字要替换的内容) 这个#define在写项目时候是经常被我们用到#define可以定义一个名字,然后将这个名字替换成它所对应的值,在项目中使用#define可以大量减少修改代码次数,当我们想要修改值的时候,可以直接在#define所要替换的值修改即可。
#define M 10int main(){printf("%dn", M);return 0;}
这里会有一个问题,#define后面要不要加分号呢?
答案是不要。为什么不要呢?
看这个例子如果M后面加上分号,第一个条件里的内容就变成M;;又多了一个分号就会导致语法错误。
#define定义宏
什么是宏呢?宏就是可以将参数替换到文本中。
#define name( parament - list ) stuff 其中的 parament - list 是一个由逗号隔开的符号表,它们可能出现在 stuff 中。 注意:参数列表的左括号必须与 name 紧邻。 如果两者之间有任何空白存在,参数列表就会被解释为 stuff 的一部分说这个可能大家都不太理解,我来用代码给大家举个例子。
#define MAX(x,y) ((x)>(y)?(x):(y))int main(){int a = 10;int b = 20;printf("max = %dn", MAX(a, b));return 0;}
这里需要注意的是
还有一点要注意的是
#define DOUBLE(x,y) x*yint main(){int a = 2;int b = 3;printf("%dn", DOUBLE(a + 2, b + 1));return 0;}
这里的答案是多少呢?难道是4*4=16么?
答案是错误的这里会将x,y替换变成,a+2*b+1 这里就会导致操作符优先级问题导致结果不是你想要的,解决办法是,当你害怕操作符优先级问题导致结果错误的时候,你就可以加上括号避免操作符优先级问题。
#define DOUBLE(x,y) (x)*(y)
我们改成这样就不会有问题了。
#define的替换规则 在程序中扩展 #define 定义符号和宏时,需要涉及几个步骤。 1、 在调用宏时,首先对参数进行检查,看看是否包含任何由 #define 定义的符号。如果是,它们首先被替换。 2、 替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被他们的值所替换。 3、 最后,再次对结果文件进行扫描,看看它是否包含任何由 #define 定义的符号。如果是,就重复上述处理过程。 总结:先对参数检查,看是否包含define定义的符号,如果有就先将他们替换,然后将替换的值插入到文本中,最后再次对结果扫描看是否有define定义的符号,如果有就继续重复。
总结:先对参数检查,看是否包含define定义的符号,如果有就先将他们替换,然后将替换的值插入到文本中,最后再次对结果扫描看是否有define定义的符号,如果有就继续重复。
#和##
#的作用是把一个宏参数变成对应的字符串
这里有一个例子
这里我们可以发现不管printf里放入几个字符串,最后都会将他们转化成一个。好我们接下来看
printf("the value of b is %dn", b);
我可不可以让双引号里的b改变呢?
当然可以这就需要用到#
#define PRINT(n) printf("the value of "#n" is %dn", n)int main(){int a = 10;//printf("the value of a is %dn", a);PRINT(a);//printf("the value of ""a"" is %dn", a);int b = 20;//printf("the value of b is %dn", b);PRINT(b);//printf("the value of ""b"" is %dn", b);return 0;}
这里宏中的#b是将原来的字以字符串的形式替换,也就是比如b 就会以”b”替换到文本中。
这里只有当字符串作为宏参数的时候才可以把字符串放在字符串中##的作用是可以将两个符号合成一起
#define ADD(x,y) x##yint main(){printf("%dn", ADD(521,1314));return 0;}
带副作用的宏参数
#define MAX(x,y) ((x)>(y)?(x):(y))int main(){int x, y, z;x = 5;y = 8;z = MAX(x++, y++);printf("x=%d y=%d z=%dn", x, y, z);return 0;}
这个答案会是什么呢?
这里要注意的是在使用宏的时候它不是将x和y算好之后在替换,而是将x,y替换成x++,y++
替换结果变为:
z = ((x++) > (y++) ? (x++) : (y++));
所以是这样算的x=5,y=8,x和y进行比较之后x++,x=6,y++,y=9,返回给z,z=9,然后y又要++,所以y=10.
宏与函数对比
我们拿两个数比较举例子
函数写法:
int get_max(int x, int y){return x > y ? x : y;}int main(){int a = 10;int b = 20;int ret = get_max(a, b);return 0;}
宏的写法:
#define MAX(x,y) ((x)>(y)?(x):(y))int main(){int a = 10;int b = 20;printf("max = %dn", MAX(a, b));return 0;}
我们从时间上考虑,先说宏,宏经过一步逻辑运算就可以得到结果,而函数呢,它需要经过函数调用,逻辑运算和返回值 这三步,所以宏比函数更有优势,但是只是这样简单的逻辑运算,宏更占优势,但是如果碰见复杂的运算,函数比宏更有优势。
我们在从类型上考虑,比如我们要比较浮点数和一个整形的大小,这时使用函数,由于函数只能返回一个值要么是整形要么是浮点数,所以在比较不同类型数值大小的时候我们就不能用函数,而宏既可以返回任意类型。
宏的缺点:
宏也有不好的地方,当一个宏代码长度很长的时候,他要替换成多处,这样就会导致代码长度增长。
宏是无法调试的
宏与类型无关,所以是不严谨的
宏还会出现操作符优先级问题,容易导致出错
宏是不可以递归的。
#undef 命令行定义 许多 C 的编译器提供了一种能力,允许在命令行中定义符号。用于启动编译过程。 例如:当我们根据同一个源文件要编译出不同的一个程序的不同版本的时候,这个特性有点用处。(假 定某个程序中声明了一个某个长度的数组,如果机器内存有限,我们需要一个很小的数组,但是另外一 个机器内存大写,我们需要一个数组能够大写。)
条件编译 在编译一个程序的时候我们如果要将一条语句(一组语句)编译或者放弃是很方便的。因为我们有条件 编译指令。 调试性的代码,删除可惜,保留又碍事,所以我们可以选择性的编译。
1、 #if 常量表达式 //..、 #endif // 常量表达式由预处理器求值。 如: #define __DEBUG__ 1 #if __DEBUG__ //.、 #endif 2. 多个分支的条件编译 #if 常量表达式 //..、 #elif 常量表达式 //..、 #else //..、 #endif 3. 判断是否被定义 #if defined(symbol) #ifdef symbol #if !defined(symbol) #ifndef symbol
文件包含
命令行定义 许多 C 的编译器提供了一种能力,允许在命令行中定义符号。用于启动编译过程。 例如:当我们根据同一个源文件要编译出不同的一个程序的不同版本的时候,这个特性有点用处。(假 定某个程序中声明了一个某个长度的数组,如果机器内存有限,我们需要一个很小的数组,但是另外一 个机器内存大写,我们需要一个数组能够大写。)
条件编译 在编译一个程序的时候我们如果要将一条语句(一组语句)编译或者放弃是很方便的。因为我们有条件 编译指令。 调试性的代码,删除可惜,保留又碍事,所以我们可以选择性的编译。
1、 #if 常量表达式 //..、 #endif // 常量表达式由预处理器求值。 如: #define __DEBUG__ 1 #if __DEBUG__ //.、 #endif 2. 多个分支的条件编译 #if 常量表达式 //..、 #elif 常量表达式 //..、 #else //..、 #endif 3. 判断是否被定义 #if defined(symbol) #ifdef symbol #if !defined(symbol) #ifndef symbol
文件包含
调试性的代码,删除可惜,保留又碍事,所以我们可以选择性的编译。
1、 #if 常量表达式 //..、 #endif // 常量表达式由预处理器求值。 如: #define __DEBUG__ 1 #if __DEBUG__ //.、 #endif 2. 多个分支的条件编译 #if 常量表达式 //..、 #elif 常量表达式 //..、 #else //..、 #endif 3. 判断是否被定义 #if defined(symbol) #ifdef symbol #if !defined(symbol) #ifndef symbol
文件包含
终于到了最后一个,这个文件包含在高质量c/c++编程书中就出过两个关于头文件包含的问题,所以这块非常重要。
我们在进行头文件包含的时候,主要使用#include命令,这里有两种方式,一种是#include后面用双引号,一种是#include后面用<>.那两种是否有区别呢?
先说#include" "
查找策略:先在源文件所在目录下查找,如果该头文件未找到,编译器就像查找库函数头文件一样在标准位置查找头文件。 如果找不到就提示编译错误。再说#include<>
查找头文件直接去标准路径下去查找,如果找不到就提示编译错误。那标准库文件也可以用include"",只不过他要查找两次更浪费时间,但是这样做查找的效率就低些,当然这样也不容易区分是库文件还是本地文件了。
总结 #include""是先在本源文内目录寻找,然后在去标准库路径寻找,而#include<>是直接去标准库路径去寻找。
头文件重复包含问题 在一个工程中如果出现头文件重复包含,如果有几十万行代码的话,再加上头文件重复包含就会导致大量浪费时间,就会导致编译工程时间要很长时间。
头文件重复包含解决方案
在一个工程中如果出现头文件重复包含,如果有几十万行代码的话,再加上头文件重复包含就会导致大量浪费时间,就会导致编译工程时间要很长时间。
第一种是使用#pragma once
另外一种是可以在头文件最前面加上这个。
#ifndef __TEST_H__#define __TEST_H__//头文件的内容#endif //__TEST_H__
高质量c/c++编程这本书出过这么两道题。