模板
概念
模板的作用是实现类型通用,降低代码的冗余度
模板可以为一种算法定义不同类型的版本
实现机制:
复制代码使用类型参数突破类型的限制,丧失一定的类型安全
模板需要实例化才能使用,实例化由编译器完成
模板的分类
函数模板
函数模板就是带类型参数的函数,函数的返回值,形参,局部变量都可以使用类型参数,函数模板支持类型推断(形参)。
rust复制代码函数模板 -----> 实例化 -----> 函数
编译
类模板
类模板就是带类型参数的类,类的成员变量,成员函数…可以使用类型参数,类模板不支持类型推断。
rust复制代码类模板 -----> 实例化 -----> 类 -----> 实例化 -----> 对象
编译 运行
模板的使用
函数模板
语法:
arduino复制代码//函数模板的声明
template<typename T/类型参数/…>
返回值类型 函数模板名(形参列表)
{
…//可以使用T作为类型
}
//函数模板的调用
函数模板名<类型…>(实参);
//如果函数模板的类型参数可以通过实参来判断,传递的类型可以省略
练习:
使用函数模板实现一个数组的排序,实现数组元素类型的通用
arduino复制代码void mysort(int *arr,int n);
类模板
语法:
arduino复制代码//类模板的声明
template <typename T/类型参数/…>
class 类模板名{
//…类中可以直接使用T类型
};
//类模板的使用
类模板名<类型…> 对象;
//类模板不支持类型推断
模板的特化
如果模板第某些特殊类型的行为需要重新定义,此时可以进行模板的特化。
函数模板的特化
语法:
arduino复制代码template <>
返回值类型 函数模板名<特化类型…>(参数列表)
{
//…重定义特化类型的行为
}
//编译器在对函数模板实例化时,如果有特化模板,有限选择特化的模板实例化
//函数模板不允许特化一部分参数,必须全特化
类模板的特化(全类特化)
语法:
arduino复制代码template <>
class 类模板名<特化类型…>{
//…类中的内容重定义位特化类型的行为
};
练习:
为排序类模板实现const char *的特化
类模板的成员特化
对于类模板而言,既可以进行全类特化,也可以只针对部分与特化类型相关的成员函数进行特化。成员函数特化时要保持特化接口和原通用模板一致。
语法:
arduino复制代码//类外进行成员特化
template <>
返回值类型 类模板名<特化类型…>::成员函数名(形参列表)
{
//…使用特化类型重定义函数的行为
}
类模板的局部特化
针对有多个类型参数的类模板,可以只特化其中一部分参数,编译器优先选择特化程度最高的版本。
特化一部分参数
arduino复制代码template <T1,T2,T3>
class xxx{…}
//局部特化
template <T1,T2>
class xxx<T1,T2,int>{…}
template
class xxx<T1,double,int>{…}
xxx<char,double,int> a; ------ 选择第三个版本进行实例化
xxx<char,long,int> b; ------- 选择第二个版本进行实例化
xxx<char,long,float> c; ------ 选择第一个版本进行实例化
//对于有多个类型参数的类模板,可以对部分参数进行特化
//编译器优先选择特化程度最高的版本
特化类型参数之间的关系
arduino复制代码template <T1,T2,T3>
class xxx{…}
//局部特化
template <T1,T2>
class xxx<T1,T2,T2>{…}
template
class xxx<T1,T1,T1>{…}
//对于有多个类型参数的类模板,可以对类型参数的关系进行特化
//编译器优先选择特化程度最高的版本
针对指针和数组类型的特化
arduino复制代码template <T1,T2,T3>
class xxx{…}
//局部特化
template <T1,T2,T3>
class xxx<T1*,T2*,T3*>{…}
template <T1,T2,T3>
class xxx<T1[],T2[],T3[]>{…}
//对于有多个类型参数的类模板,可以针对指针和数组类型进行特化
//编译器优先选择特化程度最高的版本
作业:
1.使用函数模板和类模板(函数对象)实现二分查找,同时为函数模板实现const char *的特化,类模板实现const char *的成员(查找的成员函数)特化。
模板参数的默认值,非类型参数和模板参数
模板参数的默认值
1.类模板的参数可以有默认值,有默认值的类型参数必须靠右
如果实例化不提供参数的类型,就使用默认值
2.右边类型参数的默认值可以是左边的类型参数
类模板的非类型参数
1.类模板可以接收非类型参数
2.非类型参数只能是常量,常量表达式,常属性的变量
3.非类型参数也可以有默认值
4.函数模板也可以接收非类型参数和默认值
以下了解的可以了实际开发可能用不到
模板的模板参数
模板除了可以接收类型参数和非类型参数以外,也可以接收模板作为参数,语法如下:
template <template <typename T> class xxx>
class Crab{
//...可以使用参数类模板,传入的模板保持统一
};
模板成员
模板可以作为类模板或类的成员/友元,STL的实现使用了该语法
1.类模板成员
类模板实例化的类型参数可以来自于外部类模板,也可以直接指定。
2.函数模板成员
函数模板实例化的类型可以来自于外部类模板或者直接指定,也支持类型推断
3.函数模板和类模板作为友元
如果友元使用了所在类模板的类型参数,属于约束性友元模板
否则属于非约束型友元模板
类模板的继承
类继承类模板
语法:
template <typename T>
class BaseA{
public:
//......
private:
T data;
};
//类继承类模板
class Derived:public BaseA<int>{
//......
};
类模板继承类
语法:
class BaseA{
public:
//......
private:
int data;
};
//类模板继承类
template <typename T>
class Derived:public BaseA{
public:
//.....
private:
T data2;
};
类模板继承类模板
语法:
template <typename T>
class BaseA{
public:
//......
private:
T data;
};
//类模板继承类模板
template <typename N>
class Derived:public BaseA<N>{
public:
//.....
private:
N data2;
};
继承中使用模板参数
语法:
template <typename T>
class Derived:public T{
//......
};
模板的递归实例化
所谓的模板的递归实例化就是使用类模板实例化后作为参数继续实例化本模板
MyArray<int> arr;
MyArray<MyArray<int>> arr;//递归实例化
MyArray<MyArray<MyArray<int>>> arr;//递归实例化
模板的划分
在实际的C++开发中,我们会将类的声明(模板的声明)写在头文件,类/类模板中的函数实现写在源文件,由于模板的实例化是在编译时完成的,所以必须把模板的声明和实现写在一个编译单元中,此时需要使用模板的划分来实现。
可以在模板声明的头文件中用以下语法将实现加进来
#include "xxx.cpp"
练习:
将1search.cpp的类模板实现模板的划分。
class A{
public:
void show()
{
cout<<"show A"<<endl;
}
};
int main()
{
A *pa = NULL;
pa->show();
}
注意:可以使用类类型的空指针去调用成员函数,因为成员函数不属于对象,也不存储在对象中,存储在代码段,调用成员函数只需要找到成员函数的地址即可。如果成员函数中访问了成员变量,就会发生段错误。
智能指针
将指针对象模板化实现了类型的通用,构成智能指针。
根据智能指针 拷贝/赋值 处理的不同,可以分为三种实现形式
1.拷贝/赋值时分配新的地址空间,拷贝原指针指向的内容
2.拷贝/赋值时将右边的空间和地址传递给左边,右边会失去原来的地址空间
3.拷贝/赋值时共享地址空间,同时增加一个引用计数(记录使用地址空间的对象个数)
当引用技术为0释放地址空间
C++预定义了智能指针,可以直接使用,智能指针有4种,介绍其中3种,使用它们需要添加头文件memory
//C++98加入 C++11弃用 C++17移除
auto_ptr ------- 使用方法2实现(只能一个对象记录地址空间)
//C++11添加
unique_ptr ------ 方法2实现,禁止传递的语法
shared_ptr ------ 方法3实现,采用引用计数
智能指针对象销毁时会自动释放记录的地址空间,很大程度上避免了内存泄漏的发生。
作业:
将模板成员的代码实现模板的划分
附加:用户输入两个字符串,编写代码求出两个字符串的最长公共子串
kkkhellowebye
ghsasabyehellod
===>hello
/*02-求最长公共子串*/
#include <iostream>
using namespace std;
//暴力求解
string getLCS(const string &str1,const string &str2)
{
//记录最长子串在str1中的起始位置和长度
size_t max_start = 0,max_len = 0;
for(size_t i=0;i<str1.length();i++){
for(size_t j=0;j<str2.length();j++){
//计算当前最长公共子串
size_t k,l;
for(k=i,l=j;k<str1.length()&&l<str2.length();k++,l++){
if(str1.at(k)!=str2.at(l))
break;
}
//记录最长长度的公共子串
if(k-i>max_len){
max_len = k-i;
max_start = i;
}
}
}
return str1.substr(max_start,max_len);
}
//矩阵法求解
string getLCS1(const string &str1,const string &str2)
{
//记录最长子串在str1中的结束位置和长度
int max_end = 0,max_len = 0;
//创建比较矩阵
int **arr = new int *[str1.length()];
for(size_t i=0;i<str1.length();i++){
arr[i] = new int[str2.length()];
for(size_t j=0;j<str2.length();j++){
//比较字符,将比较结果写入矩阵
if(str1.at(i)==str2.at(j)){//赋值 左上角+1
//如果左上角不存在
if(i==0||j==0)
arr[i][j] = 1;
else
arr[i][j] = arr[i-1][j-1]+1;
//记录最长长度和此时str1中最后一个字符的位置
if(arr[i][j]>max_len){
max_len = arr[i][j];
max_end = i;
}
}
else{
arr[i][j] = 0;
}
}
}
//释放空间
for(size_t i=0;i<str1.length();i++){
delete[] arr[i];
}
delete[] arr;
//返回最长公共子串
return str1.substr(max_end-max_len+1,max_len);
}
int main()
{
string str1 = "kkkhelaalowebyebye";
string str2 = "kkkhelaalowebyebye111";
cout<<getLCS(str1,str2)<<endl;
cout<<getLCS1(str1,str2)<<endl;
return 0;
}