文章目录
1.模板概念与函数模板的定义、调用
1.1 模板概念
- 模板一般分为函数模板和类模板
- 类模板:举例比如vector就是类模板,通过尖括号传入一个数据类型参数,编译器就通过传递进去的类型参数int生成一个真正的类
1.2 函数模板的定义
- 把两个int类型的参数相加和两个float类型参数相加,属于函数重载
template<typename T>
T funcadd(T a,T b)
{
T addhe = a + b;
return adddhe;
}
1.3 函数模板的调用
- 模板参数是推导出来的,根据传入的实参类型来推到
int he = funcadd(3,1);//推到成int类型
float he = funcadd(3.1f,1.2f);//推导成float,如下所示
float funcadd(float a, float b)
{
float addhe = a + b;
return addhe;
}
float he = funcadd(3,1.2f);//这样的话就报错了
1.4 非类型模板(参数必须是常量表达式)
- 类型参数表示的是一个类型,非类型参数表示的是一个值
- 第一种写法,但是有时候系统推断不出来
template<int a,int b> //定义函数模板
int funcaddv2()
{
int addhe = a + b;
return addhe;
}
//调用
int result = funcaddv2<12,13>();//通过<>来传递参数
- 第二种写法:更安全
template<typename T,int a,int b>
int funcaddv3(T c)
{
int addhe = (int)c + a + b;
return addhe;
}
//调用
int result = funcaddv3<int,11,12>(13);//得出是36
//若int改成double,系统会以传递进去的类型为准,而不是以13推断出来的类型为准
2.类模板概与类模板的定义、使用
2.1 类模板概念
- 为了避免出现很多重复的代码,引入了类模板,通过往这个类模板中传递不同的类型或非类型参数,从而实现一套代码
2.2 类模板的定义
- 以实现一个vector为例子
//myvector.h
#ifndef __MYVECTOR__
#define __MYVECTOR__
template<typename T>
class myvector
{
public:
typedef T* myiterator; //迭代器
public:
myvector(); //构造函数
~myvector(); //
myvector& operator=(const myvector&);//赋值运算符重载
//参数可以写成(const myvector<int>&)
private:
//迭代器接口
myiterator mybegin(); //迭代器起始位置
myiterator myend(); //迭代器结束位置
};
#endif
//main函数
myvector<int> tmpvec;//T换成了int
2.3 类模板的成员函数
- 一个实例化的模板,它的成员只有在使用的时候才会被实例化
//类里面的成员函数
public:
void myfunc();
//myfunc()成员函数的外部实现
template<typename T>
void myvector<T>::myfunc()
{
}
//构造函数的外部实现
template<typename T>
myvector<T>::myvector()
{
}
2.4 类模板名字的使用
- 类外面实现赋值运算符的重载代码
template<typename T>
myvector<T>& myvector<T>::operator=(const myvector<T>&)//第一个<T>表示返回的是一个实例化的myvector,第三个<T>不是必加
{
//.....
return *this;
}
2.5 非类模板参数的使用
- 创建一个新文件myarry.h,模拟一个类似数组行为的类模板
#ifndef __MYARRAY__
#define __MYARRAY__
template<typename T,int size = 10>
class myarray
{
public:
myarray();
~myarray();
public:
void myfunc();
private:
T arr[size];
};
template<typename T,int size>
void myarray<T,size>::myfunc()
{
}
#endif
3.使用typename的场合、函数模板、默认模板参数与趣味写法分析
3.1 typename的使用场合
3.2 函数指针作为其他函数的参数
3.3 函数模板趣味用法举例
3.4 默认模板参数
4.成员函数模板,模板显式实例化与声明
4.1 普通类的成员函数模板
#include <iostream>
using namespace std;
class A
{
public:
template<typename T>
void myft(T tmpt) //成员函数模板
{
std::cout<<tmpt<<std::endl;
}
};
int main()
{
A a;
a.myft(3);
return 0;
}
//输出
3
4.2 类模板的成员函数模板
#include <iostream>
using namespace std;
template<typename C>
class A
{
public:
template<typename T2>
A(T2 v1,T2 v2)//构造函数也引入自己的模板参数T2,和整个类的模板参数C没有关系
{
}
template<typename T>
void myft(T tmpt) //成员函数模板
{
std::cout<<tmpt<<std::endl;
}
C m_ic;
};
int main()
{
A<float> a(1,2);
A<float> a2(1.1,2.2);
a.myft(3);
return 0;
}
//输出
3
4.3 模板显示实例化和声明
//------------------------
//ca.h
#ifndef __CAH__
#define __CAH__
using namespace std;
template <typename C>
class A
{
public:
template<typename T2>
A(T2 v1,T2 v2);
template<typename T>
void myft(T tmpt)
{
std::cout<<tmpt<<std::endl;
}
C m_ic;
};
template<typename C>
template<typename T2>
A<C>::A(T2 v1,T2 v2)
{
std::cout<<v1<<" "<<v2<<std::endl;
}
#endif
//------------------------
//test.cpp
#include <iostream>
#include <vector>
#include "ca.h"
void myfunc()
{
A<float>a(1,2);
}
//------------------------
//MyProject.cpp
#include <iostream>
#include <vector>
#include "ca.h"
int main()
{
A<float> a(1,2);
A<float> a2(1.1,2.2);
a.myft(3);//打印3
return 0;
}
- 当两个.cpp代码中的"A<float.>a(1,2);"这行代码在编译时,因为每个.cpp文件独立编译,所以编译器在MyProject.cpp中会实例化出一个A<float.>类,在test.cpp中也会实例化出一个A<float.>类,增加了很多没有必要的编译时间
- 解决方法:显示实例化,也叫做实例化定义
template A<float>;//实例化定义,只有一个.cpp文件里面这样写,编译器为其生成代码
//这行代码的意思是让编译器实例化出一个A<float>,然后其他的.cpp不需要实例化了
//其他所有.cpp文件都这样写
extern template A<float>;//模板实例化声明
//ca.h
#ifndef __CAH__
#define __CAH__
using namespace std;
//增加一个函数模板
template<typename T>
void myfunc(T v1,T v2)
{
std::cout<<v1+v2<<std::endl;
}
template <typename C>
class A
{
public:
template<typename T2>
A(T2 v1,T2 v2);
template<typename T>
void myft(T tmpt)
{
std::cout<<tmpt<<std::endl;
}
C m_ic;
};
template<typename C>
template<typename T2>
A<C>::A(T2 v1,T2 v2)
{
std::cout<<v1<<" "<<v2<<std::endl;
}
#endif
//----------------------------------
//test.cpp
#include <iostream>
#include <vector>
#include "ca.h"
//增加
template void myfunc(int& v1,int& v2);//函数模板实例化定义,编译器会为其生成实例化代码
void myfunc()
{
A<float>a(1,2);
}
//--------------------------------------
//MyProject.cpp
#include <iostream>
#include <vector>
#include "ca.h"
//增加函数模板实例化声明
extern template void myfunc(int& v1,int& v2);//函数模板实例化声明
//这样的话,float版本的A类模板(A<float>)以及int版本的myfunc函数模板就都会在test.cpp文件中实例化,而不会在MyProject.cpp文件中实例
//当然如果int作为模板参数的类模板A还会再MyProject.cpp中被实例化
//缺点:其他所有类模板的所有成员函数都实例化了,不管用没用到
int main()
{
A<float> a(1,2);
A<float> a2(1.1,2.2);
a.myft(3);//打印3
A<int> d(6,7);
return 0;
}
5.using定义模板别名与显示指定模板参数
5.1 using定义模板别名
- 1)前面直到typedef一般用来定义类型别名
例子:
例如:
typedef unsigned int uint_t;//相当于给unsigned int 类型起了个别名uint_t
//using uint_t = unsigned int;
typedef std::map<std::string,int> map_s_i;//长名字改成短名字
//using map_s_i = std::map<std::string,int>;
map_s_i mymap;
mymap.insert({"first",1});//插入元素到容器,容器中每个元素都是一个键值对
- 2)但是typedef固定了类型,下面是C++98的方法
template<typename wt>
struct map_s
{
typedef std::map<std::string,wt> type;//定义了一个类型
};
//main函数
map_s<int>::type map1;//等价于std::map<std::string,int> map1;
map1.insert({"first",1});
- 3)C++11的做法
template<typename T>
using str_map_t = std::map<std::string,T>;
//主函数
str_map_t<int> map1;
map1.insert({"first",1});
- 4)using定义一个函数指针类型
typedef int(*FunType)(int,int);//返回类型是int,形参是两个int类型
using FunType = int(*)(int,int);//注意第一个圆括号中间的内容变成(*)了
- 5)using定义类型相关的模板
template<typename T>
using myfunc_M = int(*)(T,T);//指针所代表的函数返回类型是int类型,后面的()里是两个形参的类型
//main主函数
myfunc_M<int> pointFunc;//函数指针,该函数返回一个int,参数是两个int,注意myfunc_M<int>
//是类型名(类型别名),并不是一个类模板实例化后的类
int RealFunc(int i ,int j)
{
return 3;
}
//main函数
pointFunc = RealFunc; //把函数地址赋给函数指针
cout<<pointFunc(1,6)<<endl; //3:通过函数指针调用函数
- 6)总结一下:
1.用using定义类型相关模板与定义普通函数类型差不多,只是在前面要增加一个template开头的模板参数类型
2.在using中使用的这种模板,既不是类模板,也不是函数模板,可以看成是别名模板
5.2 显示指定模板参数
template<typename T1,typename T2,typename T3>
T1 sum(T2 i,T3 j)
{
T1 result = i + j;
return result;
}
//main主函数中
auto result = sum<double,double,double>(20000000,20000000);//第一个double表示是result的返回类型,必须显示,告诉编译器
cout<<result<<endl;
6.模板全特化与偏特化(局部特化)
6.1 类模板特化
6.1.1 类模板全特化
6.1.1.1 常规全特化
- 下面是泛化类模板的定义,写在MyProject.cpp上面:
template<typename T,typename U>
struct TC
{
TC()
{
std::cout<<"TC泛化版本构造函数"<<std::endl;
}
void funcTest()
{
std::cout<<"TC泛化版本构造函数"<<std::endl;
}
};
- 例子1:针对T和U都为int类型的特化版本要这样写,下面代码要放在TC类模板泛化版本代码的下面(因为所有类型模板参数哦都用具体类型代表,因此下面版本是一个全特化版本)
template<typename T,typename U>
struct TC
{
TC()
{
std::cout<<"TC泛化版本构造函数"<<std::endl;
}
void funcTest()
{
std::cout<<"TC泛化版本构造函数"<<std::endl;
}
};
template<> //全特化所有类型模板参数都用具体类型代表,所以<>里就空了
struct TC<int,int>
{
TC()
{
std::cout<<"TC<int,int>特化版本构造函数"<<std::endl;
}
//这里可以对该特化版本做单独处理
void funcTest()
{
std::cout<<"TC<int,int>特化版本"<<std::endl;
}
};
int main()
{
TC <int,int> tcint;//TC<int,int>特化版本构造函数
tcint.funcTest();
return 0;
}
6.1.1.2 特化类模板的成员函数
template<typename T,typename U>
struct TC
{
TC()
{
std::cout<<"TC泛化版本构造函数"<<std::endl;
}
void funcTest()
{
std::cout<<"TC泛化版本构造函数"<<std::endl;
}
};
template<> //全特化
void TC <double,double>::funcTest()
{
std::cout<<"TC<double,double>的funcTest()特化版本"<<std::endl;
}
int main()
{
TC <double,double> tdbledbl; //TC泛化版本构造函数
tdbledbl.funcTest(); //TC <double,double>的funcTest()特化版本
return 0;
}
6.1.2 类模板偏特化(局部特化)
- 上面讲的是全特化,也就是把所有类型模板参数都用具体类型代表。这里讲解的是偏特化,也叫局部特化。从模板参数的数量和范围两部分上讲:
6.1.2.1 模板参数数量上的偏特化
//模板参数数量上的偏特化
template<typename T,typename U,typename W>
struct TCP
{
TCP()
{
std::cout<<"TCP泛化版本构造函数"<<std::endl;
}
void funcTest()
{
std::cout<<"TCP泛化版本"<<std::endl;
}
};
template<typename U>
struct TCP<int,U,double>
{
TCP()
{
std::cout<<"TCP<int,U,double>偏特化版本构造函数"<<std::endl;
}
void funcTest()
{
std::cout<<"TCP<int,U,double>偏特化版本"<<std::endl;
}
};
int main()
{
TCP<double,int,double> tcpdi; //TCP泛化版本构造函数
tcpdi.funcTest(); //TCP泛化版本
TCP<int,int,double> tcpii; //TCP<int,U,double>偏特化版本构造函数
tcpii.funcTest();
return 0;
}
- 打印效果
6.1.2.2 模板参数范围上的偏特化
- 参数范围的概念:原来是int类型,如果变成const int类型,就是类型范围上就变小了!
- 或任意T类型变成T*(从数据类型缩小为指针类型)
- 或左值引用、右值引用针对T来说,从类型范围上都变小了
template<typename T>
struct TCF
{
TCF()
{
std::cout<<"TCF泛化版本构造函数"<<std::endl;
}
void funcTest()
{
std::cout<<"TCF泛化版本"<<std::endl;
}
};
template<typename T>
struct TCF<const T>
{
TCF()
{
std::cout<<"TCF<const T>特化版本构造函数"<<std::endl;
}
void funcTest()
{
std::cout<<"TCF<const T>特化版本"<<std::endl;
}
};
template<typename T>
struct TCF<T*>
{
TCF()
{
std::cout<<"TCF<T*>特化版本构造函数"<<std::endl;
}
void funcTest()
{
std::cout<<"TCF<T*>特化版本"<<std::endl;
}
};
template<typename T>
struct TCF<T&>
{
TCF()
{
std::cout<<"TCF<T&>特化版本构造函数"<<std::endl;
}
void funcTest()
{
std::cout<<"TCF<T&>特化版本"<<std::endl;
}
};
template<typename T>
struct TCF<T&&>
{
TCF()
{
std::cout<<"TCF<T&&>特化版本构造函数"<<std::endl;
}
void funcTest()
{
std::cout<<"TCF<T&&>特化版本"<<std::endl;
}
};
int main()
{
TCF<double> td1; //TCF泛化版本构造函数
td1.funcTest(); //TCF泛化版本
TCF<double*> td2;
td2.funcTest();
TCF<const int> td3;
td3.funcTest();
TCF<int&> td4;
td4.funcTest();
TCF<int&&> td5;
td5.funcTest();
return 0;
}
6.2 函数模板特化
- 未完待续
- 、、---------------------------------------------
6.2.1 函数模板全特化
6.2.2 函数模板偏特化
6.3 模板特化版本放置位置建议
- 模板定义和实现一啊不能都放在.h文件中。
- 模板的特化版本应该和模板泛化版本等都放在同一个.h文件中,并且特化版本一般放在泛化版本的后面即可