目录
概念
模板就是建立一个通用的模具,大大提高复用性
函数模板和类模板
函数模板
泛型编程主要利用的技术就是模板
函数模板的作用:
建立一个通用函数,其函数返回值类型和形参类型可以不具体指定,用一个虚拟的类型来代表
提高复用性,使类型参数化
语法
template<typename T>
函数声明或定义
/*
template--声明创建模板
typename--表明其后面的符号是一种数据类型,可以用class代替
T -- 通用的数据类型 名称可以替换
*/
基本使用
//函数模板
template <typename T>
void swap(T &t1,T &t2){
T temp = t1;
t1 = t2;
t2 = temp;
}
int a=5,b=10;
/*有两种使用函数模板
* 1.自动类型推导 swap(a,b); 即可
* 2.显示指定类型 swap<int>(a,b);
* */
swap<int>(a,b);
注意事项
- 自动类型推导,必须推导出一致的数据类型T才可以使用
template <typename T>
void swap(T &t1,T &t2){
T temp = t1;
t1 = t2;
t2 = temp;
}
int a = 5;
char b = 'b';
swap(a,b); //错误的 不是一致的数据类型
- 模板必须要确定出T的数据类型才可以用
template <typename T>
void func(){
cout<<"aaa"<<endl;
}
func();//不确定类型无法调用函数
函数模板案例
从小到大排序
template<class T>
void sort(T arr[],int len){
for (int i = 0;i<len;++i){
int minIndex = i;
for (int j = i+1; j < len; ++j) {
if (arr[minIndex]>arr[j])
minIndex = j;
}
if (minIndex!=i){
swap<T>(arr[i],arr[minIndex]);
}
}
}
int arr[] = {1,45,20,3,9,5};
sort<int>(arr,6);
for (int i = 0; i < 6; ++i) {
std::cout<<arr[i]<<" ";
}
std::cout<<std::endl;
char arr0[] = {'a','c','z','r','f','z'};
sort<char>(arr0,6);
for (int i = 0; i < 6; ++i) {
std::cout<<arr0[i]<<" ";
}
普通函数和函数模板的区别
- 普通函数调用时可以发生隐式类型转化(自动类型转化)
int add(int a,int b){
return a+b;
}
int a=10,b=20;
char c='c';
std::cout<<add(a,b)<<std::endl;//30
std::cout<<add(a,c)<<std::endl;//109 'c'的ASCII码为97,自动转化
- 函数模板使用自动推导不可以发生隐式类型转化,使用显示指定可以发生隐式类型转化
template<class T>
T myAdd(T a,T b){
return a+b;
}
std::cout<<myAdd(a,b)<<std::endl;//30
std::cout<<myAdd(a,c)<<std::endl;//报错 不能隐式类型转化
std::cout<<myAdd<int>(a,b)<<std::endl;//30
std::cout<<myAdd<int>(a,c)<<std::endl;//109 'c'的ASCII码为97,自动转化
建议使用函数模板使用显式指定
普通函数和函数模板调用规则
- 如果普通函数和函数模板都都可以实现,优先调用普通函数
- 可以通过空模板强制调用函数模板
- 函数模板也可以发生重载
- 如果函数模板可以产生更好的匹配,优先调用函数模板
void myPrint(int a,int b){
std::cout<<"ordinary:"<<"a="<<a<<"&&b="<<b<<std::endl;
}
template <class T>
void myPrint(T a,T b){
std::cout<<"template:"<<"a="<<a<<"&&b="<<b<<std::endl;
}
template <class T>
void myPrint(T a,T b,T c){
std::cout<<"template:"<<"a="<<a<<"&&b="<<b<<"&&c="<<c<<std::endl;
}
int a=10,b=20,c=30;
myPrint(a,b);//ordinary:a=10&&b=20
// 调用的是普通函数 优先普通函数,如果普通函数只有声明,就会报错不会找函数模板
myPrint<>(a,b);//空模板 强制调用模板函数
myPrint<>(a,b,c);//函数模板重载
char h = 'h',g = 'g';
myPrint(h,g);//模板产生更好的匹配 优先函数模板
总结:如果存在函数模板了,最好就不要提高同名的普通函数了,否则容易出现二义性
模板的局限性
模板通用性并不是万能的
template <class T>
void f(T a,T b){
a=b;
}
在上述代码中,如果传入的a和b是数组,那么无法实现了
C++为了解决这种问题,提供模板的重载,可以对特定的数据类型实现具体化
两个方法解决:
1、运算符重载 在类内部
2、重载函数模板 具体化模板
class P{
public:
int age;
std::string name;
P(int a,std::string s):age(a),name(s){}
};
template <class T>
bool myCompare(T &a,T &b){
return a==b;
}
//利用具体化P的版本实现代码 具体化优先调用
template<> bool myCompare(P &a,P &b){
if (a.age==b.age && a.name == b.name)
return true;
return false;
}
int a=10,b=10;
std::cout<<myCompare<int>(a,b)<<std::endl;
P p1(18,"tom");
P p2(18,"tom");
std::cout<<myCompare(p1,p2)<<std::endl;
实际上,学习模板并不是为了使用模板,而是为了STL中使用系统提供的模板
函数模板具体化
语法:template <>函数返回值类型 函数模板名 <数据类型>(参数列表){}
template<typename T>
void print(T t){...}
template<>
void print<int>(int t){...}
分文件编写
普通函数和普通函数模板声明和定义放在头文件中
函数模板具体化声明在头文件,定义在源文件
类模板
建立一个通用类,类中的成员 数据类型可以不具体指定,使用一个虚拟的类型来代表
语法
//class可由typename代替 意思是一样的 其余意思和函数模板一样的含义
template <class T,class R>
类
template<class NameType,class AgeType>
class PER{
public:
NameType name;
AgeType age;
};
类模板和函数模板的区别
- 类模板没有自动类型推导(只有函数)
- 类模板在模板参数列表中可以有默认参数(只有类,C++11函数模板也可有默认参数)
默认参数之后必须都是默认参数
template<class NameType=std::string ,class AgeType=int>
class PER{
public:
NameType name;
AgeType age;
PER():name("孙悟空"),age(999){}
PER(std::string n,int a):name(n),age(a){}
};
//但是还是要有 <> 不然编译器认为为自动类型推导 报错的
PER<>p("猪八戒",9999);
std::cout<<p.name<<std::endl;
类模板成员函数创建时机
类模板中的成员函数和普通成员函数创建时机是有区别:
- 普通成员函数在编译的时候就可以创建
- 类模板成员函数在调用的时候才会创建
类模板作为函数参数
有三种方法:推荐第三个
template <class T1,class T2>
class Person1{
public:
T1 name;
T2 age;
Person1(T1 n,T2 a):name(n),age(a){}
void printInformation(){
std::cout<< this->name<<"&&"<< this->age<< std::endl;
}
};
//1.指定传入类型(最常用)
void fun1(Person1<std::string,int>&p){
p.printInformation();
}
//2.参数模板化
template <class T1,class T2>
void fun2(Person1<T1,T2> &p){
p.printInformation();
//模板的类型名称
std::cout<<"T1的类型:"<< typeid(T1).name()<<std::endl;
std::cout<<"T2的类型:"<< typeid(T2).name()<<std::endl;
}
//3.整个类模板化 推荐第三个
template <class T>
void fun3(T &p){
p.printInformation();
std::cout<<"T的类型:"<< typeid(T).name()<<std::endl;
}
int main() {
Person1<std::string,int>p1("孙悟空",5252);
fun1(p1);
Person1<std::string,int>p2("猪八戒",1289);
fun2(p2);
Person1<std::string,int>p3("沙和尚",8289);
fun3(p3);
}
类模板与继承
注意事项
- 当子类继承的父类是一个类模板时,子类在声明的时候要指定父类的T的类型
- 如果不指定类型,编译器无法给子类分配内存
- 如果想要灵活的指定父类的T类型 子类也要变成模板
template<class T>
class Father{
public:
T name;
};
//子类必须知道父类的T类型
class Son:public Father<std::string>{
};
//如果想要灵活的指定父类的T类型 子类也有变成模板
template <class T1,class T2>
class Son2:public Father<T1>{
public:
T2 age;
};
int main() {
Son2<std::string,int>s;
s.name = "孙悟空";
s.age = 888;
}
//模板类继承模板参数给出的基类(不能是模板类)
template<class T>
class AAA:public T{
};
类模板成员函数类外实现
类模板中成员函数类外实现时,需要加上模板参数列表
template<class T1,class T2>
class An{
public:
T1 name;
T2 age;
An(T1 n,T2 a);
};
template<class T1,class T2>
An<T1,T2>::An(T1 n,T2 a){
this->name = n;
this->age=a;
}
类模板分文件编写
问题:类模板中的成员函数创建时期是在调用阶段,导致分文件编写链接不到
解决方法
- 直接包含.cpp文件(使用的比较少)
- 将声明和实现写一起,改变后缀名为.hpp hpp是约定的后缀 而不是强制
//hpp
#ifndef DU_H
#define DU_H
#include <iostream>
#include "string"
using namespace std;
template<class T1,class T2>
class Du {
public:
T1 myName;
T2 myAge;
Du(T1 name,T2 age);
void printInformation();
};
template<class T1,class T2>
Du<T1,T2>::Du(T1 name,T2 age) {
myName = name;
myAge = age;
}
template<class T1,class T2>
void Du<T1,T2>::printInformation() {
cout<< this->myName<<endl;
cout<< this->myAge<<endl;
}
#endif //DU_H
类模板和友元
- 非模板友元:友元函数不是模板函数,而是利用模板类参数生成的函数。只能在类内实现
template<class T1,class T2>
class AAAA{
private:
T1 m_x;
T2 m_y;
public:
AAAA(const T1 x,const T2 y):m_x(x),m_y(y){}
friend void showAAAA(const AAAA<T1,T2>&a){//该函数不是模板函数
cout<<a.m_x<<"--"<<a.m_y<<endl;
}
};
//void showAAAA(const AAAA<int,string>&a){
// cout<<a.m_x<<"--"<<a.m_y<<endl;
//}
int main() {
AAAA<int,string>a(18,"duy");
showAAAA(a);
AAAA<char,string>b('X',"duy");
showAAAA(b);
- 约束模板友元:模板类实例化时,每个实例化的类对应一个友元函数(更有使用价值)
template<typename T>
void showAAAA(T&t); //第一步
template<class T1,class T2>
class AAAA{
private:
T1 m_x;
T2 m_y;
public:
AAAA(const T1 x,const T2 y):m_x(x),m_y(y){}
friend void showAAAA<>(AAAA<T1,T2>&a);//第二步
};
template<typename T>//第三步、通用 友元函数模板
void showAAAA(T&t){
cout<<t.m_x<<"--"<<t.m_y<<endl;
}
template<>
void showAAAA(AAAA<int,string>&t){//第三步、具体化版本
cout<<"AAAA<int,string>&a"<<endl;
cout<<t.m_x<<"--"<<t.m_y<<endl;
}
int main() {
AAAA<int,string>a(18,"duy");
showAAAA(a);
AAAA<char,string>b('X',"duy");
showAAAA(b);
- 非约束模板友元:模板类实例化时,如果实例化了n个类,也会实例化n个友元函数,每个实例化的类都拥有n个友元函数(不科学)
template<typename T>
void showAAAA(T&t); //第一步
template<class T1,class T2>
class AAAA{
private:
T1 m_x;
T2 m_y;
public:
AAAA(const T1 x,const T2 y):m_x(x),m_y(y){}
// friend void showAAAA<>(AAAA<T1,T2>&a);//第二步
template<typename T>friend void showAAAA(T&t);
};
template<typename T>//第三步、通用 友元函数模板
void showAAAA(T&t){
cout<<t.m_x<<"--"<<t.m_y<<endl;
}
template<>
void showAAAA(AAAA<int,string>&t){//第三步、具体化版本
cout<<"AAAA<int,string>&a"<<endl;
cout<<t.m_x<<"--"<<t.m_y<<endl;
}
int main() {
AAAA<int,string>a(18,"duy");
showAAAA(a);
AAAA<char,string>b('X',"duy");
showAAAA(b);
//类模板与友元
#include<iostream>
using namespace std;
template <class T1, class T2>
class Person;
//函数模板的实现
template <class T1, class T2>
void printPerson1(Person<T1, T2> p)
{
cout << p.id << p.age << endl;
}
template <class T1, class T2>
class Person
{
//类内友元 加上friend就是全局函数
friend void printPerson0(Person<T1, T2> p)
{
cout << p.id << p.age << endl;
}
//类外实现
//普通函数的声明 friend void printPerson1(Person<T1, T2> p);
//如果全局函数是类外实现,需要编译器提前知道模板函数的存在
friend void printPerson1<>(Person<T1, T2> p);
public:
Person(T1 a,T2 b)
{
this->id = a;
this->age = b;
}
private:
T1 id;
T2 age;
};
int main()
{
Person<int, int> p(1,2);
printPerson1(p);
return 0;
}
类内实现简单,如果没有特殊需求使用类内实现就可以了
模板类的具体化
分为完全具体化和部分具体化
template <>
class A<xxx>{}
template<class T>
class A{}
template<class T>
class A<T*>{}
具体化程度高的类优先于具体化程度低的类,具体化的类优先于没有具体化的类。