C++模板学习
来源:互联网 发布:清华大学软件学院地址 编辑:程序博客网 时间:2024/05/19 02:19
一、什么是模板
在C++编程中,当我们需要获取两个变量之间的较大值时,考虑到变量的类型可能会是int、double等,最常用的方法往往是通过重载函数,实现不同类型的函数版本:
- int Max(int lhs, int rhs)
- {
- return (lhs > rhs) ? lhs : rhs;
- }
- double Max(double lhs, double rhs)
- {
- return (lhs > rhs) ? lhs : rhs;
- }
此时,当我们接到新的需求,需要获取两个char类型的变量之间的较大值时,我们就会再添加一个char类型重载函数:
- char Max(char lhs, char rhs)
- {
- return (lhs > rhs) ? lhs : rhs;
- }
每当新增一种的类型的需求,又需要实现多一个重载函数,无形中增加了开发人员的工作,当函数逻辑比较复杂,而且比较庞大时,也容易出现错误。从上面的代码可以看出,三个版本的重载函数除了类型不一样之外,逻辑基本一致,完成的功能也是相同的,因此,如果能有一个通用的函数,抽取出这些相同的代码逻辑,将可以大大减少代码的重复,提高代码的重用性。此时,使用C++的模板就可以很好的解决这些问题,通过模板,可以使得开发人员写出更通用、更灵活、类型无关的代码。
二、模板的分类和格式
C++模板(template),主要分为函数模板和类模板。
2.1 函数模板
2.1.1 函数模板的格式和使用
template <class 形参名, class 形参名, ......>
返回类型 函数名(参数列表)
{
//函数体
}
模板定义以关键字template开始,后接以<>括号括住的模板形参表,其中class是关键字,在这里class可以使用typename 代替,<>括号中的模板形参使用逗号进行分隔。下面使用函数模板实现前面的求较大值函数:
- template<typename T>
- T Max(const T &lhs, const T &rhs)
- {
- return (lhs > rhs) ? lhs : rhs;
- }
- //测试用例:
- int a = 34, b = 257;
- double c = 1.4, d = 4.4;
- char e = 'b', f = 'k';
- cout<<Max(a, b)<<endl;//调用了int版本的模板函数,输出为:257
- cout<<Max(c, d)<<endl;//调用了double版本的模板函数,输出为:4.4
- cout<<Max(e, f)<<endl;//调用了char版本的模板函数,输出为:k
当对上面定义的函数模板进行编译时,类型T就会被实际传入的类型所代替,例如Max(a, b)中a和b是int 型,这时函数模板Max中的形参T就会被int 代替,并实例化一个模板函数Max(const int &lhs, const int &rhs);同理,当实际传入的类型为double、char等其他类型时,就会实例化相应类型的模板函数,从而实现了类型无关的泛型编程。
2.1.2 函数模板一般不对实参进行隐式类型转换
好奇的你可能会问,前面几个调用都是用同一种类型,当调用Max(a, b),其中a为int类型,b为double类型,这样会得到正确的结果吗?
- #include <iostream>
- using namespace std;
- template<typename T>
- T Max(const T &lhs, const T &rhs)
- {
- return (lhs > rhs) ? lhs : rhs;
- }
- int main(int argc, const char *argv[])
- {
- int a = 10;
- double b = 5.3;
- cout<< Max(a, b)<<endl;
- }
从编译器的出错信息可以看出,上面的例子在模板形参推导的过程中出现了错误。这是由于编译器在编译时,会将函数模板的形参解析为首先遇到的类型,以上例子中,编译器首先遇到的是int类型,函数模板的形参就被解析为int类型,同时函数模板一般不对实参进行隐式类型转换(除了非const引用或指针到const引用或指针的转换、数组或函数到指针的转换),参数需要完全匹配,因此以上例子不能通过编译。
解决方案有两种:
第一:强制类型转换
- int a = 10;
- double b = 5.3;
- cout<<Max(a, (int)b)<<endl;//输出为:10
第二:在函数模板中增加一种形参类型
- template<typename T1, typename T2>
- T1 Max(const T1 &lhs, const T2 &rhs)
- {
- return (lhs > rhs) ? lhs : rhs;
- }
- int a = 10;
- double b = 10.3;
- cout<<Max(a, b)<<endl;//输出为:10
- template<typename T1, typename T2, typename T3>
- T1 Max(const T2 &lhs, const T3 &rhs)
- {
- return (lhs > rhs) ? lhs : rhs;
- }
- int a = 10;
- double b = 10.3;
- cout<<Max(a, b)<<endl;<span style="white-space:pre"> </span>
出错的原因是,编译器可以根据传入的实参推导出T2和T3的类型,却没有其它信息可以让编译器推导出T1的类型,因此,在这种情况下,我们需要在调用的时候显式指定T1的类型:
- int a = 10;
- double b = 10.3;
- cout<<Max<double>(a, b)<<endl;//输出为:10.3
2.1.3 模板函数 vs 重载函数
在前面说到,通过模板函数可以避免了重载函数的一些问题,在这里大家会不会问一句,模板函数是否可以重载?答案也是一如既往的肯定。
- template<typename T>
- T Max(const T &lhs, const T &rhs)
- {
- return (lhs > rhs) ? lhs : rhs;
- }
- template<typename T>
- T Max(const T &arg1, const T &arg2, const T &arg3)
- {
- T temp = (arg1 > arg2) ? arg1 : arg2;
- return (temp > arg3) ? temp : arg3;
- }
- int a = 1, b = 2, c = 3;
- Max(a, b);//调用两个参数的模板函数
- Max(a, b, c);//调用三个参数的模板函数
- template<typename T>
- T Max(const T &lhs, const T &rhs)
- {
- return (lhs > rhs) ? lhs : rhs;
- }
- int Max(int lhs, int rhs)
- {
- return (lhs > rhs) ? lhs : rhs;
- }
- int a = 1, b = 2;
- Max(a, b);//调用非模板函数
C++中,函数模板与同名的非模板函数重载时,应遵循下列调用原则:
a. 寻找一个参数完全匹配的函数,若找到就调用它。
b. 寻找一个函数模板,若找到就将其实例化生成一个匹配的模板函数并调用它。
c. 若上面两条都失败,则使用函数重载的方法,通过类型转换产生参数匹配,若找到就调用它。
d. 若上面三条都失败,还没有找都匹配的函数,则这个调用是一个错误的调用。
简单来说,就是非模板函数匹配优先于模板函数,没有非模板函数时,选择最匹配和最特化的模板函数。
2.2 类模板
2.2.1 类模板的格式
template<class 形参名,class 形参名,…>
class 类名
{
//...
};
类模板的定义与函数模板类似,也是以关键字template开头,后接模板参数列表。
- template<typename T>
- class Utility
- {
- pubic:
- Utility(T arg): m_value(arg){}
- T Add(T arg) const;
- T Add(T lhs, T rhs);
- private:
- T m_value;
- };
- template<typename T>
- T Utility<T>::Add(const T &arg) const
- {
- return arg + m_value;
- }
- template<typename T>
- T Utility<T>::Add(T lhs, T rhs)
- {
- return lhs + rhs;
- }
- Utility<int> util(1);cout<<util.Add(2)<<endl;//输出为:3
- cout<<util.Add(2, 3.5)<<endl;//输出为:5
上例中的util.Add(2, 3.5)调用能通过编译,实参3.5会被隐式转换为int,并能得到正确结果,而在模板函数中却会出错,函数模板和类模板对于隐式类型转换的要求是不一致的。
三、模板特化
模板特化分为全特化和偏特化两种。其中,类模板可以全特化和偏特化,函数模板只能全特化,函数的模板只能重载,没有偏特化。
3.1 函数模板全特化
- //函数模板通用版本
- template<typename T>
- int Compare(const T lhs, const T rhs)
- {
- if (lhs < rhs) return -1;
- if (rhs < lhs) return 1;
- return 0;
- }
- //函数模板全特化版本
- template<>
- int Compare(const char* lhs,const char* rhs)
- {
- return strcmp(lhs, rhs);
- }
3.2 类模板全特化和偏特化
- //类模板通用版本
- template<typename T>
- class Compare
- {
- public:
- bool IsEqual(T lhs, T rhs)
- {
- return (lhs == rhs);
- }
- };
- //类模板全特化版本
- template<>
- class Compare<const char*>
- {
- public:
- bool IsEqual(const char* lhs, const char* rhs)
- {
- return strcmp(lhs, rhs);
- }
- };
- //类模板通用版本
- template<typename T1, typename T2>
- class Compare
- {
- //...
- };
- //类模板偏特化版本
- template<typename T1, int>
- class Compare
- {
- //...
- };
四、使用模板的注意事项
4.1 typename与class的区别
在模板形参列表中,关键字typename和class没有区别,但在某些情况下,typename与class还是有区别的。
- template<typename T>
- void Func()
- {
- T::A *var;//猜一下,是声明一个指针,还是进行一次乘法操作
- }
4.2 模板声明或定义的范围
模板的声明或定义只能在全局,命名空间或类范围内进行,不能在局部范围,函数内进行,例如不能在main函数中声明或定义一个模板。
4.3 template在声明的时候就需要定义
当实现一个类的时候,我们常常会在一个.h文件中声明类结构,然后在一个.cpp文件中定义该类,下面为Utility.h文件:
- #ifndef UTILITY_H
- #define UTILITY_H
- template<typename T>
- class Utility
- {
- public:
- T Max(const T &lhs, const T &rhs);
- };
- #endif
相应的Utility.cpp文件如下:
- #include "Utility.h"
- template<typename T>
- T Utility<T>::Max(const T &lhs, const T &rhs)
- {
- return (lhs > rhs) ? lhs : rhs;
- }
- #include <iostream>
- #include "Utility.h"
- int main(int argc, const char *argv[])
- {
- Utility<int> util;
- cout<<util.Max(2, 3)<<endl;
- return 0;
- }
《C++编程思想》第15章(第300页)说明了上例出错的原因:
模板定义很特殊。由template<…> 处理的任何东西都意味着编译器在当时不为它分配存储空间,它一直处于等待状态直到被一个模板实例告知。在编译器和连接器的某一处,有一机制能去掉指定模板的多重定义。所以为了容易使用,几乎总是在头文件中放置全部的模板声明和定义。
可行的解决方法主要有三种:
第一种:把模板的声明和定义放在一起
第二种:在Utility.h文件尾部加上#include "Utility.cpp",其实就是第一种方法,只是间接将两个文件拼在一起了。
第三种:C++理论上支持export关键字,以实现模板的分隔编译,本人机器的g++版本为4.7.3,但在使用export关键字时,仍会出现以下问题:
总结:模板是C++泛型编程中不可或缺的一部分,在STL(Standard Template Library)中得到广泛的使用。理解了模板,特别是模板的特化和偏特化,也就基本理解了STL中的核心-traits编程,为STL的学习打下了坚实的基础。
- C++template ;模板学习
- 学习C++模板---模板函数
- C/C++学习之模板
- 学习C++模板---模板类带简单参数
- 学习C++模板---模板类作为基类
- C++primer学习:模板编程(2):类模板的定义
- C++primer学习:模板编成(5):模板实参推断{1}
- C++primer学习:类模板(2)类模板:模板参数,成员模板和控制实例化
- C++Template学习笔记之函数模板
- C/C++学习----第五章 模板
- C++模板 学习要点
- (C/C++学习笔记)函数模板加强
- C++primer学习:模板编程(4)
- C++Primer学习:模板特例化
- STL模板学习之set容器(C/C++)
- 学习C++模板---模板类带简单参数,并且添加缺省参数,特例模板
- C++primer学习:类模板(1):函数模板,模板参数,实例化
- C++-模板
- 直接初始化与复制初始化
- linux备份还原mysql
- 黑马程序员——Java基础——IO流(文件续写)
- HDU_ACM-2014 青年歌手大奖赛-评委打分
- Java学习要点记录
- C++模板学习
- Seafile介绍
- 黑马程序员——Java基础——IO流(文本文件读取方式)
- 实现一个无法被继承的C++类
- js实现图片预加载 imgpreLoad.js
- Eclipse luan(Eclipse4.4)tomcat(无法显示)插件安装不了的解决方法
- (总结)C++中实现代码重用的手段----继承和聚合
- 黑马程序员——Java基础——IO流(拷贝文件)
- POJ 3080:Blue Jeans:枚举求解n个字符串的最长公共连续子串