【和春笋一起学C++】(七十二)类模板

目录

[0 前言](#0 前言)

[1 类模板定义](#1 类模板定义)

[2 使用类模板](#2 使用类模板)

[3 使用非类型参数创建数组模板](#3 使用非类型参数创建数组模板)

[3.1 使用动态数组和构造函数提供大小](#3.1 使用动态数组和构造函数提供大小)

[3.2 使用模板参数来指定数组大小](#3.2 使用模板参数来指定数组大小)

[3.3 两种方法的比较](#3.3 两种方法的比较)


0 前言

继承和包含并不总是能够满足重用代码的需要。例如Stack类,Stack类是容器类,容器类被设计用来存储其他对象或数据类型。存储int类型的stack类与存储double类型的stack类除了存储的类型不同,其他都相同。对于这种情况,可以编写一个通用(独立于类型)的堆栈,然后将具体的类型作为参数传递给这个类,这样就可以通过通用的代码生成存储不同类型的堆栈。类模板就是为生成通用的类提供的解决方案。

模板提供参数化类型,即能够将类型名作为参数传递给接受方来建立类或函数。

前面文章《包含对象成员的类》中设计Student类时就使用了**模板类valarray<double>**来存储学生的分数。

1 类模板定义

类模板的定义与函数模板一样,以template<class Type>代码开头,关键字template告诉编译器,将要定义一个模板。尖括号中的内容相当于函数的参数列表,可以把关键字class看作是变量的类型名,该变量接受类型作为其值,Type是该变量的名称。关键字class并不意味着Type必须是一个类,而只是说明Type是一个通用的类型说明符。为避免混淆,较新版本的C++已使用关键字typename代替class。

cpp 复制代码
template<typename Type>

可以使用其他的通用类型名代替Type,命名规则与变量名的命名规则相同,但通常使用Type或T。

下面是Stack类模板的定义:

cpp 复制代码
#pragma once
template <typename T>
class Stack
{
public:
	Stack();
	bool isempty();
	bool isfull();
	bool push(const T& item);
	bool pop(T& item)
private:
	enum
	{
		MAX = 10;
	};
	T item[MAX];
	int top;
};

template <typename T>
Stack<T>::Stack()
{
	top = 0;
}

template <typename T>
bool Stack<T>::isempty()
{
	return top == 0;
}

template <typename T>
bool Stack<T>::isfull()
{
	return top == MAX;
}

template <typename T>
bool Stack<T>::push(const T& item)
{
	...
}

说明:上述代码都放在一个头文件中。类模板和函数模板是C++编译器指令,说明了如何生成类和成员函数定义 。模板的具体实现被称为实例化或具体化,例如用来处理int类数据的堆栈类就是具体类。因为模板不是具体函数,因此不能单独编译。将函数模板的实现放在一个单独的文件中是无法进行编译的,模板必须与特定的模板实例化请求一起使用。为此,最简单的方法是将所有模板信息放在一个头文件中,并在要使用这些模板的文件中包含该头文件。

2 使用类模板

要使用类模板,必须声明一个类型为模板类的对象,即使用所需的具体类型替换通用类型名。例如下面的语句将创建两个具体的堆栈类对象。

cpp 复制代码
Stack<int> kernels;
Stack<string> kernels;

编译器将根据上述语句生成两个独立的类声明和两组独立的类方法。Stack<int>类声明将使用int替换模板中所有的T,Stack<string>类声明将使用string替换模板中所有的T。

3 使用非类型参数创建数组模板

模板常被用作容器类,这是因为类型参数的概念非常适合于将相同的存储方案用于不同的类型,为容器类提供可重用代码是引入模板的主要目的。

使用非类型(或表达式)参数创建一个指定数组大小的数组模板。一种方法是在类中使用动态数组和构造函数参数来提供元素数目,另一种方法是使用模板参数来提供数组的大小。

3.1 使用动态数组和构造函数提供大小

使用动态数组和构造函数提供数组大小来创建一个指针数组模板类,实例代码如下:

cpp 复制代码
#pragma once

template<class T>
class Stack
{
public:
	explicit Stack(int ss = SIZE);///default
	Stack(const Stack& st);
	~Stack() { delete[] items; }
	bool isempty() { return top == 0; }
	bool isfull() { return top == stackSize; }
	bool push(const T& item);
	bool pop(T& item);
	Stack& operator=(const Stack& st);
private:
	enum 
	{
		SIZE = 10;
	};
	int stackSize;
	T* items;///存放堆栈
	int top;
};

template<class T>
Stack<T>::Stack(int ss):stackSize(ss),top(0)
{
	items = new T[stackSize];
}

template<class T>
Stack<T>::Stack(const Stack& st)
{
	stackSize = st.stackSize;
	top = st.top;
	items = new T[stackSize];
	for (int i = 0; i < top; i++)
	{
		items[i] = st.items[i];
	}
}

template<class T>
bool Stack<T>::push(const T& item)
{
	if (top < stackSize)
	{
		items[top++] = item;
		return true;
	}
	else
	{
		return false;
	}
}

template<class T>
bool Stack<T>::pop(T& item)
{
	if (top > 0)
	{
		item = items[--top];
		return true;
	}
	else
	{
		return false;
	}
}

template<class T>
Stack<T>& Stack<T>::operator=(const Stack& st)
{
	if (this == &st)return *this;
	delete[] items;
	stackSize = st.stackSize;
	top = st.top;
	items = new T[stackSize];
	for (int i = 0; i < top; i++)
	{
		items[i] = st.items[i];
	}
	return *this;
}

代码说明:

  • 该类模板创建的是一个可指定大小的指针数组。
  • 赋值操作符函数说明:赋值操作符函数原型的返回类型为Stack引用,而实际的模板函数定义中返回类型为Stack<T>。前者是后者的缩写,只能在类中使用,即只能在模板声明中使用。在类外,指定返回类型或使用作用域解析操作符时,必须使用完整的Stack<T>。
  • Stack构造函数为能够接受一个可选大小的参数来指定数组的大小。

3.2 使用模板参数来指定数组大小

代码如下:

cpp 复制代码
#pragma once
#include <iostream>
#include <cstdlib>

template<class T,int n>
class ArrayTP
{
public:
	ArrayTP(){};
	explicit ArrayTP(const T& v);
	virtual T& operator[](int i);

private:
	T ar[n];
};

template<class T, int n>
ArrayTP<T,n>::ArrayTP(const T& v)
{
	for (int i = 0; i < n; i++)
	{
		ar[i] = v;
	}
}

template<class T, int n>
T& ArrayTP<T, n>::operator[](int i)
{
	if (i < 0 || i >= n)
	{
		std::cerr << "error in array limits: " << i << " is out of range\n";
		std::exit(EXIT_FAILURE);
	}
	return ar[i];
}

代码说明:

  • 模板头template<class T, int n>中,关键字class指出T为类型参数,int n表示n为非类型参数或表达式参数。
  • 假设有声明代码:ArrayTP<double, 12>ar1,则编译器将定义一个名为ArrayTP<double, 12>的类,并创建一个类型为ArrayTP<double, 12>的ar1对象。定义类时,编译器将使用double替换T,使用12替换n。
  • 类模板中的非类型参数有一些限制,参数只能是整型、枚举、引用或指针,template<class T, double n>是不合法的。另外,模板代码不能修改参数的值,也不能使用参数的地址。所以在ArrayTP模板中不能使用n++或&n表示式。另外,在实例化模板时,非类型参数必须是常量表达式。

3.3 两种方法的比较

使用模板参数指定数组大小与使用构造函数相比,这种改变数组大小的方法有一个优点。构造函数方法使用的是通过new和delete管理的堆内存,而非类型参数方法使用的是自动变量维护的内存栈,执行速度更快,尤其是在使用许多小型数组时。

使用模板参数指定数组大小的方式缺点是每种数组大小都需要生成自己的模板类。也就是说,下面的声明,编译器将生成两个独立的模板类,类名分别是ArrayTP<double, 12>和ArrayTP<double, 13>。

cpp 复制代码
ArrayTP<double, 12>ar1;
ArrayTP<double, 13>ar2;

而下面的声明,编译器只生成一个模板类。

cpp 复制代码
Stack<int> books(5);
Stack<int> students(20);

实际编程中,使用构造函数的方法更通用,这是因为数组大小是作为类成员(而不是硬编码)存储在定义中的。这样可以将一种尺寸的数组赋给另一种尺寸的数组。

相关推荐
xlq223221 小时前
全面复习4
c++
释厄6231 小时前
智能体释放度子集律——文本 ⊂ 音频 ⊂ 视频⊂ 游戏
开发语言·游戏·microsoft·音视频
殷色玫瑰1 小时前
C++ STL:map 与 set 详解——从底层红黑树到实际应用
c语言·数据结构·c++·算法·visualstudio·rpc
Sarvartha1 小时前
Object 类
java·开发语言
Tri_Function1 小时前
背包:从入门到实战
c++
j7~2 小时前
【C++ 标准项目】发布订阅消息队列 (篇七):项目创建与基础工具模块搭建
开发语言·c++·log4j·uuid·protobuf·helper工具·消息定义类型和交换机类型定义
leisoo80972 小时前
股票筹码分布怎么用获利比例成本区间与集中度实战 IG50免费开源股票数据API接口
开发语言·jvm·数据库·python·开源
Lazionr2 小时前
unordered_map和unordered_set的使用
开发语言·c++·算法
进化中的刀盾兽2 小时前
从零实现 C++ Json-Rpc(九):从 TCP 字节流中拆出完整消息——MuduoBuffer 与 LVProtocol
c++·rpc