当前课程知识点:C++语言程序设计基础 > 第5章 数据的共享与保护 > 多文件结构和预编译命令 > 多文件结构和预编译命令(例5-10)
l 一个工程可以划分为多个源文件:
n 类声明文件(.h文件)
n 类实现文件(.cpp文件)
n 类的使用文件(main()所在的.cpp文件)
l 利用工程来组合各个文件。
//文件1,类的定义,Point.h
class Point { //类的定义
public: //外部接口
Point(int x = 0, int y = 0) : x(x), y(y) { }
Point(const Point &p);
~Point() { count--; }
int getX() const { return x; }
int getY() const { return y; }
static void showCount(); //静态函数成员
private: //私有数据成员
int x, y;
static int count; //静态数据成员
};
//文件2,类的实现,Point.cpp
#include "Point.h"
#include <iostream>
using namespace std;
int Point::count = 0; //使用类名初始化静态数据成员
Point::Point(const Point &p) : x(p.x), y(p.y) {
count++;
}
void Point::showCount() {
cout << " Object count = " << count << endl;
}
//文件3,主函数,5_10.cpp
#include "Point.h"
#include <iostream>
using namespace std;
int main() {
Point a(4, 5); //定义对象a,其构造函数使count增1
cout <<"Point A: "<<a.getX()<<", "<<a.getY();
Point::showCount(); //输出对象个数
Point b(a); //定义对象b,其构造函数回使count增1
cout <<"Point B: "<<b.getX()<<", "<<b.getY();
Point::showCount(); //输出对象个数
return 0;
}
l 如果一个变量除了在定义它的源文件中可以使用外,还能被其它文件使用,那么就称这个变量是外部变量。
l 文件作用域中定义的变量,默认情况下都是外部变量,但在其它文件中如果需要使用这一变量,需要用extern关键字加以声明。
l 在所有类之外声明的函数(也就是非成员函数),都是具有文件作用域的。
l 这样的函数都可以在不同的编译单元中被调用,只要在调用之前进行引用性声明(即声明函数原型)即可。也可以在声明函数原型或定义函数时用extern修饰,其效果与不加修饰的默认状态是一样的。
l 使用匿名的命名空间:在匿名命名空间中定义的变量和函数,都不会暴露给其它的编译单元。
namespace { //匿名的命名空间
int n;
void f() {
n++;
}
}
l 这里被“namespace { …… }”括起的区域都属于匿名的命名空间。
l 标准C++类库是一个极为灵活并可扩展的可重用软件模块的集合。标准C++类与组件在逻辑上分为6种类型:
n 输入/输出类
n 容器类与抽象数据类型
n 存储管理类
n 算法
n 错误处理
n 运行环境支持
l #include 包含指令
n 将一个源文件嵌入到当前源文件中该点处。
n #include<文件名>
– 按标准方式搜索,文件位于C++系统目录的include子目录下
n #include"文件名"
– 首先在当前目录中搜索,若没有,再按标准方式搜索。
l #define 宏定义指令
n 定义符号常量,很多情况下已被const定义语句取代。
n 定义带参数宏,已被内联函数取代。
l #undef
n 删除由#define定义的宏,使之不再起作用。
#if 常量表达式
//当“ 常量表达式”非零时编译
程序正文
#endif
......
#if 常量表达式
程序正文1 //当“ 常量表达式”非零时编译
#else
程序正文2 //当“ 常量表达式”为零时编译
#endif
#if 常量表达式1
程序正文1 //当“ 常量表达式1”非零时编译
#elif 常量表达式2
程序正文2 //当“ 常量表达式2”非零时编译
#else
程序正文3 //其他情况下编译
#endif
#ifdef 标识符
程序段1
#else
程序段2
#endif
l 如果“标识符”经#defined定义过,且未经undef删除,则编译程序段1;
l 否则编译程序段2。
#ifndef 标识符
程序段1
#else
程序段2
#endif
l 如果“标识符”未被定义过,则编译程序段1;
l 否则编译程序段2。
大家好
欢迎继续回来
学习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
同一个头文件的时候
造成一些类 函数
这些常量被重复定义的问题
-导学
--第1章导学
-计算机系统简介
--计算机系统简介
--计算机系统简介 测试题
-计算机语言和程序设计方法的发展
--计算机语言和程序设计方法的发展 测试题
-面向对象的基本概念
--面向对象的基本概念 测试题
-程序的开发过程
--程序的开发过程
--程序的开发过程 测试题
-信息的表示和储存
--计算机的数字系统
--数据的编码表示
--信息的表示和储存 测试题
-实验指导
-导学
--第二章导学
-C++语言概述
--C++语言概述 测试题
-基本数据类型、常量、变量
--程序举例
--基本数据类型、常量、变量 测试题
-运算与表达式
--运算与表达式 测试题
-实验二:简单程序设计(上)
-数据的输入和输出
--数据的输入和输出
--数据的输入和输出 测试题
-选择结构
--if语句
--switch语句
--选择结构 测试题
-循环结构
--for语句
--循环结构 测试题
-自定义类型
--自定义类型
--自定义类型
-第2章小结
--第二章小结
-实验二:C++简单程序设计(下)
-导学
--导学
-函数定义
--函数定义
--函数定义 测试题
-函数调用
--例3-2
--例3-3
--例3-4
--例3-5
--例3-6
--函数调用 测试题
-嵌套与递归
--例3-9
--例3-10
--嵌套与递归 测试题
-函数的参数传递
--函数的参数传递
--函数的参数传递 测试题
-引用类型
--引用类型 测试题
-含有可变参数的函数
--含有可变参数的函数 测试题
-内联函数
--内联函数 测试题
-constexpr函数
--CONSTEXPR函数课后习题
-带默认参数值的函数
--带默认参数值的函数 测试题
-函数重载
--函数重载 测试题
-C++系统函数
--C++系统函数习题
-第3章小结
--第三章小结
-实验三(上)函数的应用
-实验三(下)函数的应用
-导学
--导学
-面向对象程序的基本特点
--面向对象程序的基本特点 测试题
-类和对象
--类和对象的定义
--类和对象 测试题
-构造函数
--构造函数基本概念
--委托构造函数
--复制构造函数
--构造函数 测试题
-析构函数
--析构函数
--析构函数 测试题
-类的组合
--类的组合
--类的组合程序举例
--前向引用声明
--类的组合 测试题
-UML简介
--UML简介
--UML简介课后习题
-结构体与联合体
--结构体与联合体 测试题
-枚举类
--枚举类
--枚举类 测试题
-第4章小结
--第四章小结
-实验四(上)
--实验四(上)
-实验四(下)
--实验四(下)
-导学
--导学
-标识符的作用域与可见性
--标识符的作用域与可见性 测试题
-对象的生存期
--对象的生存期
--对象的生存期 测试题
-类的静态成员
--类的静态成员 测试题
-类的友元
--类的友元 测试题
-共享数据的保护
--共享数据的保护 测试题
-多文件结构和预编译命令
--多文件结构和预编译命令 测试题
-第5章小结
--小结
-实验五
--实验五
-导学
--导学
-数组的定义与初始化
--数组的定义与使用
--一维数组应用举例
--数组的定义与初始化 测试题
-数组作为函数的参数
--数组作为函数的参数 测试题
-对象数组
--对象数组
--对象数组 测试题
-基于范围的for循环
-指针的定义和运算
--指针的定义和运算 测试题
-综合实例
--综合实例
-实验六(上)
--实验六上
-指针与数组
--指针数组
--指针与数组 测试题
-指针与函数
--指针类型的函数
--指向函数的指针
--指针与函数 测试题
-对象指针
--对象指针
--对象指针 测试题
-动态内存分配
--动态内存分配 测试题
-智能指针
--智能指针
-vector对象
--vector对象
--vector对象 测试题
-对象复制与移动
--移动构造
--对象复制与移动 测试题
-字符串
--C风格字符串
--string类
--字符串 测试题
-第6章小结
--第六章小结
-综合实例
--综合实例
-实验六(下)
--实验六(下)