C++模板进阶

2023-12-14 19:33:58

目录

一.非类型模板参数

二.模板的特化

1.函数模板的特化

2.类模板的特化

类模板的全特化

类模板的偏特化

对参数的进一步限制

三、类模板分离编译(类模板不要声明和定义分离)

四.模板总结


今天下大雪了哎 ,注意保暖

一.非类型模板参数

1.模板参数分类类型形参与非类型形参。
类型形参即:出现在模板参数列表中,跟在class或者typename之类的参数类型名称
非类型形参,就是用一个常量作为类(函数)模板的一个参数,在类(函数)模板中可将该参数当成常量来使用。
2.在C语言阶段如果想要让数组的大小可以自己控制,一般都会用定义宏的方式来解决,在C++中我们可以使用非类型模板参数来进行解决,下面代码给出类模板的声明,在使用时我们可以显示实例化类模板,给非类型模板参数传一个常量,这个常量在类中可以任意使用
//#define N 10
//静态数组
//非类型模板参数 --- 是一个常量
template<class T, int N = 10>//给非类型模板参数缺省值为常量10,可以给缺省值
class Array
{

private:
	T _a[N];
};

int main()
{
	//Array<int> a1;		// 10
	//Array<double> a2;		// 1000

	Array<int,10> a1;		// 10
	Array<double,1000> a2;  // 1000

	return 0;
}

3.非类型模板参数声明时的类型必须只能是整型,其他例如自定义类型,字符串类型,浮点型等类型均不能作为非类型模板参数的类型声明,只有整型才可以

4.在显式实例化模板时,给非类型模板参数传参时,只能给常量,不能给变量,否则会报错:局部变量不能作为非类型模板参数。所以在传参时,也只能传常量。

template<class T,size_t N>
//只能给整型
void Func(const T& x)
{
	cout << N << x << endl;
}
int main()
{
	//Func<int, 100>(1);
	//Func<int, 100.11>(1);
	
	//int a = 10;
	//Func<int, a>(1);//只能给常量,局部变量不能作为非类型模板参数。

	//非类型模板参数主要还是用于,在类里面定义某些数组时,想要通过非类型模板参数的大小来定义数组大小。
	return 0;
}

二.模板的特化

1.函数模板的特化

(建议使用重载函数,不建议进行特化)

下面用一个日期类来举例说明

bool Less(Date* left, Date* right)
{
	return *left < *right;
}
class Date
{
public:
	Date(int year = 1900, int month = 1, int day = 1)
		: _year(year)
		, _month(month)
		, _day(day)
	{}

	bool operator<(const Date& d)const
	{
		return (_year < d._year) ||
			(_year == d._year && _month < d._month) ||
			(_year == d._year && _month == d._month && _day < d._day);
	}

	bool operator>(const Date& d)const
	{
		return (_year > d._year) ||
			(_year == d._year && _month > d._month) ||
			(_year == d._year && _month == d._month && _day > d._day);
	}

	friend ostream& operator<<(ostream& _cout, const Date& d)
	{
		_cout << d._year << "-" << d._month << "-" << d._day;
		return _cout;
	}

private:
	int _year;
	int _month;
	int _day;
};

// 函数模板 -- 参数匹配
template<class T>
bool Less(T left, T right)
{
	return left < right;
}

// 针对某些类型进行特殊处理 -- Date*
template<>
bool Less<Date*>(Date* left, Date* right)
{
	return *left < *right;
}
// 下面这种方式是函数重载
bool Less(Date* left, Date* right)
{
	return *left < *right;
}

int main()
{
	cout << Less(1, 2) << endl;   // 可以比较,结果正确

	Date d1(2022, 7, 7);
	Date d2(2022, 7, 8);
	cout << Less(d1, d2) << endl;  // 可以比较,结果正确

	Date* p1 = &d1;
	Date* p2 = &d2;
	cout << Less(p1, p2) << endl;  // 可以比较,结果错误,地址之间的逻辑比较,可能为0可能为1,因为地址是随机的。

	return 0;
}

1.
在main函数的测试用例中前两次的打印结果都是正常的,因为日期之间进行比较时可以直接调用日期类的运算符重载,并且Less是一个函数模板,可以接收所有的类型的比较,包括内置类型和自定义类型。

2.
但是当Less模板类型为日期类指针类型时,打印的结果就会有问题了,因为比较的是两个日期对象的地址,而地址是随机的,这时候对于日期类指针这种类型,函数模板Less就会出问题。

3.
既然是针对日期类指针类型出现的问题,那就可以通过函数模板的特化来解决,我们将Date*这样的类型单独拿出来实例化出一个现成的函数来,这样的方式就被称作函数模板的特化。

4.
在函数模板特化时,必须要先有一个基础的函数模板,然后在特化的函数上一行加一个template<>,然后在函数名后面加尖括号,尖括号里面指定特化的类型,特化函数的形参表必须要和原来的函数模板的形参表所包含的基础参数类型匹配,若不匹配,则编译器会报一些奇怪的错误。

5.
一般情况下,在遇到函数模板不能解决或者处理有误的类型时,为了实现简单,通常是用重载函数来解决的,这样的代码可读性高,容易书写。而如果遇到参数类型十分复杂的模板时,特化时需要特别给出,书写起来较为繁琐,不如直接重载函数来的快。

2.类模板的特化

类模板的全特化

全特化即是将模板参数列表中所有的参数都确定化。

类模板的全特化就是将模板参数列表中所有的参数都确定化,在显示实例化函数模板时,若显示所传参数为int和char,则不会走推演实例化的步骤,而是直接走实例化好的类,所以类模板的全特化实际就是在参数确定之后,模板实例化出具体的类。

template<class T1, class T2>
class Data
{
public:
 Data() {cout<<"Data<T1, T2>" <<endl;}
private:
 T1 _d1;
 T2 _d2;
};


//类模板特化全特化
template<>
class Data<int, char>
{
public:
 Data() {cout<<"Data<int, char>" <<endl;}
private:
 int _d1;
 char _d2;

}

类模板的偏特化

部分特化后的模板属于办成品,如果在传参时,某一个参数是属于部分特化后的参数,则编译器优先调用那个部分特化的类模板。

// 类模板的特化 --- 半特化、偏特化
template<class T1>
class Data<T1, char>
{
public:
	Data() { cout << "Data<T1, char>" << endl; }
private:

};

int main()
{
	Data<int, char> d3;// d3和d4都匹配半特化后的模板
	Data<double, char> d4;
}

对参数的进一步限制
// 对参数类型的进一步限制 --- 半特化
template<class T1, class T2>
class Data<T1*, T2*>//单独对指针类型进行特化,无论是什么类型的指针。
{
public:
	Data() { cout << "Data<T1*, T2*>" << endl; }
};

template<class T1, class T2>
class Data<T1&, T2&>//单独对引用进行特化
{
public:
	Data() { cout << "Data<T1&, T2&>" << endl; }
};

特化的本质体现的是编译器参数的匹配原则,有现成实例化出来的就优先用现成的,没有现成用半成,没有办成就自己进行实例化。

三、类模板分离编译(类模板不要声明和定义分离)

1.
在使用类模板显示实例化的地方,只有.h文件展开,而没有.cpp文件,因为在链接之前,各源文件之间是互不联系的,所以即使你显示实例化了类模板,但在类模板真正定义的地方却没有实例化,所以在链接的时候.cpp里面没有实例化出来的类模板,自然链接就会出问题,因为你用了一个并没有真正实例化出来的类,编译器就会报链接错误。

2.
解决的方式也很简单,有两种方法,将声明和定义放到一个文件 “xxx.hpp” 里面或者xxx.h文件里面,但一般喜欢用.hpp文件,这代表这个文件专门用来放类模板的声明和定义。
第二种就是在模板定义的位置也就是.cpp文件里面进行对应模板参数类型的显式实例化,但这种方式不推荐,如果我要实例化出10个类呢?那你就在类模板定义的地方连续显示实例化出10个类吗?这样的方式未免有些太挫了吧!



原文链接:https://blog.csdn.net/erridjsis/article/details/129467967

四.模板总结

【优点】
1. 模板复用了代码,节省资源,更快的迭代开发,C++的标准模板库(STL)因此而产生
2. 增强了代码的灵活性
【缺陷】
1. 模板会导致代码膨胀问题,也会导致编译时间变长
2. 出现模板编译错误时,错误信息非常凌乱,不易定位错误

文章来源:https://blog.csdn.net/m0_71920611/article/details/134982368
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。