当前课程知识点:基于Linux的C++ > 第十一讲 泛型编程 > 11.14 泛型编程实践(六) > LinuxCPP1114
接下来我们讨论类模板
类模板的目的和函数模板一样
要设计通用的类型式 以适应广泛的数据型式
类模板的定义格式其实我们刚才看到了
不有一个 Comparer 那个类吗
那就是一个类模板
标准格式:template〈模板形式的参数列表〉
class 后面跟着类的名称
template〈typename T〉 class A
类模板的成员像普通的成员函数一样定义就可以了
类模板的成员像普通的成员函数一样定义就可以了
定义在类外和类内都可以
但是你定义在类外的时候
需要注意
需要在类名的后边
使用模板参数去固定那个类的名字
我们前面在使用向量的时候
不特别谈到过吗
向量那个类不叫 vector
那叫 vector〈int〉
你想保存整数
那个向量的类名字叫 vector〈int〉
不叫 vector
vector 是那个类的模板
vector〈int〉才是它对应的
整型向量的类的名字
所以当你把类的成员函数
定义在类外的时候 每个类名的后边
都要用 “〈〉” 跟着模板的参数
必须按照这个方式
用它来区分非模板类的成员函数的
所以你必须遵照这个方式来写
就像 template〈typename T〉
返回值是 T 成员函数名字叫 f
前面的类名 解析的时候要写 “A〈T〉::”
必须按照这个方式来写才可以
类成员函数本身还可以是模板
甚至可以是和我们这个类模板
完全无关的另外一个模板
这没关系 完全可以这么做
你比如讲 我有一个 class A 这个类
这里边有一个成员函数 f()
在定义这个成员函数 f() 的时候
我仍然使用了一个模板 template〈typename U〉
对于这个函数来讲 它的模板参数
和这个类的模板参数不一样
是完全合法的
当你定义这个成员函数的时候
尤其是在类外定义这个成员函数
就需要特别注意了
这个模板的头部实际上很费劲
要写 template〈typename T 〉
继续写 template〈typename U〉
前面那个 template〈〉是指类模板的标记
后面那个 template〈〉
是指这个函数的模板的标记
这两个必须按照这个顺序写在这个地方
然后你才能写 T A〈T〉::f()
按照这个方式
完成这个类的成员函数的定义
类模板的体化和函数模板不一样
类模板体化的时候要必须给出它的模板实际参数 不能缺省
类模板体化的时候要必须给出它的模板实际参数 不能缺省
它很难去推演
这是非常重要的一个地方
必须给出它的型
以区分它是不同的类
类模板体化的时候
编译器将会生成这个模板类
和它相应的成员函数的代码
成员函数是在那个成员函数
被调用的时候体化
虚函数在类的构造的时候会体化
体化的时机是不一样的
类模板同样可以显式体化
你同样地写 template class A〈int〉
这就显式地体化了一个类
构造了一个整型的 A 类
A〈int〉这样的一个类
这是一个显式体化版本
为什么显式体化一个类模板呢
主要的目的是为了解决
模板库创建的问题
对于一个库来讲 库的使用者
他可能没有机会进行类模板的体化
而没有体化的类模板
它的定义不会出现在目标文件里
所以类模板的显式体化
对于库的设计者来讲
是非常重要的一件事情
你如果不设计库
显式体化意义就不是特别大
显式体化类模板以后
显式体化它的构造函数
其它成员函数可以体化 也可以不体化
需要用了就体化
不需要用就可以不体化
这个没关系
同样地 类模板也可以显式特化
你使用特定的型或者值来显式特化类模板 完成类模板代码的定制
你使用特定的型或者值来显式特化类模板 完成类模板代码的定制
这是可以的
你比如说 template〈〉 class A〈char〉 可以啊
后面给出这个类的定义 没有问题
就按照这个方式显式特化这个类模板
形成一个 A〈char〉类 这是可以的
显式特化的类并不需要
和原始的模板相同
特化版本可以具有不同的数据成员
和不同的成员函数 这个没关系
显式特化和函数模板一样
同样会覆盖它的体化版本
类模板还可以做部分特化
如果你在定义这个类模板的时候
它是一个模板套着一个模板
是嵌套模板定义
这同样是合法的
你可以只特化其中的一个部分
剩余部分仍然是模板
所以部分特化的类模板依然是类模板
类模板的部分特化
就允许你对类模板做部分定制
在定义类模板的时候
可以提供缺省的模板参数
后面跟着一个初始化符号
跟着一个缺省的类型的名字
或者缺省的值
就按照这个方式提供缺省模板参数
我们来看一个类模板的实现
我们提供了一个特殊的数据结构 叫队列
队列和栈不一样 它是先进先出规则
在这里我们使用一个单向的链表
来保存队列中的元素
提供了一个队列项类的前置声明
它当然是一个模板
template〈typename T〉 class JuQueueItem
这是一个对列项类
我们这里只声明没有实现
没有定义 对吧 只声明无定义
这是一个前置声明
因为我们的 class JuQueue 要用它
接下来是队列类 同样是一个模板
template〈typename T〉 class JuQueue
提供无参数的缺省构造函数
virutal ~JuQueue() 析构函数
提供 Enter() 和 Leave() 这两个重要的队列操作
一个是入队 一个叫出列
这两个操作 还有一个 IsEmpty()
判定这个队列是不是空的
它的私有字段有两个
一个是头指针 一个是尾指针
分别指向这个队列的第一个元素和最后一个元素
分别指向这个队列的第一个元素和最后一个元素
而我们前面谈链表的时候说了什么
说了这个链表中的每一个结点
必须有一个指针域指向它的下一个结点
必须有一个指针域指向它的下一个结点
这是单向链表的最本质的特征
所以 _head、_tail 这两个指针
都将指向 JuQueueItem〈T〉
这是队列类模板的定义
有几个函数
我们在后边马上就能够看到实现
我们完成队列项类模板的定义:
template〈typename T〉 class JuQueueItem
友元类 JuQueue〈T〉
就把刚才定义的那个队列类模板
体化一个〈T〉 决定它的友元类
JuQueue〈T〉就是 JuQueueItem〈T〉的友元
你存了这种型的数据
那么那个队列本身就是它的友元
以方便它访问这里边结点中的数据
就这个意思
这里有一个公开的单参数的构造函数
_item 就是 T 型的那个数据
_next 就是指向下一个结点的指针
我们实现队列类的析构函数
while( !IsEmpty() ) Leave()
当队列非空的时候
一个接着一个地删除队列中的全部的元素
然后是入队和出列这两个函数
template〈typename T〉 void JuQueue〈T〉::Enter( const T & item )
就把这个项插入到这个队列里
当然会插入到队尾
所以我们会定义一个指针
指向 JuQueueItem〈T〉这样的型
new 一个 JuQueueItem〈T〉的对象
用 item 初始化它 用 p 指向它
然后把 p 所指向的这个对象
插入到队列的尾部
当这个队列空的时候 直接设置
当队列非空的时候 插到尾部
就按这个模式工作就可以了
然后是出列
template〈typename T〉 T JuQueue〈T〉::Leave()
实现它
当队列空的时候没啥可说的
抛出一个异常 引发它就行了
JuQueueItem〈T〉 * p 初始化成 _head
找到对头那个元素
然后把首结点的 _item 字段赋值给 _retval
再接下来销毁我们队列的首结点
让首结点变成原来首结点的下一个结点
这就叫出队了嘛
最后返回那个项的值 就完了
这就是我们出列这个函数的实现
你看 不管是入队还是出列
它同样都是这个类模板中的成员函数
书写格式必须按照模板这个格式来写
具体的应用
这是我们主函数
定义一个指针 p
指向 JuQueue〈int〉类的一个对象
动态构造这个对象
把它的地址初始化给 p
写一个 for 循环 一个接着一个地
把元素插入到队列的尾部
然后我们尝试着出列一个数据
看一看它的结果
这个方式是可以的
我们是把整数作为队列项 没有问题
还有一种 我们实际上可以
把这个整数的地址放在队列里
也就是说 我存的不再是那个整数
而是指向那个整数的指针
这也是可以的
对于整数来讲 意义好像不是那么大
但是如果你是用这个队列
来存储一个复杂的数据结构
那么存指针显然要比存数据要经济得多
至少不需要频繁的拷贝和赋值 对吧
这是最重要的地方
它的具体的应用也是一样的
你看 定义一个变量 t
它是一个指向 JuQueue〈int*〉的一个指针
然后我们 new 一个 JuQueue〈int*〉类的一个对象
把它的地址初始化给 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 编程实践
-第十五讲 网络编程--编程实践提交入口