目录
前言
前面我们已经学习了一部分模板的相关知识,接下来我们将要进行更加深入的学习模板
一、非类型模板参数
模板参数特别的像我们的函数参数,非类型模板参数就是我们传入的不是类型可以是整型
template<class T,size_t N = 10>
class my_array
{
public:
T&operator[](size_t pos)
{
return _array[pos];
}
size_t size()const
{
return _size;
}
bool empty()const
{
return _size == 0;
}
size_t capacity()const
{
return N;
}
protected:
T _array[N];
size_t _size;
};
这是我们自己实现的一个简单的array,这里面使用了模板参数,同时模板参数还给了缺省值
这一点与函数参数的缺省值类似。
浮点数、类对象以及字符串是不允许作为非类型模板参数的。
非类型的模板参数必须在编译期就能确认结果。
这种原因我们也能够想到,因为如果不能确定类型,就不知道空间应该开多大。
二、array
array绝对是C++容器里面被吐槽的最多的一个
array与普通的数组基本上是没有区别的,唯一有区别的一点是它有越界检查
而普通的数组对于越界是抽查,为什么在这里说这个呢?因为它就使用了非类型模板参数
void test2()
{
std::array<int, 5> a1;
int a2[5] = { 0 };
for (size_t i = 0; i < 5; i++)
{
cout << a1[i] << " ";
}
cout << endl;
for (size_t i = 0; i < 5; i++)
{
cout << a2[5] << " ";
}
a1[20];
a2[20];
}
我们先把a2屏蔽然后观察现象
直接就崩溃了,然后我们将a1屏蔽
成功运行了,程序没有崩溃。
array对于越界的检查是更加严格的,因为它使用了迭代器,迭代器就会不停的检查是否越界。
二、模板的特化
1.概念
通常情况下,使用模板可以实现一些与类型无关的代码,但对于一些特殊类型的可能会得到一些错误的结果,需要特殊处理,比如:实现了一个专门用来进行小于比较的函数模板
函数模板的特化步骤:1. 必须要先有一个基础的函数模板2. 关键字template后面接一对空的尖括号<>3. 函数名后跟一对尖括号,尖括号中指定需要特化的类型4. 函数形参表: 必须要和模板函数的基础参数类型完全相同,如果不同编译器可能会报一些奇怪的错误。
2、函数模板特化
template<class T>
bool less(T x, T y)
{
return x < y;
}
这是我们写的一个简单的比较大小的函数
class Date
{
public:
Date(int year, int month, int day)
:_year(year)
, _month(month)
, _day(day)
{}
int GetMonthDay(int year, int month)
{
static int days[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
int day = days[month];
if (month == 2
&& ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)))
{
day += 1;
}
return day;
}
bool operator<(const Date& d)const
{
if ((_year < d._year) || (_year == d._year && _month < d._month) || (_year == d._year && _month == d._month && _day < d._day))
{
return true;
}
return false;
}
int _year;
int _month;
int _day;
};
同时,我们将原来写过的Date类拷贝过来,然后利用less这个函数来比较大小
void test3()
{
cout <<less(1, 4) << endl;
Date d1(2022, 10, 2);
Date d2(2022, 10, 1);
Date d3(2022, 9, 28);
cout << less(d1, d2) << endl;
cout << less(d2, d3) << endl;
Date* d4 = new Date(2022, 1, 2);
Date* d5 = new Date(2022, 2, 2);
Date* d6 = new Date(2022, 3, 2);
cout << less(d4, d5) << endl;
cout << less(d4, d6) << endl;
cout << less(d4, d5) << endl;
cout << less(d4, d6) << endl;
}
一部分是比较对象,另一部分,比较的是地址,本质上我们都是想要比较哪个日期比较大
但是如果我们直接比较的是地址可能结果与我们的预期不太相符
因为虽然都是new出来的,但是C++并没有规定先new出来的地址就低后new出来的地址就高,所以地址会呈现随机性,结果是不可预期的
但是如果我们使用函数模板特化,指定某一类型进行不同的操作
template<>
bool less<Date*>(Date* x, Date* y)
{
return *x < *y;
}
我们发现与我们的预期所吻合
3、类模板特化
(1)、全特化
我们的类模板可能具有不只一个模板参数,我们可以像函数模板那样进行特化,全特化就是将所有的函数模板都进行特化
template<class T>
struct less
{
bool operator()(const T& x, const T& y)
{
return x < y;
}
};
我们写了一个仿函数,这是用来判断哪个比较小的
我们还是使用上面的例子,如果是指针类型怎么办?
还是需要我们的特化,不过这回特化的是我们的类模板
template<class T>
struct less<T*>
{
bool operator()(T* x, T* y)const
{
return *x < *y;
}
};
void test4()
{
less<Date> ls1;
less<Date*> ls2;
Date d1(2022, 10, 2);
Date d2(2022, 10, 1);
Date d3(2022, 9, 28);
cout << ls1(d1, d2) << endl;
cout << ls1(d2, d3) << endl;
Date* d4 = new Date(2022, 1, 2);
Date* d5 = new Date(2022, 2, 2);
Date* d6 = new Date(2022, 3, 2);
cout << ls2(d4, d5) << endl;
cout << ls2(d4, d6) << endl;
}
void test5()
{
std::priority_queue<Date,std::vector<Date>, less<Date>> dq1;
dq1.push(Date(2022, 3, 5));
dq1.push(Date(2022, 4, 6));
dq1.push(Date(2022, 3, 9));
dq1.push(Date(2022, 5, 4));
dq1.push(Date(2022, 5, 5));
while (!dq1.empty())
{
Date& top = dq1.top();
cout << top._year << "/" << top._month << "/" << top._day << endl;
dq1.pop();
}
cout << endl;
std::priority_queue<Date, std::vector<Date*>, less<Date*>> dq2;
dq2.push(new Date(2022, 3, 5));
dq2.push(new Date(2022, 4, 6));
dq2.push(new Date(2022, 3, 9));
dq2.push(new Date(2022, 5, 4));
dq2.push(new Date(2022, 5, 5));
while (!dq2.empty())
{
Date* top = dq2.top();
cout << top->_year << "/" << top->_month << "/" << top->_day << endl;
dq2.pop();
}
cout << endl;
}
(2)、偏特化
偏特化是与全特化相对的例子,偏特化就是部分模板参数进行特化
// 将第二个参数特化为int
template <class T1>
class Data<T1, int> {
public:
Data() {cout<<"Data<T1, int>" <<endl;}
private:
T1 _d1;
int _d2;
};
//两个参数偏特化为指针类型
template <typename T1, typename T2>
class Data <T1*, T2*>
{
public:
Data() {cout<<"Data<T1*, T2*>" <<endl;}
private:
T1 _d1;
T2 _d2;
};
//两个参数偏特化为引用类型
template <typename T1, typename T2>
class Data <T1&, T2&>
{
public:
Data(const T1& d1, const T2& d2)
: _d1(d1)
, _d2(d2)
{
cout<<"Data<T1&, T2&>" <<endl;
}
private:
const T1 & _d1;
const T2 & _d2;
};
void test6 ()
{
Data<double , int> d1; // 调用特化的int版本
Data<int , double> d2; // 调用基础的模板
Data<int *, int*> d3; // 调用特化的指针版本
Data<int&, int&> d4(1, 2); // 调用特化的指针版本
}
三、模板分离编译
C++是不支持模板的分离编译的
我们将前面写过的vector的部分函数挪到类外
#pragma once
#include <cassert>
namespace ww
{
template<class T>
class vector
{
public:
typedef T* iterator;
typedef const T* const_iterator;
iterator begin()
{
return _start;
}
iterator end()
{
return _finish;
}
const_iterator begin() const
{
return _start;
}
const_iterator end() const
{
return _finish;
}
vector()
:_start(nullptr)
, _finish(nullptr)
, _end_of_storage(nullptr)
{}
~vector()
{
delete[] _start;
_start = _finish = _end_of_storage = nullptr;
}
size_t capacity() const {
return _end_of_storage - _start;
}
size_t size() const
{
return _finish - _start;
}
void reserve(size_t n)
{
if (n > capacity())
{
size_t sz = size();
T* tmp = new T[n];
if (_start)
{
for (size_t i = 0; i < sz; ++i)
{
tmp[i] = _start[i];
}
delete[] _start;
}
_start = tmp;
_finish = _start + sz;
_end_of_storage = _start + n;
}
}
void push_back(const T& x);
iterator insert(iterator pos, const T& x);
private:
iterator _start;
iterator _finish;
iterator _end_of_storage;
};
template<class T>
vector<T>::iterator insert(vector<T>::iterator pos, const T& x)
{
if (vector<T>::_finish == vector<T>::_end_of_storage)
{
size_t len = pos - vector<T>::_start;
reserve(vector<T>::capacity() == 0 ? 4 : vector<T>::capacity() * 2);
pos = vector<T>::_start + len;
}
vector<T>::iterator end = vector<T>::_finish - 1;
while (end >= pos)
{
*(end + 1) = *end;
--end;
}
*pos = x;
++vector<T>::_finish;
return pos;
}
template<class T>
void push_back(const T& x)
{
insert(vector<T>::end(), x);
}
}
它会出现一系列的错误,原因是作用域的不同所导致的,我们加上作用域之后发现它还是会报错
编译器已经提醒了我们要加上typename,这个关键字主要是告知编译器它是内嵌类型
因为静态变量的访问方式也是如此,编译器根本无法得知它是变量还是类型,所以加上typename来告知编译器。
总结
以上就是今天要讲的内容,本文仅仅简单介绍了模板。