超详细的预处理指令

目录

前言

1.预定义符号

2.预处理指令#define

        2.1#define定义标识符

        2.2#define定义宏

        2.3#define替换规则

        2.4#和##

        2.5带有副作用的宏参数

        2.6宏和函数的对比

        2.7#undef

3.命令行定义

4.条件编译

5.头文件的包含

        5.1头文件被包含的方式

        5.2头文件的嵌套包含



1.预定义符号

__FILE__        //进行编译的源文件
__LINE__        //文件当前的行号
__DATE__        //文件被编译的日期
__TIME__        //文件被编译的时间
__STDC__        //如果编译器遵循ANSI,其值为1,否则未定义

这些预定义符号都是语言内置的。

2.预处理指令#define

        2.1#define定义标识符

#define MAX 100         //定义MAX为100,其他东西也可以,代码,关键字也许
#define FOR_EVER  for(;;)     //死循环

#define在预处理阶段会被文本替换,不会进行检查;定义时后面不要加";"。

        2.2#define定义宏

#define机制有一个规定,可以把参数替换到文本中,这种实现被叫做宏(macro)或者定义宏(define macro)。

下面是宏的申明方式:

#define name(paraament-list)  stuff,其中的parament-list是一个由逗号隔开的符号表,他们可能出现在stuff中。注意:参数列表的左括号必须和name紧邻;如果两者之间有任何空白,参数列表会被解释为stuff的一部分。

#define MAX(a,b) (a)>(b)?(a):(b)

int main()
{
    int a=4;
    int b=6;
    int c=MAX(a,b);    //在预处理阶段会被替换为  int c=(a)>(b)?(a):(b);
    return 0;
}

在使用宏的时候,必须足够严谨,最好给参数加上括号,否则可能出现问题,不信你看

#define MAX(a)  a*a
#define KIANA(m)   (m)+(m)  
int main()
{
    int a=MAX(2+3); 
    int b=10*KIANA(10);
    return 0;
}

这里的计算结果是2+3*2+3=11,10*(10)+(10)=110;而不是25和200,因为"*"的优先性比“+”高,所以必须加括号,避免因为符号优先级导致出错。

        2.3#define替换规则

在程序中扩展#define定义符号和宏时,需要涉及几个步骤。
1.在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号。如果是,它们首先
被替换。
2.替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被他们的值所替换
3.最后,再次对结果文件进行扫描,看看它是否包含任何由#define定义的符号。如果是,就重复上
述处理过程。
注意:
1.宏参数和#define 定义中可以出现其他#define定义的符号。但是对于宏,不能出现递归
2.当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索

        2.4#和##

#可以把参数插入到字符串中,所以有什么用呢?我们先来看一段代码

int main()
{
	int a = 5;
	printf("the value of a is %d", a);
	int b = 10;
	printf("the value of b is %d", b);
	float c = 6.4f;
	printf("the value of c is %d", c);
	return 0;
}

分别打印这几个值,感觉特别麻烦,而且就算你用函数也没有办法,因为不知道怎么传参,这时候就可与用宏来解决,代码如下:

#define PRINT(val,format)   printf("the value of "#val" is "format"\n",val) 
int main()
{
	int a = 5;
	PRINT(a, "%d");
	float b=5.5f;
	PRINT(b, "%.2f");
	return 0;
}

在C语言里,被" "括起来的的字符串会被合成一个字符串,在这里,#val可以让val不被替换成它的值,而是替换名字,#a="a",后面只要改变参数就可以打印各种值了。

##可以把连个符号组成一个符号,但是该符号必须是有意义的,比如

#define CAT(a,b)  a##b

int main()
{
    int kiana=100;
    printf("%d",CAT(kia,na));
return 0;
}

        2.5带有副作用的宏参数

当宏参数在宏的定义中出现次数超过一次的时候,如果宏参数带有副作用,那么你在使用这个宏的时候就可能出现危险,导致不可预测的后果,副作用就是表达式求值的时候出现的永久性效果。

例如:

#define MAX(a,b)  ((a)>(b)?(a):(b))

int main()
{
    int a = 4;
    int b = 5;
    int c = MAX(++a, ++b);      //被替换成 int c=(++a)>(++b)?(++a):(++b);   
    printf("a=%d b=%d c= %d", a, b, c);
    return 0;
}

这里a和b出现了两次,导致++b在后面进行了两次运算,所以结果出错。

        2.6宏和函数的对比

宏通常被用于执行简单的运算,比如在两个数的较大值。

那为什么不用函数完成呢?有两个原因:

1.用于调用函数和从函数返回的带吗可能比实际执行这个味运算花的时间更多,所以宏比函数在程序的规模和速度方面更胜一筹。

超详细的预处理指令_第1张图片

 当然,这是在计算量很小的情况下,如果计算量很大,函数调用时间可以忽略不计。

2.函数的参数必须声明类型,但是宏可以适用与各种类型,宏和类型是无关的,宏的参数可以是类型。

#define MALLOC(a,type)  (type*)malloc(a*sizeof(type))

int main()
{
    int a = 4;
    int b = 5;
   int *p=MALLOC(10, int);
    return 0;
}

上面的宏的参数是类型,函数是无法这样实现功能的。

宏的缺点:和函数相比,宏也有不少缺点

1.宏在使用的时候会直接把代码替换进去,如果宏比较大,将会大大增加程序的长度。

2.宏是没办法通过调试的。

3.宏因为没有类型检查,所以不够严谨。

4.宏可能会带来运算符优先级的问题,导致程序出错。

5.宏不可以递归

平时为了方便区分宏和函数,一般把宏名称全部大写,函数不要全部大写。

        2.7#undef

取消标识符的定义


#include
#define M 100
int main()
{
	printf("%d", M);
#undef M
	printf("%d", M);      取消定义,此时M未定义
	return 0;
}

3.命令行定义

很多c的编译器提供了在命令行中定义符号的功能,用于启动编译过程。

比如当我们根据同一个源文件编译出一个程序的不同版本,这个功能有用处。当某个程序里面声明了某个长度的数组,如果机器内存有限,我们需要一个很小的数组,但是另外一个机器内存大,需要一个更大的数组。

#include
int main()
{
	int arr[SIZE];
	int i = 0;
	for (i = 0; i < SIZE; i++)
	{
		arr[i] = i;
	}
	for (i = 0; i < SIZE; i++)
	{
		printf("%d", arr[i]);
	}
	printf("\n");
	return 0;
}

在编译的时候,使用命令可以定义SIZE的值,从而实现数组大小的变化,不过命令行定义一般在Linux系统下使用。

4.条件编译

条件编译是指在预处理的时候,根据条件编译指令选择性的把一些代码送给编译器编译,一般有以下几种指令:

1.#if  常量表达式 

   #endif

#include 
int main()
{
#if 5>3;
	printf("条件为真,执行代码\n");  
#endif
#if 7>10 
	printf("出院\n");     7>10为假,不能执行下面的代码
#endif
	return 0;
}

2.  多个分支的条件编译

#include 
int main()
{
#if 1>3;
	printf("条件为真,执行代码\n");

#elif 17>10
	printf("出院\n");            //这一条执行
#else
	printf("haha\n");
#endif
	return 0;
}

      多个分支的条件编译只会执行一条代码,和if else语句一样的。

3.判断是否被定义

#include 
#define MAX
#define MIN
int main()
{
#ifdef MAX                   //如果定义了MAX,执行代码
	printf("执行代码\n");
#endif                       //如果没有定义MIN,执行代码
#ifndef MIN                   
	printf("执行代码\n");   
#endif // !MIN

	return 0;
}

只判断是否定义,与定义的值无关。

4.嵌套定义

#include 
#define MAX
#define OPTION2
int main()
{
#if defined(MAX)
	#ifdef OPTION1
	printf("haha\n");
	#endif
	#ifdef OPTION2
	printf("heihei\n");           //执行这条代码
	#endif
#elif defined(MIN)
	#ifdef OPTION
	printf("haihaihai\n");
	#endif
#endif

	return 0;
}

5.头文件的包含

使用#include 文件名,可以把文件包含进来,包含的方式是直接把文件里面的代码替换过来,包含几次就会替换几次。

        5.1头文件被包含的方式

1.本地文件的包含

#include"class.h"

查找路径:先在源文件所在目录下查找该头文件,如果没有找到,编译器就会想查找库函数那样到标准路径去查找,如果还找不到,就提示错误。

Linux下的查找路径

/usr/include

2.包含库文件

#include

1.直接去标准路径下查找,如果找不到,就会报错。当然库文件也可以用 #include"stdio.h",只不过这样会多查找一次,不显得高效,而且也不便于区分本地文件和库文件。

        5.2头文件的嵌套包含

在一个大工程里面,一个头文件可能会被包含多次,这样会造成程序冗余,我们可以采用以下方法避免这种情况:

第一种

#ifndef  __TEST__

#define __TEST__

.......

#endif

第二种

#pragma once

当第一次包含头文件的时候,会定义__TEST__,下一次就不会被包含了。

你可能感兴趣的:(c语言)