首先我们来看泛型编程概览
一 什么是泛型编程
泛型就是通用的型式
编写不依赖数据对象型式的代码就是泛型编程
你写的程序代码和你想要操纵的
那个数据对象的具体的型式相对无关
那么你写的这段代码
我们就称之为泛型的代码
你处理的型 当然也就是所谓的泛型
这是第一个 什么是泛型编程1200:00: 39,280 --> 00:00:42,680第二个 为什么需要泛型编程
前面讨论的 C++ 的语言规范里面
我们使用面向对象的技术
有继承 有多态
可以解决很多现实世界中的问题
但是 在纯粹的面向对象编程里边
涉及到几个问题
这几个问题不太好解决
一个是函数重载的问题
第二个是相似类的定义的问题
第三个是型式兼容性
在这里我们要补充两个知识
一个是异常处理机制
一个是运行期的型式信息
第三个是怎样进行泛型编程
泛型编程的最主要的技术手段
就是模板与型式参数化
我们首先来看第一个问题
函数重载问题
我要求同学们设计一个函数
求两个数据对象的较小者
你注意 我这个题目
我求的是两个数据对象的较小者
并没有指出这两个数据对象的具体的型是什么
所以如果你要编写这样一段程序代码
那么事实上你将提供很多很多个函数
假设我们这样的函数名字叫 Min()
因为重载的机制
所以处理不同型的这样的函数
可以命名成一样的
整数上边可以写一个 Min() 函数
在一个整数 一个浮点数上面
写一个 Min() 函数
在两个浮点数上面写个 Min() 函数
你注意因为参数的顺序的不同
所以一个整数 一个浮点数
你还需要提供两个 Min() 的版本
如果你还有一个类 A
那么还要为这个类 A 提供一个 Min() 版本
如果你有一个类 B
那么你还要为 B 类提供一个 Min() 版本
你看 你的型式是无穷尽的
所以你要写多少个Min () 函数才够啊
函数重载虽然给我们提供了强大的武器
但是要想处理所有的型
你写的 Min() 函数的代码量肯定是巨多的
这样的函数肯定是巨多的
编写的时候代码性质差不多
实现也差不多 但是函数很多
这种写法实际上是有问题的
在引入 C++ 以前
我们怎么解决函数重载问题呢
在 C++ 以前
C 是怎么解决函数重载问题的呢
因为 C 不支持函数重载啊
所以它提供了含参宏
这是一种很巧妙的解决方案
在 C 的代码里边我们可以写 #define Min(x, y) ...
然后替换文本定义成后面这一串
有了这个宏文本 有了这个宏的定义
那么当你实际编写代码的时候
你就可以使用这个宏来编写你的程序
它最终会替换
按照宏的文本最终完成替换
宏 尤其是含参宏 它是型式无关的
所以它在某种程度上
起到了我们那个函数重载的类似的效果
但是宏有一个巨大的缺点
就是它没有型式检查
没有办法在编译期
帮我们查找程序的错误
最重要的 第二个缺点
就是宏文本在替换的时候
它是一个简单的文本替换
你要注意那个替换后的代码到底对不对
有的时候因为操作符的优先级的问题
或者其它的原因
都有可能导致宏替换后的文本是不对的
你看我这个宏替换里边
文本中为什么都带着括号
就是因为确保它的操作符的优先级
不会因为宏文本的替换而发生变化
这是非常重要的一个地方
当然了 你在 C++ 代码里边
依然可以写含参宏
但是因为我们有了函数重载机制
所以很多时候含参宏使用的场合就不多了
从函数重载这个角度来讲
我们需要一种机制
在这种机制下边
能够在语法层面解决我们的宏问题
这是一个 函数重载时候的问题
第二个是相似类的定义问题
你比如讲 我要定义一个动态数组类
那么这个动态数组要存什么数据呢
可能是一个整数 可能是个浮点数
可能是个类对象
也可能是指向类对象的一个指针
所以你定义存储整数的一个动态数组库
这是我们需要的
你需要定义存储浮点数的动态数组库
你需要定义存储某类对象的动态数据库
你需要定义存储某类对象指针的
动态数据库 ... Bla bla bla 一串
这多少是个头啊 没够的
你这个动态数组想存什么
你就得定义这样相对应的动态数组类
非常非常麻烦 你定义像这样的一个类
一个是不够的 因为它要针对特定的型
做特定的处理
所以要定义很多个相似的动态数组类
那么这些代码
要维护起来肯定是非常困难的
所以我们也需要一种机制
能够在语法层面解决我们相似类的重复定义问题
降低我们的编程工作量
这是第二个问题
第三个就是型式兼容性问题
C 的型式转换是 (T)x
这个转换是个相当强的
但是它不安全
对于内建型式来讲
你比如说 int 比如说 double
这是 C 编译器已经替我们内建好的
像这样内建的型式
它的对象的转换安全性基本上能够保证的
只是有可能会损失数据的精度
但是对于类对象的转换
像这样的一种转换因为它不安全
所以有可能导致无法控制的后果
这个后果当然会很严重
到了 C++ 里边呢
它换了一个型式转换的方式
它把那个括号括在 x 上面
不是括在那个类型上了
所以看上去让这个型式转换
变成像一个函数一样
事实上它其实就是一个函数
对于一个特定的型
我们可能需要一个单参数的构造函数
和重载的型式转换操作符
才能完成这样的一个型式转换
同样地 C++ 型式转换这个模式
依然是不安全的
如果你没有实现这样的型式转换
那么它的转换本身就不存在
对于你自己定义的一个特定的类
如果你没有实现单参数构造函数
如果你没有重载型式转换操作符
这样的转换可能就是无法工作的
所以它一样是不太方便的
从类库架构的角度上来讲
类的继承或多态
频繁地要求能够通过基类的指针和引用来访问派生类的对象
这是类库层次的基本要求
所以我们需要沿着类的继承层次
频繁地进行对象的型式转换
这是必须的 而前面的两种转换
它在很多情况下边效果不令人满意
所以已有的 C/C++ 的型式转换都是静态转换
不能够适应指针的多态性
当然它同样不能适应引用的多态性
型式转换本身必须能够适应全部的型式
并且能够很自如地操作
但是很不幸 因为我们的型式是无穷尽的
所以程序员没有办法
你没有这个本事编写完备的型式转换代码
所以我们需要一种很特殊的机制
能够来保持我们的型式兼容性
这样的一种机制必须能够
确保型式转换操作本身是合法有效的
并且在失败的时候通知用户 这是一个
为了让这样一种机制能够工作
我们需要两个特别的技术手段
一个 我需要在运行期维持对象的型式信息
这个东西就叫运行期型式信息（RTTI）
第二个 就是转换结果的确认
通过转换操作的返回值来确认转换后的效果
也就是确认它的结果
如果转换是失败的
你就得触发一个特别的信号
或者给出一个特定的返回值来标记它
这是非常重要的一个地方
如果是一个指向对象的指针
当它转换失败的时候
我们简单的传递一个空指针就OK了
但是如果是一个引用
当它转换失败的时候
我们没有办法返回一个空引用
所以这个时候就需要使用异常处理机制
从实现策略角度来讲
完成这样的型式兼容性的
一个最基本的技术
就是我们这一讲
要讨论的模板与型式参数化