导引:
模板是为了解决函数类型不同所重载,带来的麻烦简化。利用一个模板(示列)代码,让编译器编写出不同类型的代码,满足所需。
int swap(int &p1,int &p2)
{
int p=p1;
p1=p2;
p2=p;
}
char swap(char &p1,char &p2)
{
char p=p1;
p1=p2;
p2=p;
}
模板可以分为:函数模板与类模板。
写法与普通函数或类无异。
1.函数模板
1.1模板格式
template(后面可以续集)
返回值类型 函数名(参数)
{
//........
}
其中函数返回值与参数多以模板定义T1为类型(根据需求自行斟酌)。
仿函数与函数模板的区别
- 语法和结构:仿函数是一个类,通过重载"operator()"来实现函数行为;而函数模板则是一个模板化的函数定义。
- 类型参数化:仿函数通常不直接支持类型参数化,但可以通过模板类来实现;而函数模板则直接支持类型参数化,可以处理任意类型的参数。
- 使用场景:仿函数更适用于需要保存状态或行为的上下文,比如算法中的回调函数或比较函数;而函数模板则更适用于那些行为不依赖于状态,但需要处理多种数据类型的场景。
- 性能:由于仿函数是对象,因此在使用时可能会涉及到对象的创建、销毁和传递等开销;而函数模板在实例化后就是普通的函数,通常没有这些额外的开销。
函数模板的特化
函数模板的特化是对模板函数的一种定制或特化形式,用于处理模板函数不能很好处理的特定类型或情况。当模板函数对于某些类型不能提供合适的实现时,我们可以为这些类型提供特化版本。特化允许我们为特定的类型或类型组合提供定制的函数实现。
特化分为全特化(Full Specialization)和部分特化(Partial Specialization)。全特化是为特定的类型参数列表提供完整的替代实现,而部分特化则允许我们为模板参数的一部分提供特化实现。
区别:
- 目的:仿函数的目的是提供可像函数一样调用的对象,通常用于算法和需要自定义行为的场景。函数模板的特化的目的是为模板函数提供针对特定类型的定制实现,以优化或扩展模板函数的行为。
- 语法和结构:仿函数是类,具有类的定义和成员函数的语法。函数模板的特化则遵循模板特化的语法,需要使用template<>关键字,并指定特化的类型。
- 类型参数化:仿函数通常不直接支持类型参数化,但它们可以嵌套在模板类中,从而间接地支持类型参数化。函数模板的特化则直接处理类型参数化,为特定的类型或类型组合提供定制的实现。
- 使用场景:仿函数在需要自定义比较、转换或算法行为时非常有用。函数模板的特化则在模板函数需要针对特定类型进行特殊处理时使用,例如当默认实现不高效或不适用于某些类型时。
- 性能和效率:仿函数的性能可能受到对象创建和销毁的影响,但在需要保存状态或行为的情况下,这是可以接受的。函数模板的特化通常与模板函数的性能相当,因为它们都是编译时确定的。
1.2使用模型
示例:
#include<iostream>
using namespace std;
template<typename T>;
T summation(const T ret1,const T ret2)
{
return ret1+ret2;
}
int main()
{
int ret=summation(10,20);
return 0;
}
注意:typename是用来定义模板参数关键字,也可以使用class(切记:不能使用struct代替class)
1.3原理
编译器编写代码的原理与形式:
在编译器编译阶段,对于模板函数的使用,编译器需要根据传入的实参类型来推演生成对应类型的函数以供调用。
1.4函数模板实例化
用不同类型的参数使用函数模板时,称为函数模板的实例化。模板参数实例化分为:隐式实例化和显式实例化。
隐式实例化与显示实例化:
#include<iostream>
using namespace std;
template<typename T>;
T summation(const T ret1,const T ret2)
{
return ret1+ret2;
}
int main()
{
int ret=summation(10,20);
double ret1=summation(10.0,20.0);
//但是如果两个类型不同就会出现错误。
//如:
//int ret2=summation(10,20.0);
//编译器就会出现报错,《不能确定您到底要定义为int还是double类型》
解决方法:
//1.强转
int ret2=summation(10,(int)20.0);
// 2.显示实例化
//函数名<类型>(参数);
int ret2=summation<int>(10,20.0);
// 3.添加模板定义
//在template<typename T>中,再添加一个" typename T1 ".
//T summation(const T ret1,const T1 ret2)
//该方法与函数无异,实属无奈使用。
return 0;
}
1.5匹配规则
- 一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这个非模板函数
- 对于非模板函数和同名函数模板,如果其他条件都相同,在调动时会优先调用非模板函数而不会从该模板产生出一个实例。如果模板可以产生一个具有更好匹配的函数, 那么将选择模板
- 模板函数不允许自动类型转换,但普通函数可以进行自动类型转换
// 专门处理int的加法函数
int Add(int left, int right)
{
return left + right;
}
// 通用加法函数
template<class T1, class T2>
T1 Add(T1 left, T2 right)
{
return left + right;
}
void Test()
{
Add(1, 2); // 与非函数模板类型完全匹配,不需要函数模板实例化
Add(1, 2.0); // 模板函数可以生成更加匹配的版本,编译器根据实参生成更加匹配的Add函数
}
2.类模板
2.1 类模板的定义格式
template<class T1, class T2, ..., class Tn>
class 类模板名
{
// 类内成员定义
};
注意:类模板中函数放在类外进行定义时,需要加模板参数列表
template <class T>
Vector<T>::~Vector()
{
//......
}
2.2 类模板的实例化
类模板实例化与函数模板实例化不同,类模板实例化需要在类模板名字后跟<>,然后将实例化的类型放在<>中即可,且与模板对象个数相同,类模板名字不是真正的类,而实例化的结果才是真正的类。
// Vector类名,Vector<int>才是类型
Vector<int> s1;
Vector<double> s2;
2.3 类模板与函数模板的区别
类模板与函数模板区别主要有两点:
- 类模板没有自动类型推导的使用方式
- 类模板在模板参数列表中可以有默认参数
//类模板与函数模板的区别
template<class NameType, class AgeType = int> //指定默认参数
class Person
{
public:
Person(NameType name, AgeType age)
{
this->m_Age = age;
this->m_Name = name;
}
void showPerson()
{
cout << "name: " << this->m_Name << " age: " << this->m_Age << endl;
}
NameType m_Name;
AgeType m_Age;
};
void test01()
{
//Person p("孙悟空", 1000);错误的,类模板无法用自动类型推导
Person<string, int>p("孙悟空", 1000);//正确,只能用显式指定类型推导
p.showPerson();
}
void test02()
{
Person<string>p("猪八戒", 999); //类模板在参数列表中有默认参数
}
int main()
{
test01();
system("pause");
return 0;
}
2.4 类模板中成员函数创建时机
类模板中成员函数和普通类中成员函数创建时机是有区别的:
- 普通类中的成员函数一开始就可以创建
- 类模板中的成员函数在调用时才创建
2.5类模板对象做函数参数
学习目标:类模板实例化出的对象,向函数传参的方式。
一共有三种传入方式:
- 指定传入的类型:直接显示对象的数据类型
- 参数模板化:将对象中的参数变为模板进行传递
- 整个类模板化:将这个对象类型模板化进行传递
如:
//类模板对象做函数参数
template<class T1,class T2>
class Person
{
public:
Person(T1 name,T2 age)
{
this->m_Age = age;
this->m_Name = name;
}
void showPerson()
{
cout << "name: " << this->m_Name << " age:" << this->m_Age << endl;
}
T1 m_Name;
T2 m_Age;
};
//1、指定传入类型
void printPerson1(Person<string, int>&p)
{
p.showPerson();
}
void test01()
{
Person<string, int>p("孙悟空", 199);
printPerson1(p);
}
// 2、参数模板化
template<class T1,class T2>
void printPerson2(Person<T1,T2>&p)
{
p.showPerson();
cout << "T1的类型为:" << typeid(T1).name() << endl;
cout << "T2的类型为:" << typeid(T2).name() << endl;
}
void test02()
{
Person<string, int>p("猪八戒", 90);
printPerson2(p);
}
// 3、整个类模板化
template<class T>
void printPerson3(T &p)
{
p.showPerson();
cout << "T的类型为:" << typeid(T).name() << endl;
}
void test03()
{
Person<string, int>p("唐僧", 60);
printPerson3(p);
}
int main()
{
test01();
test02();
test03();
system("pause");
return 0;
}
注:使用比较广泛的是指定传入类型的传参方式。
2.6 类模板与继承
当类模板碰到继承时,需要注意以下几点:
- 当子类继承的父类是一个类模板时,子类在声明的时候,要指定出父类中T的类型
- 如果不指定,编译器无法给子类分配内存
- 如果想灵活指定出父类中T的类型,子类也需为类模板
//类模板与继承
template<class T>
class Base
{
T m;
};
//class Son: public Base //错误,必须要知道父类中的T类型,才能继承给子类
class Son :public Base<int>
{
};
void test01()
{
Son s1;
}
//如果想灵活指定父类中T的类型,子类也需要变成类模板
template<class T1,class T2>
class Son2 : public Base<T2>
{
public:
Son2()
{
cout << "T1的类型为:" << typeid(T1).name() << endl;
cout << "T2的类型为:" << typeid(T2).name() << endl;
}
T1 obj;
};
void test02()
{
Son2<int,char> s2;
}
int main()
{
test02();
system("pause");
return 0;
}
2.7类模板成员函数类外实现
代码理解:
//类模板成员类外实现
template<class T1, class T2>
class Person
{
public:
Person(T1 name, T2 age);
/*{
this->m_Name = name;
this->m_Age = age;
}*/
void showPerson();
/*{
cout << "姓名:" << this->m_Name << " 年龄:" << this->m_Age << endl;
}*/
T1 m_Name;
T2 m_Age;
};
//构造函数的类外实现
template<class T1,class T2>
Person<T1, T2>::Person(T1 name, T2 age)
{
this->m_Name = name;
this->m_Age = age;
}
//成员函数的类外实现
template<class T1, class T2>
void Person<T1, T2>::showPerson()
{
cout << "姓名:" << this->m_Name << " 年龄:" << this->m_Age << endl;
}
void test01()
{
Person<string, int>p("Tom", 30);
p.showPerson();
}
int main()
{
test01();
system("pause");
return 0;
}
2.8类模板分文件编写
如果工程中需要利用多个类模板,那么将这些类模板都写在同一个文件中将会导致代码可读性变差,所以有必要对类模板进行分文件编写,但是类模板的分文件编写面临着一些问题,以下是类模板分文件编写面临的问题及解决方法。
问题:类模板中成员函数创建时机是在调用阶段,导致分文件编写时链接不到。
解决:
解决方式1:直接包含.cpp源文件(粗暴)
解决方式2:将声明和实现写到同一个文件中,并更改后缀名为.hpp,hpp是约定的名称,并不是强制的
示例1:(未进行分文件编写)
template<class T1, class T2>
class Person
{
public:
Person(T1 name, T2 age);
/*{
this->m_Name = name;
this->m_Age = age;
}*/
void showPerson();
/*{
cout << "姓名:" << this->m_Name << " 年龄:" << this->m_Age << endl;
}*/
T1 m_Name;
T2 m_Age;
};
//构造函数的类外实现
template<class T1,class T2>
Person<T1, T2>::Person(T1 name, T2 age)
{
this->m_Name = name;
this->m_Age = age;
}
//成员函数的类外实现
template<class T1, class T2>
void Person<T1, T2>::showPerson()
{
cout << "姓名:" << this->m_Name << " 年龄:" << this->m_Age << endl;
}
void test01()
{
Person<string, int>p("Tom", 30);
p.showPerson();
}
int main()
{
test01();
system("pause");
return 0;
}
例2:(进行分文件编写,利用.cpp)
1.创建头文件person.h,写一些声明
#pragma once
#include <iostream>
using namespace std;
#include <string>
template<class T1, class T2>
class Person
{
public:
Person(T1 name, T2 age);
void showPerson();
T1 m_Name;
T2 m_Age;
};
2.创建person.cpp,写具体实现
#include "person.h"
//构造函数的类外实现
template<class T1, class T2>
Person<T1, T2>::Person(T1 name, T2 age)
{
this->m_Name = name;
this->m_Age = age;
}
//成员函数的类外实现
template<class T1, class T2>
void Person<T1, T2>::showPerson()
{
cout << "姓名:" << this->m_Name << " 年龄:" << this->m_Age << endl;
}
实例3:(分文件编写,利用.hpp)
将person.h和person.cpp的内容写到一起,并将后缀名改为.hpp,这是类模板分文件编写最常用的方式
1.编写person.hpp文件:
#pragma once
#include <iostream>
using namespace std;
#include <string>
template<class T1, class T2>
class Person
{
public:
Person(T1 name, T2 age);
void showPerson();
T1 m_Name;
T2 m_Age;
};
//构造函数的类外实现
template<class T1, class T2>
Person<T1, T2>::Person(T1 name, T2 age)
{
this->m_Name = name;
this->m_Age = age;
}
//成员函数的类外实现
template<class T1, class T2>
void Person<T1, T2>::showPerson()
{
cout << "姓名:" << this->m_Name << " 年龄:" << this->m_Age << endl;
}
2.编写main函数
#include <iostream>
using namespace std;
#include <string>
#include "person.hpp"
void test01()
{
Person<string, int>p("Tom", 30);
p.showPerson();
}
int main()
{
test01();
system("pause");
return 0;
}
2.9类模板与友元
全局函数类内实现:直接在类内声明友元即可
全局函数类外实现:需要提前让编译器知道全局函数的存在
1.全局函数的类内实现
template<class T1, class T2>
class Person
{
//全局函数类内实现
friend void printPerson(Person<T1, T2> p)
{
cout << "姓名:" << p.m_Name << " 年龄:" << p.m_Age << endl;
}
public:
Person(T1 name, T2 age)
{
this->m_Name = name;
this->m_Age = age;
}
private:
T1 m_Name;
T2 m_Age;
};
void test01()
{
Person<string, int>p("Tom", 30);
printPerson(p);
}
int main()
{
test01();
system("pause");
return 0;
}
2.全局函数类外实现
//提前让编译器知道Person类的存在
template<class T1, class T2>
class Person;
//类外实现
template<class T1, class T2>
void printPerson(Person<T1, T2> p)
{
cout << "姓名:" << p.m_Name << " 年龄:" << p.m_Age << endl;
}
template<class T1, class T2>
class Person
{
//全局函数类外实现
//加空模板参数列表
//如果全局函数是类外实现,需要让编译器提前知道这个函数的存在
friend void printPerson<>(Person<T1, T2> p);
public:
Person(T1 name, T2 age)
{
this->m_Name = name;
this->m_Age = age;
}
private:
T1 m_Name;
T2 m_Age;
};
void test01()
{
Person<string, int>p("Tom", 30);
printPerson(p);
}
int main()
{
test01();
system("pause");
return 0;
}
注:需要注意各个函数声明之间的顺序。在Person类模板中有友元的声明friend void printPerson<>(Person p),因为类模板中友元的类外实现需要让编译器提前知道这个函数,所以需要将printPerson函数写在前面。而printPerson函数中又涉及Person类,所以在printPerson函数前面需要提前声明Person类模板的存在。
总结:建议全局函数做类内实现,用法简单,而且编译器可以直接识别。
3.模板的优缺点
优点:
1.模板复用了代码,使其节约资源,更快的迭代开发,
2.增加了代码灵活性
缺点:
1.模板会导致代码膨胀,导致时间变长(动态库可调集【未改变】)
2.信息凌乱,不易查找。