【C++】模板进阶

目录

前言

一、非类型模板参数

二、array

二、模板的特化

1.概念

2、函数模板特化

3、类模板特化

(1)、全特化

(2)、偏特化

三、模板分离编译

总结


前言

前面我们已经学习了一部分模板的相关知识,接下来我们将要进行更加深入的学习模板


一、非类型模板参数

模板参数分类类型形参与非类型形参。
类型形参即:出现在模板参数列表中,跟在class或者typename之类的参数类型名称。
非类型形参,就是用一个常量作为类(函数)模板的一个参数,在类(函数)模板中可将该参数当成常量来使用

模板参数特别的像我们的函数参数,非类型模板参数就是我们传入的不是类型可以是整型

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来告知编译器。 


总结


以上就是今天要讲的内容,本文仅仅简单介绍了模板。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值