【编程与算法基础】C语言——预处理指令详解

1. 预定义符号

预定义符号都是语言内置的。
下面是一些常用的:

#include <iostream>
#include <stdio.h>

int main()
{
	printf("%s\n",__FILE__);		//进行编译的源文件路径,数据类型为字符串
	printf("%d\n", __LINE__);		//文件当前的行号,数据类型为整型
	printf("%s\n",__DATE__);		//文件被编译的日期,数据类型为字符串
	printf("%s\n",__TIME__);		//文件被编译的时间,数据类型为字符串
	printf("%s\n",__FUNCTION__);	//代码所在的函数
}

执行结果:
在这里插入图片描述

2. 宏定义预处理指令

2.1 #define

2.1.1 #define预定义标识符

语法规则:#define 标识符名 内容(标识符名通常为大写)
内容可以是值、关键字、甚至是一段代码:

#define MAX 1000
#define reg register			//为 register这个关键字,创建一个简短的名字
#define do_forever for(;;)		//用更形象的符号来替换一种实现
#define CASE break;case			//在写case语句的时候自动把 break写上。

如果定义的内容过长,可以分成几行写,除了最后一行外,每行的后面都加一个’'(续行符)。

#define DEBUG_PRINT printf("file:%s\tline:%d\t		\
							date:%s\ttime:%s\n" ,	\
							__FILE__,__LINE__ ,		\
							__DATE__,__TIME__ )

2.1.1 #define预定义宏

#define 机制包括了一个规定,允许把参数替换到文本中(只是替换,不能传参),这种实现通常称为宏(macro)或定义宏(define macro)。
语法规则:#define 宏名(参数) 内容(宏名通常为大写)

#define SQUARE( x ) x * x

int a = SQUARE(5);	//等价于int a = 5*5;

预定义宏只是文本的替换,不是像函数一样可以传参:

#define SQUARE( x ) x * x
int b = 5;
int a = SQUARE(b+1);	//等价于int a = 5+1*5+1 = 11;(不是int a = 36;)

#define SQUARE( x ) (x) * (x)
int b = 5;
int a = SQUARE(b+1);	//等价于int a = (5+1)*(5+1) =36;
#define DOUBLE(x) (x) + (x)
int a = 5;
int b = 10 * DOUBLE(a);		//等价于int b = 10 * (a) + (a) = 55;

#define DOUBLE(x) ((x) + (x))
int a = 5;
int b = 10 * DOUBLE(a);		//等价于int b = 10 * ((a) + (a)) = 100;

用于对数值表达式进行求值的宏定义都应该用这种方式加上括号,避免在使用宏时由于参数中的操作符或邻近操作符之间不可预料的相互作用。

宏通常被应用于执行简单的运算。比如在两个数中找出较大的一个。
简单运算中宏相对函数的优点:

  • 用于调用函数和从函数返回的代码可能比实际执行这个小型计算工作所需要的时间更多。所以宏比函数在程序的规模和速度方面更胜一筹。
  • 更为重要的是函数的参数必须声明为特定的类型。所以函数只能在类型合适的表达式上使用。反之这个宏怎可以适用于整形、长整型、浮点型等可以用于>来比较的类型。宏是类型无关的。

简单运算中宏相对函数的缺点:

  • 每次使用宏的时候,一份宏定义的代码将插入到程序中。除非宏比较短,否则可能大幅度增加程序 的长度。
  • 宏是不能调试的。
  • 宏由于类型无关,因此不够严谨。
  • 宏可能会带来运算符优先级的问题,导致程容易出现错。

一般来讲函数和宏的使用语法很相似。所以语言本身没法帮我们区分二者。
因此平时习惯将宏名全大写,函数名不是全大写,单词首字母大写。

2.1.3 #define替换规则

  • 在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的标识符。如果是,它们首先被替换。
  • 替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被他们的值替换。
  • 最后,再次对结果文件进行扫描,看看它是否包含任何由#define定义的符号。如果是,就重复上述处理过程。

注意:

  1. 宏参数和#define 定义中可以出现其他#define定义的变量。但是对于宏,不能出现递归。
  2. 当文本替换#define定义的标识符的时候,字符串常量的内容并不会被替换。

2.2 #与##

#和##的作用就是将参数插入到字符串中。
字符串是有自动连接特点:

	//三个都输出hello world
	printf("hello world\n");
	printf("hello " "world\n");
	printf("hel" "lo " "world\n");

那么可以实现如下代码:

#define PRINT(FORMAT, VALUE)\
printf("the value is "FORMAT"\n", VALUE)

int main()
{
	PRINT("%d", 10);	//printf("the value is ""%d"\n", 10)
	//the value is 10
}

还可以用#实现:

#define PRINT(VALUE)\
printf("the value is "#VALUE"\n")

int main()
{
	PRINT(10);	//printf("the value is "10"\n);
	//the value is 10
}

这里’#'的作用就是将10变成一个字符串。
也可以实现如下代码:

#define ADD_TO_SUM(num, value) \
sum##num += value

int main()
{
	int sum5=0;
	ADD_TO_SUM(5, 10); //sum5 += 10;
	printf("%d", sum5); 
}	

这里##可以把位于它两边的符号合成一个存在的标识符。

2.3 #undef

该指令用于移除一个#define定义的标识符或宏。
如果现存的一个#define定义的标识符或宏不再需要或者需要被重新定义,那么可以用该指令移除或移除后重定义。
在这里插入图片描述
这里在移除宏后仍使用,因此报错。

3. 条件编译预处理指令

程序中的一些代码删除可惜,保留又碍事,所以我们可以使用条件编译预处理指令选择性的编译。
条件编译预处理指令类似C程序中的条件分支语句if-else

3.1 #if

语法规则:#if 常量表达式 ··· #endif
如果常量表达式为1,就编译#if-#endif间的代码段,否则相当于没有#if-#endif间这段代码。

#include <iostream>
#include <stdio.h>
#define __DEBUG__ 1

int main()
{
	int i = 0;
	int arr[10] = { 0 };
	for (i = 0; i < 10; i++)
	{
		arr[i] = i;
#if __DEBUG__
		printf("%d\n", arr[i]);//为了观察数组是否赋值成功。
#endif //__DEBUG__
	}
	return 0;
}

这里__DEBUG__为1,那么就编译#if-#endif间的代码段。
这个预编译指令与C程序中的if-else相似,也可以多分支和嵌套使用。

3.2 #if defined()&&#ifdef

语法规则:#if defined(标识符) ··· #endif
如果标识符被定义过,就编译#if defined(标识符)-#endif间的代码段,否则相当于没有#if defined(标识符)-#endif间这段代码。
与此相对应的是#if !defined(标识符) ··· #endif,与#if defined(标识符) ··· #endif,其功能正好相反。

#include <iostream>
#include <stdio.h>
#define __DEBUG__ 

int main()
{
	int i = 0;
	int arr[10] = { 0 };
	for (i = 0; i < 10; i++)
	{
		arr[i] = i;
#if defined(__DEBUG__)
		printf("%d\n", arr[i]);//为了观察数组是否赋值成功。
#endif //__DEBUG__
	}
	return 0;
}
#include <iostream>
#include <stdio.h>

int main()
{
	int i = 0;
	int arr[10] = { 0 };
	for (i = 0; i < 10; i++)
	{
		arr[i] = i;
#if !defined __DEBUG__
		printf("%d\n", arr[i]);//为了观察数组是否赋值成功。
#endif //__DEBUG__
	}
	return 0;
}

这里__DEBUG__被定义过,那么就编译代码段。
这里也可以使用#ifdef实现相同的功能,当然也有与此相对应的ifndef,其功能与#ifdef是相反的。
语法规则:#ifdef 标识符 ··· #endif

#include <iostream>
#include <stdio.h>
#define __DEBUG__ 

int main()
{
	int i = 0;
	int arr[10] = { 0 };
	for (i = 0; i < 10; i++)
	{
		arr[i] = i;
#ifdef __DEBUG__
		printf("%d\n", arr[i]);//为了观察数组是否赋值成功。
#endif //__DEBUG__
	}
	return 0;
}
#include <iostream>
#include <stdio.h>

int main()
{
	int i = 0;
	int arr[10] = { 0 };
	for (i = 0; i < 10; i++)
	{
		arr[i] = i;
#ifndef __DEBUG__
		printf("%d\n", arr[i]);//为了观察数组是否赋值成功。
#endif //__DEBUG__
	}
	return 0;
}

同样的,这个预编译指令与C程序中的if-else相似,也可以多分支和嵌套使用。

4. 文件包含预处理指令

4.1 头文件被包含的方式

  • 本地文件包含: #include “文件名”。
    查找策略:先在源文件所在目录下查找,如果该头文件未找到,编译器就像查找库函数头文件一样在标准位置查找头文件。
  • 库文件包含:#include <文件名>。
    查找策略:查找头文件直接去标准路径下去查找,如果找不到就提示编译错误。

当然用本地文件包含也可以查找库文件中的文件,只是本地文件包含会优先查找源文件所在目录,然后再查找库文件中的文件。这样效率会低一些

4.2 条件编译头文件

在程序编译时,会先预编译,预编译会将#define宏定义的内容替换和包含的头文件替换,当一个文件中包含多个相同的头文件,那么头文件就会被替换多次,相当于多份头文件中的内容出现在文件中。这样会影响编译的效率。
因此使用条件编译的方式解决,即在每个头文件首部写这样代码段:

#ifndef __TEST_H__
#define __TEST_H__
//头文件的内容
#endif //__TEST_H__

或者如下代码段:

#pragma once

这样就可以避免头文件的重复引入。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值