模板:
建立通用的与数据类型无关的算法模板。可复用性增加,冗余减少。C++泛型编程的知识其实没有那么多,但是能够玩出的花式确实非常多,最好的就是 参考STL标准库的学习,这是泛型编程的巅峰之作!!
首先来看C++的体系图
设计模式是通过增加代码复杂性,来换取代码的灵活性,而学习设计模式最好从项目中汲取经验,不然就等着面试的时候背一下八股文,应付一下也是无所谓的。
泛型编程,比如STL,Boost库代表了C++顶尖开发者的水平。
主要分为两个部分,函数模板和类模板。
函数模板
基本范例
#include<iostream>
using namespace std;
namespace _nmspl{
//template<typename T> //T称之为类型模板
template <class T> //语法规定,此处calss可以取代typename,这里的class没有类的含义
T Sub(T tv1, T tv2){
return tv1-tv2;
}
}
int main(){
cout<<_nmspl::Sub(3,5)<<endl;
cout<<_nmspl::Sub(3.0,5.7)<<endl;
return 0;
}
模板的使用可以增强代码的可复用性,减少冗余,如上范例所示实现了一个简单的支持各种数据类型(如float,double,int,long等)的减法运算。
这里模板的几个规范如下
- 模板的定义是以template关键字开头
- 模板参数T前面用typename修饰,此处typename可以用class修饰,因为此处class没有类的含义。
- 模板参数T以及前面的修饰词都用<>括起来
- 模板参数T可以换成任意其他表示符
类模板
函数模板在调用的时候,可以自动类型推导
类模板必须显示指定类型
类模板在继承的时候也需要指定具体的类型,因为类的对象需要分配内存,所以你需要具体的指定,或者再一次使用模板
template<class T>
class Animal{
};
template<class T>
class Cat : public Animal<T>{
};
//调用
Cat<int> cat;
所以我们所知道的vector,map等容器都是模板的实现。
注意:
-
在类模板的情况下,不要滥用友元函数,这样在Linux和windows下的编译情况是不一样的。
-
类模板分离解决方案,在cpp文件中需要在类名前面加上
template<class T> Person<T>::Person(){ }
但是编译过程会出现错误。因为你的模板cpp文件只进行了一次编译,还没有生成具体的函数,而你的主函数编译过后,调用连接器寻找的时候,找不到对应的执行函数。
解决办法:
就是include的时候,包含cpp文件而不是.h文件,这样模板cpp就可以进行二次编译,但是这样显得很蠢,那还要.h文件干什么呢
所以就出现了一种新的方法,那就是hpp,将h文件和cpp文件的内容写到一起。
-
类模板中出现static关键字
- static关键字不归类模板所有,而是具体的类所有。所以你每生产一个具体的类之后,也会获得一个独有的关键字
应用:
- 重载是参数的重载,模板是数据类型的泛化
这里我们尝试实现一个vector的泛型数组,首先是定义类
template<class T>
class Myarray
然后是成员,比如容量,当前元素个数,以及数组
private:
//一共可以容下多少元素
int mCapacity;
//当前数组有多少元素
int mSize;
//保存数据首地址
T* pAddr;
首先可以进行初始构造
//初始构造
Myarray(int capacity) {
this->mCapacity = capacity;
this->mSize = 0;
this->pAddr = new T(this->mCapacity);
}
然后拷贝构造方法也可以实现一下
//拷贝构造
Myarray(const Myarray& arr) {
//容量、大小赋值
this->mCapacity = arr.mCapacity;
this->mSize = arr.mSize;
//申请空间
this->pAddr = new T(this->mCapacity);
//数据拷贝
for (int i = 0; i < this->mSize; i++)
{
this->pAddr[i] = arr.pAddr[i];
}
等号赋值构造也比较常用。所以把等号也重载一下
//重载等号运算符
Myarray<T> operator=(const Myarray<T>& arr) {
if (this->pAddr != NULL)
{
delete[] this->pAddr;
}
//容量、大小赋值
this->mCapacity = arr.mCapacity;
this->mSize = arr.mSize;
//申请空间
this->pAddr = new T(this->mCapacity);
//数据拷贝
for (int i = 0; i < this->mSize; i++)
{
this->pAddr[i] = arr.pAddr[i];
}
return *this;
}
容器查找元素也可以通过myarry[i],所以把中括号重载一下
T& operator[](int index) {
return this->pAddr[index];
}
容器一般有push_back函数,把push_back函数实现一下
void push_back(T& data) {
//判断索引是否大于容器位置
if (this->mSize >= this->mCapacity)
{
return;
}
this->pAddr[this->mSize] = data;
this->mSize++;
}
push_back如果直接传值,类似于push_back(100),因为常量是右值,不能直接引用,需要重载一个push_back函数
void push_back(T&& data) {
//判断索引是否大于容器位置
if (this->mSize >= this->mCapacity)
{
return;
}
this->pAddr[this->mSize] = data;
this->mSize++;
}
最后再加上析构函数
~Myarray() {
if (this->pAddr != NULL)
{
delete[] this->pAddr;
}
整体实现代码如下
template<class T>
class Myarray {
public:
//初始构造
Myarray(int capacity) {
this->mCapacity = capacity;
this->mSize = 0;
this->pAddr = new T(this->mCapacity);
}
//拷贝构造
Myarray(const Myarray& arr) {
//容量、大小赋值
this->mCapacity = arr.mCapacity;
this->mSize = arr.mSize;
//申请空间
this->pAddr = new T(this->mCapacity);
//数据拷贝
for (int i = 0; i < this->mSize; i++)
{
this->pAddr[i] = arr.pAddr[i];
}
}
//运算符重载
T& operator[](int index) {
return this->pAddr[index];
}
//重载等号运算符
Myarray<T> operator=(const Myarray<T>& arr) {
if (this->pAddr != NULL)
{
delete[] this->pAddr;
}
//容量、大小赋值
this->mCapacity = arr.mCapacity;
this->mSize = arr.mSize;
//申请空间
this->pAddr = new T(this->mCapacity);
//数据拷贝
for (int i = 0; i < this->mSize; i++)
{
this->pAddr[i] = arr.pAddr[i];
}
return *this;
}
void push_back(T& data) {
//判断索引是否大于容器位置
if (this->mSize >= this->mCapacity)
{
return;
}
this->pAddr[this->mSize] = data;
this->mSize++;
}
void push_back(T&& data) {
//判断索引是否大于容器位置
if (this->mSize >= this->mCapacity)
{
return;
}
this->pAddr[this->mSize] = data;
this->mSize++;
}
~Myarray() {
if (this->pAddr != NULL)
{
delete[] this->pAddr;
}
}
学会基础知识的你,如果想要深入了解,下一步的计划应该是STL库的学习。