C复习-预处理器:define+条件编译+文件包含

参考: 里科《C和指针》


预定义符号

__FILE__ // 进行编译的源文件名
__LINE__ // 文件当前行的行号
__DATE__ // 文件被编译的日期 Jan 31 1997
__TIME__ // 文件被编译的时间

#define宏

写法:

1)宏的名字全大写,以区分宏与函数。

2)如果一个现存的名字需要重新定义,首先需要使用#undef name移除旧定义。

3)在那些对表达式进行求值的宏中,每个宏参数出现的地方都应该加上括号,并且在整个宏定义的两边也加上括号(避免因运算符优先级不同造成歧义)

使用宏而非函数的情况

1)宏的规模和速度更快(函数存在调用、返回的开销)

2)宏与类型无关,函数的参数必须声明为一种特定类型

3)某些无法使用函数的情况

宏的缺点是每次使用时,必须将一份宏定义代码拷贝到程序中,因此除非宏非常短,不然可能大幅增加程序的长度

// 可以有多行,但是要加\
// 这个适合多次打印信息的情况,比如我需要知道某个变量在某些操作后的结果
// 因为define是直接替换的,一定不要多输;
#define DEBUG_PRINT printf("xxx" \
											xxxx \)

#define SQUARE(x) x * x
int main()
{
    int i = 2;
    // 输出25
    printf("%d\n", SQUARE(5));
    // 实际替换为 i + 1 * i + 1,所以是5
    // 可以将define语句改为:#define SQUARE(x) (x) * (x)
		printf("%d\n", SQUARE(i + 1 ));
    return 0;
}

#define DOUBLE(x) (x) + (x)
a = 5;
// 此时会先计算乘法,所以应该修改define为#define DOUBLE(x) ( (x) + (x) )
printf("%d\n", 10 * DOUBLE( a ));

//利用邻近字符串自动连接特性
#define PRINT(FORMAT, VALUE) \
        printf("The value is " FORMAT "\n", VALUE)
int i = 2;
PRINT("%d", i + 3);
// 如果希望将表达式转为字符串,用#
#define PRINT(FORMAT, VALUE) \
        printf("The value of " #VALUE \
        " is " FORMAT "\n", VALUE)
// The value of i + 3 is 5
PRINT("%d", i + 3);

// 如果需要传递一个标识符名字,比如想把一个值加到sum1、sum2、sum3...
// 使用##。但是需要保证该名字的变量是有的
#define ADD_TO_SUM( NUM, VAL ) \
        sum ## NUM += VAL
int sum1 = 0;
ADD_TO_SUM(1, 25);
printf("%d", sum1); // 25

// 无法使用函数的情况下,使用宏
#define MALLOC(n, type) \
        ( (type *)malloc( (n) * sizeof( type ) ) )
int* pi = MALLOC(25, int);

带副作用的宏参数

副作用指表达式求值时会出现永久后果。比如 x + 1不会改变表达式里x的值,但是x++就会。getchar()也有副作用,因为它会消耗输入的一个字符,后续调用会得到不同的字符。

#define MAX( a, b ) ( (a) > (b) ? (a) : (b) )
...
x = 5;
y = 8;
z = MAX( x++, y++);
// 此时x = 6, y = 10, z = 9
// 因为带入后z = ( (x++) > (y++) ? (x++) : (y++) )
// 所以y增加了两次,z取的是y增加一次时的值

命令行定义

许多C编译器允许在命令行中定义符号,比如跨平台运行时,某个数组的大小可以根据平台能力修改。下面的ARRAY_SIZE可以在命令行中指定。

int array[ARRAY_SIZE];

比如unix中,编译命令可以是:(格式:-Dname=value)

cc -DARRAY_SIZE=100 prog.c

但是在程序中只能是用参数的地方才可以用,比如循环内部需要使用字面值常量访问数组时,就不能用。如果要去除这个定义,使用-Uname。

条件编译

例如,调试代码可以写成下面这样,如果要debug,用#define DEBUG 1即可,如果要忽略这段代码,定义为0。

#if DEBUG
	printf("x=%d", x);
#endif

elif可以有很多个

#if constant-expression
...
#elif constant-exp
...
#else
..
#endif

是否被定义可以使用#ifdef或者#ifndef,但是用#if更好些,因为可以扩展条件

// symbol是否被定义?
#ifdef symbol
#ifndef symbol

// 等价
#if defined(symbol)
#if !defined(symbol)

// 但是if可以判断多个条件
#if X > 0 || defined( ABC ) && defined( BCD )

#if和#ifdef这些可以嵌套,如果某个部分非常长,最好在#endif后加一些注释说明每个选择的含义

#if defined( OS_UNIX )
		#ifdef OPTION1
					 unix_version_of_option1();
		#endif /* sth about OPTION1 */
		#ifdef OPTION2
					 unix_version_of_option2();
		#endif /* sth about OPTION2 */
#elif defined( OS_MSDOS )
		#ifdef OPTION2
					 msdos_version_of_option2();
		#endif /* sth */
#endif

文件包含

#include

包含库头文件用<>,本地头文件用“”。也可以使用绝对路径

#include 
#include "errno.h"

标准要求编译器必须支持至少8层的头文件嵌套,但没有规定最大值,但实际操作中,一般会避免#include指令的嵌套深度超过2层。嵌套可能有多次包含同一个文件,为了消除这个可以使用条件编译,这样第二次被包含时其内容会被忽略,但是读取文件仍然会拖慢编译速度,所以还是应该避免多次包含。

#ifndef _HEADERNAME_H
#define _HEADERNAME_H
/* All the stuff that you want in header file */
#endif

其他指令

#error

产生错误信息

#elif ...
...
#else
	#error No option selected!

#line

很少用,最常用在将其他语言的代码转成C代码时。C编译器产生的错误信息可以引用源文件而非翻译程序产生的C中间源文件的文件名和行号。

它通知预处理器number是下一行输入的行号。如果给出了“string”,预处理器就把string当作当前文件的名字。因此会修改__LINE__和__FILE__的值。

#line number "string"

#progma
因编译器而异,不能移植,允许编译器提供不标准的处理过程,比如向一个函数插入内联的汇编代码。

无效指令null directive

就是一个#开头的空行,跟空行一样起到一个分隔作用,但是预处理器会直接删除

你可能感兴趣的:(C和指针,c语言)