欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

C语言--预处理

程序员文章站 2024-02-22 15:34:04
...

在平时的学习中,当我们看到类似于“#define ...”的时候都会说,这是一个宏,与此相关的,还会说这个是预处理指令,那这些知识点之间有什么关系呢?今天,我们来一起捋一捋吧!

我们先来一起看几个特别常识性的问题!

1.预处理功能主要包含:宏定义、条件编译、文件包含三部分。分别对应宏定义命令、条件编译命令、文件包含命令三部分实现;

2.预处理过程读入源代码,检查包含预处理指令的语句和宏定义,并对源代码进行响应的转换,预处理过程还会删除程序中的注释和多于的空白字符;

3.在C语言中,包含“#”号的都是预处理;

4.预处理指令都不需要加“;”号。

下面,我们分别对三部分进行学习。

一、宏定义

(1)数值宏常量

#define PI 3.141592654

以后遇到PI的地方会自动改成3.141592654这个数字,而且#define宏定义的数据没有类型。

为了安全,在定义一些宏常量的时候用const代替,编译器会给const修饰的只读变量做类型检验,减少错误的可能。但一定要注意const修饰的不是常量而是readonly的变量,const修饰的只读变量不能用来作为定义数组的维数,也不能放在case关键字后面

(2)字符串宏常量

#define PATH_1 C:\program\x_line\WEB\program1

有的时候,代码里面包含路径的时候比较麻烦,这样定义一个宏就比较方便了,但需要注意的是,有的系统里面规定路径的要用双反斜杠“\\”,如下:

#define PATH_1 C:\\program\\x_line\\WEB\\program1

(3)用define宏定义表达式

例如:定义一个两个表达式的相乘

#define SUM(x) x*x

需要注意的是:宏函数被调用时是以实参代换形参,而不是“值传递”。

所以上面的这样的写法是会出现问题的,比如将x写成1+5,这时,就会变成1+5*1+5;很明显,这不是我们想要的结果,因此在这里写程序替换的时候一定要加括号,如下:

#define ADD(x) ((x)+(x))

(4)用define宏定义注释符号

#define ADD//
#define SUM/*
#define SQL*/

需要注意的是:注释都在预处理指令之前先处理,当这两行被展开成//...或/*...*/时,注释应处理完,当再次用宏定义处理时,就会出现问题,因此,用宏定义的方法开始或结束一段注释是不行的。

(5)#define中的空格

#define ADD (x) ((x)+(x))

上面这样的写法是不正确的,这样的写法,编译器会将空格以后的内容(即:(x) ((x)+(x)))看成一部分,而这个空格仅在定义的时候有效,在使用这个宏函数的时候,空格会被编译器忽略掉。应该这样写代码,如下:

#define ADD(x) ((x)+(x))

(6)#undef

#undef是用来撤销宏定义的

它的用法我们用一段代码来看看,如下:

#include<stdio.h>
#define X 3
#define Y X*2
#undef X
#define X 2
int main()
{
	int Z = Y;
	printf("Z=%d\n", Z);
	return 0;
}

运行结果:

C语言--预处理

二、条件编译

有三种条件编译的形式,如下:

#ifdef 标识符
coding 1
#else
coding 2
#endif

功能:若标识符已经被#define命令定义过,则对程序段1进行编译,否则对程序段2进行编译,其中,#else可以没有。

#ifndef 标识符
coding 1
#else
coding 2
#endif

功能:和第一种恰恰相反,若标识符没有被#define命令定义过,则对程序段1进行编译,否则对程序段2进行编译。

#if 常量表达式
coding 1
#else
coding 2
#endif

功能:若常量表达式的值为非0(真),则对程序段1进行编译,否则对程序段2进行编译

补充一点:#elif命令的意义与else if相同,它形成一个if else-if阶梯状语句,可进行多种编译选择。

三、文件包含

在程序中,文件包含有两种形式:

#include<...>
#include"..."

第一种形式(用尖括号将头文件括起来):在编译器自带的或外部库的头文件中搜索被包含的头文件;

第二种形式(用双引号将头文件括起来):程序在当前被编译的应用程序的源代码文件中搜索被包含的头文件,当找不到时,在和第一种情况一样,搜索编译器自带的头文件。

说明:采用这两种不同的包含格式的理由在于,编译器是安装在公共子目录下的,而被编译器的应用程序是在他们自己私有的子目录下的。一个应用程序既包含编译器提供的公共头文件,也包含自定义的私有文件。采用两种不同的包含格式使得编译器能够在很多头文件中区别出一组公共的头文件。

四、一些特殊的预处理指令

(1)#error预处理

作用:在程序编译时,只要遇到这条预编译指令,程序就会生成一个编译错误提示消息,并且停止编译

格式如下:

#error error-message

(2)#line预处理

作用:可以改变当前的行数和文件名

格式如下:

#line number["filename"]

这个预处理指令可以保证文件名是固定的,而不会被中间文件代替,有利于我们进行分析

(3)#pragma预处理

这个预处理的作用还是挺大的,这里我们就举几个重要的预处理指令来学习一下

一般格式:

#pragma para

1.pragma message

作用:在编译信息输出窗口中,输出相应的信息,这对于源代码信息的控制是非常重要的

2.pragma code_seg

格式:

#pragma code_seg(["section-name"[,"section-class"]]

作用:能够在设置程序中函数代码存放的代码段

3.#pragma once(常用)

作用:只要头文件的最开始加入这行代码,就能够保证头文件被编译一次

4.#pragma hdrstop

作用:表示预编译头文件到此为止

5.#pragma recource "*.dfm"

作用:把*.dmf文件中的资源加入工程

6.#pragma warning

#pragma warning(disable:4507 34;once:4385;error:164)
//等价于
#pragma warning(disable:4507 34)//不显示4507和34号警告信息
#pragma warning(once:4385)//4385号警告信息仅报告一次
#pragma warning(error:164)//把164号警告信息作为一个错误

//#pragma warning也支持以下格式
#pragma warning(push [,n])//这里n代表一个警告等级(1--4)
#pragma warning(pop)

7.#pragma comment

作用:该指令将一个注释记录放入一个对象文件或可执行文件中

常用的lib关键字,可以帮我们连入一个库文件
#pragma comment(lib,"user32.lib")//该指令用来将user32.lib库文件加到本工程中

8.#运算符

我们通过一段代码来看看

#define SQR(x) printf("The square of x is %d.\n",((x)*(x)));
//SQR(8)
//结果:The square of x is 64.
//加上#运算符以后就是
#define SQR(x) printf("The square of "#x" is %d.\n",((x)*(x)));
//结果:The square of 8 is 64.

引号中的字符x被当做普通文本来处理,而不是被当做一个可以替换的语言符号。而#运算符可以防止这一点

9.##运算符

我们也来看一段代码

#define XNAME(n) x##n
//加入是XNAME(8)
//结果为;x8
因此,##号的作用就是将前后两部分粘合起来。