当前课程知识点:基于Linux的C++ > 第八讲 链表与程序抽象 > 8.8 函数指针(一) > LinuxCPP0808
在讨论如何编写更抽象的链表库之前
我们还需要补充一个知识点
就是函数指针
函数指针的目的和意义
主要是用于抽象的数据和抽象代码的表示
我们前面讲过 数据和算法之间
它是对立统一的关系
不是有这样的一个公式吗
程序=数据结构+算法
那么如果你仅仅按照
这样一个方式来看待它
数据结构就是数据结构
算法就是算法 两者是泾渭分明的关系
那么你实际上就犯了孤立主义的错误
马克思主义原理告诉我们
这种东西你完全孤立看是不对的
它相当于矛盾的双方
它实际上是可以互相转化的
数据结构和算法从某种程度上来讲
是一个辩证统一的
它们既对立又统一
有了函数指针这个概念之后
那么我们的数据和我们的算法
就能够统一起来
如果没有它 它们就是对立的
但是有了它 两者就可以统一起来
为什么能够做到这一点呢
最主要的一点
我们来看我们执行一个函数
我们怎么执行它
我们需要知道这个函数的入口地址
对于我们计算机系统结构来讲
只要知道这个函数的入口地址
实际上就知道这个函数
第一条指令应该怎么去做
只要知道它第一条指令怎么去做
那么这个函数就能够做下去了
做完第一条指令 做第二条指令
接下来做第三条指令
如果有跳转 就跳转到指定的指令
当所有指令都做完 它有个return语句
它能够返回它的主调函数
这是很明确的执行流程
这也就是说
对于一个函数的执行过程来讲
函数的入口地址是个关键的地方
入口地址 那也是地址啊
它和我们数据的地址有差别吗 没差别
它放在我们计算机的内存条里
不管是数据还是代码
那个编号是统一编址的
所以说数据的地址也好
算法和函数的地址也好
对于我们计算机系统结构来讲
两者是无差别的 这就暗示着
我们能不能够将一个函数的入口地址
也保存起来呢
答案当然是可以的 这个就叫函数指针
什么意思 就是指向一个函数的指针
一旦我们将函数的入口地址
这样一个数值作为数据保存起来
那么我们就可以通过这个指针
指向那个函数的入口地址
然后通过指针的引领操作符
来访问这个指针所指向的目标函数
这个就是函数指针的主要的目的
首先我们要来看一个非常重要的数据型void*
它是一个哑型指针
很特殊的一个哑型指针
它用来表达抽象的目标数据对象
这是非常非常有用的一个数据型
为什么呢 因为对于一个指针来讲
不管它指向哪一种类型
指针的数据地址
它实际上存储空间是固定的
所以它事实上可以表达指向任意类型
对象的数据的这样一个概念
它既然可以表达任意对象的地址
就可以代表着任意类型的对象
到了这里 我们就很明确了
哑型指针它将充当我们抽象数据类型的概念
这就是哑型指针的根本目的
我们前面讲过 我们抽象的链表
它实际上不抽象 它只能存点数据结构
作为一个容器只能存储一个固定的一个对象
这个显然是不恰当的
当我们想把抽象的链表和它所存储点的
数据结构完全给割裂开的时候
那么就必须保证抽象的链表数据结构里面
不能有对抽象的点库的任意的函数的调用
也不能使用点库中定义的任意的类型
这样才能让它们完全地独立开
那么我们怎么指代抽象链表里面的
节点里面的data字段
我们怎么指代哑型指针
我原来用的是point* 现在把它替换成void*
这就意味着我们的抽象链表中
将不再保存指向一个点的结构体一个指针
而是指向一个哑型的指针
特别注意这个概念
ADT作为一个指向void类型的一个指针
并不意味着它的目标数据对象
是一个“无”的类型的一个概念
是一个void 但是并不意味着
它的目标数据对象的类型真的是没有
不 仅仅意味着
我们的目标数据对象类型是未知的
我不知道它具体的类型是什么
我们只知道有一个指针
指向一个目标数据对象
这样就完成了一个通用性的编程
这就是抽象数据类型的根本意义之所在
特别重要这个地方
接下来我们来看怎么定义像这样函数指针
它的定义格式有点特殊
数据类型 小括号对
小括号对里面有一个“*”号
后面跟着一个函数指针的数据对象的名称
小括号对结束之后
后面再跟着一个小括号对
跟着一个形式参数列表
后面跟着一个分号
你比如讲我这里边有一个例子
就char * ( * as_string )( ADT object )
这个我们就定义了函数指针类型的变量
这个变量的名字叫as_string
它是一个指针 它将指向一个函数
那个函数带有一个ADT类型的参数
返回值是char *
这个就是我们as_string函数指针变量定义
所表达的根本的意思
如果没有第一个小括号对
这个定义就是不对的
不是说这个定义在语法上是非法的
不 在语法上它仍然是成立的
但是定义本身和我们这个
函数指针类型变量的定义就不符了
这就意味着如果没有第一个小括号对
这就意味着我们实际上声明了一个函数
这是一个函数原型了
在这个函数原型里面函数名字是as_string
它带一个ADT类型的参数
返回值是什么呢 返回值是char * *
也就是指向指向字符的指针的指针
这个本身是合规的
但是和我们刚才那个想法是不符的
我们想定义什么呢
是指向一个函数的指针
所以第一个小括号对是一定不能省略的
有了这个函数指针变量的定义之后
那么我们就可以把as_string
作为一个普通变量一样
你就使用就OK了
它可以被赋值 可以赋值给别人
你就按照这个方式操作就行了
但是你要记得 这个变量的类型很特殊
它指向一个函数
那个函数有一个ADT类型参数返回值是char *
凡是这样的函数 它就都能够指向
这就意味着凡是具有这样特征的函数
都可以把它的入口地址
赋值给as_string作为它的值
同学们一定要明白这个概念
现在我们来看这样一个例子
假设有一个函数名字叫
char * DoTransformObjectIntoString(ADT object)
它带一个ADT类型的参数
函数返回值很简单
我就调用一个PtTransformIntoString
然后把我们这个object
转换成PPOINT类型之后
再转换成字符串返回
这就是我们的DoTransformObjectIntoString
这个函数要做的事情
你注意看这个函数
它带有一个ADT类型的参数 返回值是char *
OK 有了这个函数
那么我们就可以将这个函数的入口地址
赋值给我们的函数指针变量as_string
我们把DoTransformObjectIntoString
赋值给as_string
这就完成了函数指针变量的赋值
你注意 当对函数指针变量进行赋值的时候
因为我们要传递的是函数的入口地址
所以后面只能出现这个函数的名字
千万不能跟着一个小括号对
那个就是函数调用
有了小括号对就是一个函数调用
有了函数调用那实际上意味着
把这个函数调用的返回值传给as_string
而不是把这个函数的入口地址传给as_string
如果像这样一个函数指针变量
已经被赋值了
那么你就可以通过这个函数指针
来访问它所指向的那个被调函数
来访问它指向的那个目标函数
首先你要知道 函数指针被赋值之后
它实际上就保存了函数的入口地址
那么它就指向那个地方
这是很明确的一件事情
我们通过函数指针
就可以调用它的目标函数
怎么调用 引领
引领完 它就是那个目标函数
然后调用函数它 就OK了
我们看这个例子char * returned_value
这是一个返回值这样的一个字符串
我假设我有一个PPOINT类型的变量pt
然后我把它初始化成PtCreate(10, 20)
我用10、20来构造一个点
然后把它初始化给pt
我们就有一个点出来了
你回忆一下点库我们的设计
PtCreate实际上是我们new(POINT)出来的
是个动态构造出来的
然后我DoTransformObjectIntoString
把这个函数入口地址赋值给as_string之后
就可以像普通的函数一样调用as_string
你就可以认为as_string
就是那个对应的实际函数
DoTransformObjectIntoString 就OK了
因为as_string需要带的是ADT类型的参数
而不是PPOINT类型的参数
而实际上是PPOINT类型的对象
要传进去 所以我要把它转换成ADT
在它的内部
DoTransformObjectIntoString这个函数的内部
还要把pt这个对象
从ADT类型再重新转换成PPOINT
然后才能去操作
中间这两步转换其实都是必要的
这个就是函数指针的用法
还是我刚才讲的
严格讲起来as_string它实际上是一个指针
你要想访问它的目标数据对象
或者它的目标函数
需要使用一个引领操作符
其实标准格式就应该这么写:*as_string
我就引领它的目标数据对象
这是一个什么 这是一个函数
那我们就调用这个函数
后面传一个参数给它
这个小括号对同样是必不可少的
如果没有这个小括号对
那就相当于as_string 就是一个函数 然后调用
这里面因为它是一个函数指针
那么你还可以调用 没问题
as_string就调用这个函数指针变量
所指向的那个目标函数DoTransformObjectIntoString
然后去做点的转换
转换完了以后 它会返回一个字符串
前面用“*”去引领
返回的那个目标字符串
我们实际上得到了
返回的目标字符串的0号字符
然后你想赋值给returned_value 赋值不兼容
把字符赋值给一个字符串
编译器会告诉你“错了” 这事做不了
所以第一个小括号对一定是不能省略的
-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 编程实践
-第十五讲 网络编程--编程实践提交入口