c语言预处理(预定义符、宏、条件编译、文件包含)

编程入门 行业动态 更新时间:2024-10-08 22:20:36

c语言预处理(预定义符、宏、<a href=https://www.elefans.com/category/jswz/34/1771358.html style=条件编译、文件包含)"/>

c语言预处理(预定义符、宏、条件编译、文件包含)

目录

前言:编译与链接

预定义符号:

命令行定义:

#define定义标识符:

#define 定义宏:

1.定义宏的格式

2.宏的作用

3.#define的替换规则

4.含格式标识符宏的封装

5.宏可能出现的错误

6.宏和函数的对比

7.#undef

条件编译:

1.单分支条件编译

2.多分支条件编译

3.判断是否被定义

4.嵌套指令

文件包含:

1.头文件被包含的方式

2.嵌套文件包含及过多包含文件的解决方法


前言:编译与链接

源文件在成为可执行程序之前往往要经过“翻译环境”和“执行环境”,其中翻译环境可细分为“编译”和“链接”,“编译”又可细分为“预编译(处理)”、“编译”和“汇编”,本章讲的是预处理阶段的知识,包括预定义符、宏、条件编译、文件包含等……(主要为宏)


预定义符号:

c语言为我们提供了一些预定义符号,让我们可以快速的实现某种功能,如输出文件当前被编译的时间、日期等。

常见的几个预定义符号:

__FILE__
__LINE__
__DATE__
__TIME__
__STDC__

__FUNCTION__

//进行编译的源文件  %s
//文件当前的行号     %d
//文件被编译的日期  %s
//文件被编译的时间  %s
//如果编译器遵循ANSI C,其值为1,否则未定义  %d

//当前执行的函数     %s

这些预定义符号存储了一些信息,我们可以用相应格式输出出来。

例:

 

命令行定义:

许多C 的编译器提供了一种能力,允许在命令行中定义符号。用于启动编译过程。
例如:当我们根据同一个源文件要编译出一个程序的不同版本的时候,这个特性有点用处。(假定某个程序中声明了一个某个长度的数组,如果机器内存有限,我们需要一个很小的数组,但是另外一个机器内存大些,我们需要一个数组能够大些。)
 

#define定义标识符:

#define 定义的标识符在预处理阶段会被替换成指定内容,我们可以通过它压缩代码的长度,以及增加代码的可读性。

具体操作如下:

 用#define 定义标识符MAX,在源文件预处理阶段,该语句后的代码如果出现MAX则会自动替换为10,这一操作为文本操作。(注:由于#difine定义标识符内容不可更改,因此具有常属性,MAX也称之为标识符常量)

 那么除了定义标识符常量,我们还可以用#define做什么?

①关键字等进行缩写,如register,不过会降低代码可读性

#define reg register

②创建一个死循环

#define sss for( ; ; );

③选择语句中简写case语句

#define CASE  break;case

case 1:
{
……
}
break;
CASE 2:
{
}
CASE 3:
{
}

④实现某一固定功能,如上文中打印当前编译状态的操作

#define STATUS  printf("当前正在编译的文件:%s\n文件被编译的日期:%s\n文件被编译的时间:%s\n文件当前的行号:%d\n", __FILE__,__DATE__,__TIME__,__LINE__)

以上例子都说明了#define定义标识符本质上完成的是文本替换的工作,另外要注意的还有#define这行语句是不需要加“;”的,如果加上的话也被视为替换内容,这点在标识符常量使用时可能会产生错误,所以不建议加“;”。

#define 定义宏:

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

#define定义标识符和#define定义宏都与#define相关,按理该划为一个模块,但我觉得#define定义宏完全可以单独拿出来细讲,所以单独划出一个模块来。

1.定义宏的格式

宏区别于标识符,而又类似于函数的一点在于,它是有参数的,并且参数用括号围起来的,格式如下:

#define name( parament-list ) stuff

其中的 parament-list 是一个由逗号隔开的符号表,它们可能出现在stuff中,其中括号要和name紧紧相连不能有空格,否则就变成定义标识符了。

对于宏名还有一个约定俗成的规则:采用纯大写字符来命名以区分函数。

2.宏的作用

宏也会在预处理阶段被替换到代码之中,但宏体内的参数也会被替换。

如定义宏#define ADD(x,y) x+y

int main()
{int a = ADD(1 + 5, 1 + 3);return 0;
}

对于上述代码,预处理后为:

int main()
{int a = 1+5+1+3;return 0;
}

3.#define的替换规则

在程序中扩展#define定义符号和宏时,需要涉及几个步骤。
1. 在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号。如果是,它们首先被替换。
2. 替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被他们的值所替换。
3. 最后,再次对结果文件进行扫描,看看它是否包含任何由#define定义的符号。如果是,就重复上述处理过程。

注意:
1. 宏参数和#define 定义中可以出现其他#define定义的符号。但是对于宏,不能出现递归。
2. 当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索。

#include <stdio.h>#define ADD(x,y) ADD(x,y)+ADD(x,y)int main()
{int a = ADD(3, 4);return 0;
}

4.含格式标识符宏的封装

4.1现象

对于一些函数,如printf(),我们可能用到%d、%s等格式标识符,如果定义一个宏,想要实现打印两种自由指定元素组成的字符串时,就要将格式标识符作为参数传过去,而在宏中,宏的参数在printf()的字符串参数中会被视为字符串的一部分。

#include <stdio.h>
#define FF(x) printf("x打印一个数字%d\n",x);
int main()
{FF(1);return 0;
}

 4.2解决方案

①利用相邻字符串自动合并的特性

两个相邻的字符串会自动合并为一个字符串

例:"abcdefg""hijklmn\n"

#include <stdio.h>int main()
{char arr[] = "abcdefg""hijklmn";printf("%s\n", arr);printf("abcdefg""hijklmn\n");return 0;
}

 具体的操作方法可以是不直接以标识符作为参数而是以标识符为内容的字符串为参数,利用其自动合并的特性达到目的。

#include <stdio.h>
#define FF(X,x,Y,y) printf("生成:"X"和"Y,x,y)
int main()
{FF("%s","我很好","%d",250);return 0;
}

 注:宏的参数不要连着写("生成:"XY),不然会报错,可以穿插""空字符串。

②利用操作符#和##

# :把一个宏参数变成对应的字符串
对于#%d 等价于 "%d"。

那么利用#的特性,参数就可以直接写为%d、%s而不用转换为字符串。

#include <stdio.h>
#define FF(X,x,Y,y) printf("生成:"#X""#Y,x,y)
int main()
{FF(%s,"我很好",%d,1);return 0;
}

既然说到了#,那就顺便提一嘴##:

 ##:##可以把位于它两边的符号合成一个符号。它允许宏定义从分离的文本片段创建标识符。

#include <stdio.h>#define he(x,y) x##yint main()
{printf("%d\n", he(NU,LL));return 0;
}

5.宏可能出现的错误

5.1操作符优先级可能导致的问题

可能导致问题的宏导致问题
#deinfe  s(x,y) x*ys(1+3,4-2)      1+3*4-2
#define  s(x,y)  x+y10*s(1,6)    10*1+6
#define s(x,y)  (x)+(y)

10*s(1+3,6)   10*(1+3)+6

对于这样的问题,我们可以为每个参数,和宏体加上括号。

例:#define s(x,y)  ((x)+(y))

5.2区别于函数的传参

函数的传参,函数的参数传输前后分为形参和实参,形参是实参值的拷贝;而宏对于参数采用的是直接替换,并无实参、形参之分,如果参数本身能对参数自身进行更改的话,可能导致结果不符合预期的情况,同时也可能埋下一些隐性的问题。

#include <stdio.h>#define ADDS(x,y) ((x)+(y)+(x)+(y))int adds(x, y)
{return x + y + x + y;
} int main()
{int a = 10;int b = 20;printf("%d\n", adds(a++, b++));printf("%d\n", ADDS(a++, b++));return 0;
}

6.宏和函数的对比

优势:

① 用于调用函数和从函数返回的代码可能比实际执行这个小型计算工作所需要的时间更多。
所以宏比函数在程序的规模和速度方面更胜一筹

函数在被调用时往往要经历三个:准备(函数传参、函数栈帧空间的维护,可通过汇编代码观察到。)——>函数使用(主要运算)——>返回(函数返回返回值,栈帧空间销毁)。

宏本质上是代码的替换,所以不需要经历准备和返回,这无疑会提高程序的运行效率。

②函数的参数必须声明为特定的类型

函数有参数的类型检查,因此只能传指定类型的参数,而宏则不需要考虑这点,利用这点,同样的代码,在宏中我们可以拓展出更多的功能。

例:

#include <stdio.h>#define GG(x,y) ((x)>(y)?(x):(y))int  G(int x, int y)
{return x > y?x:y;
}int main()
{int a = 10, b = 11;char c = 'a', d = 'A';printf("函数实现判断较大值:%d\n",G(a,b));printf("宏实现判断较大值:%d  %c\n",GG(a,b),GG(c,d));return 0;
}

 如图所示,宏相对函数而言可以兼容的类型要多,这一宏还可以兼容到浮点数,短整型上。

缺点:

①每次使用宏的时候,一份宏定义的代码将插入到程序中。除非宏比较短,否则可能大幅度增加程序的长度。

②宏是没法调试的。

③宏由于类型无关,也就不够严谨。

④宏可能会带来运算符优先级的问题,导致程容易出现错误。

⑤宏无法递归

总结:

宏更适用于执行小规模的不复杂的运算

函数则与之相反

(额外,可以了解一下内联函数)

7.#undef

这条指令用于移除一个宏定义。

#undef NAME
//如果现存的一个名字需要被重新定义,那么它的旧名字首先要被移除。

条件编译:

在编译一个程序的时候我们如果要将一条语句(一组语句)编译或者放弃是很方便的。因为我们有条件编译指令。

一种类似判断语句(if)的编译指令,有#if、#elif、#else、#if defined()、#endef、#ifdef

1.单分支条件编译

编译指令根据后方表达式判断,为真(非零)后方语句被编译,为假(0)后方语句不被编译。

单分支语句由#if和#endef构成,其中#endef是条件编译的结束标志。

  • #if 表达式
  • 语句;
  • #endef

如下述代码执行后什么都不会发生。

#include <stdio.h>int main()
{
#if 0printf("我是大聪明\n");
#endifreturn 0;
}

2.多分支条件编译

多分支语句相对但分支语句多了#elif和#else,单看名字想必就能推导出它们的用法,#elif类比else if ,#else 类比 else。

注:多分支语句其中一个执行,其余不执行。

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

3.判断是否被定义

#if define()和 #ifdef 都是用来判断标识符是否被定义的,当后方标识符未定义时语句不执行,不过格式上略有区别

  • #if define(MAX)                                                                                                                                       语句;                                                                                                                              #endif
  • #ifdef MAX                                                                                                                                               语句;                                                                                                                            #endif

如果程序当前 MAX 被定义(例:#define MAX 10),则后方语句执行。

除了判断被定义时执行语句,判断未定义时执行语句也可以,不过#if define 要变为 #if !define ,#ifdef 要变为 #ifndef。

  • #if !define(MAX)                                                                                                                                       语句;                                                                                                                            #endif
  • #ifndef MAX                                                                                                                                               语句;                                                                                                                          #endif

4.嵌套指令

在if判断语句中,是支持嵌套的,if后方的以if开头的判断语句整体上被视为一个语句,条件编译中也一样。

例:

#if defined(OS_UNIX)

  • #ifdef OPTION1
  • unix_version_option1();
  • #endif
  • #ifdef OPTION2
  • unix_version_option2();
  • #endif

#elif defined(OS_MSDOS)

  • #ifdef OPTION2
  • msdos_version_option2();
  • #endif

#endif
 

文件包含:

头文件被#define引用会在代码执行处替换头文件的内容。

注意,头文件被#define引用几次就包含头文件几次。

1.头文件被包含的方式

#define <stdio.h>
#define "test.h"

以上是头文件被包含的两种形式,这两种形式的区别在于编译器查找文件的策略。

①""式(自定义)

查找策略:先在源文件所在目录下查找,如果该头文件未找到,编译器就像查找库函数头文件一样在标准位置查找头文件。
如果找不到就提示编译错误。
②<>式(库文件)

查找策略:查找头文件直接去标准路径下去查找,如果找不到就提示编译错误。

两种方式都是可以查找库文件的,但对于自定义的头文件<>式是无法查找到的,因为查找头文件直接去标准路径下去查找。

但要注意的是,虽然两种方式都可以查找到库文件,但""式需要两次查找,而<>只需要一次,效率更高,所以对于引用头文件还是专门化一些好。

2.嵌套文件包含及过多包含文件的解决方法

试想一下,如果一个头文件中包含另一个头文件,被包含的这个头文件又被其他头文件包含,这个文件又引用了第一个头文件,而主文件同时引用了这三个头文件会发生什么?

 如图,代码会变得一团乱麻,且编译后的代码产生了大量冗余的部分,这些部分可能又会相互作用……

又或者,在一个文件中,反复包含同一头文件:

#define <stdio.h>
#define <stdio.h>
#define <stdio.h>
#define <stdio.h>int main()
{
return 0;
}

解决方案:

将文件内容加入如下代码:

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

分析,多次引用时,首次__TEST_H__被定义,头文件内容被编译,再次引用时,由于__TEST_H__已被定义,#ifndef判断不能通过,因此之后的语句不会被执行,也就不会被编译。

更多推荐

c语言预处理(预定义符、宏、条件编译、文件包含)

本文发布于:2024-02-13 17:01:28,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1760021.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:条件   语言   文件

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!