当前课程知识点:C++语言程序设计进阶 > 第九章 模板与群体数据 > 模板 > 函数模板
思考:如果重载的函数,其解决问题的逻辑是一致的、函数体语句相同,只是处理的数据类型不同,那么写多个相同的函数体,是重复劳动,而且还可能因为代码的冗余造成不一致性。
解决:使用模板
例:求绝对值函数的模板
语法形式:
template <模板参数表>
模板参数表的内容
类型参数:class(或typename) 标识符
常量参数:类型说明符 标识符
模板参数:template <参数表> class标识符
//9_1.cpp #include <iostream> using namespace std; template <class T> //定义函数模板 void outputArray(const T *array, int count) { for (int i = 0; i < count; i++) cout << array[i] << " "; //如果数组元素是类的对象,需要该对象所属类重载了流插入运算符“<<” cout << endl; } int main() { const int A_COUNT = 8, B_COUNT = 8, C_COUNT = 20; int a [A_COUNT] = { 1, 2, 3, 4, 5, 6, 7, 8 }; double b[B_COUNT] = { 1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 7.7, 8.8 }; char c[C_COUNT] = "Welcome!"; cout << " a array contains:" << endl; outputArray(a, A_COUNT); cout << " b array contains:" << endl; outputArray(b, B_COUNT); cout << " c array contains:" << endl; outputArray(c, C_COUNT); return 0; } 运行结果如下: a array contains: 1 2 3 4 5 6 7 8 b array contains: 1.1 2.2 3.3 4.4 5.5 6.6 7.7 8.8 c array contains: W e l c o m e!
一个函数模板并非自动可以处理所有类型的数据
只有能够进行函数模板中运算的类型,可以作为类型实参
自定义的类,需要重载模板中的运算符,才能作为类型实参
大家好
欢迎继续学习C++语言程序设计
这一节我们来学习函数模板
大家有没有这样的经历呢
可能我们写一个函数
它的主体逻辑都是一样的
但是
当我们需要用这样的算法逻辑
去处理不同类型数据的时候呢
我们就要为它写不同的重载函数
比如说
如果我们要写一个
求绝对值的函数
不管你去求整数的绝对值
还是浮点数的绝对值
那么它的算法逻辑都是一样的
但是呢
由于处理的数据类型不同
也就是函数的参数不同
我们就要写多种重载形式的
重载函数
实际上是C++里面
很好的一种机制
但是重载函数
它是对函数的使用者
提供了很大的方便
那么函数的编写者
你还是要写多个重载的函数体
那么多个完全一样
或者是几乎一样的重载函数体
带来的问题不仅仅是冗余
还有可能呢
当你在修改算法的时候
如果没有在各个函数体中
进行同步的修改的话呢
还会造成
你在同一个系统中
处理同类型的问题
用的算法不一致
那么这就是不一致性的问题
其实呢
C++解决这种问题
有一个非常好的办法
就是用函数的模板
接下来呢
我们先通过一个简单的例子
也就是求绝对值函数的例子
来感觉一下
为什么我们需要函数模板
函数模板能为我们节省
什么样的工作量
带来什么样的便利
现在我们看到这里
是两个求绝对值的函数
这两个函数的函数体
语句都是一样的
只是它们的参数类型不一样
那么像这样的重载形式的函数
我们如果这样写
多个重载的函数体呢
实际上是冗余的
在C++中呢
我们可以有比较好的方案
解决这样的冗余问题
就是使用函数模板
我们可以创建一个通用的函数
实际上就是一个函数模板
它可以支持多种不同的形参类型
这样就可以简化
重载函数的函数体设计
具体的呢我们来看看
将这两种函数合在一起
两个重载形式合在一起
实际上它的函数体是一样的
那么它参数类型怎么写呢
写成抽象类型
可定制的类型
这个T类型呢
实际上就是类型参数
我们看到在这个函数头上
这儿有template关键字
后面这有尖括号
尖括号里面呢
就列出来是类型参数
这个类型参数呢
在我们运行的时候
需要调用函数的时候
可以去指定这个类型参数
我们来看这个例子中
定义好了这样的函数模板以后
在主函数中呢
看我们首先调用
求绝对值的函数abs
第一次给的这个参数
是一个int类型的参数n
int类型的
那么编译器在编译的时候
可以根据这个实参的类型
它推导出
在这次调用的时候呢
模板里面的这个T应该为int
因此呢
编译器就会以这个模板为依据
将T都替换为int
为我们生成了一个
可以使用的这个真正的函数
函数模板就不可以直接被调用的
那么调用的时候
都要生成一个函数
这是由编译器生成的
所以这就生成了一种
重载形式的函数
它接收int参数 返回int值
那么在第二次调用的时候呢
给它的这个参数d
是个double类型
同样的道理
编译器因此就推导出
这一次调用的时候
应该将T 替换为double
然后编译器
又为我们生成了一个函数
那第二次调用的呢
是这个接收double类型参数的
这种重载形式的函数
那么我们看到
运行结果分别得到了
整数的绝对值
和浮点数的绝对值
好 看了上面的例子以后呢
接下来我们来看看
函数模板的语法是什么样的
那么定义函数模板呢
要用template关键字
后面尖括号里面是模板参数表
这个是定义普通函数的时候
没有的
那么这个模板参数表里面
容纳的是什么东西呢
实际上就是类型参数
在此之前我们只用过数据参数
函数的形参表里面
列出来都是数据参数
其实类型也是可以作为参数的
类型参数要用到class
或者typename关键字
然后要有标识符
这个标识符呢
就代表一个抽象的类型
那在模板的参数表中
也可以有常量参数
下面呢
我们再来看一个函数模板的例子
这个例题中呢
定义了一个函数模板outputArray
用来输出数组的元素
那么这个数组
它是什么类型的数组呢
数组元素类型用的是
一个抽象的类型
也就是类型参数 T类型
这样的话
这个函数模板就比较通用
可以用来输出
任何类型的一维数组的元素
但是呢
我们也要注意
将来我们使用的时候
打算用它来输出
什么类型的数组呢
如果是基本类型的数组
没有问题
都可以执行这个插入运算
去输出数组的元素
如果这个数组的元素类型
是对象
那么我们就要看一下
这个对象所属的类
是否重载了比如插入运算符
如果它没有重载流插入运算符
我们硬是要用它输出数组元素
那程序会出错的
所以这个时候就要做一下检查
如果没有重载的话呢
需要重载
我们为类重载一下
这个插入运算符
好 现在我们来看在主函数中
用一下这个函数模板
为此我们提前准备好了
三个不同类型的数组
int数组 double数组
和字符数组
然后呢
分别三次调用这个函数模板
实际上在这三次调用的时候呢
编译器都会根据
我们的数组元素的类型呢
去置换这个T
为我们生成每一次
针对这个类型
适用的outputArray函数
这样
编译器实际上为我们生成了
三种重载形式的outputAarray
分别接收不同类型的数组
那我们看到运行结果
正确地输出了每一种
不同类型的数组中的元素
看了这样两个
函数模板的例题以后
大家一定会觉得
模板真是个好东西
但是它也不是
放之四海而皆准的 万能的
本来
不同的运算符
会适用于不同的数据类型
尤其是我们自定义类型
如果我们没有为这个类型
重载某些运算符
那这些运算符
就不能用在这个类型上
所以使用模板的时候
并不是说我们写一个模板
然后给它指定任意类型
编译器都可以为我们
生成合适的函数
我们给模板去指定
类型参数的时候
这个类型
必须能够进行模板函数
函数体所写出来的那些运算
比如说
插入运算符
比如说加法运算符
它能用吗
能用我们才可以给这个类型实参
如果不能用呢
比如说是自定义类型
我们也想用这个函数模板
来处理它
怎么办呢
先给自定义类型
去重载相关的运算符
-导学
--导学
-继承的基本概念和语法
-第七章 继承与派生--继承的基本概念和语法习题
-继承方式
-第七章 继承与派生--继承方式
-基类与派生类类型转换
-第七章 继承与派生--基类与派生类类型转换
-派生类的构造和析构
--派生类的构造函数
--派生类的析构函数
--第七章 继承与派生--派生类的构造和析构
-派生类成员的标识与访问
--虚基类
-第七章 继承与派生--派生类成员的标识与访问
-小结
--小结
-综合实例
--第七章综合实例
-实验七
--实验七
-导学
--导学
-第八章 多态性--导学
-运算符重载
--运算符重载的规则
-第八章 多态性--运算符重载
-虚函数
--虚函数
--虚析构函数
--虚表与动态绑定
-第八章 多态性--虚函数
-抽象类
--抽象类
--第八章 多态性--抽象类
-override与final
-第八章 多态性--override与final
-小结
--第八章小结
-综合实例
--第八章综合实例
-实验八
--实验八
- 第八章讲义
-导学
--导学
-模板
--函数模板
--类模板
-第九章 模板与群体数据--模板
-线性群体
--线性群体的概念
-第九章 模板与群体数据--线性群体
-数组
--数组类模板
-链表
--链表类模板
-第九章 模板与群体数据--链表
-栈
--栈类模板
--栈类模板课后习题
--例9-9 栈的应用课后习题
-队列
--队列类模板
-第九章 模板与群体数据--队列
-排序
--排序概述
--插入排序
--选择排序
--交换排序
-第九章 模板与群体数据--排序
-查找
--查找
--查找课后习题
-小结
--小结
-综合实例
--综合实例
-实验九
--实验九
- 第九章讲义
-导学
--导学
-泛型程序设计及STL的结构
--STL简介
-第十章 泛型程序设计与C++标准模板库--泛型程序设计及STL的结构
-迭代器
--迭代器
-第十章 泛型程序设计与C++标准模板库--迭代器
-容器的基本功能与分类
-第十章 泛型程序设计与C++标准模板库--容器的基本功能与分类
-顺序容器
--顺序容器的特征
--第十章 泛型程序设计与C++标准模板库--顺序容器
-关联容器
--集合
--映射
-第十章 泛型程序设计与C++标准模板库--关联容器
-函数对象
--函数对象
--函数适配器
-算法
--算法
-小结
--第十章小结
-综合实例
--综合实例
-实验十
--实验十
- 第十章讲义
-导学
--导学
-I/O流的概念及流类库结构
-第十一章 流类库与输入/输出--I/O流的概念及流类库结构
-输出流
--输出流概述
--向文本文件输出
--向二进制文件输出
--向字符串输出
-第十一章 流类库与输入/输出--输出流
-输入流
--输入流概述
--输入流应用举例
--从字符串输入
-第十一章 流类库与输入/输出--输入流
-输入/输出流
--输入/输出流
-第十一章 流类库与输入/输出--输入/输出流
-小结
--小结
-综合实例
--综合实例
-实验十一
--实验十一
- 第十一章讲义
-导学
--第12章导学
-异常处理的思想与程序实现
-第十二章 异常处理--异常处理的思想与程序实现
-异常处理中的构造与析构
-第十二章 异常处理--异常处理中的构造与析构
-标准程序库异常处理
-第十二章 异常处理--标准程序库异常处理
-小结
--第12章小结
-综合实例
--综合实例
-实验十二
--实验十二
- 第十二章讲义