预处理等等

2023-11-05

预处理

  • #define 宏定义是个演技非常高超的替身演员,但也会经常耍大牌的,所以我们用它要慎之又慎。它可以出现在代码的任何地方,从本行宏定义开始,以后的代码就就都认识这个宏了;也可以把任何东西定义成宏。因为编译器会在预编译的时候用真身替换替身,而在我们的代码里面却又用常常用替身来帮忙。
#define PI 3.141592654
在此后的代码中你尽可以使用PI 来代替3.141592654,而且你最好就这么做。
不然的话,如果我要把PI 的精度再提高一些,你是否愿意一个一个的去修改这串数呢?
你能保证不漏不出错?而使用PI 的话,我们却只需要修改一次。
这种情况还不是最要命的,我们再看一个例子.
#define ERROR_POWEROFF -1
如果你在代码里不用ERROR_POWEROFF 这个宏而用-1,尤其在函数返回错误代码的时候
(往往一个开发一个系统需要定义很多错误代码)。肯怕上帝都无法知道-1 表示的是什么意
思吧。这个-1,我们一般称为“魔鬼数”,上帝遇到它也会发狂的。所以,我奉劝你代码里
一定不要出现“魔鬼数”。
  • 第一章我们详细讨论了const 这个关键字,我们知道const 修饰的数据是有类型的,而define 宏定义的数据没有类型。为了安全,我建议你以后在定义一些宏常数的时候用const代替,编译器会给const 修饰的只读变量做类型校验,减少错误的可能。但一定要注意const修饰的不是常量而是readonly 的变量,const 修饰的只读变量不能用来作为定义数组的维数,也不能放在case 关键字后面。

字符串宏常量

  • 除了定义宏常数之外,经常还用来定义字符串,尤其是路径:
A),#define ENG_PATH_1 E:\English\listen_to_this\listen_to_this_3
B),#define ENG_PATH_2 “E:\English\listen_to_this\listen_to_this_3”
  • 噢,到底哪一个正确呢?如果路径太长,一行写下来比较别扭怎么办?用反斜杠接续
    符啊:
C), #define ENG_PATH_3 E:\English\listen_to_this\listen\
_to_this_3
  • 还没发现问题?这里用了4 个反斜杠,到底哪个是接续符?回去看看接续符反斜杠。反斜杠作为接续符时,在本行其后面不能再有任何字符,空格都不行。所以,只有最后一个反斜杠才是接续符。至于A)和B),那要看你怎么用了,既然define 宏只是简单的替换,那给ENG_PATH_1 加上双引号不就成了:“ENG_PATH_1”。但是请注意:有的系统里规定路径的要用双反斜杠“\”,比如:
#define ENG_PATH_4 E:\\English\\listen_to_this\\listen_to_this_3

#undef

  • #undef 是用来撤销宏定义的,用法如下
#define PI 3.141592654// code
#undef PI
//下面的代码就不能用PI 了,它已经被撤销了宏定义。

条件编译

  • 条件编译的功能使得我们可以按不同的条件去编译不同的程序部分,因而产生不同的目标代码文件。这对于程序的移植和调试是很有用的。条件编译有三种形式,下面分别介绍:
第一种形式:
#ifdef 标识符
程序段1
#else
程序段2
#endif
  • 它的功能是,如果标识符已被#define 命令定义过则对程序段1 进行编译;否则对程序段2进行编译。如果没有程序段2(它为空),本格式中的#else 可以没有,即可以写成:
#ifdef 标识符
程序段
#endif
  • 第二种形式:
#ifndef 标识符
程序段1
#else
程序段2
#endif
  • 与第一种形式的区别是将“ifdef”改为“ifndef”。它的功能是,如果标识符未被#define 命令定义过则对程序段1 进行编译,否则对程序段2 进行编译。这与第一种形式的功能正相反。

文件包含

  • 文件包含是预处理的一个重要功能,它可用来把多个源文件连接成一个源文件进行编译,结果将生成一个目标文件。C语言提供#include 命令来实现文件包含的操作,它实际是宏替换的延伸,有两种格式
格式1#include <filename>
其中,filename 为要包含的文件名称,用尖括号括起来,也称为头文件,表示预处理
到系统规定的路径中去获得这个文件(即C 编译系统所提供的并存放在指定的子目录下的头文件)。
找到文件后,用文件内容替换该语句。
格式2#include “filename”
其中,filename 为要包含的文件名称。双引号表示预处理应在当前目录中查找文件名为filename 的文件
若没有找到,则按系统指定的路径信息,搜索其他目录。
找到文件后,用文件内容替换该语句。

pragma

  • 在所有的预处理指令中,#pragma 指令可能是最复杂的了,它的作用是设定编译器的状态或者是指示编译器完成一些特定的动作。#pragma 指令对每个编译器给出了一个方法,在保持与C 和C ++语言完全兼容的情况下,给出主机或操作系统专有的特征。依据定义,编译指示是机器或操作系统专有的,且对于每个编译器都是不同的。其格式一般为#pragma para,其中para 为参数,下面来看一些常用的参数。
  • message 参数:Message 参数是我最喜欢的一个参数,它能够在编译信息输出窗口中输出相应的信息,这对于源代码信息的控制是非常重要的。其使用方法为:
    #pragma message(“消息文本”)
  • 当编译器遇到这条指令时就在编译输出窗口中将消息文本打印出来。当我们在程序中定义了许多宏来控制源代码版本的时候,我们自己有可能都会忘记有没有正确的设置这些宏,此时我们可以用这条指令在编译的时候就进行检查。假设我们希望判断自己有没有在源代码的什么地方定义了_X86 这个宏可以用下面的方法
#ifdef _X86
#Pragma message(“_X86 macro activated!”)
#endif

*当我们定义了_X86 这个宏以后,应用程序在编译时就会在编译输出窗口里显示“_X86 macro activated!”

#pragma pack

  • 字,双字,和四字在自然边界上不需要在内存中对齐。(对字,双字,和四字来说,自然边界分别是偶数地址,可以被4 整除的地址,和可以被8 整除的地址。)无论如何,为了提高程序的性能,数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;然而,对齐的内存访问仅需要一次访问。
  • 一个字或双字操作数跨越了4 字节边界,或者一个四字操作数跨越了8 字节边界,被认为是未对齐的,从而需要两次总线周期来访问内存。一个字起始地址是奇数但却没有跨越字边界被认为是对齐的,能够在一个总线周期中被访问。某些操作双四字的指令需要内存操作数在自然边界上对齐。如果操作数没有对齐,这些指令将会产生一个通用保护异常。双四字的自然边界是能够被16 整除的地址。其他的操作双四字的指令允许未对齐的访问(不会产生通用保护异常),然而,需要额外的内存总线周期来访问内存中未对齐的数据。
  • 使用指令#pragma pack (n),编译器将按照n 个字节对齐。
  • 使用指令#pragma pack (),编译器将取消自定义字节对齐方式。

#运算符

  • #define SQR(x) printf(“The square of x is %d.\n”, ((x)*(x)));如果这样使用宏:SQR(8);则输出为:
    The square of x is 64.注意到没有,引号中的字符x 被当作普通文本来处理,而不是被当作一个可以被替换的语言符号。
  • 假如你确实希望在字符串中包含宏参数,那我们就可以使用“#”,它可以把语言符号转
    化为字符串。上面的例子改一改:
    #define SQR(x) printf(“The square of “#x” is %d.\n”, ((x)*(x)));再使用:SQR(8);则输出的是:The square of 8 is 64.

##预算符

  • 和#运算符一样,##运算符可以用于宏函数的替换部分。这个运算符把两个语言符号组合成单个语言符号。看例子:#define XNAME(n) x ## n,如果这样使用宏:XNAME(8)则会被展开成这样:x8看明白了没?##就是个粘合剂,将前后两部分粘合起来。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

预处理等等 的相关文章