Halo,这里是Ppeua。平时主要更新C++,数据结构算法,Linux与ROS…感兴趣就关注我bua!
文章目录
- 0.泛型编程
- 1.模板
- 2 函数模板:
- 2.1函数模板的特化
- 3. 类模板
- 3.1 非类型模板参数
- 3.2 类的模板刻画
- 3.2.1 全特化
- 3.2.2 偏特化
0.泛型编程
假设有下面这个场景:
需要获得一个swap函数可以交换整形.
void swap(int &a,int &b)
{
int tmp=a;
a=b;
b=tmp;
}
如果这时候我有需要一个能够交换浮点型与浮点型、自定义类型与自定义类型…等函数呢
这些需求有一个共同点,他们的整体逻辑是一样的,但仅因为类型的改变就需要重构一份代码.这显然是效率低下不可取的.
所以C++引入了一个新的特性:泛型编程.
可以想象为:钢厂里需要制造钢具,他们的模子是一样的,但是颜色不同.所以我们可以 根据需求将相同的模子刷上不同的颜色
这里的模子就是代码逻辑,颜色就是类型.
1.模板
模板提供了多样的类型,这是泛型编程的基础.
template<class T>
// template<typename T>
code......
模板基础语法如上,其中class与typename没有什么大的差别.T为自定义名称,通常习惯为T(就如常量大写一样)
该模板的作用域是紧贴的那一段code(class或者function)
经过实例化之后,每一个函数都是不同的函数,每一个类也是不同的类,即使他们逻辑相同
将通过两个方面来解释模板:
-
函数模板
-
类模板
2 函数模板:
还是上面swap的例子.利用模板的方法是这么写:
template<class t>
void Swap(t t1,t t2)
{
t tmp;
tmp=t1;
t1=t2;
t2=tmp;
}
int main()
{
int a=0;
int b=2;
Swap(a,b);
}
此时就可以通过int去交换.
可以直接将t看做成一个类如int double…等.所以正常写法上出现类型的地方都可以用t去代替
上文为隐式实例化:就是由编译器去自动推导需要刻画一个什么样的模板
但我们也可以自己去告诉编译器我们需要一个什么样的模板.也就是显式实例化
Swap<int>(a,b);
通常情况下,我们用隐式实例化即可.但如果有下面这样的函数,我们就需要显式实例化
T* Alloc(int n=10)
{
return new T[n];
}
int main()
{
auto array=Alloc<int>(2);
}
该函数作用为创建一段默认为10大小的数组空间并返回.
这时候如果我们不告诉编译器我们需要什么样的数组,它又怎么会知道呢?
这就是显式实例化的意义所在
-
当调用时有一个函数与模板函数同名时,会优先去查找这个函数是否满足要求.若满足要求则会优先调用已有的函数,而不是用模板去刻画一个函数.
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); }
输出结果为
- 函数模板不可自动类型转换,但普通函数可以
这里可以理解为:函数模板会去适配参数类型,而普通函数需要参数类型去适配普通函数
2.1函数模板的特化
如果我想要设计一个函数根据不同的传入对象做不同的事情.就需要用到函数的特化这一概念
函数特化的步骤:
- 必须要先有一个基础的函数模板
- 关键字template后面接一对空的尖括号<>
- 函数名后跟一对尖括号,尖括号中指定需要特化的类型
template<class T>
bool Less(T left,T right)
{
return left<right;
}
template <>
bool Less<int*>(int *left,int *right)
{
return *left<*right;
}
但这样写还不如直接函数重载,所以实用性不大
3. 类模板
这里放上之前提到过的Vector部分源代码演示,想要进一步了解vector的可以看这篇文章:vector的理解与使用
#pragma once
#include<iostream>
namespace H
{
template<class T>
class vector {
public:
typedef T* iterator;
typedef const T* const_iterator;
vector()
{
}
vector(size_t n, const T& val = T())
{
resize(n, val);
}
vector(int n, const T& val = T())
{
resize(n, val);
}
template<class InputIterator>
vector(InputIterator begin, InputIterator end)
{
while (begin != end)
{
push_back(*begin);
begin++;
}
}
vector(const vector<T>& v)
{
_start = new T[v.capacity()];
for (size_t i = 0; i < v.size(); i++)
{
_start[i] = v._start[i];
}
//_finish = v._finish; 只是令地址相等
_finish = _start + v.size();
_endofstorage = _start + v.capacity();
}
vector<T>& operator= (const vector<T>& v)
{
swap(v);
return *this;
}
~vector()
{
if (_start)
{
delete[]_start;
_start = _finish = _endofstorage = nullptr;
}
}
iterator begin()
{
return _start;
}
iterator end()
{
return _finish;
}
const_iterator begin()const
{
return _start;
}
const_iterator end()const
{
return _finish;
}
size_t capacity()const
{
return _endofstorage - _start;
}
size_t size()const
{
return _finish - _start;
}
void resize(size_t n, const T& val)
{
if (n < size())
{
_finish = _start + n;
}
else
{
reserve(n);
while (_finish != _start + n)
{
*_finish = val;
_finish++;
}
}
}
void reserve(size_t n)
{
if (n > capacity())
{
size_t sz = size();
T* tmp = new T[n];
if (_start)
{
memcpy(tmp, _start, sizeof(T) * size());
delete[] _start;
}
_start = tmp;
_finish = _start + sz;
_endofstorage = _start + n;
}
}
void push_back(const T& x)
{
if (_finish == _endofstorage)
{
reserve(capacity() == 0 ? 1 : capacity() * 2);
}
*_finish = x;
_finish++;
}
void swap(vector<T>v)
{
std::swap(_start, v._start);
std::swap(_finish, v._finish);
std::swap(_endofstorage, v._endofstorage);
}
iterator insert(iterator pos, const T& val)
{
assert(pos < _finish&& pos >= _start);
if (_finish == _endofstorage)
{
size_t newpos = pos - _start;
reserve(capacity() == 0 ? 1 : capacity() * 2);
pos = _start + newpos;
}
iterator end = _finish - 1;
while (end >= pos)
{
*(end + 1) = end;
end--;
}
*pos = val;
++_finish;
return pos;
}
iterator erase(iterator pos)
{
assert(pos < _finish&& pos >= _start);
iterator it = pos + 1;
while (it != _finish)
{
*(it - 1) = *it;
it++;
}
_finish;
return pos;
}
T& operator[](size_t pos)
{
assert(pos < size());
return _start[pos];
}
const T& operator[](size_t pos) const
{
assert(pos < size());
return _start[pos];
}
void pop_back()
{
erase(--end());
}
private:
iterator _start = nullptr;
iterator _finish = nullptr;
iterator _endofstorage = nullptr;
};
}
这就是类代码的实例化,与函数模板的实例化大差不差,但是调用的时候需要显示实例化,这也很好理解,你不传参数他并不知道需要创造出一个什么样的类.
vector<int>v;
用到模板时:类名不再是类型,类型为vector,若实例化之后T为具体的类型
因为存在静态成员变量,所以使用泛型编程的时候,遇到使用模板参数去申明变量的时候.前面需要先申明该参数为类型而不是变量
template <class Container>
void Print(Container&v)
{
//成分不明确 静态类型对象与类型
//typename 明确告诉编译器 这里是类型的模板实例化
typename Container::const_iterator it=v.begin();
//auto it=v.begin() auto为类型所以不需要加typename
//vector<int>::const_iterator
while(it!=v.end())
{
cout<<*it<<endl;
it++;
}
cout<<endl;
}
例如假设Data类中有一个st静态成员变量.进行赋值的时候是
Data::st=10;
而假设Data类中有一个st类型需要定义时是这样的
Data::st stt=10;
这两明显意义不同但编译阶段容易被编译器混淆.所以需要加上typename关键字
typename Data::st stt=10;
上面的Data在泛型编程中,被替换为模板关键字,用来表示类的类型.
3.1 非类型模板参数
我们有时候想要动态的创建一个栈(若这个栈不扩容),所以需要我们在实例化的时候动态的给定一个大小.这时候就需要一个非类型模板参数来传递这个数据.
template <class T,size_t N=10>
class Stack{
public:
T a[N];
int _top;
int capacity=N;
};
传入参数通过这样传入:
Stack<int,20> st20;
但注意,非类型模板参数只支持整形
这也很好理解,难道创建数组空间的大小可以为浮点型嘛.
3.2 类的模板刻画
与函数相同,刻画时仍然需要有原始类.
template<class T1,class T2>
class Date{
public:
void print()
{
cout<<"Date<T1,T2>"<<endl;
}
private:
T1 a1=0;
T2 a2=0;
};
3.2.1 全特化
顾名思义:是将全部的的函数参数都进行特化
template<>
class Date<int,double>
{
public:
void print()
{
cout<<"Date<int,double>"<<endl;
}
private:
int a1=0;
double a2=0;
};
3.2.2 偏特化
部分参数仍然使用模板,部分参数特化
template<class T>
class Date<T,double>
{
public:
void print()
{
cout<<"Date<int,double>"<<endl;
}
private:
T a1=0;
double a2=0;
};