C语言——预处理和宏定义

预处理和宏定义

今天分享一点C语言的预处理还有宏相关的内容。

目录

预处理和宏定义

一、一个C程序的诞生

1、预编译阶段(.c-->.i)

2、编译阶段(.i-->.s)

3、汇编阶段(.s-->.o)

4、链接阶段(该程序的所有.o-->.exe)

二、宏相关内容

1、C语言中部分内置宏

2、宏的功能

(1)、使用宏定义常量

(2)、使用宏重命名运算符

(3)、使用宏重新定义关键字的别名

(4)、宏还能定义一段代码片段

(5)、宏还可以作为“编译开关”

(6)、使用宏完成算数表达式

三、预处理指令

1、#if 和 #elif

2、#ifdef

3、#ifndef

4、#if defined

5、#undef


一、一个C程序的诞生

当我们写好一个一段C语言的代码并且按下编译并运行这个按钮时,它共经历了这样几个过程:

1、预编译阶段(.c-->.i)

该阶段主要进行这几个操作,宏替换、头文件的展开、预编译指令的处理等。

2、编译阶段(.i-->.s)

编译器将 .i 文件翻译成文本文件 .s ,每条语句都以标准的文本格式确切描述一条低级机器语言指令;也是在这一个阶段进行语法语义的分析检查。

3、汇编阶段(.s-->.o)

汇编器将 .s 文件翻译成机器语言指令。把这些指令打包成可重定位目标文件,即 .o 文件。这里 .o 是一个二进制文件,前面两个阶段都还有字符。

4、链接阶段(该程序的所有.o-->.exe)

将刚才生成的还有有头文件库中的 .o 文件以某种方式合并到一个 .o 文件中(这个文件是主函数所在的那个 .o 文件),在经过一系列处理(如:合并段表,符号表的合并和符号表的重定位等)生成一个 .exe 文件。

 

二、宏相关内容

1、C语言中部分内置宏

 

宏名 功能
_ _FILE_ _ 进行编译的源文件
_ _LINE_ _ 文件当前的行号
_ _DATE_ _ 文件被编译的日期
_ _TIME_ _ 文件被编译的时间
_ _STDC_ _  如果编译器遵循ANSI C,其值为1,否则未定义

注意,这里是两个下划线,中间没有空格。

	//这些基本都是语言内置的宏
	//预定义符号是预处理的一部分,所以显示的都是编译时候的信息而不是执行时候的信息
	printf("file:%s\n", __FILE__);//进行编译的源文件
	printf("line:%d\n", __LINE__);//当前编译的行号
	printf("date:%s\n", __DATE__);//文件被编译的日期
	printf("time:%s\n", __TIME__);//文件被编译的时间
	//printf("stdc:%d\n", __STDC__);//编译器是否遵循ANSI C,如果遵循其值为1,否则不遵循,显然VS不遵循

 

运行结果如下:

2、宏的功能

首先得知道宏在实际的使用中是一种简单的文本替换

例如:

#define  SIZE  10

int  a = SIZE;

在预处理阶段,将文件中所有出现 SIZE 的地方全部换成 10 ,于是int  a = SIZE; 被替换为int  a = 10; 

正是因为宏独特的特点,所以我们可以使用宏完成很多事情。

(1)、使用宏定义常量

// 1. 使用宏定义常量
#define SIZE 10

(2)、使用宏重命名运算符

// 2. 使用宏给运算符重命名
#define and &&
#define or ||

(3)、使用宏重新定义关键字的别名

// 3. 使用宏重新定义关键字的别名. 
#define uint unsigned int

(4)、宏还能定义一段代码片段

在某些场景,我们可能会频繁的使用一些代码片段,例如:

实际开发中经常有这种需求:

如果程序执行成功,就继续往下走如果程序执行失败, 就结束程序

int main(){
	 //实际开发中经常有这种需求: 
	 //如果程序执行成功, 就继续往下走
	 //如果程序执行失败, 就结束程序
	int ret = 0;
	ret = login();
	if (ret == 0) {
		printf("执行失败\n"); 
		return 1; 
	}
	ret = enterRoom();
	if (ret == 0) {
		printf("执行失败\n");
		return 1;
	}
	ret = startMatch();
	if (ret == 0) {
		printf("执行失败\n");
		return 1;
	}
	ret = acceptGame();
	if (ret == 0) {
		printf("执行失败\n");
		return 1;
	}
	return 0;
}

我们发现这个代码中多次出现了

    if (ret == 0) {
        printf("执行失败\n");
        return 1;
    }

为了使代码更为简洁,我们可以使用宏定义:

// 5. 宏还能定义一段代码片段
#define CHECK(ret) if (ret == 0) { \
	printf("执行失败\n"); \
	return 1; \
}

int main(){
	 //实际开发中经常有这种需求: 
	 //如果程序执行成功, 就继续往下走
	 //如果程序执行失败, 就结束程序
	int ret = 0;
	ret = login();
	CHECK(ret);
	ret = enterRoom();
	CHECK(ret);
	ret = startMatch();
	CHECK(ret);
	ret = acceptGame();
	CHECK(ret);
	return 0;
}

注意  \  在C语言中还有换行的意思。

这里和函数很像,但是这里千万不能写成函数因为函数执行完之后只是退出它本身,但是我们这里的需求是退出主函数。

(5)、宏还可以作为“编译开关”

某些宏可以根据条件让一些代码能编译或者不编译。

_CRT_SECURE_NO_WARNINGS

这个宏想必使用VS 编译器的同学们应该不陌生,没有这个宏的定义的时候, VS 就会多编译一些对于 scanf 等函数安全检查的逻辑;有这个宏定义, 相关的检查代码就不被编译了。

需要注意的是,这个宏的声明应该在 stdio.h 这个头文件的上面,因为这个头文件包含了 scanf 等函数的声明。

(6)、使用宏完成算数表达式

比如我们想要算平方,就可以直接用宏来定义:

#define SQUARE( x ) x * x

那这样的定义方法可行吗?

SQUARE( 5+1 )为多少?我们想要的结果是 6 的平方 36 ,但是宏在编译的过程中实际上是文本替换结果为 5+1*5+1,结果为11,所以我们需要给每一个可能出现问题的地方加上括号。

#define SQUARE( x ) ((x) * (x))

这样就万无一失了。

三、预处理指令

1、#if 和 #elif

代码大概长这样:

#if   整型常量表达式1
程序段1
#elif   整型常量表达式2
程序段2
#elif   整型常量表达式3
程序段3
#else
程序段4
#endif

这段代码的意思是:“表达式1”的值为真(非0),就对“程序段1”进行编译,否则就计算“表达式2”,结果为真的话就对“程序段2”进行编译,为假的话就继续往下匹配,直到遇到值为真的表达式,或者遇到 #else。这一点和 if else 非常类似。

2、#ifdef

代码大概长这样

#ifdef  宏名
程序段1
#else
程序段2
#endif

它的意思是,如果当前的宏已被定义过,则对“程序段1”进行编译,否则对“程序段2”进行编译。

3、#ifndef

代码大概长这样

#ifndef 宏名
程序段1
#else
程序段2
#endif

与 #ifdef 相比,仅仅是将 #ifdef 改为了 #ifndef。它的意思是,如果当前的宏未被定义,则对“程序段1”进行编译,否则对“程序段2”进行编译,这与 #ifdef 的功能正好相反。

需要注意的是,#if 后面跟的是“整型常量表达式”,而 #ifdef 和 #ifndef 后面跟的只能是一个宏名,不能是其他的。

4、#if defined

代码大概长这样

#if defined 宏名
代码段
#endif

判断该宏有没有被定义,如果有则执行代码段,否则跳过,可以在改变宏的值之前进行判断。

5、#undef

#undef 宏名

这个指令用于移除宏定义,当一个现存的宏需要被重新定义的时候,先移除它。

这个指令经常和上面的#if defined一起使用,我们可以看下面的例子:

#define B 20
#if defined B //判断宏B是否被定义
#undef B //若被定义,则撤销定义
#endif 
#define B 200 //改变宏的值

 

今天就先分享到这里,希望大家多多评论。

你可能感兴趣的:(C语言,编译器,c语言,c++,面试,其他)