大家好
欢迎继续回来
学习C++语言程序设计
这一节我们来学习
多文件结构和编译预处理命令
我们在写程序的时候呢
如果需要分工合作
如果程序的规模略微大一些
我们就会需要把程序
分别放在不同的文件中
可以分别编译
然后从而放在一起连接
接下来我们就来看
在C++程序中
可以把文件怎样划分成
多文件结构
那C++程序的一般组织结构
是这样的
一个工程文件可以划分为
多个源文件
比如说我们可以将类的声明
放在头文件中
就是后缀.h的文件
可以把类成员函数的实现
放在一个
或多个独立的C++文件中
那使用类的人
和写这个类的代码的人
可能不是一个人
也可能就不是一个团队的人
那么这种情况下
使用类的文件
和类的定义以及实现文件
往往是分开的
所以我们可以模拟这种情况
将主函数所在的文件
放在另一个
独立的C++文件中
然后我们利用工程
来组合各个文件
下面我们就通过一个例子
来看一下
如何将多个文件
组织在一个C++的工程中
现在呢
我们将以前演示过的这个例题
拿来来演示多文件工程
是怎么样组织文件
以及它编译和连接的时候
都完成了一些什么任务
原来我们用过的这个点类的例子
在这儿我们把它分割成
多个文件了
不是把全部代码
都放在一个文件里面
当我们要做
稍微规模大一点的应用的时候
肯定在一个工程
一个project里面要有多个文件的
不会把所有的东西
都放在一个C++文件中
比如这个例子呢
我们就将类的定义
放在一个头文件里面
这是Point类的定义
放在Point.h文件中
系统这个类库里面
提供的这些头文件
都是不带后缀h的
而我们自己写的头文件
通常是带后缀h的
那看这是类的成员函数
实现的文件
在Point.cpp里面
实现这个类的成员函数
以及要初始化的
类的数据静态成员这些
注意这儿有一个包含
include Point.h
我们自己写的这些头文件
就要用双引号引起来
而系统类库里面的头文件
用尖括号括起来
用尖括号括起来的这样的头文件
它会直接到你
安装的时候的默认目录下
去找这个文件
而你用双引号引起来的呢
会首先在当前的工作目录下
去找这个文件
如果找不到
才会到系统约定的目录下去找
所以我们自己定义的头文件
一般是用双引号引起来
而且后缀是有h
那接下来呢再看
这是主函数所在的文件
也就是刚才我们声明了类
而定义了类的成员函数
接下来要使用类了
类的定义者和使用者
在很多情况下
可能不是一个人
甚至于不是一个团队的人
那么这些程序
它很可能又存在于不同的文件中
如果我们要使用呢
可以把它都集中到一个工程
一个project里面来
但是代码不在同一个文件中
现在在主函数中
我们要使用Point类
当然也得将Point类的定义
Point.h作为头文件包含进来
不然的话
编译器在编译的时候
它就不知道Point这个符号
是什么了
好 这三个文件分别编辑好了
接下来我们看一下
整个编译和连接的过程
是什么样的
我们来看这个示意图
Point.h是这个定义
Point类的这个头文件
然后分别在类的实现文件
Point.cpp里面
还有主函数所在的文件
我们起名叫5-10.cpp
分别在这两个文件里面
都包含了Point.h
然后这两个cpp文件
分别进行编译
它们分别进行编译
编译以后各自形成
后缀为obj的文件是编译过程
编译完了以后
这样的文件是不可以执行的
它不是可执行程序
要生成可执行程序呢
还需要连接过程
这个连接过程
是将我们编译好的了这个obj文件
还有这个obj文件 多个
一个project里面经过编译以后
可能有多个obj文件
这些object文件呢
都要连接在一起
除此而外
我们不是还是调用了
系统库里面的功能吗
比如说我们用了cout
那么它就是系统库里面
预定义好的输出流对象
所以我们还需要
将系统运行库里面有关的内容
也连接在一起
最后就形成了
这样一个可执行的文件
但是大家在目前
比较流行的可视化编译环境中
去编译的时候呢
好象看不到这些细节的步骤
比如说在visual studio里面
我们要生成一个工程的话
直接生成就可以了
直接build就完成了
它会将编译和连接
都完全都给你做好
当然中间编译有错的话
会报错让你去改错
如果连接中间有文件找不到的话
也会报错 让你改错
但是如果都没有错的话
一键就build成功了
你基本上就没有注意到
它有编译过程 有连接过程
但是我们作为学习的过程
还是应该了解一下
它实质上的编译连接
是怎么样一个过程
以及在每个阶段都解决什么问题
这样的话
以后我们做稍微大一点的项目
需要有多个文件的时候
就知道怎么样把这多个文件
放到一个工程里面了
由于我们可以将一个程序中的
不同的函数
不同的类
以及它们的实现放在
不同的C++文件中
那么这个时候如果我们需要使用
在其他文件中定义的变量
可不可以呢 可以
这个时候呢我们就可以
把它作为外部变量来使用
外部变量呢
就是不在本程序文件中
定义的变量
那么如果你要使用一个在别处
定义的
在同一个工程文件中的变量呢
我们要用extern关键字来修饰
在文件作用域中的变量
默认情况下
就是可以作为外部变量的
另外函数它定义的位置
和调用的位置
也很可能不在同一个文件中
这个时候如果我们想使用
在别的文件中定义的函数
这种情况下
也叫做使用外部函数
那么定义一个函数的默认情况下
它具有全局作用域的函数
它就是可以作为外部函数的
如果我们要使用一个
在别处定义的函数
怎么办呢
只要在当前文件中
给它声明了就可以了
在使用之前
声明函数的原型就可以了
有的时候呢
我们并不希望在当前文件中
定义的这些标识符
能拿到别的文件中去使用
那这种情况下怎么办呢
我们可以把它限制在一个
命名空间中
我们可以用命名空间来限制
当前文件中定义的这些变量
函数
都只能在当前文件中使用
实际上我们在写程序中
其实本身就用到了大量
外部的这些功能模块
是什么呢
是C++的库里面
预定义好的这些功能模块
比如说我们用到库里面的函数
用到库里预定义好的类
那么C++标准库里面
到底给我们提供了哪些东西呢
标准C++库是一个极为灵活
并且可以扩展的
可重用的软件模块的集合
在逻辑上呢
这些预定义好的模块
分成六种类型
一个就是我们已经多次使用的
这个输入输出类
比如说我们用的cin对象
cout对象
就是输入输出类的
预定义好的对象
还有就是容器类和抽象数据类型
我们知道
如果一个程序
你能处理的数据
只是我单独列出来的
那么一个两个
五个十个变量
其实这个程序好象
没有太大的用途
我们经常会写程序
用来处理大批量的数据
那么这样的大批量数据
在程序中怎么存储呢
其实标准C++库
已经给我们准备好了这些
在标准库里头
有一类叫做容器类
它是一些类模板的总称
关于类模板我们会在第九章介绍
关于容器类
我们也会在第十章给大家介绍
这些预定义好的容器类模板
是非常有用的
它不仅能够存储与管理数据
而且带有很多数据操作的
有用的函数
另外一个类型就是存储管理类
还有C++标准库中
还带有着大量的预定义好的算法
比如说查找算法 排序算法
我们拿来用就行了
非常好用
还包括错误处理
以及运行环境支持
那么这些就是C++库里面的
这些预定义好的模块的
这样几大类型
现在呢
我们再来学习编译预处理命令
我们看这里列出了几个
编译预处理命令
第一个include指令
是大家比较熟悉的
它能将一个头文件包含进来
也就是把它的内容
插入到当前的位置
包含头文件有两种方式
一种是用尖括号括起文件名
表示按标准的方式搜索
文件位于C++系统目录的
include子目录底下
另外一种
就是用双引号括起文件名来
那它就会首先在当前目录中搜索
如果没有
再按标准方式搜索
好 那咱们再看这个define
这是一个宏定义指令
在C语言中
就有这样的编译预处理指令define
它可以定义符号常量
定义带参数的宏
但是这样的功能
在C++中基本上已经不用了
C++中要定义常量的话
用const
如果要定义宏的话
实际上在C++中
被内联函数取代了
那么在C++中
仍然有的时候我们会用
define这个宏定义指令
那只是为了定义一些符号 标记
用于在其他的编译预处理指令中
比如说进行选择性编译等等
那么undefine呢
undef实际上就是取消define指令
定义的那个符号
那这个if和endif构成的呢
是一个条件编译指令
也就是说
我们可以告诉编译器
当某种条件满足的时候
你就编译这一段程序正文
如果这个常量表达式为假
也就是为0
那么就不编译这一段程序正文
通过这种方式呢
我们在每一次编译之前
如果需要的话
可以重新设置
这个常量表达式的值
你把它设成0
这一段程序
编译器就会忽略它 不会编译
你把它设成非0
编译器就会编译这一段程序
所以我们也可以有选择性地
告诉编译器
在这一次编译的时候
生成这个版本的时候
我们要还是不要这一段程序
条件编译指令
还可以有两个选择
当常量表达式为非0的时候
编译程序正文这个程序段1
否则就编译程序正文2
这样就可以有目的性地
去设置这个常量表达式
然后让编译器
在我们不同的设置条件下
编译程序的不同部分
那么那个不编译的部分
就相当于我们在这一次
编译的时候
把那个代码都删掉了
可是你又没有真的删掉
你要恢复它就在那儿
这样比较方便
这个是多选择的条件定义
可以判断多个常量表达式
根据需要来设计
让编译器
到底编译哪一段程序正文
那这个ifdef跟else配合起来呢
能起到什么作用呢
能起到就是说
我看这个标识符
在这儿被定义过没有
如果定义过了
我就编译程序段1
否则就编译程序段2
那么这个标识符
到底什么叫做被定义过
很多初学的同学
就会纠结于这个标识符被定义过
它被定义成什么了
它被定义成几了
其实呢所谓被定义过
就是它通过define命令
去定义过这个标识符
但是我们并不需要
把它定义成值是几
只要这个标识符
在define中定义过
就说明我们树起了一个标签
树起了一个记号
按照这个记号所规定的意义
我们应该让编译器
去编译程序段1
否则就编译程序段2
那么也有一个反过来的
如果没有定义标识符
你去编译程序段1
否则编译程序段2
那么我们用这样的条件编译
可以在头文件中
设置这样一个标识符
如果没有被定义过这个标识符
那就编译某一段程序
同时马上把这个标识符定义一下
那如果这一个程序
被多次包含的话
当第二次包含的时候
他就会发现标识符已经被定义了
那么重复的程序段
就不会再被编译
这样就避免你多次重复include
同一个头文件的时候
造成一些类 函数
这些常量被重复定义的问题