1.预定义符号
FILE //进行编译的源文件
LINE//文件当前的行号
DATE//文件被编译的日期
TIME//文件被编译的时间
STDC//如果编译器遵循ANSI C,其值为1,否则未定义
2.#define
#define允许把参数替换到文本中,这种实现通常被称为宏(macro)或定义宏(define macro)
#define name(parament-list)stuff 其中的parament-list是一种由逗号隔开的符号表,它们可能出现在stuff中
参数列表的左括号必须与name紧邻,如果两者之间有任何空白存在,参数列表就会被解释为stuff的一部分
所以对于数值表达式进行求值的宏定义都应该使用这种方式加上括号,避免在使用宏时由于参数中的操作符或领近操作符之间不可预料的相互作用
define替换规则
①.在调用宏时,首先对参数进行检查,看看是否包含由任何#define对应的符号,如果是,它们首先被替换
②.替换文本随后被插入到程序中原来文本的位置,对于宏,参数名被他们的值所替换
③.最后,再次对文件扫描,看看它们是否包含任何由#define定义跌符号,如果是,就重复上述处理过程
宏参数和#define定义中可以出现其他#define定义的符号,但是对于宏,不能出现递归
当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索
#define M 10
printf("hello M");
那么这里的M是不会被搜索的,也就不会被替换
c语言中字符串是具有自动链接属性的
printf("hello world\n");
printf("hello"" world\n");
事实上,这两个语句输出的都是hello world
#的作用
#可以把一个宏参数变成对应的字符串
我用这样的一个例子来深入理解一下:
int main()
{
int a=10;
printf("the value of a is %d\n", a);
float b=2.5;
printf("the value of b is %lf\n", b);
return 0;
}
当我想要打印这两个语句时,发现这两句代码有许多是重复的,不同的只有名字和打印格式。所以为了简便,我们很自然的想到将这个封装成一个函数,但遗憾的是,我们无法将要打印的格式进行传参,这时候就只能依靠#define定义宏来完成这件事了
所以借助宏,上面代码将变为:
#define PRINT(val,format) printf("the value of"#val" is "format"\n",val)
int main()
{
int a=10;
PRINT(a,"%d");
float b=2.5;
PRINT(b,"lf");
return 0;
}
由于#的作用,所以a就变成了“a”
注意:format传过去时本身就是一个字符串
在预编译阶段下,代码如下:
#define PRINT(VALUE,FORMAT) printf("the value of "#VALUE" is "FORMAT" \n ",VALUE);
#include<stdio.h>
int main() {
int a = 10;
PRINT(a, "%d");
float b = 2.5;
PRINT(b, "%lf");
return 0;
}
可变参数
#define SHOWLIST(..) printf(#__VALUE__)
prinf("fish,c,520");
将下面的参数转换为字符串打印
##的作用
##可以把位于它两边的符号合成一个符号,它允许宏定义从分离的文本片段创建标识符
我们通过下面的例子理解一下:
#define CAT(A,B) A##B
int main()
{
int Class107=100;
printf("%d\n",CAT(Class,107));
return 0;
}
输出结果:100
就是说##,将其两边的标识符合并为另一个标识符
注意:这样的连接必须产生一个合法的标识符。否则其结果就是未定义的
##几乎用不到
带副作用的宏参数
当宏参数在宏的定义中出现超过一次的时候,如果参数带有副作用,那么你在使用这个宏的时候就可能出现危险,导致不可预测的后果。副作用就是表达式求值的时候出现的永久性效果
例如:
x+1;//不带副作用
x++;//带有副作用
MAX宏可以证明具有副作用的参数所引起的问题
#define MAX(x, y) ( (x) > (y) ? (x) : (y) )
int main()
{
int a=3;
int b=4;
int m=MAX(++a,++b);
printf("m=%d a=%d b=%d\n",m,a,b);
return 0;
}
输出结果:6 4 6
这里就是如开头所说,带副作用的宏参数引起的问题
宏和函数对比
宏的优点
宏常用于执行简单运算
比如,要找出两个数中的较大数#define MAX(a, b) ((a)>(b)?(a):(b))
为什么不用函数呢?其因有二:
用于调用函数和从函数返回的代码可能比实际执行这个小型计算工作所需要的时间更多,所以宏比函数在程序的规模和速度方面更胜一筹
(函数调用时间的花费:①.函数调用前准备(传参,函数栈帧维护)
②.主要运算
③.函数返回:返回值的处理,函数栈帧的销毁)
使用宏要保证计算的过程足够简单,所用的时间足够短
假设计算很复杂。花费时间很长的话,那么函数的调用和返回所花费的时间相比于计算来说就不值一提了,这时候就是函数更好了
更为重要的是函数的参数必须声明为特定的类型,而宏是类型无关的
所以函数只能在类型合适的表达式上使用,反之这个宏怎可以适用于整形、长整型、浮点型等可以用于>来比较的类型
此外,宏有时候可以做函数做不到的事情。比如:宏的参数可以出现类型,但是函数做不到
#define MALLOC(num, type) (type *)malloc(num * sizeof(type))
//使用
MALLOC(10, int);//类型作为参数
//预处理器替换之后:
(int *)malloc(10 * sizeof(int));
宏的缺点
每次使用宏的时候,一份宏定义的代码将插入到程序中。除非宏比较短,否则可能大幅度增加程序的长度
宏是没法调试的
宏由于类型无关,也就不够严谨
宏可能会带来运算符优先级的问题,导致程容易出现错
宏和函数的对比(图)
这里说一下带有副作用的参数这一点:
test(2+3);//函数的参数是在**传参之前求值**
TEST(2+3);//宏的参数在**传参之前不求值,是整体替换过去**
命名约定
由于宏和函数的语法是很相似的,所以语言本身没法帮我们区分二者
所以我们从命名上来帮助区分,我们约定:
把宏名全部大写
函数名不要全部大写
c/c++引入了一个inline的关键字 内联函数:具有了函数的优点,也具有了宏的优点
inline int square(int x){
return x*x;
}
内联函数虽然节省了函数调用的时间消耗,但由于每一个函数出现的地方都需要进行替换,因此增加了代码编译的时间,另外,不是所有函数都能变成内联函数
#undef指令与条件行定义
#undef 这条指令用于移除一个宏定义
#undef NAME //如果现存的一个名字需要被重新定义,那么它的旧名字首先要被移除
命令行定义
许多C 的编译器提供了一种能力,允许在命令行中定义符号,用于启动编译过程
例如:当我们根据同一个源文件要编译出一个程序的不同版本的时候,这个特性有点用处。(假定某个程序中声明了一个某个长度的数组,如果机器内存有限,我们需要一个很小的数组,但是另外一个机器内存大些,我们需要一个数组能够大些,这时通过命令行定义,可以控制数组的大小)
提前不给,在命令行给
4.条件编译
所谓条件编译,即满足条件则参与编译,不满足条件则不参与编译
条件编译和条件选择语句的区别在于,条件编译的代码如果条件不成立,则会在预处理阶段就将条件不成立的代码直接删掉了
注意,条件编译语句都是写在函数内的
①.#if 常量表达式
#endif 用于结束条件编译
例如
#define A 1
#if A 1
printf("3");
#endif
int a=2;
#ifdef a 2
是错误的,因为a的创建是在程序的运行阶段,而条件编译是在编译阶段
②.多个分支的条件编译
#define A 6
#if A 1
printf("1");
#elif A 3
printf("3");
#else A 5
printf("5");
#endif
③.判断是否被定义
#if defined A
#if def A
#ifdefined 与#ifdef的区别在于#ifdef是#if defined的简化版,只是判断单个宏是否被定义时可用#ifdef,其它复杂条件都得用#if defined。
#if (defined A)&&(defined B)
④.嵌套指令
#if defined(OS_UNIX)
#ifdef OPTION1
unix_version_option1();
#endif
#ifdef OPTION2
unix_version_option2();
#endif
#elif defined(OS_MSDOS)
#ifdef OPTION2
msdos_version_option2();
#endif
#endif
意思为:如果是UNIX系统则走上面的代码,如果是MSDOS系统则走下面的代码
5.文件包含
#include指令可以使另外一个文件被编译,就像它实际出现于#include指令的地方一样
这种替换的方式很简单:
预处理器先删除这条指令,并用包含文件的内容替换
这样一个源文件被包含10次,那就实际被编译了10次
头文件被包含的方式
库文件包含
#include <filename.h>
查找方式:查找头文件直接去标准路径下去查找,如果找不到就提示编译错误
本地文件包含
#include “filename”
查找方式:先在源文件所在目录下查找,如果该头文件未找到,编译器就像查找库函数头文件一样在标准位置查找头文件。如果找不到就提示编译错误
对于库文件也可以使用""的使用包含
可以,但是这样做查找的效率就低一些,当然这样也不容易区分是库文件还是头文件
嵌套文件包含
使用条件编译解决嵌套文件包含
每个开头写
#ifndef _TEST_H_
#define _TEST_H_
//头文件的包含
#endif
或者#pragma once
就可以避免头文件的重复引入