C++学习之模板初阶学习
今天我们来学习C++中模板的学习。但是模板是C++中比较难的部分,因此本节我们直接出相对比较初阶的部分。
目录
泛型编程
函数模板
函数模板格式
函数模板的原理
函数模板的特性
函数模板的实例化
模板参数的匹配原则
类模板
类模板定义格式
类模板实例化
泛型编程
引例:写一个通用类型的交换函数
如果用函数重载来写,就是这样
void swap(int& a, int& b)
{int temp = a;a = b;b = temp;
}
void swap(double& a, double& b)
{double temp = a;a = b;b = temp;
}
void swap(char& a, char& b)
{char temp = a;a = b;b = temp;
}
这样当然也是可以。 但是显然我们发现,函数重载写起来十分的麻烦。每次出现一个类型都要写一个函数重载,并且很危险,一旦一个出错了所有重载都错了。
那么有没有一个办法能更简便呢?答案是有的,C++为咯应对这个情况有一个东西:模板
模板的作用就是告诉编译器一个模板,让编译器根据不同的类型利用该模板生成代码。
因此:泛型编程就是编写与类型无关的通用代码,是代码复用的一种手段。模板是泛型编程的基础。
模板分为函数模板和类模板
函数模板
函数模板代表了一个函数家族,该函数模板与类型无关,在使用时被参数化,根据实参类型产生函数的特定类型版本
函数模板格式
语法结构如下:
//模板
template<class T>
template<typename A>
注意:typename是用来定义模板参数的关键字,也可以使用class来定义(但是切记不要用struct代替class)
总结为
//模板
template<typename T1, typename T2,...... typename Tn>
返回值类型 函数名(参数列表)
{}
因此交换数据的函数我们可以这样改写:
//模板
template<typename T>
void swap(T& a, T& b)
{T temp = a;a = b;b = temp;
}
int main()
{int i = 1, j = 2;double d1 = 3.14, d2 = 2.71;char c1 = 'a', c2 = 'b';swap(i,j);swap(d1,d2);swap(c1,c2);cout << "i=" << i << " j=" << j << endl;cout << "d1=" << d1 << " d2=" << d2 << endl;cout << "c1=" << c1 << " c2=" << c2 << endl;return 0;
}
结果如下:
函数模板的原理
函数模板是一个蓝图,本身不是函数,是编译器用使用方式产生特定具体类型函数的模板,所以模板就是将本来我们要做的事情交给了编译器去做
在编译器阶段,对于模板函数的使用,编译器需要根据传入的实参类型来推演生成对应类型的函数以供调用。例如double类型使用函数模板时,编译器通过对实参类型的推演,将T确定为double类型,然后产生一份专门处理double类型的代码。
函数模板的特性
上面的交换代码中,是不可以这么写的。
swap(i, d1); //编译错误,类型不匹配
我们可以有以下几种方法处理:类型强转和实例化
函数模板的实例化
用不同类型的参数使用函数模板的时候,成为函数模板的实例化。函数模板实例化分为隐式实例化和显式实例化
1.隐式实例化:让编译器根据实参推演模板参数的实际类型
#include <iostream>
using namespace std;
//模板
template<typename T>
void swap(const T& a, const T& b)
{T temp = a;a = b;b = temp;
}
int main()
{int i = 1, j = 2;double d1 = 3.14, d2 = 2.71;char c1 = 'a', c2 = 'b';swap(i,j);swap(d1,d2);swap(c1,c2);cout << "i=" << i << " j=" << j << endl;cout << "d1=" << d1 << " d2=" << d2 << endl;cout << "c1=" << c1 << " c2=" << c2 << endl;//隐式实例化swap((double)i, d1); return 0;
}
2.显式实例化
#include <iostream>
using namespace std;
//模板
template<typename T>
void swap(const T& a, const T& b)
{T temp = a;a = b;b = temp;
}
int main()
{int i = 1, j = 2;double d1 = 3.14, d2 = 2.71;char c1 = 'a', c2 = 'b';swap(i,j);swap(d1,d2);swap(c1,c2);cout << "i=" << i << " j=" << j << endl;cout << "d1=" << d1 << " d2=" << d2 << endl;cout << "c1=" << c1 << " c2=" << c2 << endl;//显式实例化swap<int>(i, d1); return 0;
}
模板参数的匹配原则
1.一个非模板函数可以和一个同名函数模板同时存在,而且函数模板还可以被实例化为这个非模板函数
#include <iostream>
using namespace std;
//专门的函数
void swap(int& a, int& b)
{int temp = a;a = b;b = temp;
}
//函数模板
template<typename T>
void swap(const T& a,const T& b)
{T temp = a;a = b;b = temp;
}
int main()
{int i = 1, j = 2;double d1 = 3.14, d2 = 2.71;char c1 = 'a', c2 = 'b';swap(i,j);//与非模板函数匹配,编译器无需优化swap(d1,d2);swap(c1,c2);cout << "i=" << i << " j=" << j << endl;cout << "d1=" << d1 << " d2=" << d2 << endl;cout << "c1=" << c1 << " c2=" << c2 << endl;swap(i,d1);//调用编译器优化的模板函数return 0;
}
2.对于非模板函数和同名函数模板,如果条件相同,在调用的时候会优先调用非模板函数而不是从该模板实例化一个函数。如果模板可以产生一个更好匹配的函数则选择模板
#include <iostream>
using namespace std;
//专门的函数
void swap(int& a, int& b)
{int temp = a;a = b;b = temp;
}
//函数模板
template<typename T>
void swap(const T& a,const T& b)
{T temp = a;a = b;b = temp;
}
int main()
{int i = 1, j = 2;double d1 = 3.14, d2 = 2.71;char c1 = 'a', c2 = 'b';swap(i,j);//与非模板函数匹配,编译器无需优化swap(d1,d2);swap(c1,c2);cout << "i=" << i << " j=" << j << endl;cout << "d1=" << d1 << " d2=" << d2 << endl;cout << "c1=" << c1 << " c2=" << c2 << endl;swap(i,d1);//调用编译器优化的模板函数更匹配,根据实参生成更匹配的函数return 0;
}
3.模板函数不允许自动类型转换,但是普通函数可以进行自动类型转换
类模板
类模板定义格式
template<class T1,class T2......class Tn>
class 类模板名
{//类内成员定义
}
模板不建议声明与定义分离,容易出问题。
#include<iostream>
using namespace std;
namespace MyNamespace
{template<typename T>class stack {public:stack(int n = 4):_a(new T[n]), _size(0), _capacity(n){}void print() {cout << "MyClass data: " << data << endl;}~stack(){delete[] _a;_a = nullptr;_size = 0;_capacity = 0;}private:T* a;size_t _size;size_t _capacity;};
}
int main()
{//类模板只能显式实例化MyNamespace::stack<int> s(10);MyNamespace::stack<double> d(3.14);return 0;
}
类模板实例化
类模板实例化与函数模板实例化不同。类模板实例化需要类模板名字后面加<>,然后将实例化的类型放在<>中即可,类模板名字不是真的类,而实例化的结果才是真的类。
//stack是类名,stack<int>才是类型
stack<int>st1; //int
stack<double>st2 //double
在之前写stack的时候,我们会用typedef来解决的
#include<iostream>
using namespace std;
namespace MyNamespace
{typedef int STDataType;class stack {public:stack(int n = 4):_a(new STDataType[n]), _size(0), _capacity(n){}void print() {cout << "MyClass data: " << data << endl;}~stack(){delete[] _a;_a = nullptr;_size = 0;_capacity = 0;}private:STDataType* a;size_t _size;size_t _capacity;};
}
但是typedef的原理与类模板完全不一样(后者更复杂)。typedef相当于宏定义,在预处理阶段直接替换 ,但是我们用typedef的时候没办法实例化两个类型不一样的类,因此我们需要实例化两类型的类的时候需要用类的模板
本期博客就先到这里了,模板的内容我们后续会更加深入的学习。各位读者大大求一个点赞,谢谢