__FILE__ //进行编译的源文件
__LINE__ //文件当前的行号
__DATE__ //文件被编译的日期
__TIME__ //文件被编译的时间
__STDC__ //如果编译器遵循ANSI C,其值为1,否则未定义
#define reg register //为 register这个关键字,创建一个简短的名字
#define do_forever for( ; ; ) //用更形象的符号来替换一种实现
#define CASE break;case //在写case语句的时候自动把 break写上。
// 如果定义的 stuff过长,可以分成几行写,除了最后一行外,每行的后面都加一个反斜杠(续行符)。
#define DEBUG_PRINT printf(“file:%s\tline:%d\t \
date:%s\ttime:%s\n” ,\
FILE,LINE , \
DATE,TIME )
注意:在预编译时#define之后的东西都会被直接替换,所以一般我们不会在最末尾加上 ; ,因为这样容易导致语法错误。
#define 机制包括了一个规定,允许把参数替换到文本中,这种实现通常被我们称为宏(定义宏)。
#define name( parament-list ) stuff
其中的parament-list
是一个由逗号隔开的符号表,它们可能出现在stuff中。
我们一般讲宏的名字全部写成大写,用于与函数名进行区分
注意:
参数列表的左括号必须与name紧邻。
如果两者之间有任何空白存在,参数列表就会被解释为stuff的一部分
#define SQUARE( x ) x * x
int main() {
printf("%d", SQUARE(5));//25
return 0;
}
#define SQUARE( x ) x * x
int main() {
int a = 4;
printf("%d", SQUARE(a + 1));//9
return 0;
}
上面两个代码结果不同,因为第二个代码传过去的是4 + 1
,而不是计算之后的结果5
,所以用于对数值表达式进行求值的宏定义都应该加上括号,避免在使用宏时由于参数中的操作符或邻近操作符之间不可预料的相互作用。
#define SQUARE( x ) ( ( x ) * ( x ) )
int main() {
int a = 4;
printf("%d", SQUARE(a + 1));//9
return 0;
}
属性 | #define定义宏 | 函数 |
---|---|---|
代码长度 | 每次使用时,宏代码都会被插入到程序中。除了非常小的宏之外,程序的长度会大幅度增加 | 函数代码只出现于一个地方;每次使用这个函数时,都调用那个地方的同一份代码 |
执行速度 | 更快 | 存在函数的代用和返回,需要额外开销,速度相对较慢一些 |
操作符优先级 | 宏参数的求值是在所有周围表达式上文环境里,除非加上括号,否则临近操作符的优先级会影响运算结果,所以一般在写宏时,我们会尽可能多的加括号 | 函数参数只在代用的时候求值一次,它的结果传递给函数。表达式的求值结果更容易预测 |
带有副作用的参数 | 参数可能被替换到宏体中的多个位置,所以如果参数能对自身操作,就会导致求值结果出现问题 | 函数参数只在传参的时候求值一次,结果明确更易控制 |
参数类型 | 宏的参数与类型无关,只要对参数的操作是合法就是可以 | 函数的参数是与类型无关的,如果参数的类型不同,就需要不同的函数,即使他们执行的任务是相同的 |
调试 | 宏是不方便调试的 | 函数可以进行逐句调试 |
递归 | 不能递归 | 可以递归 |
用于移除一个宏定义
#undef NAME
//如果现存的一个名字需要被重新定义,那么它的旧名字首先要被移除。
在编译一个程序的时候我们如果要将一条语句(一组语句)编译或者放弃是很方便的。因为我们有条件编译指令。
#if 常量表达式
//...
#endif
//常量表达式由预处理器求值。
如:
#define __DEBUG__ 1
#if __DEBUG__
//..
#endif
多个分支的条件编译
#if 常量表达式
//...
#elif 常量表达式
//...
#else
//...
#endif
判断是否被定义
#if defined(symbol)
#ifdef symbol
#if !defined(symbol)
#ifndef symbol
嵌套指令
#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
#include “filename”
查找策略:先在源文件所在目录下查找,如果该头文件未找到,编译器就像查找库函数头文件一样在标
准位置查找头文件。
如果找不到就提示编译错误。
#include
查找头文件直接去标准路径下去查找,如果找不到就提示编译错误。
库函数也可以用""包含来查找,但是这样会先从本地文件中查找,效率会有一定的降低,并且如果本地文件与库文件有重名的,也不能进行区分
comm.h和comm.c是公共模块。
test1.h和test1.c使用了公共模块。
test2.h和test2.c使用了公共模块。
test.h和test.c使用了test1模块和test2模块。
这样最终程序中就会出现两份comm.h的内容。这样就造成了文件内容的重复。
这时我们可以使用条件编译解决这一问题。
我们一般使用#pragma once
来避免头文件重复引入。
注:个人觉得#和##很鸡肋,所以没写。