当前课程知识点:基于Linux的C++ > 第八讲 链表与程序抽象 > 8.3 数据抽象(二) > LinuxCPP0803
接下来我们看
最重要的一个知识点——数据封装
数据封装 它的最主要的目的
是将数据的结构细节隐藏起来
我们假设有这样一个结构体
struct DYNINTS
这是一个动态数组的定义
在这里面它有几个字段呢
有4个字段
一个是capacity,一个是count,
一个是items,一个是modified
有这样4个字段
这4个字段是分别用来表达什么的呢
如果你定义了动态数组类型的一个变量
那么capacity将会用来表达
像这样的一个动态数组
它最多可以包含多少个元素
而count就是表示在程序运的某一个时刻
它的元素个数到底是多少个
所以count肯定是小于等于capacity的
items 它是指向整数的一个指针
它就将用来表达那个数组真实元素序列
理论上来讲
你应该使用动态内存分配的方式
来维护真正的整数数组
modified 它是个布尔量
用来表达这个数组有没有发生改变
如果发生改变的话
modified这个量就会变成true
否则就会一直维持着false
简单的4个字段
就构造了动态数组的结构体
那么我们怎么知道动态数组结构体
在程序运行过程中它的某一个变量
当时的元素个数是多少个呢
一个很简单的方案
如果我们有这样一个
动态数组结构体的变量
我们假设它的名字叫a
如果想知道这个结构体里面的元素个数
那么我们就a.count
就能够获得动态数组的元素个数
但是这种方案对于一个复杂的程序来讲
它是不好的
如果你将来改变了结构体的定义
把这个count这个成员的名字改成了
number_of_elements
那么所有使用结构体类型的程序
都需要相应地做出改变
而这个改变是很要命的
因为你可能写了一个函数库给别人用
如果他的程序也跟着你来改
那么实际上这个工作量是巨大的
你对这个库的改变将会必然地传播到
所有库的使用者的程序代码里面去
这实际上是一个非常不好的设计方式
那么我们就想一个办法
就是我们提供一个函数
我为你实现一个函数的接口DiGetCount
“Di”就是DYNINTS这个动态数组的缩写
我们就用这样一个函数
来获取动态数组的元素个数
这是一个很直接的函数
看上去很简单
那个if语句就是用来作为一个测试的
除此之外就是简单的return语句 很典型
但是它给我们提供了一个很好的设计
就是什么呢
因为这样一个数据类型
它上面有数据的表示 还有数据的功能
我们前面不还说了嘛
数据的表示是容易变的
而数据的功能是不容易变的
那么当我设计好这个动态数组库
你使用动态数组的库
你只使用这个动态数组库
所提供的这些操作集的时候
只要DiGetCount这个函数名字不变
函数的参数不变 函数的返回值不变
那么不管这个函数是怎么实现的
不管这个动态数组结构体怎么变
库的使用者的代码实际上不用变
你比如讲这个count我想改成
number_of_elements
然后DiGetCount这个函数接口不变
但是内部这个实现我改成
return a->number_of_elements
这个改变对于库的设计者来讲
他只要重新设计一下库就可以了
而对于库的使用者来讲
他完全不用关心这个问题
因为这个问题对他来说透明的
我们希望通过一个技术手段
能够将整个程序分解成很多个库
并且这些库的修订、升级、改变这样的操作
只要在接口不变的情况下
不影响其它的库
或者我们这些库的使用者
数据封装就是一个最重要的概念
我们要将数据结构的细节隐藏起来
你不把细节藏起来
你就没有办法保证这个库
和其它的库是隔离的
我们怎么实现数据封装呢
就是这里面看到的
要对结构体里面的数据成员
提供相应的存取函数
我们想获取count值 提供DiGetCount
你想设置这个count
那你就提供一个类似的函数叫DiSetCount
这个就叫存取函数,一个是存,一个是取
所以叫Get/Set函数
这是第一个 数据封装
你如果只有数据封装 这个是不够的
我们假设实现那个结构体
你也提供一系列的存取函数
把数据已经封装起来了
这实际上是暗示这个库的使用者
就是如果你想访问结构体里面的成员
你该使用相应的存取函数
而不是直接访问成员的名字
这样的话数据才能封装起来
但只有这些是不够的
因为struct这个结构体类型
它的设计并没有限定用户
不能访问它的数据成员
你提供了DiGetCount这个函数没错
可是你不能限制不使用这个函数
而直接访问那个count成员
也就是说 对于库的使用者来讲
他可以在DiGetCount函数的调用
和直接访问那个count字段之间
自由地切换
你没有办法限定他只能使用存取函数
而不访问这个结构体的数据成员
这是非常重要的一个地方
也就是说 只有数据封装是不够的
数据封装最主要的一个问题就是
库的使用者实际上能够看到这个定义
他不调用 你没有办法限制他
那我怎么办呢
一个简单的解决方案就是
我把这个数据结构的这样数据成员
全部不让你看
你看不见这个数据结构的这些成员
那么这就意味着不能访问它
只能通过存取函数来访问
这是非常重要的一个地方
这个就叫信息隐藏
数据封装和信息隐藏合在一起
才是编写抽象程序的关键
这是最重要的两个核心概念:
数据封装、信息隐藏
那么在C/C++代码里面怎么实现呢
你如果把数据结构写在头文件里
别人就会包含这个库的头文件
一包含这个库的头文件
那么就能够看到这个结构体的定义
看到这个结构体的定义
就没有办法限制他不使用
所以就把结构体的定义从头文件里
给它挪到源文件里
那么对于库的使用者来讲
他事实上只能看到头文件
他看不见源文件
源文件你可以不提供
只提供编译好的二进制代码
他如果只能看到你的头文件
那么事实上就不知道这个库 这个结构体
它到底是如何实现的
就真正地做到了信息隐藏
而我又要保证
他能够正确使用这个结构体
那么就会在头文件里给出
这个结构体的声明
并且提供一个指向这个结构体的指针
我们保证只使用这个指针
来操纵它的目标结构体的数据对象
看这样一个例子
设计能够存储二维平面上点的
抽象数据类型
按照我们数据封装与信息隐藏的基本介绍
那么在库的接口里面
给出结构体POINT的声明
然后给出PPOINT类型
为指向结构体的一个指针
注意 因为我们目标结构体的定义是未知的
那么实际上这样PPOINT形
虽然可以在函数原型里面使用
但是我们实际上不能使用
它的目标结构体的任何内部信息
这些都放在我们这个点库
放在它的头文件里 “point.h”
像这样的一个点库
我们需要提供一系列的操作
因为我们没有结构体类型的真实定义
要保证库的使用者
能够正确地操纵我们这个点库
必须提供一个点库的接口
所以我们这里面提供
PtCreate、PtDestroy这两个函数
分别用来创建一个点的结构体
和销毁点的结构体
因为我们这里面使用到的全都是指针
所以这里面有创建有销毁
他们都将是动态分配的
还有提供一系列的Get/Set函数
比如PtGetValue、PtSetValue
设置或获取这个点的值
然后我们提供两个函数
PtCompare和PtTransformIntoString
一个是比较,一个是转换
PtPrint 最后一个函数
完成点的数据信息的打印
接下来的代码就是点库的实现细节
放在“point.cpp”里
代码本身实际上是非常非常简单的
我们只是将原始的程序换成了一个
符合数据封装与信息隐藏的模式来编写
最主要的 你看PtCreate
我们会构造这样的一个POINT
然后完成一个标准的赋值动作以后(用x和y去构造)
然后我们会返回
然后PtDestroy 我们就销毁点的对象
这些代码你看
实际上都非常非常简单
但是它给我们提供最基本的
抽象数据类型的定义或实现的一个模式
我们都应该按照这样模式来
最主要地 你看我们struct POINT
结构体类型的定义
我们写在“point.cpp”里面
而没有写到“point.h”里面
就是我刚才讲的 我们完成了数据封装
最重要的 接下来你要做的
就是要把这个信息藏起来
不要给别人看 叫信息隐藏
PtPrint按照一个特殊的格式
我们这里面用“(%d,%d)”的格式
完成这个打印 这是PtCompare
这个地方判断的是两者是不是相等
接下来PtTransformIntoString要完成一个
点的数据对象到一个字符串的转化
也是按照“(%d,%d)”的格式进行转化
在这个转化的过程中
它需要使用这个DuplicateString
完成一个字符串复制的动作
因为我们一开始不知道
点转换成一个字符串以后
占多大字节的存储空间
因为数字转化以后
它的字符串长度是可变的
所以我们先使用一个缓冲区把它保存进去
然后我们才把它复制出来
这个就是PtTransformIntoString
它需要使用一个辅助函数DuplicateString
-1.1 提纲
-1.2 程序设计的基本概念
-1.3 简单C/C++程序介绍
-1.4 程序设计的基本流程
-1.5 基本语法元素
-1.6 程序设计风格
-1.7 编程实践
-第一讲 C/C++基本语法元素--编程实践提交入口
-2.1 提纲
-2.2 结构化程序设计基础
-2.3 布尔数据
-2.4 分支结构
-2.5 break语句
-2.6 循环结构
-2.7 编程实践
-第二讲 程序控制结构--编程实践提交入口
-3.1 提纲
-3.2 函数声明、调用与定义
-3.3 函数调用栈框架
-3.4 编程实践
-第三讲 函数--编程实践提交入口
-4.1 提纲
-4.2 算法概念与特征
-4.3 算法描述
-4.4 算法设计与实现
-4.5 递归算法(一)
-4.6 递归算法(二)
-4.7 容错与计算复杂度
-4.8 编程实践
-第四讲 算法--编程实践提交入口
-5.1 提纲
-5.2 库与接口
-5.3 随机数库(一)
-5.4 随机数库(二)
-5.5 作用域与生存期
-5.6 典型软件开发流程(一)
-5.7 典型软件开发流程(二)
-5.8 编程实践
-第五讲 程序组织与开发方法--编程实践提交入口
-6.1 提纲
-6.2 字符
-6.3 数组(一)
-6.4 数组(二)
-6.5 结构体
-6.6 编程实践
-第六讲 复合数据类型--编程实践提交入口
-7.1 提纲
-7.2 指针基本概念
-7.3 指针与函数
-7.4 指针与复合数据类型(一)
-7.5 指针与复合数据类型(二)
-7.6 字符串
-7.7 动态存储管理(一)
-7.8 动态存储管理(二)
-7.9 引用
-7.10 编程实践
-第七讲 指针与引用--编程实践提交入口
-8.1 提纲
-8.2 数据抽象(一)
-8.3 数据抽象(二)
-8.4 链表(一)
-8.5 链表(二)
-8.6 链表(三)
-8.7 链表(四)
-8.8 函数指针(一)
-8.9 函数指针(二)
-8.10 抽象链表(一)
-8.11 抽象链表(二)
-8.12 编程实践
-第八讲 链表与程序抽象--编程实践提交入口
-9.1 提纲
-9.2 程序抽象与面向对象
-9.3 类类型
-9.4 对象(一)
-9.5 对象(二)
-9.6 类与对象的成员(一)
-9.7 类与对象的成员(二)
-9.8 类与对象的成员(三)
-9.9 继承(一)
-9.10 继承(二)
-9.11 继承(三)
-9.12 多态(一)
-9.13 多态(二)
-9.14 编程实践
-第九讲 类与对象--编程实践提交入口
-10.1 提纲
-10.2 四则运算符重载(一)
-10.3 四则运算符重载(二)
-10.4 关系与下标操作符重载
-10.5 赋值操作符重载(一)
-10.6 赋值操作符重载(二)
-10.7 赋值操作符重载(三)
-10.8 赋值操作符重载(四)
-10.9 赋值操作符重载(五)
-10.10 流操作符重载(一)
-10.11 流操作符重载(二)
-10.12 流操作符重载(三)
-10.13 操作符重载总结
-10.14 编程实践
-第十讲 操作符重载--编程实践提交入口
-11.1 提纲
-11.2 泛型编程概览
-11.3 异常处理机制(一)
-11.4 异常处理机制(二)
-11.5 运行期型式信息(一)
-11.6 运行期型式信息(二)
-11.7 模板与型式参数化
-11.8 题外话:术语翻译
-11.9 泛型编程实践(一)
-11.10 泛型编程实践(二)
-11.11 泛型编程实践(三)
-11.12 泛型编程实践(四)
-11.13 泛型编程实践(五)
-11.14 泛型编程实践(六)
-11.15 泛型编程实践(七)
-11.16 泛型编程实践(八)
-11.17 泛型编程实践(九)
-11.18 泛型编程实践(十)
-11.19 编程实践
-第十一讲 泛型编程--编程实践提交入口
-12.1 提纲
-12.2 程序执行环境(一)
-12.3 程序执行环境(二)
-12.4 程序执行环境(三)
-12.5 程序执行环境(四)
-12.6 输入输出(一)
-12.7 输入输出(二)
-12.8 文件系统
-12.9 设备
-12.10 库(一)
-12.11 库(二)
-12.12 makefile文件(一)
-12.13 makefile文件(二)
-12.14 makefile文件(三)
-12.15 编程实践
-第十二讲 Linux系统编程基础--编程实践提交入口
-13.01 提纲
-13.02 进程基本概念
-13.03 信号
-13.04 进程管理(一)
-13.05 进程管理(二)
-13.06 进程管理(三)
-13.07 进程间通信(一)
-13.08 进程间通信(二)
-13.09 进程间通信(三)
-13.10 进程间通信(四)
-13.11 进程池
-13.12 编程实践
-第十三讲 进程编程--编程实践提交入口
-14.1 提纲
-14.2 线程基本概念
-14.3 线程管理(一)
-14.4 线程管理(二)
-14.5 线程管理(三)
-14.6 线程管理(四)
-14.7 线程同步机制(一)
-14.8 线程同步机制(二)
-14.9 C++11线程库(一)
-14.10 C++11线程库(二)
-14.11 C++11线程库(三)
-14.12 C++11线程库(四)
-14.13 C++11线程库(五)
-14.14 编程实践
-第十四讲 线程编程--编程实践提交入口
-15.1 提纲
-15.2 Internet网络协议
-15.3 套接字(一)
-15.4 套接字(二)
-15.5 编程实践
-第十五讲 网络编程--编程实践提交入口