当前课程知识点:基于Linux的C++ >  第八讲 链表与程序抽象 >  8.10 抽象链表(一) >  LinuxCPP0810

返回《基于Linux的C++》慕课在线视频课程列表

LinuxCPP0810在线视频

LinuxCPP0810

下一节:LinuxCPP0811

返回《基于Linux的C++》慕课在线视频列表

LinuxCPP0810课程教案、知识点、字幕

有了函数指针的概念之后

我们现在就可以设计抽象的链表

就像我刚才讲的qsort那个函数里

它有一个函数指针类型的形式参数

那个函数指针指向的那个函数

我们就称它为回调函数

允许通过函数指针

调用未来才会实现的代码

这个就叫回调函数 凡是回调函数

它都会需要依赖后续的设计

否则你不知道它调用哪一个

你传哪一个函数入口地址给它

它就调用哪一个 对吧

像DoCompareObject

你把这个DoCompareObject传给qsort

qsort就会调用DoCompareObject

你如果传的不是这个函数

传的是另外一个函数

它就会调用另外一个函数 这是肯定的

所以说 你传哪一个 它就调用哪一个

而这个函数本身还是你未来才会实现的

就相对于qsort那个函数的设计

和编写的那个实现而言

DoCompareObject这个函数实现

是远远在它的未来之后

所以说对qsort来说

它会调用你未来才会实现的DoCompareObject

替你排序未来才会实现的数组

qsort本身 它只能完成排序的基本动作

它不知道你的数组是什么样子

它只知道你的数组的入口地址在哪里

你的数组里包含多少个元素

你的数组每个元素尺寸是多少

所有的这些还都是

你在调用qsort的时候告诉它的

其次它也不知道怎么比较元素的关系

怎么比较呢 也是你告诉它的

你写一个比较函数

把这个函数的入口地址给它

它就调用你的函数 替你比较你的数据

这是非常重要的一个设计

回调函数的作用就体现在这里

有的时候我们需要在回调函数

和主调函数之间

产生一个很特殊的信息的交互

这种东西我们称它为附加的信息

我可能想传附加的信息

然后你可能会说

那我传多少个附加的数据信息是够的呢

你可能就会说我传一个

那如果是传俩信息怎么办

我如果写俩参数

如果需要传三个信息怎么办呢 也不成啊

难道就真的一点办法都没有吗 当然不是

我们前面不讲了吗

我们有一个通用型的东西

有一个通用型的数据对象

有一个通用型的数据抽象类型

叫什么 哑型指针 void*,ADT嘛

我就用它来表达这个附加的参数

如果传的附加信息是一个

我们就直接传这个附加信息

或这个附加信息的地址

如果这个附加信息

和我们的哑型指针尺寸刚好是一般大

你就不用传它的地址了

你就传它就可以了

附加信息如果是一个

很典型 我用一个ADT代替它

如果附加信息是两个呢

然后你还搞两个ADT 吗 不需要

我就把附加信息——不论它多少个

只要超过两个 大于等于2

对吧 超过一个了

把它做成一个结构体

然后传那个结构体的地址

转换成ADT 不就够了吗

所以一个ADT事实上就可以传递所有的东西

不管这个附加信息有多少

一个ADT就足够用了 特别重要这一条

回到我们的抽象链表的考虑

我们这里面链表的每一个结点的data字段

它会存储我们这个链表

作为一个容器所容纳的目标数据对象

或者目标数据对象的地址

那么我们现在就有一个巨大的问题

如果我们这个链表结点的data字段里面

存的是一个目标数据对象的地址

那么当我在删除这个结点的时候

毫无疑问必须在销毁这个结点之前

完成目标数据的销毁

这个你必须要做到

就像我们抽象链表

刚才那个版本所实现的那样

想删除我们这个结点

在此之前你必须先删除

data字段所指向的点数据结构

然后才能删除那个结点本身

把结点本身全都删完了

然后你才能删除这个链表本身

这是一个非常明确的存储管理的顺序

如果我们使用这个链表来保存的是一些整数

就像我刚才说的

我实际上是可以把这个int直接转换成void*

(注:在32位系统和编译器下两者存储尺寸一致)

就存在data字段里面的

这个时候我们的data字段

它并不指向任意的有意义的存储区

它其实的原始意义就是一个整数

结果被我们当成void* 存在data字段里面了

所以在这种时候

如果我需要销毁这个链表的结点

那么data字段是不需要去管理它的

也就是说这个时候

你是不可以销毁data字段

所指向的那个目标数据区域的

那个并不是我们真正

内存分配出来的存储区域

所以当我们设计抽象链表的时候

就需要考虑到这一点

就是说你删除这个链表结点的时候

它里面的目标数据对象是不是需要删除

如果是 你怎么办 如果不是 你怎么办

这个在你设计抽象链表的时候

你必须能够考虑到

第二个 如果链表的结点存储的是指针

你就需要参数 否则就不需要

设计抽象链表的时候

我们并不需要了解这个结点

实际存储的数据是不是指针

我们设计抽象链表的时候

我们可能并不了解

结点实际存储的数据是不是指针

所以有的时候可能无法来决定这个结点

它的操作的逻辑

这是非常重要的一个地方

就是说我用一个抽象链表存了一个数据

那么我们还不知道

它所存储的那个目标数据

完整的意义的时候

我们实际上是不了解那个东西的——data字段

是指针吗 也许

不是指针吗 也许 怎么办呢

所以这个时候就需要特别特别去注意

我们先来看回调函数怎么用

我要同学们编写一个函数

遍历我们的链表

然后结点数据的具体操作方法呢

我们现在是不知道的 不知道该怎么做

我就是你遍历这个链表然后做某件事

注意某件事就是某件事

并不是一定是某件事 什么意思

其实就是说当我遍历这个链表的时候

我只知道要做一件事情

但是这件事情具体做什么

其实我不清楚

至少我在实现这个函数的时候我不清楚

也就是说有些信息

在我实现这个函数的时候是未知的

怎么办呢

凡是未知的东西都应该使用量来代替

就像我们使用量来代替一个具体的数一样

你不知道那个数是什么 没关系

我们操纵的是这个量

你不知道操作是什么 没关系

我们使用一个函数指针 就是这个意思

所以我们需要使用一个回调函数

来提供这个结点数据的具体的操作方法

你看这里面有一个函数指针类型

MANIPULATE_OBJECT

有一个操纵对象的函数指针类型

它接受一个ADT类型的一个参数

返回值是void

接下来我们实现LlTraverse函数 遍历函数

传一个链表进去

然后传一个MANIPULATE_OBJECT

这样的操纵对象的函数指针类型的指针

形式参数名字叫manipulate

注意 平时我们用参数的名字

不管形式参数还是实际参数的名字

还是变量的名字

我们平时都使用名词 对吧

我们这个bool量 偶尔会使用形容词

我们这里面 我们使用的是动词

为什么 因为它是函数指针类型的变量

所以它往往代表的是一个动作

所以我们使用一个动词 这是可以的

manipulate 操纵

定义一个PNODE类型的一个量t

初始化成list->head

让t指向链表表头结点 循环

如果manipulate这个值不是NULL

就表示你传了一个实际的函数的入口地址

那么我们就调用它(*manipulate)(t->data)

t->data就是ADT类型 对吧

我就调用它

然后t=t->next 然后循环

因为我在LlTraverse这个函数里面

提供了一个回调函数

这实际上就意味着我们这个LlTraverse

可以在遍历的时候

执行你想要它做的操作

你想要它打印 你就写一个打印函数

作为实际参数传给LlTraverse

让manipulate指向那个打印函数

它就会打印 如果你想求和

那么你写一个求和的函数传给manipulate

manipulate就会指向那个求和函数

然后求和 按照这样的程序设计

我们LlTraverse这个函数

它的适用性显然

要比原来的那个方式要灵活得多

至少你看到 在我们LlTraverse这个函数内部

没有对PtTransformIntoString函数的调用

这就意味着对LlTraverse这个函数来讲

它不需要知道PPOINT、点库的任何细节

管你是点库还是什么库

尤其是注意到 你看我这个数据

一旦形成一个抽象的链表库的话

那我们的data字段

它压根就不是PPOINT类型的

它是ADT类型的 完全把它抽象化了

你想用的时候把PPOINT转换成ADT

然后在内部再重新把ADT转换成PPOINT回去

它是按照这样方式将链表库和点库

完全给独立开来的

数据结构 它们互相不使用了

算法 它们互相之间也不使用了

那不就独立了嘛

点库是点库 抽象链表库是链表库

而我们抽象链表库

它的应用场合也就不再局限于点库

这个就是我们LlTraverse

有了回调函数以后给我们揭示的

最深刻的一个地方

接下来 我们的回调函数参数

我们可以对这个MANIPULATE_OBJECT

这个函数指针类型做一个改变

添加第二个参数 ADT tag

作为我们的附加参数

也就是说 这个函数指针类型MANIPULATE_OBJECT

它带两个参数 第一个参数是ADTe

第二个参数是ADT tag

返回值是一个void

这就是我们的操纵函数

如果你觉得这个函数指针类型的声明

仍然不够一般化 那我建议你

还可以把它的返回值也设成void*

就什么东西都可以带回来了 也可以

没有什么东西可带 返回NULL

有什么东西可带

你就构造一个动态数据对象

然后把它的地址返回来就完了

这也很典型吧

可以按照这个方式去做

然后看我们LlTraverse这个函数

这个时候我就需要传三个参数

第一个参数是抽象的链表list

第二个参数是操纵对象 是个manipulate

第三个参数就是我们的附加参数tag

调用LlTraverse的那个程序员或者那片代码

它负责传三个实际参数进来

我们先看LlTraverse这个函数的实现

三个参数 PLIST类型的list

MANIPULATE_OBJECT类型的manipulate

ADT类型的tag

tag就是我们的附加参数

我首先让list->head初始化给t

PNODE类型的t 然后while

if(manipulate),(*manipulate)(t->data, ag),

t = t->next

你看到了 我们的程序代码

就是把这个第三个参数tag

也就是我们的附加参数

传递给manipulate所指向的目标函数

tag这个参数传递给LlTraverse的

LlTraverse本身不用它

它内部没有使用过tag

它就是简单地拿到一个附加参数

然后转手扔给了manipulate

所指向的那个目标函数

它自己没对它做任何操作

基于Linux的C++课程列表:

第一讲 C/C++基本语法元素

-1.1 提纲

--LinuxCPP0101

-1.2 程序设计的基本概念

--LinuxCPP0102

-1.3 简单C/C++程序介绍

--LinuxCPP0103

-1.4 程序设计的基本流程

--LinuxCPP0104

-1.5 基本语法元素

--LinuxCPP0105

-1.6 程序设计风格

--LinuxCPP0106

-1.7 编程实践

--LinuxCPP0107

-第一讲 C/C++基本语法元素--编程实践提交入口

第二讲 程序控制结构

-2.1 提纲

--LinuxCPP0201

-2.2 结构化程序设计基础

--LinuxCPP0202

-2.3 布尔数据

--LinuxCPP0203

-2.4 分支结构

--LinuxCPP0204

-2.5 break语句

--LinuxCPP0205

-2.6 循环结构

--LinuxCPP0206

-2.7 编程实践

--LinuxCPP0207

-第二讲 程序控制结构--编程实践提交入口

第三讲 函数

-3.1 提纲

--LinuxCPP0301

-3.2 函数声明、调用与定义

--LinuxCPP0302

-3.3 函数调用栈框架

--LinuxCPP0303

-3.4 编程实践

--LinuxCPP0304

-第三讲 函数--编程实践提交入口

第四讲 算法

-4.1 提纲

--LinuxCPP0401

-4.2 算法概念与特征

--LinuxCPP0402

-4.3 算法描述

--LinuxCPP0403

-4.4 算法设计与实现

--LinuxCPP0404

-4.5 递归算法(一)

--LinuxCPP0405

-4.6 递归算法(二)

--LinuxCPP0406

-4.7 容错与计算复杂度

--LinuxCPP0407

-4.8 编程实践

--LinuxCPP0408

-第四讲 算法--编程实践提交入口

第五讲 程序组织与开发方法

-5.1 提纲

--LinuxCPP0501

-5.2 库与接口

--LinuxCPP0502

-5.3 随机数库(一)

--LinuxCPP0503

-5.4 随机数库(二)

--LinuxCPP0504

-5.5 作用域与生存期

--LinuxCPP0505

-5.6 典型软件开发流程(一)

--LinuxCPP0506

-5.7 典型软件开发流程(二)

--LinuxCPP0507

-5.8 编程实践

--LinuxCPP0508

-第五讲 程序组织与开发方法--编程实践提交入口

第六讲 复合数据类型

-6.1 提纲

--LinuxCPP0601

-6.2 字符

--LinuxCPP0602

-6.3 数组(一)

--LinuxCPP0603

-6.4 数组(二)

--LinuxCPP0604

-6.5 结构体

--LinuxCPP0605

-6.6 编程实践

--LinuxCPP0606

-第六讲 复合数据类型--编程实践提交入口

第七讲 指针与引用

-7.1 提纲

--LinuxCPP0701

-7.2 指针基本概念

--LinuxCPP0702

-7.3 指针与函数

--LinuxCPP0703

-7.4 指针与复合数据类型(一)

--LinuxCPP0704

-7.5 指针与复合数据类型(二)

--LinuxCPP0705

-7.6 字符串

--LinuxCPP0706

-7.7 动态存储管理(一)

--LinuxCPP0707

-7.8 动态存储管理(二)

--LinuxCPP0708

-7.9 引用

--LinuxCPP0709

-7.10 编程实践

--LinuxCPP0710

-第七讲 指针与引用--编程实践提交入口

第八讲 链表与程序抽象

-8.1 提纲

--LinuxCPP0801

-8.2 数据抽象(一)

--LinuxCPP0802

-8.3 数据抽象(二)

--LinuxCPP0803

-8.4 链表(一)

--LinuxCPP0804

-8.5 链表(二)

--LinuxCPP0805

-8.6 链表(三)

--LinuxCPP0806

-8.7 链表(四)

--LinuxCPP0807

-8.8 函数指针(一)

--LinuxCPP0808

-8.9 函数指针(二)

--LinuxCPP0809

-8.10 抽象链表(一)

--LinuxCPP0810

-8.11 抽象链表(二)

--LinuxCPP0811

-8.12 编程实践

--LinuxCPP0812

-第八讲 链表与程序抽象--编程实践提交入口

第九讲 类与对象

-9.1 提纲

--LinuxCPP0901

-9.2 程序抽象与面向对象

--LinuxCPP0902

-9.3 类类型

--LinuxCPP0903

-9.4 对象(一)

--LinuxCPP0904

-9.5 对象(二)

--LinuxCPP0905

-9.6 类与对象的成员(一)

--LinuxCPP0906

-9.7 类与对象的成员(二)

--LinuxCPP0907

-9.8 类与对象的成员(三)

--LinuxCPP0908

-9.9 继承(一)

--LinuxCPP0909

-9.10 继承(二)

--LinuxCPP0910

-9.11 继承(三)

--LinuxCPP0911

-9.12 多态(一)

--LinuxCPP0912

-9.13 多态(二)

--LinuxCPP0913

-9.14 编程实践

--LinuxCPP0914

-第九讲 类与对象--编程实践提交入口

第十讲 操作符重载

-10.1 提纲

--LinuxCPP1001

-10.2 四则运算符重载(一)

--LinuxCPP1002

-10.3 四则运算符重载(二)

--LinuxCPP1003

-10.4 关系与下标操作符重载

--LinuxCPP1004

-10.5 赋值操作符重载(一)

--LinuxCPP1005

-10.6 赋值操作符重载(二)

--LinuxCPP1006

-10.7 赋值操作符重载(三)

--LinuxCPP1007

-10.8 赋值操作符重载(四)

--LinuxCPP1008

-10.9 赋值操作符重载(五)

--LinuxCPP1009

-10.10 流操作符重载(一)

--LinuxCPP1010

-10.11 流操作符重载(二)

--LinuxCPP1011

-10.12 流操作符重载(三)

--LinuxCPP1012

-10.13 操作符重载总结

--LinuxCPP1013

-10.14 编程实践

--LinuxCPP1014

-第十讲 操作符重载--编程实践提交入口

第十一讲 泛型编程

-11.1 提纲

--LinuxCPP1101

-11.2 泛型编程概览

--LinuxCPP1102

-11.3 异常处理机制(一)

--LinuxCPP1103

-11.4 异常处理机制(二)

--LinuxCPP1104

-11.5 运行期型式信息(一)

--LinuxCPP1105

-11.6 运行期型式信息(二)

--LinuxCPP1106

-11.7 模板与型式参数化

--LinuxCPP1107

-11.8 题外话:术语翻译

--LinuxCPP1108

-11.9 泛型编程实践(一)

--LinuxCPP1109

-11.10 泛型编程实践(二)

--LinuxCPP1110

-11.11 泛型编程实践(三)

--LinuxCPP1111

-11.12 泛型编程实践(四)

--LinuxCPP1112

-11.13 泛型编程实践(五)

--LinuxCPP1113

-11.14 泛型编程实践(六)

--LinuxCPP1114

-11.15 泛型编程实践(七)

--LinuxCPP1115

-11.16 泛型编程实践(八)

--LinuxCPP1116

-11.17 泛型编程实践(九)

--LinuxCPP1117

-11.18 泛型编程实践(十)

--LinuxCPP1118

-11.19 编程实践

--LinuxCPP1119

-第十一讲 泛型编程--编程实践提交入口

第十二讲 Linux系统编程基础

-12.1 提纲

--LinuxCPP1201

-12.2 程序执行环境(一)

--LinuxCPP1202

-12.3 程序执行环境(二)

--LinuxCPP1203

-12.4 程序执行环境(三)

--LinuxCPP1204

-12.5 程序执行环境(四)

--LinuxCPP1205

-12.6 输入输出(一)

--LinuxCPP1206

-12.7 输入输出(二)

--LinuxCPP1207

-12.8 文件系统

--LinuxCPP1208

-12.9 设备

--LinuxCPP1209

-12.10 库(一)

--LinuxCPP1210

-12.11 库(二)

--LinuxCPP1211

-12.12 makefile文件(一)

--LinuxCPP1212

-12.13 makefile文件(二)

--LinuxCPP1213

-12.14 makefile文件(三)

--LinuxCPP1214

-12.15 编程实践

--LinuxCPP1215

-第十二讲 Linux系统编程基础--编程实践提交入口

第十三讲 进程编程

-13.01 提纲

--LinuxCPP1301

-13.02 进程基本概念

--LinuxCPP1302

-13.03 信号

--LinuxCPP1303

-13.04 进程管理(一)

--LinuxCPP1304

-13.05 进程管理(二)

--LinuxCPP1305

-13.06 进程管理(三)

--LinuxCPP1306

-13.07 进程间通信(一)

--LinuxCPP1307

-13.08 进程间通信(二)

--LinuxCPP1308

-13.09 进程间通信(三)

--LinuxCPP1309

-13.10 进程间通信(四)

--LinuxCPP1310

-13.11 进程池

--LinuxCPP1311

-13.12 编程实践

--LinuxCPP1312

-第十三讲 进程编程--编程实践提交入口

第十四讲 线程编程

-14.1 提纲

--LinuxCPP1401

-14.2 线程基本概念

--LinuxCPP1402

-14.3 线程管理(一)

--LinuxCPP1403

-14.4 线程管理(二)

--LinuxCPP1404

-14.5 线程管理(三)

--LinuxCPP1405

-14.6 线程管理(四)

--LinuxCPP1406

-14.7 线程同步机制(一)

--LinuxCPP1407

-14.8 线程同步机制(二)

--LinuxCPP1408

-14.9 C++11线程库(一)

--LinuxCPP1409

-14.10 C++11线程库(二)

--LinuxCPP1410

-14.11 C++11线程库(三)

--LinuxCPP1411

-14.12 C++11线程库(四)

--LinuxCPP1412

-14.13 C++11线程库(五)

--LinuxCPP1413

-14.14 编程实践

--LinuxCPP1414

-第十四讲 线程编程--编程实践提交入口

第十五讲 网络编程

-15.1 提纲

--LinuxCPP1501

-15.2 Internet网络协议

--LinuxCPP1502

-15.3 套接字(一)

--LinuxCPP1503

-15.4 套接字(二)

--LinuxCPP1504

-15.5 编程实践

--LinuxCPP1505

-第十五讲 网络编程--编程实践提交入口

课程文档

-课程PDF文件

LinuxCPP0810笔记与讨论

也许你还感兴趣的课程:

© 柠檬大学-慕课导航 课程版权归原始院校所有,
本网站仅通过互联网进行慕课课程索引,不提供在线课程学习和视频,请同学们点击报名到课程提供网站进行学习。