第1节 模板概念、函数模板定义、调用
一、概述
1、所谓泛型编程,是以独立于任何特定类型的方式编写代码,使用泛型编程时,我们需要提供具体程序实例所操作的类和值。
2、模板是泛型编程的基础,是创建类或者函数的蓝图或公式(编译时变成类或函数)
3、模板支持将类型作为参数,从而实现了对泛型程序设计的直接支持
二、模板函数的定义
使用模板的目的:比如对于返回int和double的相同功能的函数,其实有很多代码是重复的!
template<typename/class 模板名> 参数列表多个用,分隔。
只带类型模板参数的模板函数定义:
template<typename T> //模板参数列表
T funcadd(T a, T b) {
T sum = a + b;
return sum;
}
三、函数模板的调用
调用时,编译器根据实参推断T是什么,推断不出来的时候需要主动提供。
int k1 = funcadd(1,2);
此时编译器推断T为int,所以实例化出一个把T改为int的函数版本。
四、非类型模板参数(int、double之类的)
模板实例化时,非类型模板参数的值必须是常量表达式(编译时就能确定)
带非类型模板参数的定义:
template<int a,int b> //非类型模板参数
int funcadd2() {
int sum = a + b;
return sum;
}
template<typename T,int a, int b>
int funcadd3(T c) {
int sum = int(c) + a + b;
return sum;
}
带非类型模板参数的实现(系统以<>中类型为准进行T的类型推断,而不是()里内容的类型):
int k2 = funcadd2<10, 20>(); //显示提供参数
int k3 = funcadd3<int,200,300>(100);
比较两个字符串是否相等(系统先判断L1为4,L2为5,然后调用成员函数比较两个字符串):
template<unsigned L1, unsigned L2> //非类型模板参数
inline //模板函数可以是内联函数
int charscomp(string p1,string p2){
//上面这一行其实应该这么写:int charscomp(const char(&p1)[L1], const char(&p2)[L2]){
return strcmp(p1,p2);
}
//实现
int k4 = charscomp("xzh", "xzh1"); //系统会根据字符串长度推断L1和L2是多少
//k4=-1
五、总结
1、模板定义并不会导致编译器生成代码,实例化后才有了版本;
2、函数模板定义通常放在.h文件中,多个文件include同一个template是没有重定义问题的(普通函数就不行)!
第2节 类模板概念、定义、使用
一、概述:用类模板实例化一个特定的类
编译器不能为类模板推断模板类型参数,所以为了使用,需要在<>提供额外信息!
二、类模板定义
实例化类模板时,必须有类的全部信息,包括类模板中成员函数的函数体(写在类模板中或写在一个.h文件中)。
template<typename T> //myvector这个容器中保存的元素类型为T
class myvector {
public:
typedef T* myiterator; //省的以后每次都T*了
public:
myvector();
myvector& operator=(const myvector &); //不需要类模板参数
//或者写成 myvector<T>& operator=(const myvector &);
public:
myiterator mybegin(); //起始位置
myiterator myend();
public:
//void func() {}
//如果写在类定义外面,成员函数的模板参数就体现出来了
void func();
};
在这个类模板的定义中:myvector是类模板名,myvector<int>是类型名。
三、类模板的成员函数
1、类模板成员函数的函数体若写在类模板中,会被隐式声明为inline
2、每一次对类模板的实例化,每个新实例都有自己的成员函数,这些成员函数的模板参数肯定是不同的(int,double等)
3、若成员函数写在定义外,需要特别注意格式!!!
//普通成员函数的实现
template<typename T> //这里要加上类模板定义的头
void myvector<T>::func() { //这里要加上一个<T>,有几个参数,加几个参数的名字
//...
return;
}
//构造函数的实现
template<typename T>
myvector<T>::myvector() {
//...
return;
}
类模板成员函数的实例化和调用:
myvector<int> myvec; //这行生成了一个具体的类,int取代T
myvec.func();
先调用myvector类的构造函数,再调用成员函数func()。
四、模板类名字的使用
template<typename T>
myvector<T>& myvector<T>::operator=(const myvector &) { //<t>表示返回的是实例化了的myvector
return *this;
}
五、非类型模板参数
template<typename T,int size = 10> //定义的时候赋初值
class myarray {
private:
T arr[size]; //可以直接用size
public:
void myfunc();
};
template<typename T, int size>
void myarray<T,size>::myfunc() { //重点是<T,size>的写法
cout << size << endl;
}
//调用
myarray<int, 100> tmparray;
tmparray.myfunc(); //100
myarray<int> tmparray2;
tmparray2.myfunc(); //10
※ 限制:浮点型(double、float)和类类型不能做非类型模板参数!
第3节 用typename场合、默认模板参数、趣味写法分析
一、typename使用场合
1、在模板定义里,表明其后的模板参数是参数类型(这个typename可以换成class)
template<typename T,int a>
2、使用类的类型成员,用typename来标识一下这是一个类型
//类中定义:myiterator为类型成员
typedef T* myiterator;
//类外实现返回值为myiterator的mybegin()函数
typename myvector<T>::myiterator myvector<T>::mybegin();
这里如果不写typename关键字,编译器不知道myiterator是静态成员还是类型!
二、函数指针做其他函数参数
//定义
typedef int(*FunType)(int, int);
int fun(int a, int b) {
return a + b;
}
void testfun(int i,int j, FunType t) {
cout << "testfun" << endl;
}
//调用
testfun(0, 0, fun);
三、默认模板参数
1、类模板
若类模板中如此定义
template<typename T=int,int size = 10>
可以这样调用
myarray<> abc;
若类模板中如此定义
template<typename T,int size = 10>
可以这样调用
myarray<int> def;
2、函数模板
模板定义行初始化 + 模板函数参数列表初始化
typedef int(*FunType)(int, int);
int fun(int a, int b) {
return a + b;
}
template<typename T,typename F=FunType> //默认给F一个FunType类型
void testfunc(const T&i, const T&j, F tmp= fun){ //默认值必须有
cout << tmp(i,j) << endl;
}
//调用时输出8
testfunc(3,5);
第4节 成员函数模板、显式实例化、声明
一、普通类的成员函数模板
即:类中的成员函数时个模板,这个函数不可以是virtual的
二、类模板的成员函数模板
类模板的成员函数只有用到的时候才进行实例化!
三、模板显式实例化、模板声明
为了防止在多个.cpp文件中实例化相同的类模板,c++11提出显式实例化
template A<float>; //只有一个cpp中有此定义即可,编译器会实例化出一个A<float>
extern template A<float>; //其他cpp的声明
第5节 using定义模板别名,显式指定模板参数
一、using定义模板别名
普通用法:
typedef map<string,int> m;
//或者
using m = map<string,int>;
需求:我想定义一个map,key是string,但value不固定,可能是int,可能是string,怎么实现?
typedef方法:
template<typename st>
struct map_s{
typedef map<string,st> m;
}
//调用
map_s<int>::m map1;
map1.insert({"xzh",1});
using方法(using用于定义模板类型的时候功能大于typedef):
template<typename st>
using map_s = map<string,st>; //using是用来给模板类型其别名的
//调用
map_s<int> map2;
map2.insert({"xzh",1});
二、显式指定模板参数
要与对应的模型参数匹配(在尖括号中自己写的类型优先与系统对于括号里参数的类型推断)!
第6节 模板全特化、偏特化(局部特化)
一、类模板特化
特化:对特殊模板类型参数进行特殊的对待(再写一个覆盖),写专用代码!
※ 必须现有泛化版本才能存在特化版本
1、类模板全特化
1)常规全特化:所有类型模板参数都用具体类型代替!(参数相同,系统优先特化版本,可以写无限个)
template<typename T,typename U>
struct st {
void func() {
cout << "泛化版本" << endl;
}
};
template<>
struct st<double,int> {
void func() {
cout << "全特化版本" << endl;
}
};
//调用
st<double, double> test1;
test1.func(); //泛化版本
st<double, int> test2;
test2.func(); //全特化版本
2)特化类的成员函数而不是模板
template<typename T,typename U>
struct st {
st() {
cout << "泛化版本的构造函数" << endl;
}
void func() {
cout << "泛化版本的func" << endl;
}
};
template<>
struct st<double,int> {
st() {
cout << "全特化版本的构造函数" << endl;
}
void func() {
cout << "全特化版本的func" << endl;
}
};
template<>
void st<double, double>::func() {
cout << "对全特化的类模板中的成员函数的特殊处理!" << endl;
};
//调用
st<double, double> test1;
test1.func();
//结果为
//泛化版本的构造函数
//对全特化的类模板中的成员函数的特殊处理!
st<double, int> test2;
test2.func();
//结果为
//全特化版本的构造函数
//全特化版本的func
若调用的参数类型只符合特化的成员函数,则先调用泛化版本的构造函数,再调用特化的成员函数!
2、类模板偏特化(局部特化)
1)模板参数数量:绑了两个留了一个
template<typename T,typename U>
struct tc {
void functest() {
cout << "泛化版本" << endl;
}
};
template<typename T>
struct tc<T,int> {
void functest() {
cout << "偏特化版本" << endl;
}
};
//调用
tc<char, int> t1;
t1.functest(); //偏特化版本
tc<double, string> t2;
t2.functest(); //泛化版本
2)模板参数范围:const int < int;T* < T;T&、T < T
template<typename T>
struct tc {
void functest() {
cout << "泛化版本" < endl;
}
};
template<typename T>
struct tc<const T> {
void functest() {
cout << "const的特化版本" << endl;
}
};
template<typename T>
struct tc<T*> {
void functest() {
cout << "T*的特化版本" << endl;
}
};
//调用
tc<int*> t1;
t1.functest(); //T*的特化版本
tc<const int> t2;
t2.functest(); //const的特化版本
特化部分更改的是 struct tc<x> x处的参数!
三、函数模板特化
1、函数模板全特化:等价于实例化一个函数模板
template<typename T,typename U>
void fun(const T&i, const U&j) {
cout << "泛化版本" << endl;
cout << i << endl;
cout << j << endl;
}
template<>
void fun(const int&i, const double&j) {
cout << "特化版本" << endl;
cout << i << endl;
cout << j << endl;
}
void fun(int a,double b) {
cout << "普通函数版本" << endl;
}
//调用
fun(7, 7); //泛化版本
fun(7, 7.5); //普通函数版本,若没有普通函数,调用特化版本
※ 编译器选择顺序:普通函数(重载函数)→ 特化版本 → 泛化版本
如传一个字符串,数组类型模板参数优先于指针类型模板参数
2、函数模板偏特化:不能偏特化!
三、模板特化版本放置建议
模板的定义和实现放在.h文件中,模板特化、泛化版本也放在同一个.h文件中。
第7节 可变参模板
允许模板中含有零到任意个模板参数。
一、可变参函数模板
1、简单范例
template<typename... T>
void myfunct(T... args) {
cout << sizeof...(T) << endl;
//等价于cout << sizeof...(args) << endl;
}
void func() {
myfunct(); //0
myfunct("afd","we",4); //3
myfunct(10,20); //2
}
【值得注意】
a)args称为一堆或一包参数,这些参数的类型可以各不相同
b)T后面跟了...就被称为可变参类型,代表一包类型,那args就是一包形参
template<typename T, typename... U>
void myfunct(const T& args1, const U&... args2) { //注意...出现的位置!!!
cout << sizeof...(args2) << endl;
}
void func() {
myfunct(135); //0
myfunct("afd", "we", 4); //2
myfunct(10, 20); //1
}
2、参数包的展开
如何获取这一包参数并进行处理?
答:一般都是用一个递归函数来展开参数包,这样就需要一个参数包展开函数和一个同名的递归终止函数。
每次把args2的内容拆成一个和n个!
void myfunct() {
cout << "end" << endl;
}
template<typename T, typename... U>
void myfunct(const T& args1, const U&... args2) {
//cout << sizeof...(args2) << endl;
cout << "收到的参数值为:" << args1 << endl;
myfunct(args2...);
}
void func() {
myfunct(5,"afd", "we", 4);
}
二、可变参类模板(用到的不多)
通过递归继承方式展开参数包,比较复杂。