`
dogasshole
  • 浏览: 845520 次
文章分类
社区版块
存档分类
最新评论

多文件结构和编译预处理命令

 
阅读更多
预处理指令
声明预处理指令的一般形式为:
# directive tokens
符号“#”必须是该行第一个非空白字符,但前面有空白符或退格符都可以,#与directive之间也可以有多个空白符,如下代码具有完全相同的效果:

#define size 100
#define size 100
# define size 100


一般一行声明一条预处理指令,但不排除用多行声明一条指令,字符“\”用在一行的末尾表示下行仍然接着该行,比如下面两个声明是完全对等的:

#define CheckError \
if (error) \
exit(1)

#define CheckError if (error) exit(1)


预处理指令声明中出现的注释以及一行单独一个#符号的情况在预编译处理过程中都会被忽略掉。
表1是所有预处理指令和意义

指令 意义
#define 定义宏
#undef 取消定义宏
#include 包含文件
#ifdef 其后的宏已定义时激活条件编译块
#ifndef 其后的宏未定义时激活条件编译块
#endif 中止条件编译块
#if 其后表达式非零时激活条件编译块
#else 对应#ifdef, #ifndef, 或 #if 指令
#elif #else 和 #if的结合
#line 改变当前行号或者文件名
#error 输出一条错误信息
#pragma 为编译程序提供非常规的控制流信息


宏定义
#define指令定义宏,宏定义可分为两类:简单宏定义,带参数宏定义。
简单宏定义有如下一般形式:
#define 名字 替换文本
它指示预处理器将源文件中所有出现名字记号的地方都替换为替换文本,替换文本可以是任何字符序列,甚至可以为空(此时相当于删除掉文件中所有对应的名字)。
简单宏定义常用于定义常量符号,如:
#define size 512
#define word long
#define bytes sizeof(word)
因为宏定义对预编译指令行也有效,所以一个前面已经被定义的宏能被后来的宏嵌套定义(如上面的bytes定义用到了word)。对于下面这句代码:
word n = size * bytes;
它的宏扩展就是:
long n = 512 * sizeof(long);
使用简单宏定义定义常量符号起源于C语言,但在C++中,定义常量可以用const关键字,并且还附加类型检查的功能,因此C++中已经尽量避免使用宏定义来定义常量了。
带参数宏定义的一般形式为:
#define 名字(参数) 替换文本
其中参数是一个或多个用逗号分割的标识符;在“名字”和“(”之间不允许有空格,否则整个宏定义将退化为一个置换文本为“(参数) 替换文本”的简单宏定义。下例表示定义一个求两数中较大者的带参数宏Max。
#define Max(x,y) ((x) > (y) ? (x) : (y))
带参数宏的调用有点类似于函数调用,实参数目必须匹配形参。首先,宏的替换文本部分置换掉调用的代码,接着,替换文本部分的形参又被置换为相应的实参,这个过程叫做宏扩展。见下例:
n = Max (n - 2, k +6);
的宏扩展为:
n = (n - 2) > (k + 6) ? (n - 2) : (k + 6);
注意,宏扩展时有可能发生不预期的运算符优先级的变化,这时如果定义宏时将替换文本里出现的每个形参都用括号括起来就不会出现问题(如上述宏MAX所示)。
仔细考察带参数宏与函数调用的异同可以发现,由于宏工作在文本一层,相同功能的宏和函数调用产生的语义有时是不完全相同的,比如:
Max(++i, j)
扩展为
((++i) > (j) ? (++i) : (j))
可见i最后自增了两次,但相同功能的函数能够保证只自增一次。
带参数宏定义在C++中的使用同样也在减少,因为:1,C++的内联函数提供了和带参数宏同样高的代码执行效率,同时没有后者那样的语义歧义;2,C++模板提供了和带参数宏同样高的灵活性,还能够执行语法分析和类型检查。
最后讨论一点内容是宏能够被重定义,在重定义前,必须使用#undef指令取消原来的宏定义,#undef如果取消的是一个原本不存在的宏定义则视为无效。如:
#undef size
#define size 128
#undef Max

引用操作符和拼接操作符
预处理提供了两个特殊操作符操作宏内的参数。引用操作符(#)是一元的,后跟一个形参作为运算对象,它的作用是将该运算对象替换为带引号的字符串。
如有一个调试打印宏检查指针是否为空,为空时输出警告信息:
#define CheckPtr(ptr) \
if ((ptr) == 0) cout << #ptr << " is zero!\n"
此时#操作符将表达式中的变量ptr当成字符串输出为警告信息的一部分。因此,如下的调用:
CheckPtr(tree->left);
扩展为:
if ((tree->left) == 0) cout << "tree->left" << " is zero!\n";
注意:如果按照下面这样定义宏
#define CheckPtr(ptr) \
if ((ptr) == 0) cout << "ptr is zero!\n"
是不会得到期望结果的,因为宏不能在字符串内部进行置换。
拼接操作符(##)是二元的,被用来连接宏中两个实际参数,比如,如下宏定义
#define internal(var) internal##var
如果执行
long internal(str);
则被扩展为:
long internalstr;

在一般编程时很少用到拼接操作符,但在编写编译器程序或源代码生成器时特别有用,因为它能轻易的构造出一组标识符。

#include文件的一个不利之处在于一个头文件可能会被多次包含,为了说明这种错误,考虑下面的代码:
#include "x.h"
#include "x.h"

显然,这里文件x.h被包含了两次,没有人会故意编写这样的代码。但是下面的代码:
#include "a.h"
#include "b.h"

看上去没什么问题。如果a.h和b.h都包含了一个头文件x.h。那么x.h在此也同样被包含了两次,只不过它的形式不是那么明显而已。

多重包含在绝大多数情况下出现在大型程序中,它往往需要使用很多头文件,因此要发现重复包含并不容易。要解决这个问题,我们可以使用条件编译。如果所有的头文件都像下面这样编写:
#ifndef _HEADERNAME_H
#define _HEADERNAME_H

...

#endif

那么多重包含的危险就被消除了。当头文件第一次被包含时,它被正常处理,符号_HEADERNAME_H被定义为1。如果头文件被再次包含,通过条件编译,它的内容被忽略。符号_HEADERNAME_H按照被包含头文件的文件名进行取名,以避免由于其他头文件使用相同的符号而引起的冲突。

但是,你必须记住预处理器仍将整个头文件读入,即使这个头文件所有内容将被忽略。由于这种处理将托慢编译速度,所以如果可能,应该避免出现多重包含。
举例:

cpp1.h

main.cpp





分享到:
评论

相关推荐

    预编译资料- 预编译资料

    预编译详细资料,共90页。 掌握宏定义的使用方法;  掌握文件包含的使用方法;  了解条件编译的使用方法。...C语言提供三种编译预处理命令: 1. 宏定义; 2. 文件包含; 3. 条件编译。

    c++语言程序设计(第三版)

    5.7 多文件结构和编译预处理命令 5.8 小结 习题 第6章 数组、指针与字符串 6.1 数组 6.2 指针 6.3 动态内存分配 6.4 字符串 6.5 小结 习题 第7章 继承与派生 7.1 继承与派生 7.2 访问控制 7.3 派生类的构造和析构...

    鸡啄米:C++编程入门系列

    编译预处理命令) 第六部分:数组、指针和字符串 鸡啄米:C++编程入门系列之二十五(数组、指针和字符串:数组的声 明和使用) 鸡啄米:C++编程入门系列之二十六(数组、指针和字符串:数组的存 储与初始化、...

    C++多文件结构及预处理命令

     在编译的时候,由Clock.h和Clock.cpp编译生成Clock.obj,由main.cpp和Clock.h编译生成main.obj,然后是链接过程,Clock.obj和main.obj链接生成main.exe可执行文件。  #include &lt;文件名&gt;  使用这种写法时,...

    C语言全套资料 C语言程序设计 C语言算法 C语言课件

    C语言全套资料 C语言程序设计 C语言算法 C语言课件 C语言顺序程序设计 C语言数组 C语言循环控制 C语言预处理命令 C语言文件操作指针 C语言...第九章编译预处理命令 第十章 指针 第十一章 结构体与共用体 第十三章 文件

    由浅入深学C语言基础、进阶与必做430题.part3

    本书简单易懂,内容丰富,...第3篇是C语言高级应用,包括算法、预编译命令、文件和图形,以及预处理命令等内容;第4篇是C语言开发案例,详细讲解了C语言项目开发案例,读者在这里可以学习到C语言项目的整个开发过程。

    《由浅入深学C语言基础、进阶与必做430题》.part2

    本书简单易懂,内容丰富,...第3篇是C语言高级应用,包括算法、预编译命令、文件和图形,以及预处理命令等内容;第4篇是C语言开发案例,详细讲解了C语言项目开发案例,读者在这里可以学习到C语言项目的整个开发过程。

    《由浅入深学C语言基础、进阶与必做430题》.part1

    本书简单易懂,内容丰富,...第3篇是C语言高级应用,包括算法、预编译命令、文件和图形,以及预处理命令等内容;第4篇是C语言开发案例,详细讲解了C语言项目开发案例,读者在这里可以学习到C语言项目的整个开发过程。

    由浅入深学C语言——基础、进阶与必做430题.崔久,蒋欣(带详细书签)

    必须两个压缩分卷part1、...第3篇是C语言高级应用,包括算法、预编译命令、文件和图形,以及预处理命令等内容;第4篇是C语言开发案例,详细讲解了C语言项目开发案例,读者在这里可以学习到C语言项目的整个开发过程。

    由浅入深学C语言——基础、进阶与必做430题.崔久,蒋欣(带详细书签).part2.rar

    必须两个压缩分卷part1、...第3篇是C语言高级应用,包括算法、预编译命令、文件和图形,以及预处理命令等内容;第4篇是C语言开发案例,详细讲解了C语言项目开发案例,读者在这里可以学习到C语言项目的整个开发过程。

    计算机软件工程练习.doc

    编译预处理命令:不带参宏定义和带参宏定义的使用;"文件包含"的处理。 6.指针:指针与指针变量的概念,指针与地址运算符;指针与数组、字符串和函数 的配合使用。 7.结构体:结构体数据的定义和使用;结构体数组...

    谭浩强C语言程序设计,C++程序设计,严蔚敏数据结构,高一凡数据结构算法分析与实现.rar

    3.9 多分支选择结构和switch语句 3.10 编写选择结构的程序 3.11 循环结构和循环语句 3.11.1 用while语句构成循环 3.11.2 用do-while语句构成循环 3.11.3 用for语句构成循环 3.11.4 几种循环的比较 3.12 循环的嵌套 ...

    《C语言程序设计教程——基于Visual C++6.0环境》[PDF]

    较全面地讲述了C语言程序设计的基本知识,主要内容包括概述,数据类型、运算符及表达式,基本输入/输出函数,选择结构,循环结构,数组,函数,指针,编译预处理命令,结构与共用,文件和图书管理系统案例介绍等,...

    linux网络编程-宋敬彬-part2

    1.5.2 Linux的文件结构 9 1.6 GNU通用公共许可证 10 1.6.1 GPL许可证的历史 10 1.6.2 GPL的自由理念 10 1.6.3 GPL的基本条款 11 1.6.4 关于GPL许可证的争议 12 1.7 Linux软件开发的可借鉴之处 12 1.8 ...

    linux网络编程-宋敬彬-part3

    1.5.2 Linux的文件结构 9 1.6 GNU通用公共许可证 10 1.6.1 GPL许可证的历史 10 1.6.2 GPL的自由理念 10 1.6.3 GPL的基本条款 11 1.6.4 关于GPL许可证的争议 12 1.7 Linux软件开发的可借鉴之处 12 1.8 ...

    谭浩强C语言程序设计,C++程序设计,严蔚敏数据结构,高一凡数据结构算法分析与实现.rar )

    3.9 多分支选择结构和switch语句 3.10 编写选择结构的程序 3.11 循环结构和循环语句 3.11.1 用while语句构成循环 3.11.2 用do-while语句构成循环 3.11.3 用for语句构成循环 3.11.4 几种循环的比较 3.12 循环的嵌套 ...

    C语言标准教程第一章 C语言概论

    预处理命令还有其它几种,这里的include 称为文件包含命令,其意义是把尖括号""或引号内指定的文件包含到本程序来,成为本程序的一部分。被包含的文件通常是由系统提供的,其扩展名为.h。因此也称为头文件或首部文件...

    linuxC编程实战.part2.rar(文件已加密)

     第5章 C语言预处理、结构体和make的使用  第二篇 Linux系统编程  第6章 文件操作   第7章 进程控制   第8章 线程控制   第9章 信号及信号处理   第10章 进程间通信  第三篇 Linux网络和图形...

    linux网络编程-宋敬彬-part1

    1.5.2 Linux的文件结构 9 1.6 GNU通用公共许可证 10 1.6.1 GPL许可证的历史 10 1.6.2 GPL的自由理念 10 1.6.3 GPL的基本条款 11 1.6.4 关于GPL许可证的争议 12 1.7 Linux软件开发的可借鉴之处 12 1.8 ...

Global site tag (gtag.js) - Google Analytics