当前课程知识点:基于Linux的C++ > 第八讲 链表与程序抽象 > 8.11 抽象链表(二) > LinuxCPP0811
我们看怎么使用 你就会明白了
假设我实现了一个函数叫PtTransformIntoString
它里面带着两个参数
一个是const char * format 一个是point
你看它的实现
我定义一个buffer 缓冲区
然后如果有一个point
我们就按照它的那个format传进来的格式
生成它对应的字符串
然后我们返回这个字符串
这个字符串的格式
就不是刚才那个“(%d,%d)”格式
你随便 你用“(%d,%d)”也行
你用“[%d,%d]”写也行
哪怕“<%d,%d>”也成
也就是说这个时候
你可以按照特定的一个字符串格式
来设定这个点的输出信息
这个就是我们的PtTransformIntoString这个函数
我做了修正
关键的是你看我们怎么去调用它
我会写一个函数叫DoPrintObject
打印特定的对象
传一个ADT类型的参数 传tag
因为这是一个回调函数
它将会被LlTraverse所调用
当你有了抽象的链表库和抽象的点库之后
你想使用这个抽象的链表库
来存储这个抽象的点
然后你就会写一个函数来调用LlTraverse
来遍历这个抽象的链表库里面全部的点
然后把它数据给打印出来
你要想完成这样任务
你就会写这样的函数LlTraverse
你就会这样调用
LlTraverse(list, DoPrintObject, “(%d,%d)”)
比如讲当你想“(%d,%d)”格式调用它的时候
那么你就按照这个格式传一个字符串进去
这个就是它的第三个参数 附加参数
现在明白了
我们现在有一个抽象的链表库
有一个抽象的点库
然后我要写一个程序
使用抽象链表库来存储我们抽象的点库
注意抽象的链表库是我写的
抽象的点库可能是张三写的
你呢 要使用我和张三
写的那段程序代码来实现你的程序
那么当你想按照一个特定格式
输出这些点信息的时候
我们就要调用LlTraverse这个函数
传递抽象的链表 传递回调函数
你遍历这个链表的时候想打印它嘛
想输出它的信息嘛
传递回调函数DoPrintObject 传打印的格式
这个就是附加的参数
LlTraverse 当接收到这个链表
还接收到了回调函数之后
它在内部调用的时候
就会调用这个回调函数
去执行你写的那段程序代码
同时将这个回调函数的那个附加的参数
它接收过来的第三个参数
转手扔给DoPrintObject
看我们的DoPrintObject
第一个参数ADT e 第二个参数ADT tag
第一个参数是什么
是我们点的结构体的地址
就是指向那个点结构体的指针
tag是什么 ADT
它实际上是什么 实际上是char *
现在我们很明确
在DoPrintObject里面 我们怎么办
调用PtTransformIntoString
把tag转换成const char *
作为第一个参数传进去
第二个参数是e 转换成PPOINT 传进去
那么PtTransformIntoString
就会以tag所指定的格式
将e这个点转换成一个字符串
然后我printf打印这个字符串 printf输出
你用cout也行 相当重要的一个地方
这个就是我们的回调函数的参数
那么你可能就会说
如果我不写这个回调函数的参数行不行呢
你不是就是这个特定的格式吗
我就直接写在DoPrintObject
这个函数的实现里面哪
写小括号对不就完了吗
不能啊 对吧
你不能把“(%d,%d)”写在这里啊
你一写在这里就只能做小括号对了吗
不能做中括号对了吗
对吧 不就不能改了嘛
你必须作为参数啊
你也不可能作为参数放在这里
你一旦直接写const char * format这个参数
放到DoPrintObject里面
那么不就意味着
这个函数第二个参数它不是tag了吗
它本来就需要一个ADT类型的参数
作为一个附加参数
所有的信息都可以表示
那你干嘛使用一个具体的信息
放在那个地方呢
当然使用一个单一的抽象附加信息表示
要更通用啊
这个就是我们ADT tag这个附加参数的意义
你看到这个有趣的地方吧
我用一个附加参数作为我们的映射函数
就是我们的回调函数的主调函数 对吧
和我们的回调函数
两者信息交互的一个关键点
就是它们交流的信息 就用它来代表
在这个里面 程序的参与者
我们可以说有三个人
抽象链表的设计者 点库的设计者
使用这两者在链表库里面
存储点数据的那个程序员
这里面是三个人
当然我们现在这个作为示例
三个都是我写的 对吧
但实际上我们编程的时候
它可能是三个人
互相之间完全不了解其它人的实现细节
他不知道抽象链表是怎么实现的
它的接口是什么 都不知道
设计链表库的人
他不知道点库是怎么实现的
点库接口是什么 他也不知道
只有使用抽象链表库
来存储点的那个程序员
他才知道抽象链表库的接口是什么
点库的接口是什么
但是对于抽象链表库的实现是什么
点库的实现是什么
第三个程序员也一样不知道
他不需要知道 反正能用就行了 对吧
抽象的目的就体现在这里
我让这些模块尽可能地独立
你能用就行了
你压根就不需要知道它内部的实现细节
抽象的链表作为一个容器
和容器中的一个对象
我就把它独立开了 分离开了
我们的容器就用来表达
能够容纳其它数据对象集的
像这样一个东西
我们的抽象链表就是这样的
它能够容纳其它数据对象
我们的抽象链表做的就是这个事情
我们的点数据结构
就是我们的容器中的对象 对吧
两者是完全无关的
就像我前面那个例子
抽屉和抽屉里面所能够存的
铅笔、钢笔、笔记本、钱包
它有关系吗 它没关系
容器和容器所容纳的对象完全独立
这个就叫抽象
没有抽象的容器和抽象的点
这个事情是做不了的
我们编程的时候需要特别特别
注意这个问题
抽象是解决这些问题的关键
按照这样的一个实现
我们的抽象链表事实上
可以容纳任意类型的数据对象
什么东西都可以存 一点问题都没有
还有一些知识点我们需要知道
抽象链表中一旦要存储
一些目标数据对象的时候
那么这些目标数据对象的存储和删除
在编写程序的时候是需要特别去注意的
第一 你链表中的那个data域是不是一个指针
第二 data域是不是指向真正存在的
目标数据对象
如果不指向怎么办
如果指向怎么办
是否需要指向一个真正存在的
目标数据对象
这个目标数据对象是你动态分配的
还是静态分配的
这个都需要你在
使用抽象链表库的时候明确的
第三个 如果结点要被删除的话
我们的data域
所指向的一个目标数据对象
是否需要被删除
链表这个结点被删除了
data域所指向的目标数据对象
是不是需要删除
就像我们前面讲的
如果我是把一个int直接转换成void*
存在data域里面的
那我这个结点被删除的时候
目标数据对象不存在 所以不需要删除
那个data字段
它真地指向目标数据对象
当我删除这个结点的时候
那个目标数据对象是不是一定需要删除
虽然在大部分情况下真需要删除
但是也并不意味着每次总是这样
并不意味着一定需要删除
这个地方同学们一定要注意
编程的时候要非常非常小心才可以
第四个 如果需要删除 你怎么删除
第五个 抽象链表的设计者
能不能完成这样的删除任务
如果能 你就删就行了
第六个 如果你不能 你怎么办
这一点 你写程序的时候
必须要时刻地注意到
我们可以看到这样的一个例子
我这个数据对象如果需要删除
那我就提供一个函数指针类型
这就是DESTROY_OBJECT
销毁目标数据对象的一个函数指针类型
传递一个哑型指针ADT e
用来代表待销毁的
那个目标数据对象的地址
在LlDelete这个函数的实现中
我们要使用一个回调函数
作为它的函数参数:DESTROY_OBJECT destroy
这个destroy如果你传了一个非0值
不是NULL的一个值
那么我们就调用destroy所指向的那个
目标销毁函数 销毁它的目标数据对象
如果你传了一个NULL
我就不销毁 很明确吧
这个就是我们数据对象的存储与删除
一般要做的事情
所以说 如果你需要删除目标数据对象
那么正常情况下边
你需要写一个回调函数DoDestroyObject
然后在这个里面去销毁那个目标数据对象
你在调用LlDelete这个函数的时候
传递一个函数的入口地址给它
把DoDestroyObject函数的入口地址
作为第三个参数传给LlDelete
然后它就调用DoDestroyObject
替你销毁那个目标数据对象
如果你不需要删除目标数据对象
那么你就传递一个NULL给LlDelete
它就什么也不做
这就为我们编程提供了很大的灵活性
最后 我们的抽象链表库该怎么去设计
我们这里面当然会有一个
struct LIST *定义成PLIST
数据封装与信息隐藏
然后我们定义了三个函数指针类型
COMPARE_OBJECT、DESTROY_OBJECT、
MANIPULATE_OBJECT
重新修正了抽象链表库的接口
LlCreate没变 LlDestroy发生了变化
传递了一个销毁对象的回调函数
一个函数指针
LlAppend没变 LlInsert没变
LlDelete同样要传递
DESTROY_OBJECT类型的一个函数指针
LlClear当然也需要
LlTraverse需要传递一个操纵对象的回调函数
LlSearch要传递一个比较对象的回调函数
这个就是我们最终的抽象链表库的接口
-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 编程实践
-第十五讲 网络编程--编程实践提交入口