当前课程知识点:基于Linux的C++ > 第十一讲 泛型编程 > 11.12 泛型编程实践(四) > LinuxCPP1112
接下来我们讨论怎么设计自己的模板
首先从函数模板开始
函数模板的最主要的目的
是为了设计通用的函数
以适应广泛的数据型式
基本定义格式是这样的
template〈模板型式参数列表〉
后者跟着是函数返回值类型、函数的名称、函数的参数列表
后者跟着是函数返回值类型、函数的名称、函数的参数列表
就是在整个函数的前边
加上template〈模板型式参数列表〉
对于函数原型来讲就应该这么写
template〈classT 〉void Swap( T & a, T & b )
如果是函数实现你就 “{}” 后面跟着函数体就完了
如果是函数实现你就 “{}” 后面跟着函数体就完了
都按照这个模式
class 这个关键字和 typename 是一模一样的
我前面不讲了吗
在这个地方用 class 也行 用 typename 也行
推荐使用 typename
函数模板的体化和特化 有一些需要说明的
函数模板的体化和特化 有一些需要说明的
针对于特定型的参数
在声明和第一次调用的时候
这个模板才会被体化
也就是说 函数体化是在它声明和第一次调用的时候进行的 这是一个
也就是说 函数体化是在它声明和第一次调用的时候进行的 这是一个
第二个 每次体化
它都形成针对特定型参数的重载函数版本
对于一个型 T1
它会生成一个体化的函数
对于一个型 T2
它会生成一个体化的函数
这两个函数显然是不一样的
因为它们的型不一样
这是两个重载的函数 特别注意这一点
第三个 文件最终只保留特定型参数的一份体化版本
第三个 文件最终只保留特定型参数的一份体化版本
如果对于同一个型
实际上你生成了好多个体化版本的函数
那么最终它保留其中的一份就够了
多余的没必要 这是第三个
第四个 显式体化主要用于库的设计
你可以做显式体化
但是这种东西主要是用于库设计的
还有你可以做显式的特化
显式特化会覆盖体化的同样的版本
对于同型的那样的函体
如果已经有了一个体化的版
后来你又做了特化的版
那特化版就会覆盖那个体化的版
它不会去调用那个体化的
如果它先看到了特化
甚至它连体化那个版本都不会去做了
就是直接调用你特化那个版本
去执行就完了
那我们看这样一个例子
对于一个函数模板
基本的声明格式和定义的格式就是这个样子
基本的声明格式和定义的格式就是这个样子
如果你想显式地体化它
那么你可以有两种方式
一 使用显式的长整型模板参数来体化这个函数 f() 这是可以的
一 使用显式的长整型模板参数来体化这个函数 f() 这是可以的
前面加一个 template 关键字
就表示它是一个显式的体化
后面没有 “〈〉” 的
所以这就是一个显式体化
只有一个 template 关键字
你想让编译器自动去推导这个函数的版本
那么你可以不写这个 “〈〉”
第二种方式
它会利用 d 的型来推导这个函数模板
它的模板实际参数应该是什么型
它的模板实际参数应该是什么型
这一样是显式体化
显式特化和体化是不一样的
template那个关键字后边
会有一个 “〈〉”
里面什么东西都没有的 “〈〉”
这个就叫显式特化
我们这个里边使用显式的整型参数来做显式特化
我们这个里边使用显式的整型参数来做显式特化
你同样地可以让编译器去自动地推导这个函数的模板实际参数型
你同样地可以让编译器去自动地推导这个函数的模板实际参数型
第二个 “〈〉”里面不写 可以
但是 template〈〉 那个不能省了
那是显式特化的标志
只有 template 那是显式体化
不是显式特化
有 template〈〉 里面还带着东西——typename 的
有 template〈〉 里面还带着东西——typename 的
那个就是模板
它既不是体化也不是特化
我们看这样一个例子
这个是我们的交换函数 Swap()
带两个参数 都是两个引用
因为我想交换两个数据对象的值嘛
这两个数据对象的型被我们参数化 写在模板参数里
这两个数据对象的型被我们参数化 写在模板参数里
template〈class T 〉void Swap( T & a, T & b )
返回值是个 void
两个参数的型就是 T &、T &
都是对那个 T 型的引用
函数里就可以使用那个模板参数的型
就相当于那个型已知一样
去写你的程序代码
具体实现很简单 三步互换
这个没什么可说的
我们看怎么用
我定义两个量 m、n
然后定义俩字符量 a 和 b
定义两个 double 量 c 和 d
我们可以 Swap( m, n )
像普通函数一样调用 Swap()
括号里传两个参数 m、n
这是一个正确调用 它会体化 Swap()
它就会根据函数模板
来体化一个 Swap() 函数
体化后的这个函数将会是什么样子呢
它将会接受两个整型引用的参数
因为你传的是 m 和 n
它发现可以把 T 替换成 int 完成这个调用
它发现可以把 T 替换成 int 完成这个调用
那么它就会体化这个 Swap( int &, int & )
然后形成这个调用
体化这个 把这个 T 全都换成 int 形成一个函数
体化这个 把这个 T 全都换成 int 形成一个函数
然后调用形成后的这个函数
它是正确体化的
第二个 Swap〈char〉( m, n )
你想把它当成一个字符来调用
行不行呢 行
因为正常情况下
m 和 n 本身的型是整数
所以如果你要体化它
它就会体化整数版本
但是你在这里边用一个〈char〉
指明让它体化 char 版本
它的意思就是用 char 来替代这个函数模板中的 T
它的意思就是用 char 来替代这个函数模板中的 T
生成一个 Swap() 函数
这个地方就会调用生成的那个函数
所以它体化的将是 Swap( char &, cha r& ) 这个函数
所以它体化的将是 Swap( char &, cha r& ) 这个函数
然后我们调用的就是这个函数
第三个 Swap〈double〉( c, d )
这也没问题
你传一个 double 进去
就表示这个 T 型是 double
它就给你生成一个 double 版本的 Swap()
然后调用它 这是我们的交换函数
你看函数模板的实现和使用
看上去实际上是相当直接的
接下来的一个重要概念就是函子
我们要同学们写一个函数
求某个数据集的最小元
元素类型是什么呢 是 T
也就是说什么类型都有可能
那么你怎么解决这个问题
我要你写一个抽象的算法
那么有一种实现策略
就是使用函数指针作为回调函数参数
我们讲数据抽象与程序抽象的时候
特别谈到过 我们要使用一个函数指针作为我们那个函数的回调函数参数
特别谈到过 我们要使用一个函数指针作为我们那个函数的回调函数参数
用它来完成具体的数据的操作
这样的话 我们写的原来那个函数才是一个抽象的算法 抽象的函数
这样的话 我们写的原来那个函数才是一个抽象的算法 抽象的函数
就这个意思 所以这道题本身仍然可以使用函数指针
就这个意思 所以这道题本身仍然可以使用函数指针
作为我们的回调函数参数来进行编程
这是第一种实现策略
第二种实现策略呢
就是使用我们现在要讲的函子来作为回调函数的参数
就是使用我们现在要讲的函子来作为回调函数的参数
我们首先来看函数指针实现
template〈typename T〉
Min() 函数本身
返回值是一个 const T &
接受的三个参数 一个是 const T * a
指向这个数据集的基地址
一个 int n 表示元素的个数
还有一个 comparer
这个比较函数 传的三个参数
我们内部实现
首先把索引定义成 0
然后写一个 for 循环
调用 comparer那个函数指针
所指向的比较函数进行两个元素的比较
返回较小的那一个
for 循环里记录最小元的索引
最后返回那个最小元 就可以了
你看这个里面
模板起到了非常重要的作用
T 型被我们抽取出来作为模板的参数
从而保证了我们所写的这段代码
可以适应广泛的型式
不管这个 T 型是什么
-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 编程实践
-第十五讲 网络编程--编程实践提交入口