【C++面向对象】模板进阶用法 | 读完小白也能变成大神

绪言

模板是泛型编程实现的重要手段。关于模板的基本用法,可以看我的这篇文章,这里不再过多地赘述。本篇文章主要是面向掌握了C++面向对象编程基本语法,希望深入学习的读者。建议大家根据自身情况自行选择阅读与否。

实例化

模板实例化是模板的重要概念。什么是实例化呢?在编写一个模板时,我们并没有具体指定模板类型参数,而是使用了一个占位符(例如T。而当我们在创建一个类模板对象,或者调用一个函数模板时,无论是显式指定模板类型参数,或者隐式推导,系统最终都会得到我们定义的占位符在调用时的具体指代。这个过程就叫做模板实例化

模板实例化分为显式实例化和隐式实例化,下面将分别讲述。

显式实例化

显式实例化,即手动指定模板中的类型参数,一般放在尖括号<>内,置于函数名之后调用函数模板,或者置于类名之后创建类模板对象。如果您还不能清晰地理解显式实例化,请看下面的实例:

#include <iostream>
using namespace std;

template <typename T>
class myclass
{
public:
	myclass() {};
	myclass(int p) :id(p) {};
	~myclass() {};

public:
	int id;
	myclass <T> operator+(const myclass <T>& b)
	{
		myclass <T> ret(this->id + b.id);
		return ret;
	}
};

template <typename T>
T add(T a, T b)
{
	return a + b;
}

int main()
{
	myclass <int> a(5);
	myclass <int> b(6);

	cout << add <myclass <int>>(a, b).id << endl;
	return;
}

运行上面的代码,将会输出以下结果:

11

观察main()函数,函数的一、二两行各定义了一个类模板对象,我们显式地用int类型代替了占位符T;函数的输出行调用了模板函数add,我们显式地用类型myclass <int>代替了占位符T。在这个实例中我们都显式地指定了类型参数。这就是显式实例化。

隐式实例化

与显式实例化相对的是隐式实例化。隐式实例化即不明确指定类型参数,而是系统通过函数的参数来推导类型参数。所以请注意:由于定义一个类模板对象时不一定都有参数可供推导,所以在C++标准中,隐式实例化是模板函数的专利,不能用于类模板。这也就告诉我们,一个类模板的名称并不是类型名,一个指定了类型参数的类模板名称才是类型名,例如在上面的实例中,myclass是非法的类型名,而myclass <int>是合法的。

隐式实例化不必指定类型参数,请看下面的实例,它与上面的实例是等价的

#include <iostream>
using namespace std;

template <typename T>
class myclass
{
public:
	myclass() {};
	myclass(int p) :id(p) {};
	~myclass() {};

public:
	int id;
	myclass <T> operator+(const myclass <T>& b)
	{
		myclass <T> ret(this->id + b.id);
		return ret;
	}
};

template <typename T>
T add(T a, T b)
{
	return a + b;
}

int main()
{
	myclass <int> a(5);
	myclass <int> b(6);

	cout << add(a, b).id << endl;
}

相比于之前的实例,这里只是把函数add()的调用更改了一下,去除了函数名之后指定类型参数的内容。这就是隐式实例化。

特化

与模板实例化相似的概念还有模板特化。模板特化,就是对于特殊的模板类型参数,我们想实现不同的功能时补充的专门性的函数。特化分为全特化和偏特化。

全特化

全特化的一般形式为:

类模板全特化
template <typename T>
class a
{
public:
	T ori;
	//class body
};

template <>
class a <const char*>
{
public:
	const char* ori;
	//class body
};
函数模板全特化
template <typename T>
T add(T a, T b)
{
	return a + b;
}

template <>
char add <char>(char a, char b)
{
	return a + b;
}
偏特化

由于函数模板的偏特化可以通过重载实现,所以函数模板没有偏特化,只有类模板有偏特化。

template <typename T1, typename T2>
class a
{
public:
	T1 ori1;
	T2 ori2;
	//class body
};

template <typename T>
class a<const char*, T>
{
public:
	const char* ori1;
	T ori2;
	//class body
};

多个模板参数

模板参数可以不止一个。例如在上面的实例中,类a中就有两个类型参数T1T2。在含有多个模板参数的模板参数列表中,与函数参数类似地,每个参数之间用,隔开。

缺省的模板参数

模板参数也可以缺省,但所有缺省参数只能放在普通参数之后模板参数可以只含有缺省参数,但即使如此,类模板对象创建时也要加上尖括号<>。请看下面的实例:

template <typename T1 = int, typename T2 = int>
class a
{
public:
	T1 A;
	T2 B;

public:
	a() {};
	a(T1 first, T2 second) :A(first), B(second) {};
	~a() {};
};

int main()
{
	a <> Classa;
	a <char> Classb;
	a <double, double> Classc;
	return 0;
}
带参数包的类型参数

在类型参数列表中的typenameclass关键字后加上运算符...可以声明一个类型参数是参数包类型,即该类型带有参数包。这样写的好处是可以与可变参数相结合编程。以下是一个实例:

#include <vector>
#include <type_traits>
#include <iostream>
using namespace std;

template <typename T>
class myclass
{
private:
	vector <T> ori;

public:
	myclass() {};

	template <typename... _Args>
	myclass(_Args... args)
	{
		vector <T> tmp{ forward <T>(args)... };
		this->ori = tmp;
	}
	~myclass() {};

public:
	void print()
	{
		for (int i = 0; i < this->ori.size(); i++)
		{
			cout << ori[i] << ' ';
		}
		cout << endl;
		return;
	}
};

int main()
{
	myclass <int> a{ 1,2,3,4,5 };
	a.print();
	return 0;
}

运行上面的代码,将会输出以下结果:

1 2 3 4 5

非类型模板参数

模板的参数不一定必须是类型参数,也可以像函数参数一样是一个具体的对象。以下是一个实例,展现了模板非类型参数实现变长数组的方法:

#include <iostream>
using namespace std;

template <typename T, size_t N>
class arr
{
private:
	T ori[N];

public:
	arr()
	{
		memset(ori, 0, sizeof(ori));
	}

	~arr() {};
};

int main()
{
	arr <int, 5> a;
	return 0;
}

这里模板参数N就不是类型参数,而是一个size_t类型的变量。调用时也与一般的类模板相同,将模板参数置于尖括号<>内。

希望大家多多支持!多评论、多点赞、多收藏、多关注!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值