目录
函数模板格式
template<typename T1, typename T2,......,typename Tn>
返回值类型 函数名(参数列表){}
template<typename T>
void Swap( T& left, T& right)
{
T temp = left;
left = right;
right = temp;
}
函数模版原理
在编译器编译阶段,对于模板函数的使用,编译器需要根据传入的实参类型来推演生成对应
类型的函数以供调用
函数模板概念
函数模板代表了一个函数家族,该函数模板与类型无关,在使用时被参数化,根据实参类型产生函数的特定类型版本
函数模板的实例化
不同类型的参数使用函数模板时,称为函数模板的实例化
模版不建议声明和定义分离到两个文件.h 和.cpp会出现链接错误
模板参数实例化分为:隐式实例化和显式实例化
隐式实例化(模板实参省略):让编译器根据实参推演模板参数的实际类型 Add(a1, (int)d1)
template<class T>
T Add(const T& left, const T& right)
{
return left + right;
}
int main()
{
int a1 = 10, a2 = 20;
double d1 = 10.0, d2 = 20.0;
Add(a1, a2);
Add(d1, d2);
cout << Add(a1, (int)d1) << endl;
return 0;
}
显式实例化:在函数名后的<>中指定模板参数的实际类型 Add<int>(a1, d1)
template<class T>
T Add(const T& left, const T& right)
{
return left + right;
}
int main()
{
int a1 = 10, a2 = 20;
double d1 = 10.0, d2 = 20.0;
Add(a1, a2);
Add(d1, d2);
cout << Add(a1, (int)d1) << endl;
cout << Add<int>(a1, d1) << endl;
return 0;
}
模板参数的匹配原则
1. 一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这个非模板函数
// 专门处理int的加法函数
int Add(int left, int right)
{
return left + right;
}
//通用加法函数
template<class T>
T Add(T left, T right)
{
return left + right;
}
void Test()
{
Add(1, 2); // 与非模板函数匹配,编译器不需要特化
Add<int>(1, 2); // 调用编译器特化的Add版本
}
int main()
{
Test();
}
2. 对于非模板函数和同名函数模板,如果其他条件都相同,在调动时会优先调用非模板函数而不会从该模板产生出一个实例。如果模板可以产生一个具有更好匹配的函数, 那么将选择模板
// 专门处理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); // 模板函数可以生成更加匹配的版本,编译器根据实参生成更加匹配的
}
int main()
{
Test();
}
3. 模板函数不允许自动类型转换,但普通函数可以进行自动类型转换
类模板
template<class T1, class T2, ..., class Tn>
class 类模板名
{
// 类内成员定义
};
// 类模版
template<typename T>
class Stack
{
public :
Stack(size_t capacity = 4)
{
_array = new T[capacity];
_capacity = capacity;
_size = 0;
}
~Stack()
{
delete[] _array;
_array = nullptr;
_size = _capacity = 0;
}
void Push(const T& data)
{
if (_size == _capacity)
{
T* tmp = new T[_capacity * 2];
memcpy(tmp, _array, sizeof(T) * _size);
delete[]_array;
_array = tmp;
_capacity *= 2;
}
_array[_size++] = data;
}
private:
T* _array;
size_t _capacity;
size_t _size;
};
int main()
{
//类模板都是显示实例化
Stack<int> s1;
s1.Push(1);
s1.Push(2);
s1.Push(3);
s1.Push(4);
Stack<double> s2;
s2.Push(1.1);
s2.Push(2.1);
s2.Push(3.1);
s2.Push(4.1);
}
// 类模版
template<typename T>
class Stack
{
public :
Stack(size_t capacity = 4)
{
_array = new T[capacity];
_capacity = capacity;
_size = 0;
}
~Stack()
{
delete[] _array;
_array = nullptr;
_size = _capacity = 0;
}
void Push(const T& data);
private:
T* _array;
size_t _capacity;
size_t _size;
};
//声明和定义分离时,需要再写一次类模板
template<typename T>
void Stack<T>::Push(const T& data)
{
if (_size == _capacity)
{
T* tmp = new T[_capacity * 2];
memcpy(tmp, _array, sizeof(T) * _size);
delete[]_array;
_array = tmp;
_capacity *= 2;
}
_array[_size++] = data;
}
int main()
{
//类模板都是显示实例化
Stack<int> s1;
s1.Push(1);
s1.Push(2);
s1.Push(3);
s1.Push(4);
Stack<double> s2;
s2.Push(1.1);
s2.Push(2.1);
s2.Push(3.1);
s2.Push(4.1);
}
知识点
template <typename T, int size>
class Array {
public:
T arr[size]; // 使用非类型模板参数
};
template <typename T>
void func(T arg) {
// 函数体
}
//T是一个类型模板参数
可用来创建动态增长和减小的数据结构(模板可以具有非类型参数,用于指定大小,可以根据指定的大小创建动态结构)
模板类型无关,提高了代码复用性
模板运行时不检查数据类型,也不保证类型安全,相当于类型的宏替换
平台无关的,可移植性(只要支持模板语法,模板的代码就是可移植的)
模板实参省略意思为隐式实例化
类模板与模板类所指的不是同一概念(类模板是一个类家族,模板类是通过类模板实例化的具体类)
(
MyClass
是一个类模板,而MyClass<int>
、MyClass<double>
和MyClass<std::string>
是模板类的具体实例)类模板中的成员函数全是模板函数(所有类模板的成员函数,放在类外定义时,需要在函数名前加类名,而类名实际为ClassName<T>,所以定义时还需加模板参数列表)
template<class T> size_t Stack<T>::size(){ return _size; }
非类型模板参数
类型形参即:出现在模板参数列表中,跟在class或者typename之类的参数类型名称。
非类型形参,就是用一个常量作为类(函数)模板的一个参数,在类(函数)模板中可将该参数当成常量来使用。整形常量:char,short,int ,bool,long
namespace bite
{
// 定义一个模板类型的静态数组
template<class T, size_t N = 10>
class array
{
public :
T & operator[](size_t index) { return _array[index]; }
const T& operator[](size_t index)const { return _array[index]; }
size_t size()const { return _size; }
bool empty()const { return 0 == _size; }
private:
T _array[N];
size_t _size;
};
}
1. 浮点数(c++20版本可以)、类对象以及字符串是不允许作为非类型模板参数的。
2. 非类型的模板参数必须在编译期就能确认结果。
template<size_t N = 10, bool flag = false>
class Stack
{
private:
int _a[N];
int _top;
};
// C++20
template<double D>
class A
{
private:
};
int main()
{
Stack<> s0;//c++14支持,建议使用
Stack s0;//c++20支持该写法
Stack<5> s1;
Stack<10> s2;
return 0;
}
array对比静态数组,越界读和写都有检查
静态数组: 越界检查的问题
静态数组,抽查
越界读不检查,越界写抽查array在栈上开辟空间,比vector在堆上开辟空间效率更高
模板的特化
通常情况下,使用模板可以实现一些与类型无关的代码,但对于一些特殊类型的可能会得到一些错误的结果,需要特殊处理
#include"iostream"
using namespace std;
class Date
{
friend ostream& operator<<(ostream& _cout, const Date& d);
public:
Date(int year = 1900, int month = 1, int day = 1)
: _year(year)
, _month(month)
, _day(day)
{}
bool operator<(const Date& d)const
{
return (_year < d._year) ||
(_year == d._year && _month < d._month) ||
(_year == d._year && _month == d._month && _day < d._day);
}
bool operator>(const Date& d)const
{
return (_year > d._year) ||
(_year == d._year && _month > d._month) ||
(_year == d._year && _month == d._month && _day > d._day);
}
private:
int _year;
int _month;
int _day;
};
// 函数模板 -- 参数匹配
template<class T>
bool Less(T left, T right)
{
return left < right;
}
int main()
{
cout << Less(1, 2) << endl; // 可以比较,结果正确
Date d1(2022, 7, 7);
Date d2(2022, 7, 8);
cout << Less(d1, d2) << endl; // 可以比较,结果正确
Date* p1 = &d1;
Date* p2 = &d2;
cout << Less(p1, p2) << endl; // 可以比较,结果错误
return 0;
}
p1指向的d1显然小于p2指向的d2对象,但是Less内部并没有比较p1和p2指向的对象内
容,而比较的是p1和p2指针的地址,这就无法达到预期而错误。
对模板进行特化,即:在原模板类的基础上,针对特殊类型所进行特殊化的实现方
式。模板特化中分为函数模板特化与类模板特化。
函数模板特化
1. 必须要先有一个基础的函数模板
2. 关键字template后面接一对空的尖括号<>
3. 函数名后跟一对尖括号,尖括号中指定需要特化的类型
4. 函数形参表: 必须要和模板函数的基础参数类型完全相同,如果不同编译器可能会报一些奇怪的错误。
#include"iostream"
using namespace std;
class Date
{
friend ostream& operator<<(ostream& _cout, const Date& d);
public:
Date(int year = 1900, int month = 1, int day = 1)
: _year(year)
, _month(month)
, _day(day)
{}
bool operator<(const Date& d)const
{
return (_year < d._year) ||
(_year == d._year && _month < d._month) ||
(_year == d._year && _month == d._month && _day < d._day);
}
bool operator>(const Date& d)const
{
return (_year > d._year) ||
(_year == d._year && _month > d._month) ||
(_year == d._year && _month == d._month && _day > d._day);
}
private:
int _year;
int _month;
int _day;
};
// 函数模板 -- 参数匹配
template<class T>
//bool LessFunc(T const & left, T const & right)
bool LessFunc(const T& left, const T& right)
{
return left < right;
}
// 特化
template<>
bool LessFunc<const Date*>(const Date* const& left, const Date* const& right)
{
return *left < *right;
}
template<>//Date*特殊处理
//const此时修饰的*指向的内容,该写法与原类模板不符
//bool LessFunc<Date*>(const Date* & left,const Date* & right)
bool LessFunc<Date*>(Date* const& left, Date* const& right)
{
return *left < *right;
}
// 推荐
bool LessFunc(const Date* left, const Date* right)
{
return *left < *right;
}
bool LessFunc(Date* left, Date* right)
{
return *left < *right;
}
int main()
{
cout << Less(1, 2) << endl; // 可以比较,结果正确
Date d1(2022, 7, 7);
Date d2(2022, 7, 8);
cout << Less(d1, d2) << endl; // 可以比较,结果正确
Date* p1 = &d1;
Date* p2 = &d2;
cout << Less(p1, p2) << endl; // 可以比较,结果错误
const Date* p3 = &d1;
const Date* p4 = &d2;
cout << Less(p3, p4) << endl; // 可以比较,结果错误
/*const int i = 0;
int const j = 0;
const int& rx = i;
int const& ry = i;*/
return 0;
}
类模板特化
全特化
全特化即是将模板参数列表中所有的参数都确定化。
#include"iostream"
using namespace std;
template<class T1, class T2>
class Data
{
public :
Data(){ cout << "Data<T1, T2>" << endl; }
private:
T1 _d1;
T2 _d2;
};
template<>
class Data<int, char>
{
public :
Data() { cout << "Data<int, char>" << endl; }
};
void TestVector()
{
Data<int, int> d1;
Data<int, char> d2;
}
int main()
{
TestVector();
}
偏特化
template<class T1, class T2>
class Data
{
public:
Data() { cout << "Data<T1, T2>" << endl; }
private:
T1 _d1;
T2 _d2;
};
// 全特化
template<>
class Data<int, char>
{
public:
Data() { cout << "Data<int, char>" << endl; }
};
// 偏特化/半特化
template<class T1>
class Data<T1, double>
{
public:
Data() { cout << "Data<T1, double>" << endl; }
};
template<class T1>
class Data<T1, char>
{
public:
Data() { cout << "Data<T1, char>" << endl; }
};
// 偏特化,传的类型是指针
template <typename T1, typename T2>
class Data <T1*, T2*>
{
public:
Data()
{
cout << "Data<T1*, T2*>" << endl;
}
};
template <typename T1, typename T2>
class Data <T1&, T2&>
{
public:
Data()
{
cout << "Data<T1&, T2&>" << endl;
}
};
template <typename T1, typename T2>
class Data <T1&, T2*>
{
public:
Data()
{
cout << "Data<T1&, T2*>" << endl;
}
};
int main()
{
Data<int, int> d1;
Data<int, char> d2;
Data<int, double> d3;
Data<char, double> d4;
Data<char, char> d5;
Data<char*, char*> d6;
Data<int*, char*> d7;
Data<int&, char&> d8;
Data<int&, double&> d9;
Data<int&, int*> d10;
return 0;
}
模板分离编译
解决方法
1. 将声明和定义放到一个文件 "xxx.hpp" 里面或者xxx.h其实也是可以的。推荐使用这种。
2. 模板定义的位置显式实例化。这种方法不实用,不推荐使用。
模板总结
【优点】
1. 模板复用了代码,节省资源,更快的迭代开发,C++的标准模板库(STL)因此而产生
2. 增强了代码的灵活性
【缺陷】
1. 模板会导致代码膨胀问题,也会导致编译时间变长
2. 出现模板编译错误时,错误信息非常凌乱,不易定位错误