c++类——模版(三)

本文介绍了C++中的模板特性,包括非类型模板参数的使用,如常量表达式、转换规则;默认模板类型形参的新规则,以及全特化和部分特化的概念和应用。示例展示了如何在类和函数模板中使用这些特性,并给出了实际代码示例。
摘要由CSDN通过智能技术生成

如有兴趣了解更多请关注我的个人博客https://07xiaohei.com/

本文介绍模版的各种特殊情况:

(一)非类型模板参数(通常不应用于函数模版中):

  • 模板的非类型形参是内置类型形参,模板参数不是一个类型而是一个具体的值,且值是常量表达式,因此调用非类型模板形参的实参必须是一个常量表达式(因此模板代码不能修改参数的值,也不能使用参数的地址)。

    例如template<class T, int a> class B{}; 其中int a就是非类型的模板形参。

    通常能被传入的常量表达式包括全局变量的地址或引用,全局对象的地址或引用const类型变量,sizeof的表达式的结果以及const int 整型变量。

  • 形参只能是整型,枚举,指针和引用,如double,string等是不可以的,但是可以使用double&或者string*等类型是允许的。

  • 当一个模板被实例化时,非类型参数被一个用户提供的或者编译器推断出的值所代替。正因为模板在编译阶段编译器为我们生成一个对应的版本,所以其值应该能够编译时确定,那么他应该是一个常量或者常量表达式。

  • 非类型模板形参的形参和实参间所允许的转换

    • 允许从数组到指针,从函数到指针的转换。如:template <int *a> class A{}; int b[1]; A<b> m;即数组到指针的转换。
    • const修饰符的转换。如:template<const int *a> class A{}; int b; A<&b> m; 即从int *到const int *的转换。
    • 提升转换。如:template<int a> class A{}; const short b=2; A<b> m; 即从short到int的提升转换。
    • 整值转换。如:template<unsigned int a> class A{}; A<3> m; 即从int 到unsigned int的转换。
    • 常规转换。略。
#include<iostream>
using namespace std;

//定义一个栈,用数组实现,MAXSIZE为非类型模版类参数,其类型确定
template<typename T, int MAXSIZE>
class Stack
{
private:
	int REALSIZE;
	//这个非类型模版类参数可以在类中使用其名称
	T elems[MAXSIZE];
public:
	Stack() :REALSIZE(0) {}
	bool empty() const { return REALSIZE == 0; }
	bool full() const { return REALSIZE == MAXSIZE; }
	void push(const T&);
	void pop();
	T& top();
	const T& top() const;

};
template<typename T, int MAXSIZE>
void Stack<T, MAXSIZE>::push(const T& elem)
{
	if (full())
		throw std::out_of_range("Stack<>::push(): full stack");
	elems[REALSIZE++] = elem;
}
template<typename T, int MAXSIZE>
void Stack<T, MAXSIZE>::pop()
{
	if (!empty())
		REALSIZE--;
	else
		throw std::out_of_range("Stack<>::pop(): empty stack");
}
template<typename T, int MAXSIZE>
T& Stack<T, MAXSIZE>::top()
{
	if (empty())
		throw std::out_of_range("Stack<>::top(): empty stack");
	return elems[REALSIZE - 1];
}
template<typename T, int MAXSIZE>
const T& Stack<T, MAXSIZE>::top() const
{
	if (empty())
		throw std::out_of_range("Stack<>::top(): empty stack");
	return elems[REALSIZE - 1];
}
//double类型,类类型不允许作为非类型模版类参数使用
//template<typename T, double MAXSIZE,string name>
//class A{};

//double引用类型,类指针类型允许作为非类型模版类参数使用
//枚举也可以,但不再演示
template<typename T,double& MAXSIZE,string*name>
class A
{
private:
	double REALSIZE;
	T elems[MAXSIZE];
	string names;
public:
	A(double d):REALSIZE(d) { names = *name; }
};

template<typename T, const string* p>
class B {};
template<typename T, unsigned int ui>
class C {};

double alld = 10.0;
double& alldd = alld;
string alls = "帅";
string* allss = &alls;
string s[3] = { "aaaaaa","bbbbbb","cccccc" };
int main()
{
	//传入常量表达式
	const int i = 5;
	Stack<int, 10> s1;
	Stack<string, i> s3;

	//sizeof也可以
	Stack<double, sizeof(double)> s2;

	//int ii = 5;
	//Stack<string, ii> s4; //错误,不能传入非常量变量

	//double d = 10.0;
	//double& dd = d;
	//string s = "帅";
	//string* ss = &s;
	//A<int, dd, ss>;   //错误,不能传入局部变量,非常量变量

	//可以使用全局引用和全局变量的局部地址传入
	A<int, alld, &alls> a1(10);
	//A<int, alld, allss>; //错误,不能传入全局指针变量

	//可以传入全局数组,会将其转换为指针传入
	A<double, alld, s> a2(3.14);

	//可以将string*提升为const string*
	B<int, &alls> b; 

	//可以将short int提升为int
	const short bbb = 2;
	Stack<char, bbb>;

	//可以将int提升为unsigned int
	const int n = 9;
	C<int, n>;
	return 0;
}

(二)默认模板类型形参:

在c++11新规则中,可以为类模板或者函数模版的类型形参提供默认值。

形式为:template<class T1, class T2, … , class Tk=默认类型 , … , class Tn=默认类型 > class 类名{ 类体 };

or template<class T1 =默认类型 , class T2, … , class Tk=默认类型 , … , class Tn> 返回类型 类型名(形参列表){函数体}

类模版为多个默认模版参数声明指定默认值时,必须遵照“从右往左”的规则进行指定。

函数模版不需要遵循此规则,可以任意指定默认值。

函数模板的参数推导规则简单来说就是:能够从函数实参中推导出类型的话,那么默认模版参数就不会被使用,否则使用默认模版参数。

因此,函数模版如果需要提供默认类型,因为其形参列表是从右往左给出默认值的,所以只有其默认类型也是从右往左给出且与值的类型匹配(也就是不会出现不匹配情况)、值的个数匹配,才是有效的默认类型。

#include<iostream>
using namespace std;
template <typename T1, typename T2 = int, typename T3=double>
class A {
public:
	T1 Aval1;
	T2 Aval2;
	T3 Aval3;
};
//template <typename T1 = int, typename T2>
//class DefClass2 {};  // 错误:模板参数的默认值没有遵循“由右往左”的规则
//
//template <typename T, int i = 0>
//class DefClass3 {};
//
//template <int i = 0, typename T>
//class DefClass4 {};  // 错误:即使是非类型模版参数,模板参数的默认值也必须遵循“由右往左”的规则

//尽管可以进行下面这样的声明,但几乎不会用上这个默认类型参数,可以直接无视
template <typename T1 = int, typename T2>
void B1(T1 a, T2 b) {
	cout << a << endl;
	cout << typeid(T1).name() << endl;
	cout << b << endl;
	cout << typeid(T2).name() << endl;
	return;
}; // 函数模板不用遵循“由右往左”的规则

//允许进行这样的声明,当未提供第二个参数时,自动默认b为int类型且值为0
template <typename T1, typename T2= int>
void B2(T1 a, T2 b=0) {
	cout << a << endl;
	cout << typeid(T1).name() << endl;
	cout << b << endl;
	cout << typeid(T2).name() << endl;
	return;
}; // 函数模板不用遵循“由右往左”的规则


template <int i = 0, typename T>
void B3(T a) {
	cout << i << endl;
	cout << typeid(i).name() << endl;
	cout << a << endl;
	cout << typeid(T).name() << endl;
	return;
};  // 非类型模版参数也不用遵循“由右往左”的规则


int main() 
{
	A<string> a1;
	cout << typeid(a1.Aval1).name() << endl;
	cout << typeid(a1.Aval2).name() << endl; //为默认的int
	cout << typeid(a1.Aval3).name() << endl; //为默认的double
	A<char, unsigned int> a2;
	cout << typeid(a2.Aval1).name() << endl;
	cout << typeid(a2.Aval2).name() << endl; //unsigned int覆盖默认的int
	cout << typeid(a2.Aval3).name() << endl; //为默认的double
	A<char, unsigned int,float> a3;
	cout << typeid(a3.Aval1).name() << endl;
	cout << typeid(a3.Aval2).name() << endl; //unsigned int覆盖默认的int
	cout << typeid(a3.Aval3).name() << endl; //float覆盖默认的double
	
	B1(3, 4.1); //推导出int,double类型,和默认类型无关
	B1(3.1, 4); //推导出double,int类型,覆盖默认类型
	B2("s");    //前者推导出const char[2],后者为默认的int且值为0
	return 0;
}
//运行结果:
//  class std::basic_string<char, struct std::char_traits<char>, class std::allocator<char> >
//	int
//	double
//	char
//	unsigned int
//	double
//	char
//	unsigned int
//	float
//	3
//	int
//	4.1
//	double
//	3.1
//	double
//	4
//	int
//	s
//	char const* __ptr64
//	0
//	int

(三)模版的特化:

模版的特化是模版参数在某种特定类型下的具体实现,是对单一模版提供的一个特殊实例,它将一个或多个模版参数绑定到特定的类型或值上。

通常情况下,使用模板可以实现一些与类型无关的代码,但对于一些特殊类型的可能会得到一些错误的结果,比如字符串不能进行比较,此时就需要特化出一个专门用于字符串的模版参数。

特化分为函数模版特化和类模版特化:

  • 函数模板特化:

    必须要先有一个基础的函数模板,且使用特换模板函数时格式有要求:

    1. 关键字template后面接一对空的尖括号<>。
    2. 函数名<特化类型>(特化类型 参数1, 特化类型 参数2 , …) 在函数名后跟<>其中写要特化的类型。

    函数形参表必须要和模板函数的基础参数类型完全相同,如果不同编译器可能会报一些奇怪的错误。

    (实际上,此为全特化,c++11中,函数模版也可以进行偏特化的部分特化相似操作,但实际上这是一种重载,会导致函数有多个匹配,此时无法区分;不能进行偏特化的部分限制)

    #include <iostream>
    using namespace std;
    
    template< typename T >
    int compare(const T& a, const T& b)
    {
    	if (a > b)return 1;
    	else if (a == b)return 0;
    	else return -1;
    }
    
    template<>
    int compare(const char* const & a, const char* const & b)
    {
    	return strcmp(a, b);
    }
    int main()
    {
    	cout << compare(3, 4) << endl;
    	cout << compare(4.2, 2.3) << endl;
    	cout << compare((const char*)"vvv", (const char*)"ttt") << endl;
    	return 0;
    }
    
  • 类模版特化:

    分为全特化和偏特化:

    • 全特化:将模版参数类型中所有的参数都确定化。

      全特化的优先级比偏特化要高。

    • 偏特化:对于模版的类型进行限制。

      偏特化有两种表现方式:

      • 部分特化:将模版参数表中的一部分参数特化。
      • 对于模版类型进行进一步的限制。(如必须特化为指针类型、引用类型)
    #include<iostream>
    using namespace std;
    //类模版
    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;
    };
    //偏特化1:部分特化
    template<class T1>
    class Data<int, T1>
    {
    public:
    	Data() {
    		cout << "Data<int,T1>" << endl;
    	}
    private:
    	int _d1;
    	T1 _d2;
    };
    
    //偏特化2:添加限制
    //两个参数偏特化为指针类型
    template <typename T1, typename T2>
    class Data <T1*, T2*>
    {
    public:
    	Data() {
    		cout << "Data<T1*,T2*>" << endl;
    	}
    private:
    	T1 _d1;
    	T2 _d2;
    };
    
    //两个参数偏特化为引用类型
    template<class T1, class T2>
    class Data<T1&, T2&> {
    	T1& _d1;
    	T2& _d2;
    public:
    	Data(T1& a, T2& b) :
    		_d1(a),
    		_d2(b)
    	{
    		cout << "Data<T1&,T2&>" << endl;
    	}
    };
    
    
    int main()
    {
    	Data<int, int> d1;	     // Data<T1,T2>
    	Data<string, string> d2; // Data<T1,T2>
    	Data<int, char> d3;	     // Data<int,char>,其实部分特化也满足,但优先进行全特化
    	Data<int, double> d4;    // Data<int,T1>
    	Data<int*, double*> d5;  // Data<T1*,T2*> 
    	int i = 5;
    	int& pi = i;
    	double d = 6.5;
    	double& pd = d;
    	Data<int&, double&> d6(pi,pd); //Data<T1&,T2&>
    	return 0;
    }
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

xiaohei07

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值