章节目录

第26章总结

本节阅读量:

模板允许我们使用类型作为占位符来编写函数或类,从而用不同的类型实例化同一份函数或类代码。

所有模板函数或类都必须以template参数声明开头,该声明会告诉编译器后面的函数或类是模板。在模板参数声明中,可以指定模板类型参数或非类型参数。模板类型参数只是占位符类型,通常命名为T、T1、T2,或其他单字母名称(例如S、U)。非类型参数通常是整型,也可以是指向函数、类对象或成员函数的指针或引用。

拆分模板类定义和成员函数定义不像普通类那样简单——不能简单地将类的成员函数定义放在.cpp文件中。通常最好将它们全部保存在头文件中,并将成员函数定义放在类定义的下方。

当我们想要覆盖模板化函数或类在特定类型下的默认行为时,可以使用模板特化。如果覆盖所有模板参数,则称为完全特化。类还支持部分特化,即只特化其中一部分模板参数。函数不能部分特化。

C++标准库中的许多类都使用模板,包括std::array和std::vector。模板通常用于实现容器类,因此容器只需编写一次,就可以与任何合适的类型一起使用。


习题

问题 1

有时把数据成对定义会很有用。编写一个名为Pair1的模板类,让用户只定义一个模板类型,并把这个类型同时用于这一对中的两个值。下面这个函数应当能够工作:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
int main()
{
	Pair1<int> p1 { 5, 8 };
	std::cout << "Pair: " << p1.first() << ' ' << p1.second() << '\n';

	const Pair1<double> p2 { 2.3, 4.5 };
	std::cout << "Pair: " << p2.first() << ' ' << p2.second() << '\n';

	return 0;
}

并打印:

Pair: 5 8
Pair: 2.3 4.5
查看答案
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <iostream>

template <typename T>
class Pair1
{
private:
	T m_x {};
	T m_y {};

public:
	Pair1(const T& x, const T& y)
		: m_x{ x }, m_y{ y }
	{
	}

	T& first() { return m_x; }
	T& second() { return m_y; }
	const T& first() const { return m_x; }
	const T& second() const { return m_y; }
};

int main()
{
	Pair1<int> p1 { 5, 8 };
	std::cout << "Pair: " << p1.first() << ' ' << p1.second() << '\n';

	const Pair1<double> p2 { 2.3, 4.5 };
	std::cout << "Pair: " << p2.first() << ' ' << p2.second() << '\n';

	return 0;
}

问题 2

编写一个Pair类,允许为这一对中的两个值分别指定不同的类型。

注意:我们给这个类取的名字与上一个不同,因为C++目前不允许「重载」仅在模板形参个数或类型上不同的类。

下面这个程序应当能够工作:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
int main()
{
	Pair<int, double> p1 { 5, 6.7 };
	std::cout << "Pair: " << p1.first() << ' ' << p1.second() << '\n';

	const Pair<double, int> p2 { 2.3, 4 };
	std::cout << "Pair: " << p2.first() << ' ' << p2.second() << '\n';

	return 0;
}

并打印:

Pair: 5 6.7
Pair: 2.3 4

提示:要定义使用两个不同类型的模板,请用逗号分隔模板形参声明中的两个类型。更多信息参见「11.7 具有多个模板类型的函数模板」。

查看答案
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <iostream>

template <typename T, typename S>
class Pair
{
private:
	T m_x;
	S m_y;

public:
	Pair(const T& x, const S& y)
		: m_x{x}, m_y{y}
	{
	}

	T& first() { return m_x; }
	S& second() { return m_y; }
	const T& first() const { return m_x; }
	const S& second() const { return m_y; }
};

int main()
{
	Pair<int, double> p1 { 5, 6.7 };
	std::cout << "Pair: " << p1.first() << ' ' << p1.second() << '\n';

	const Pair<double, int> p2 { 2.3, 4 };
	std::cout << "Pair: " << p2.first() << ' ' << p2.second() << '\n';

	return 0;
}

问题 3

字符串-值对(string-value pair)是一种特殊的pair,其中第一个值总是字符串类型,第二个值可以是任意类型。编写一个名为StringValuePair的模板类,它继承自一个(以std::string作为第一个类型,并允许用户指定第二个类型的)偏特化Pair类。

下面这个程序应当能够运行:

1
2
3
4
5
6
7
int main()
{
	StringValuePair<int> svp { "Hello", 5 };
	std::cout << "Pair: " << svp.first() << ' ' << svp.second() << '\n';

	return 0;
}

并打印:

Pair: Hello 5

提示:在StringValuePair的构造函数中调用Pair的构造函数时,不要忘记把模板形参作为Pair类名的一部分写出来。

查看答案
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include <iostream>
#include <string>
#include <string_view>

template <typename T, typename S>
class Pair
{
private:
	T m_x{};
	S m_y{};

public:
	Pair(const T& x, const S& y)
		: m_x{ x }, m_y{ y }
	{
	}

	T& first() { return m_x; }
	S& second() { return m_y; }
	const T& first() const { return m_x; }
	const S& second() const { return m_y; }
};

template <typename S>
class StringValuePair : public Pair<std::string, S>
{
public:
	StringValuePair(std::string_view key, const S& value)
                // std::string_view 不会隐式转换为 std::string,必须显式转换
		: Pair<std::string, S>{ static_cast<std::string>(key), value }
	{
	}
};

int main()
{
	StringValuePair<int> svp{ "Hello", 5 };
	std::cout << "Pair: " << svp.first() << ' ' << svp.second() << '\n';

	return 0;
}

26.5 指针的部分模板特化

上一节

27.0 为什么需要异常机制

下一节