1、预定义符号
C语言设置了一些预定义符号,可以在预处理阶段直接使用:
1 __FILE__ //进行编译的源文件
2 __LINE__ //文件当前的行号
3 __DATE__ //文件被编译的日期
4 __TIME__ //文件被编译的时间
5 __STDC__ //如果编译器遵循ANSI C,其值为1,否则未定义
比如:
2、#define定义常量
这个比较简单,和我们的用法差不多,
#define MAX 100 #define REG register
当我们定义的语句比较长,可以分成多行来写,比如:
#define DEBUG_PRINT printf("file:%s\tline:%d\t \ date:%s\ttime:%s\n" ,\ __FILE__,__LINE__ , \ __DATE__,__TIME__ )
在每行的末尾处加上反斜杠 \ ,也叫续行符,最后一行不用加
在定义#define时,最后不用加 ; 容易出现语法错误,
3、#define定义宏
#define机制包括了一个规定,允许把参数替换到文本中,这种实现通常成为宏或者定义宏
宏的声明方式:
#define name( parament-list ) stuff
其中parament-list是一个由逗号隔开的符号表,他们可能出现在stuff中,
注意:
参数列表的左括号必须与name紧邻,如果两者之间有任何空⽩存在,参数列表就会被解释为stuff的⼀部分。
例如:
注意,#define只是完成了替换,这时候又一个问题出现了,运算符优先级问题.比如:
为了避免这种问题,我们要对每个x加上 (),这样就解决问题了,可能比较麻烦
#define SQUARE(x) ((x)*(x))
这样写是最安全的,可以避免由于运算符优先级出现的问题
4、带有副作用的宏参数
当宏参数在宏的定义中出现超过一次的时候的时候,如果宏参数带有副作用,那么在使用这个宏的时候就会出现一些不好的后果,副作⽤就是表达式求值的时候出现的永久性效果。
x+1 //没有副作用 x++ //有副作用
比如:
这是不带副作用的情况下,a和b的值都不变
这时候我们要注意,尽量避免这样写
5、宏的替换规则
1.在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号,如果是,他们先替换
2.替换文本被插入到程序中原来的位置中,这些都是在预处理阶段就完成的
3.最后,再次对文件进行扫描,看看是否还有包含#define定义的符号,如果是,就重复上述步骤;
注意:
1.宏参数和宏定义中可以出现其他宏定义的符号,但是不能出现递归;
2.当预处理器搜索#define定义的符号时,字符串常量的内容并不被搜索;
比如:
字符串中的M并不被搜索
6、宏和函数的对比
了解一下7、
7、#和##
#运算符将宏的一个参数转换成字符串字面量,他仅允许出现在带参数的替换列表中
#运算符执行的操作可以理解为“字符串化”
比如:
##运算符可以把位于他两边的符号合成一个符号,##被称为记号粘合,对于这个东西用的少,能看懂被人用的什么意思就好
比如:
看看就好,用的不多
8、命名约定
平常的习惯是宏名全部大写
函数名不要全部大写
9、#undef
这条指令用于移除一个宏定义
#undef NAME //如果现存的⼀个名字需要被重新定义,那么它的旧名字⾸先要被移除。
比如:
我们要修改M的值,要先移除这个宏,然后再重新定义;
10、条件编译
在编译一个程序的时候我们如果要将一条语句编译或者放弃的时候,可以用条件编译指令
比如:
常见的条件编译指令:
1.单分支的条件编译
#if 常量表达式 //如果表达式语句为真,则执行#if里面的语句,否则不执行 //语句 #endif
2.多分支的条件编译:
#if 常量表达式 //语句 #elif 常量表达式 //语句 #elif 常量表达式 //语句 #else //语句 #endif
比如:
只有第一个表达式为真,所以只执行第一条语句,如果有多个表达式都为真, 则只执行最先为真的那一个表达式
3.判断是否被定义
#ifdef 宏常量 //语句 #endif
他还·等价于
#if defined(宏常量) //语句 #ednif
如果宏常量被定义,那么就执行条件里面的语句,否则不执行,比如:
红框框里的两条语句时等价的;
如果__DEBUG__被定义,则执行语句,否则不执行
4.嵌套指令
#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
11、头文件的包含方式
本地文件包含:
#include"filename.h"
先在源文件所在目录下查找,如果找不到,就在标准库头文件里面查找,如果还找不到,就提示编译错误;
库文件包含:
#include<stdio.h>
查找库函数头文件直接在标准库里面去查找,如果找不到就提示编译错误
如果是这样,那么标准库头文件也可以用“ ” 来包含?
是这样的,但是这样查找效率低下,而且也不容易区分是库文件还是本地文件
12、嵌套文件包含
我们已经知道#include指令可以使另一个文件被编译,就是在#include这行指令处展开被编译的文件,但是如果我们重复包含,对编译的压力就会比较大,运行起来比较慢
如何解决头文件被重复包含的问题?
条件编译
在每个头文件的开头写:
#ifndef __TEST_H__ #define __TEST_H__ //语句 #endif
意思是:
如果 __TEST_H__ 没有被定义,那么 #define __TEST_H__ 定义,然后执行头文件的内容,
如果包含的有相同的头文件,先判断 #ifndef __TEST_H__ 发现 __TEST_H__ 被定义了,那么不再执行头文件中的内容;
另外还有一种更加方便的写法:
#pragma once
作用和上面是一样的,都是防止头文件被重复包含。