C++ STL之模板 template 全解
文章目录
- 1. 函数模板
- 1.1. 基本范例
- 1.2. 实例化
- 1.3. 参数模板的推断
- 1.4. 参数模板的重载
- 1.5. 特化
- 2. 类模板
- 2.1. 基本范例
- 2.2. 类模板是在运行时确定运行的方法
- 2.3. 类模板做函数的参数
- 2.4. 类模板继承问题
- 2.5. 类模板的类外实现成员函数
- 2.6. 类模板的友元
- 3 类模板的应用
- 3.1 类模板vector的实现 ==重要*==
- 3.2 vector的迭代器底层实现
- 知识点
- a. 显式构造关键字 `explicit`
模板函数的使用模板类
1. 函数模板
1.1. 基本范例
#include <iostream>
#include <vector>
using namespace std;
namespace ns1{
// int Add(int a, int b){
// return a+b;
// }
// float Add(float a, float b){
// return a+b;
// }
// 使用函数模板解决上面的重复编写的问题
// 方法1
template<class T> // T 代表一个类型, 除了class以外也可以使用typename, 这里的class并不是"类""
T Add(T a, T b){
return a+b;
}
// 方法2
template<class T1, class T2>
T1 Add(T1 a, T2 b){
cout << "使用T1, T2" <<endl;
return a+b;
}
}
void test1(){
int a = ns1::Add(1, 5); // 自动匹配到方法1
float b = ns1::Add(1.3, 5.5); // 自动匹配到方法1
double c = ns1::Add(1.4, 4); // 自动匹配到方法2
double d = ns1::Add<double, int>(4, 4); //指定参数类型后, 就可以使用方法2了
}
1.2. 实例化
实例化 : 编译时, 用具体的类型代替类型模板参数, 的过程叫做实例化
比如使用g++ test.cpp -S -o test.s
会发现生成了三个函数call
, 这是因为上面的Add
相当于三个函数被调用了
call _ZN3ns13AddIiEET_S1_S1_
call _ZN3ns13AddIdEET_S1_S1_
call _ZN3ns13AddIdiEET_S1_T0_
1.3. 参数模板的推断
#include <iostream>
#include <vector>
using namespace std;
namespace ns1{
// 方法3
template <typename V, class T, class U>
V sub(T a, U b){
return a-b;
}
// 方法4
template <class T, class U>
// auto sub(T a, U b){ // auto 用于表达式自动推到返回值类型的含义
auto sub(T a, U b) -> decltype(a-b){ // 返回类型后置语法, 没有推导类型的含义
return a-b;
}
// 方法5
template<class T>
T mydouble(T t){
return t*2;
}
}
void test3(){
// cout << ns1::sub(12, 32.5) <<endl; // error: 缺一个类型的匹配
cout << ns1::sub<int, int, double>(12, 32.5) <<endl; // 可以全匹配指定
cout << ns1::sub<double>(12, 32.5) <<endl; // 也可以按顺序指定一部分
cout << ns1::mydouble(15) << endl; // 自动推断
double result2 = ns1::mydouble<double>(15);// 指定类型模板参数
auto result3 = ns1::mydouble<>(15.34); // <> 为空这里没有意义, 但是如果还有个重写的mydouble方法, 那这时候<> 就有意义了, 指定使用模板函数调用
}
1.4. 参数模板的重载
- 当普通函数和函数模板都匹配时, 优先调用普通函数
- 选择特殊或合适的? 编译器内部有比较复杂的排序规则, 规则也在不断更新
namespace ns1{
template<class T>
void myfunc(T a){
cout << "T a 执行了" <<endl;
}
template<class T>
void myfunc(T* a){
cout << "T* a 执行了" <<endl;
}
void myfunc(int a){
cout << "int a 执行了" <<endl;
}
} // namespace ns1
void test4(){
// 重载
ns1::myfunc(12);
ns1::myfunc(32.43);
char *p = nullptr;
ns1::myfunc(p);
}
/*
int a 执行了
T a 执行了
T* a 执行了
*/
1.5. 特化
- 泛化: 大众常规的, 都是泛化函数模板
- 特化: 代表泛化出来的一组子集
- 全特化: 把tfunc繁华版本的所有类型都转化为特化版本
- 实例化后
tfunc<const char *, int>
- 优先顺序就是 普通版本→全特化版本→泛化版本
- 函数模板不存在范围上或者数量上的偏特化, 因为这种实际上是函数模板的重载
namespace ns2{
template<class T, class U>
void tfunc(T &a, U &b){
cout<< "函数泛化版本 " << a << " " << b << endl;
}
// 全特化版本,
template<> // 为空
void tfunc<int, double>(int &a, double &b){ // <int, double>可以省略, 因为根据传入的参数可以推导
cout<< " tfunc<int, double> : 全特化版本 " << a << " " << b << endl;
}
void tfunc(int &a, double &b){
cout << "普通函数" << a << " " << b << endl;
}
// 偏特化或局部特化: 模板数量上, 范围上
/*
template<class U> // 数量上, 是错误的
void tfunc<int, U>(int &a, U &b){
cout<< "函数偏特化版本 int指定, U不变" << a << " " << b << endl;
}
template<class U> // 范围上 int → const int, 类型变小, T→T& 针对T类型, 范围都变小了, 实际上来将, 不存在参数范围上的偏特化
void tfunc<int, U>(int &a, U &b){
cout<< "函数偏特化版本 int指定, U不变" << a << " " << b << endl;
}
可以通过重载 模板函数进行偏特化
*/
template<class U, class T> // 范围上 int → const int, 类型变小, T→T& 针对T类型, 范围都变小了, 实际上来将, 不存在参数范围上的偏特化
void tfunc(const U &a, T &b) {
cout<< "通过重载, 增加const来实现偏特化 " << a << " " << b << endl;
}
template<class T> // 范围上 int → const int, 类型变小, T→T& 针对T类型, 范围都变小了, 实际上来将, 不存在参数范围上的偏特化
void tfunc(int &a, T &b) {
cout<< "通过重载, 增加int来实现偏特化 " << a << " " << b << endl;
}
} // namespace ns2
void test5(){
const char *p = "I love china ";
int a = 23;
ns2::tfunc(p, a); // 函数泛化版本 I love china 23
double b = 3.14;
ns2::tfunc(a, b); // 优先选择普通函数
ns2::tfunc<>(a, b); // tfunc<int, double> : 全特化版本 23 3.14
// 重载实现偏特化
ns2::tfunc(p, b);
int c = 350;
ns2::tfunc(c, p); // 通过重载, 增加int来实现偏特化 350 I love china
}
2. 类模板
2.1. 基本范例
- 不能自动类型推导, 必须制定
// template<class NameType, class AgeType>
template<class NameType , class AgeType = int> // 类模板参数可以有默认参数, 但是是在后面,
class Persion{
private:
NameType name;
AgeType age;
public:
Persion(NameType n, AgeType a): name(n), age(a){}
void PrintInfo(){
cout << name << " " << age <<endl;
}
};
void test1(){
// Persion *p = new Persion("孙悟空", 100); error 类模板不能自动类型推导
// 显式指定类型
Persion<string, int> *p = new Persion<string, int>("孙悟空", 100);
p->PrintInfo();
}
2.2. 类模板是在运行时确定运行的方法
class Persion1{
public:
void test1(){
cout << "Persion 1" <<endl;
}
};
class Persion2{
public:
void test2(){
cout << "Persion 2" <<endl;
}
};
template<class T>
class MyClass{
public:
T obj;
void fun1(){
obj.test1();
}
void fun2(){
obj.test2();
}
};
void test2(){
MyClass<Persion1> m1;
m1.fun1(); // 可以运行成功,
// m2.fun2(); error 由此可以推导出, 模板方法是在运行时处理的
}
2.3. 类模板做函数的参数
- 显式指定
- 参数模板化
- 整体模板化
- 查看参数类型
typeid(参数).name()
template<class NameType , class AgeType> // 类模板参数可以有默认参数, 但是是在后面,
class Persion{
private:
NameType name;
AgeType age;
public:
Persion(NameType n, AgeType a): name(n), age(a){}
void PrintInfo(){
cout << name << " " << age <<endl;
}
};
void doWork(Persion<string, int> &p){
p.PrintInfo();
}
template<class T1, class T2> // string, int
void doWork2(Persion<T1, T2> &p){
cout << "利用函数模板进行推导" << typeid(T1).name() <<endl;
cout << "利用函数模板进行推导" << typeid(T2).name() <<endl;
p.PrintInfo();
}
// 整体模板化
template<class T> // Persion
void doWork3(T &p){
cout << "利用整体模板化" << typeid(T).name() <<endl;
p.PrintInfo();
}
void test(){
Persion<string, int> p("小明", 23);
doWork(p);
doWork2(p);
doWork3(p);
}
2.4. 类模板继承问题
- 基类如果是模板类, 必须子类告诉编译器, 模板类型, 否则基类无法创建内存
- 利用参数列表创建类
template<class T>
class Base{
public:
T m_a;
};
// child 继承base, 必须告诉base中T的类型, 否则T无法分配内存
class Child : public Base<int>{
};
// Child2<int, double> child;
// 显式告诉类的模板类型
template<class T1, class T2>
class Child2 : public Base<T2>{
public:
Child2(){
cout << typeid(T1).name() <<endl;
cout << typeid(T2).name() <<endl;
}
};
void test(){
Child2<int, double> child;
}
2.5. 类模板的类外实现成员函数
template<class T1, class T2>
class Person{
public:
Person(T1 name, T2 age): m_name(name), m_age(age){}
void show();
// void show(){
// cout<< m_name << " " << m_age <<endl;
// }
private:
T1 m_name;
T2 m_age;
};
// 类外实现成员函数
template<class T1, class T2>
void Person<T1, T2>::show(){
cout<< m_name << " " << m_age <<endl;
}
2.6. 类模板的友元
- 类内实现
friend void PrintPerson(Person<T1, T2> &p)
- 类外实现
// 下面用到Print2中的Person, 所以需要声明
template<class T1, class T2>
class Person;
// 普通全局函数, 所以需要先声明编译器才能知道
template<class T1, class T2>
void Print2(Person<T1, T2> &p);
template<class T1, class T2>
class Person{
public:
Person(T1 name, T2 age): m_name(name), m_age(age){}
private:
T1 m_name;
T2 m_age;
// 友元函数类内实现
friend void PrintPerson(Person<T1, T2> &p){
cout << "name=" << p.m_name << " " << p.m_age <<endl;
}
// friend void Print2(Person<T1, T2> &p); // 普通函数的声明
friend void Print2<>(Person<T1, T2> &p); // 普通函数的声明, 加一个<> 表明模板函数, 还需要在前面声明
};
// 友元函数类外实现
template<class T1, class T2>
void Print2(Person<T1, T2> &p){
cout << p.m_name << " " << p.m_age <<endl;
}
void test(){
Person<string, int> p1("小明", 27);
PrintPerson(p1);
Print2(p1);
}
3 类模板的应用
3.1 类模板vector的实现 重要*
- 类模板的应用
- 仿照vector的底层实现
- 重载赋值运算符
- 按照对象生成对象
-
explicit
防止隐式转换关键字 -
[]
重载
下面的例子实现了上面的所有知识点;
实现了vector底层的基础方法
template<class T>
class MyArray{
public:
MyArray(){} // 注意这个必须要有, 因为 MyArray<int> arr2; 会用到
explicit MyArray(int capacity){ //explicit 防止隐式类型转换, 防止MyArray arr = 10; 的写法
cout << "根据设定的大小构造对象空间 " << capacity << endl;
this->m_capacity = capacity;
this->m_size = 0;
this->pAddress = new T[this->m_capacity]; // 开辟空间存储 T类型的对象
}
explicit MyArray(const MyArray &array){ // explicit 防止隐式类型赋值, 防止 MyArray<int> arr2 = array;
cout<< "利用已有对象构造新对象" << endl;
if(this == &array){
cout << "咋还是相同的" << endl;
}
this->m_capacity = array.m_capacity;
this->m_size = array.m_size;
this->pAddress = new T[m_size]; // 注意这里需要重新开空间
for(int i=0; i<m_size; i++){
this->pAddress[i] = array.pAddress[i];
}
// *this = array; // 不能这样做么?
}
~MyArray(){
cout << "析构函数 清理指针所指向的空间内容" << endl;
if(pAddress != nullptr){
delete []pAddress;
pAddress = nullptr;
}
}
// = 赋值操作符重载
MyArray& operator=(MyArray &array){
cout << "调用了赋值操作符重载, 用于构造新的对象" <<endl;
// 先判断原始数据, 有就先清空
if(this->pAddress != nullptr){
delete []pAddress;
pAddress = nullptr;
}
this->m_capacity = array.m_capacity;
this->m_size = array.m_size;
this->pAddress = new T[m_size]; // 注意这里需要重新开空间
for(int i=0; i<m_size; i++){
this->pAddress[i] = array.pAddress[i];
}
}
// [] 重载
T &operator[] (int index){
return this->pAddress[index];
}
// 尾插
void push_back(T val){
pAddress[m_size] = val;
m_size++;
}
// 获取大小
int getSize(){
return m_size;
}
// 获取容量
int getCapacity(){
return m_capacity;
}
private:
T *pAddress = nullptr; // 指向堆区的指针, 注意指针初始化一定要初始化一定要初始化
int m_capacity; // 容量
int m_size;
};
void PintArray(MyArray<int> &array){
for(int i=0; i<array.getSize(); i++){
cout << array[i] << " ";
}cout << endl;
}
void test(){
MyArray<int> array(10); // 注意这里创建的是对象, 不是对象的指针
for(int i=0; i<10; i++){
array.push_back(i);
}
MyArray<int> arr2;
arr2 = array; // 利用赋值运算符的重载
arr2[6] = 0;
MyArray<int> arr3(array); // 利用构造函数
arr3[4] = 0;
PintArray(arr2);
PintArray(arr3);
PintArray(array);
/*
根据设定的大小构造对象空间 10
调用了赋值操作符重载, 用于构造新的对象
利用已有对象构造新对象
0 1 2 3 4 5 0 7 8 9
0 1 2 3 0 5 6 7 8 9
0 1 2 3 4 5 6 7 8 9
析构函数 清理指针所指向的空间内容
析构函数 清理指针所指向的空间内容
析构函数 清理指针所指向的空间内容
*/
}
class Person{
public:
Person(){} // 注意这个必须要有, 因为MyArray<Persion> 创建时需要用到这个空的构造函数
Person(string n, int age): m_name(n), m_age(age){}
void showInfo(){
cout << m_name << " " << m_age <<endl;
}
// private:
string m_name = "";
int m_age = 0;
};
void PintPerson(MyArray<Person> &arr){
for(int i=0; i<arr.getSize(); i++){
arr[i].showInfo();
}
}
// 类数组的存储
void test2(){
MyArray<Person> arr(10);
Person p1("小明", 23);
Person p2("行为", 23);
Person p3("无线", 23);
Person p4("不管", 23);
Person p5("热无", 23);
arr.push_back(p1);
arr.push_back(p2);
arr.push_back(p3);
arr.push_back(p4);
arr.push_back(p5);
PintPerson(arr);
}
3.2 vector的迭代器底层实现
这里起始就是 设计模式中的迭代器模式实现 具体而言, 当创建一个聚合类用于保持内部数组的数据, 并且不对外开放的时候, 但是又需要获得内部数组的数据, 这个时候就需要用到迭代器了, 通过构造迭代器, 并且在聚合类中声明迭代器, 就可以实现访问迭代器相当于访问数组; 重点在于 []
操作符的重载
#include <iostream>
#include <vector>
using namespace std;
template<class T>
class Iterator;
template<class T>
class ConcreteIterator;
template<class T>
class Aggregate;
template<class T>
class ConcreteAggregate;
// 聚集抽象类
template<class T>
class Aggregate{
public:
// 创建一个迭代器
virtual Iterator<T> *CreateIterator(){};
};
// 具体聚集类
template<class T>
class ConcreteAggregate : public Aggregate<T>{
private:
vector<T> items; // 声明一个泛型变量, 用于存放聚合对象
public:
// 创建一个自己的迭代器
Iterator<T> *CreateIterator(){
return new ConcreteIterator<T>(this);
}
// 返回聚集总数
int Size(){
return items.size();
}
// [] 重载
T &operator[] (int index){
cout << "使用重载" << index << endl;
return items[index];
}
void push_back(T val){
items.push_back(val);
}
void print(){
cout << "当前聚类里面的内容有: " << endl;
for(auto & a : items){
cout << a << " ";
}cout<< "/n" << endl;
}
};
// 迭代器模式
// 迭代器抽象类
template<class T> // l类似于Java中的object, 也就是所有类的祖宗
class Iterator{
public:
// 用于定义得到开始, 下一个, 是否结尾, 当前对象的方法的统一接口
virtual T First(){};
virtual T Next(){};
virtual bool IsDone(){};
virtual T CurrentItem(){};
};
// 具体迭代器
template<class T>
class ConcreteIterator : public Iterator<T>{
private:
ConcreteAggregate<T> *aggregate = nullptr; // 定义了一个具体的聚集对象
int current = 0;
public:
// 初始化时 具体的聚集对象传入
ConcreteIterator(ConcreteAggregate<T> *aggregate) : aggregate(aggregate){
cout << "当前聚类中长度有: " << this->aggregate->Size() << endl;
}
// 得到第一个聚集对象, 由于传过来的是指针, 需要通过(*指针)在通过[] 重载运算符访问iter里面对应的对象
T First(){
return (*aggregate)[0];
}
// 得到当前的
T CurrentItem(){
return (*aggregate)[current];
}
// 得到下一个
T Next(){
T ret = "";
current++;
if(current < aggregate->Size()){
ret = (*aggregate)[current];
}
return ret;
}
bool IsDone(){
return current >= aggregate->Size() ? true : false;
}
};
void test(){
ConcreteAggregate<string> *a = new ConcreteAggregate<string>(); // 10个乘客的公交车, 聚集对象
a->push_back("大鸟");
a->push_back("算法");
a->push_back("放到");
a->push_back("奇偶偶");
a->print();
Iterator<string> *i = new ConcreteIterator<string>(a); // 生成迭代器
string item = i->First();
cout << "first: " << item << endl;
cout << "currentItem: " << i->CurrentItem() << endl;
cout<< "----------------" << endl;
while(!i->IsDone()){
cout << i->CurrentItem() << endl;
i->Next();
}
delete a; a = nullptr;
delete i; i = nullptr;
}
int main(int argc, char const *argv[])
{
test();
return 0;
}
/*
当前聚类里面的内容有:
大鸟 算法 放到 奇偶偶 /n
当前聚类中长度有: 4
使用重载0
first: 大鸟
currentItem: 使用重载0
大鸟
----------------
使用重载0
大鸟
使用重载1
使用重载1
算法
使用重载2
使用重载2
放到
使用重载3
使用重载3
奇偶偶
*/
知识点
a. 显式构造关键字 explicit
具体应用见上面的vector实现
#include <iostream>
using namespace std;
// explicit函数的介绍!!!
// explicit函数的作用:explicit构造函数是用来防止隐式转换的
class Test1{
public:
Test1(int n){ // 普通隐式的构造函数
num = n;
}
private:
int num;
};
class Test2{
public:
explicit Test2(int n){ //explicit(显式)构造函数
num = n;
}
private:
int num;
};
int main(){
Test1 t1 = 12; // 隐式调用其构造函数,成功
// Test2 t2 = 12; 编译错误,不能隐式调用其构造函数
Test2 t3(12); // 显式调用成功
return 0;
}