【编程语言】C语言编译与预处理指令

预处理指令

C语言规定:源程序中可以加入一些预处理指令。但预处理指令并不是C语言本身的组成部分,编译器不能识别它们,不能直接对这些指令进行编译。在使用时需要以“#”开头,用以与C语言区分。

所谓预处理,就是指源程序在进行编译的第一遍扫描(词法分析和语法分析)之前所做的工作由预处理程序完成。当对一个源程序进行编译时,系统将自动引用预处理程序对源程序中的预处理部分进行处理(代替),处理完毕自动进入对源程序的编译。


宏定义

在C语言源程序中允许用一个标识符来表示字符串,称为宏。宏定义是以“#”开头的,均为预处理命令,同时使用“define”作为宏定义命令。宏定义,根据标识符的形式,大体可以分成两种,分别是变量式宏定义和函数式宏定义。变量式宏定义的书写类似于变量的声明,用来定义常量;函数式宏定义的书写类似于函数,用来定义稍复杂些的带参数的表达式。

变量式宏定义

变量式宏定义也就是不带参数的宏定义,其定义的一般形式为:

#define 标识符 字符串

其中:字符串部分可以为浮点数、运算符、字符串、表达式等。比如:

#define PI 3.1415926
#define LARGE >
#define NEWLINE "\n"
#define M (x*x+2*x)

在使用标识符代替表达式的时候,需要注意:

  • 在表达式中出现了x,那么在主函数中必须定义变量x,否则就会编译错误
  • 宏定义中,表达式两边的括号不能少,否则可能会发生错误。

文件中的宏定义默认是从定义就有效到文件结束,但也可以对其作用范围进行圈定,使用的方法为:

#define                /* 宏定义开始 */
    ...                /* 宏定义内容 */
#undef                /* 宏定义结束 */

宏定义嵌套

在宏定义的过程中,宏也是可以嵌套定义的。比如:

#define A (1+2)
#define B A*A
#define C B+B

那么,宏展开对应为:

#define A (1+2)
#define B (1+2)*(1+2)
#define C (1+2)*(1+2)+(1+2)*(1+2)

函数式宏定义

函数式宏定义也就是带参数的宏定义,其定义的一般形式为:

#define 标识符(参数列表) 字符串

其中:字符串中应包含标识符中的参数。比如:

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

对于这两个函数我们乍一看没有什么问题,我们看一下下面的例子:

#include <stdio.h>

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

int main()
{
	int a, b, c, d;
	scanf_s("%d %d %d %d", &a, &b, &c, &d);
	printf("MAX(%d,%d)=%d\n", a + b, c + d, MAX(a + b, c + d));
	printf("MUL(%d,%d)=%d\n", a + b, c + d, MUL(a + b, c + d));

	return 0;
}

这段程序的运行结果为:

3 4 5 6
MAX(7,11)=11
MUL(7,11)=29
请按任意键继续. . .

我们向函数式宏定义的参数传递的是表达式a+b,c+d,而不是单纯的一个变量,这个时候我们进行代替看一下:

MAX(a + b, c + d)=a + b > c + d ? a + b : c + d
MUL(a + b, c + d)=a + b * c + d

  • MAX函数我们看一下,根据运算符的优先级和结合性,没有问题;
  • MUL函数我们看一下,根据运算符的优先级和结合性,很显然不是我们预期的。

也就是说,在函数式宏定义的时候,我们必须将每个参数用小括号括起来:

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

多行宏定义

通常宏定义必须是单行的,但是也可以使用多行来定义一个宏,定义的方法就是使用反斜杠“\”。比如:

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

这里第一行的末尾必须是反斜杠,第二行的开头可以为了美观增添一些空格。也就是说,反斜杠后面必须紧跟回车符。

宏定义与操作符的区别:

宏定义是C语言的预处理命令之一,它是一个替代操作,不进行计算和表达式求解,不占用内存和编译时间。


文件包含

文件包含指令的功能是把指定的文件插入该指令行位置取代指令行,从而把指定的文件和当前的源文件连成一个源文件。我们通常以头文件来编写这些被包含的文件,也就是以.h作为这些文件的扩展名。当然不是非要这样命名才可以。也可以使用.c为扩展名,或者干脆不写扩展名,这样都是允许的。只是使用.h更为普遍而已。

文件包含通常有以下两种形式:

#include "文件名"
#include <文件名>

两种文件包含方式的区别:

  • <>:文件包含标准头文件。搜索顺序是:从系统目录下开始搜索,然后再搜索PATH环境变量所列出的目录,不搜索当前目录。如stdio.h、assert.h、stdlib.h等;
  • "":文件包含自定义文件。搜索顺序是:从当前目录开始搜索,然后是系统目录和PATH环境变量所列出的目录。如led.h、usart.h、dac.h等。


条件编译

所谓“条件编译”,就是程序的内容指定编译的条件。一般情况下,源程序的所有行都参与编译,但是我们希望部分行在满足一定条件的情况下再进行编译,这就要用到条件编译。

条件编译的形式

一般条件编译的形式有#ifdef、#ifndef、#if三种形式,下面依次介绍:

  • #ifdef形式的一般形式如下:
#ifdef 标识符
    程序段1
#else
    程序段2
#endif

作用是:如果标识符已经被定义了,则对程序段1进行编译;否则编译程序段2。但也可以不写#else分句,也是没有问题的。

其中:标识符是用#define指令进行定义的,程序段可以是语句,可以是命令行。

  • #ifndef形式的一般形式如下:
#ifndef 标识符
    程序段1
#else
    程序段2
#endif

作用是:如果标识符没有被定义了,则对程序段1进行编译;否则编译程序段2。但也可以不写#else分句,也是没有问题的。

  • #if形式的一般形式如下:
#if 表达式
    程序段1
#else
    程序段2
#endif

作用是:如果if表达式的值非0,则对程序段1进行编译;否则编译程序段2。但也可以不写#else分句,也是没有问题的。

文件嵌套包含和条件编译

由于嵌套包含文件的原因,一个头文件可能会被多次包含在一个源文件中,而使用条件指示符就可以防止这种头文件的重复处理。例如:

#ifndef COMMONEILE_H
    #define COMMONFILE_H
    ...(.h文件内容)
#endif

条件指示符检测COMMONFILE_H标识符是否已经被定义了,如果没有定义,就定义并包含头文件到源文件;如果定义了,说明该头文件已经被包含进去了,就不执行接下来的内容,从而达到防止文件的嵌套包含。


assert()宏

assert()宏是在标准库<assert.h>头文件中定义的,称为断言。assert()宏可以让开发人员在程序中插入任何的表达式,用来诊断表达式的值是否为false,也就是0。作为assert()宏的参数的表达式的结果是一个整型数据。例如:

assert(a == b);

当a和b相等时,表达式的结果为true,也就是1;否则为false,也就是0。

断言机制可以和符号常量NDEBUG配合使用,由NDEBUG的符号决定是否启用断言机制。

如果在<assert.h>引用之前定义了NDEBUG,如下:

#define NDEBUG
#include <assert.h>

这样,源文件中使用的assert()就会被忽略掉。在某些系统里,断言机制默认是不可使用的,在这种情况下,需要开启断言机制:

#undef NDEBUG
#include <assert.h>

猜你喜欢

转载自blog.csdn.net/qq_38410730/article/details/80249460