预处理详解

目录

一、预定义符号

 二、#define

1.认识#define

2.使用#define

2.1#define定义常量

2.2#define定义宏

 2.3#define的替换规则

三、宏定义的其他内容

1.#和##

1.1#

1.2##

 2.宏的副作用

3.宏的命名规则

4.undef

 5.条件编译


一、预定义符号

预处理详解_第1张图片

#include
int main()
{
	printf("%s %s", __DATE__,__TIME__);
	//将程序编译的日期和时间打印出来
}

预处理详解_第2张图片

 二、#define

1.认识#define

#define是c语言中的一个预处理命令,值得一提的是,预处理命令在编译代码的时候是通过替换的方式将代码编译过去的,但由于笔者用的是vs2019编译器,并不可以展示这一过程,但我们可以通过代码的计算来证明这一点。

2.使用#define

2.1#define定义常量

#include
#define x 99
int main()
{
	int a = x;
	int arr[x] = { 0 };
	//在c语言中对数组长度的定义只能用常量
	//可知#define可以定义常量
	printf("%d\n", x);
	int i = 0;
	for (i = 0; i < x; i++)
	{
		arr[i] = i;
		//简单的赋值
	}
	for (i = 0; i < x; i++)
	{
		printf("%d ",arr[i]);
		//打印内容
	}
}

预处理详解_第3张图片

2.2#define定义宏

#define 机制包括了一个规定,允许把参数替换到文本中,这种实现通常称为宏(macro)或定义
宏(define macro)

下面是宏的申明方式:
#define name( parament-list ) stuff
其中的 parament-list 是一个由逗号隔开的符号表,它们可能出现在stuff中

值得注意的一点就是参数列表的左括号必须要与name紧紧挨着,如果它们中间有空格,那么这个符号表就会被看作stuff的部分

#include
#define abc(x) x*x
//定义一个宏,宏的名字为abc,参数为x,执行的操作是x*x
int main()
{
	int a = 3;
	printf("%d",abc(a));
}

预处理详解_第4张图片
就这样,我们成功地定义了一个宏并使用了宏,但我们应该注意的一点就是,在使用宏的时候,其实是在替换,编译器不会那么智能,它只是做替换操作,比方说这串代码

#include
#define abc(x) x*x
//定义一个宏,宏的名字为abc,参数为x,执行的操作是x*x
int main()
{
	printf("%d",abc(3+1));
}

你大可以猜一猜这个会打印出什么东西,初学者肯定会脱口而出,4*4嘛,16,很合理,但编译器不会这样去算,编译器是先将内容替换为3+1*3+1,再计算,打印出来的显然是7

预处理详解_第5张图片
 要满足我们所需要的操作,也很简单,多加几个括号就行

#define abc(x) ((x)*(x))

 这样它把3+1替换过去就是((3+1)*(3+1)),百分之百是16

 2.3#define的替换规则

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

三、宏定义的其他内容

1.#和##

#include
int main()
{
	printf("abcdef\n");
	printf("abc""def""\n");
}

根据这一串代码我们可以知道,字符串是有自动连接特点的,说完了这一点,接下来就来教学一下如何使宏中的参数也作为字符串打印。

1.1#

#在宏中可以使参数转化为字符串的形式,而根据字符串自动连接的特点,我们就可以实现一些很方便的操作。要注意的一点则是,只有宏参数才能够被#转化为字符串

先不讲真正的例子,先简单的使用一下

#include
#define test(x,y) printf("%d"#y"acb",x)
int main()
{
	test(5, 123);
}

预处理详解_第6张图片 

我们可以看到传递过去的123被当作字符串打印出来了,接下来笔者举一个能真正使用这项功能的例子。

#include
#define print1(x,y) printf("这项装备名叫"#x"价值"#y"\n")
int main()
{
	print1(小明, 100);
	print1(小黄, 200);
	print1(小红, 300);
}

预处理详解_第7张图片 

通过这串代码我们就可以轻松的实现打印不同装备名字和价值的操作,不需要写那么多次printf,写得又臭又长还不好看。

1.2##

##的作用是将两个符号合成为一个符号,注意##只能够在宏中使用。直接举例

#include
#define abc(x,y) x##y=3
//##将x,y合成变成了xy因此这串代码的意思是给xy赋值为3
//当然x,y只是代号
int main()
{
	int xy = 0;
	int abc = 0;
	abc(x,y);
	abc(ab, c);
	printf("xy=%d abc=%d\n", xy, abc);
}

预处理详解_第8张图片

 2.宏的副作用

1. 每次使用宏的时候,一份宏定义的代码将插入到程序中。除非宏比较短,否则可能大幅度增加程序的长度。
2. 宏是没法调试的,因为在编译过程中,计算机已经把程序中带有宏的内容都根据宏的规则替换为了相应的代码,也就是说,你去调试的话你是看不到具体的变化的,因为这个变化体现在编译过程3. 宏由于类型无关,也就不够严谨。
4. 宏可能会带来运算符优先级的问题,导致程容易出现错。

3.宏的命名规则

在使用宏的时候我们按照规定将宏的所有字母都大写,而函数名则不会将所有的字母都大写。这是约定俗成的规定,也可以称为程序猿的默契。

4.undef

undef的作用则是用来移除一个已经定义好的宏

比方说这串代码,我将a这个宏移除,然后再使用它,编译器就会在编译的过程中报错,提醒你没有定义a,其实undef最主要的作用是给宏重新定义,因为得先移除才能够重新定义。

预处理详解_第9张图片

 5.条件编译

#if可以理解为满足条件之后才会触发后面的内容,而#endif则是结束这次条件编译,#ifdef的含义则是如果这个宏被定义过(还可以写作#if defined)则执行后面的操作。#ifndef的含义则是如果这个宏没被定义过(还可以写作if !defined)则执行后面的操作。

多个分支的条件编译也是可以实现的

#if 常量表达式
//...
#elif 常量表达式
//...
#else
//...
#endif

条件编译可以实现在不同的情况下进行不一样的编译,笔者猜测一款游戏要能够兼容不同的系统,它一定要根据不同的系统进行条件编译,毕竟环境已经不一样了。

条件编译最常用的用法就是避免头文件被多次包含

#ifndef __abc__
#define __abc__
#include
#endif
//这串代码的含义就是如果__abc__没被定义则定义__abc__,同时引用头文件stdio.h
//如果我们在别的文件中也这样写,便只可能引用一次stdio.h
//__abc__是笔者随便弄的一个名字

而如果你嫌麻烦,你可以用#pragma once这个也可以保证你的文件内容只被引用一次,但是这个写法偏现代,也就是它可能不兼容老版本的编译器。

好了,今天的分享到这里就结束了,感谢各位友友的来访,祝各位友友前程似锦O(∩_∩)O 

你可能感兴趣的:(c++,c语言,算法)