C++ 类模板:从概念到实践
C++ 类模板:从概念到实践(学习自用)
在 C++ 编程中,当我们需要编写可以处理多种数据类型的容器或工具类时,类模板(Class Template)是一种强大的解决方案。它就像一个 “类的模具”,让我们用一套代码就能应对不同的数据类型,极大地提升了代码的复用性和灵活性。
一、类模板的核心概念
类模板的本质是参数化类型,即把类中使用的数据类型抽象成一个模板参数。在使用时,我们再为这个参数传入具体的类型(如int、double、std::string等),编译器会根据传入的类型生成一个具体的类。
与函数模板不同,编译器无法自动推断类模板的参数,因此在实例化类模板时,必须显式地在类名后用尖括号<>指定模板参数。例如:
std::vector<int> vec;
这里vector是类模板,<int>就是为模板参数指定的具体类型,最终生成一个存储int类型的vector容器。
二、类模板的定义与实现
2.1 基本语法
类模板的定义以template关键字开头,后跟模板参数列表,然后是类的定义。语法如下:
template <typename 模板参数1, typename 模板参数2, ...>
class 类名
{
// 类的成员和方法
};
如果有多个模板参数,参数之间用逗号分隔。
2.2 完整示例:实现一个简单的容器 myvector
为了演示类模板的用法,我们实现一个简化版的vector容器myvector。
头文件 myvector.h
#ifndef __MYVECTOR__
#define __MYVECTOR__
#include <iostream>
template <typename T>
class myvector
{
public:
// 迭代器类型定义
using iterator = T*;
public:
// 构造函数
myvector() : m_data(nullptr), m_size(0), m_capacity(0) {}
// 拷贝构造
myvector(const myvector& other);
// 析构函数
~myvector() { delete[] m_data; }
// 重载赋值运算符
myvector& operator=(const myvector& other);
public:
// 迭代器接口
iterator begin() { return m_data; }
iterator end() { return m_data + m_size; }
// 成员函数
void push_back(const T& value);
void myfunc();
size_t size() const { return m_size; }
private:
T* m_data;
size_t m_size;
size_t m_capacity;
};
// 拷贝构造函数实现
template <typename T>
myvector<T>::myvector(const myvector& other)
{
m_size = other.m_size;
m_capacity = other.m_capacity;
m_data = new T[m_capacity];
for (size_t i = 0; i < m_size; ++i)
{
m_data[i] = other.m_data[i];
}
}
// 赋值运算符重载
template <typename T>
myvector<T>& myvector<T>::operator=(const myvector& other)
{
if (this != &other)
{
delete[] m_data;
m_size = other.m_size;
m_capacity = other.m_capacity;
m_data = new T[m_capacity];
for (size_t i = 0; i < m_size; ++i)
{
m_data[i] = other.m_data[i];
}
}
return *this;
}
// 向容器尾部添加元素
template <typename T>
void myvector<T>::push_back(const T& value)
{
if (m_size >= m_capacity)
{
m_capacity = (m_capacity == 0) ? 1 : m_capacity * 2;
T* new_data = new T[m_capacity];
for (size_t i = 0; i < m_size; ++i)
{
new_data[i] = m_data[i];
}
delete[] m_data;
m_data = new_data;
}
m_data[m_size++] = value;
}
// 自定义成员函数
template <typename T>
void myvector<T>::myfunc()
{
std::cout << "myvector size: " << m_size << std::endl;
}
#endif
2.3 关键注意事项
-
头文件中实现:由于类模板的实例化需要完整的定义,因此类模板的成员函数实现通常需要和类的定义放在同一个头文件中,而不是分开到
.cpp文件。其他源文件只需包含这个头文件即可。 -
模板参数的作用域:在类模板内部,可以直接使用模板参数
T来定义成员变量和函数的参数、返回值类型。 -
外部实现成员函数:成员函数在类模板外部实现,需要在函数前再次声明模板参数,并在类名后加上
template <typename T> void myvector<T>::push_back(const T& value) { // ... }
三、类模板的实例化与使用
类模板本身并不是一个具体的类,只有当我们为模板参数传入具体类型后,编译器才会生成一个具体的类,这个过程称为实例化。
3.1 实例化示例
在主函数中,我们可以这样实例化并使用myvector:
#include "myvector.h"
#include <string>
int main()
{
// 实例化存储int类型的myvector
myvector<int> int_vec;
int_vec.push_back(10);
int_vec.push_back(20);
int_vec.push_back(30);
int_vec.myfunc(); // 输出: myvector size: 3
// 遍历容器
for (auto it = int_vec.begin(); it != int_vec.end(); ++it)
{
std::cout << *it << " "; // 输出: 10 20 30
}
std::cout << std::endl;
// 实例化存储string类型的myvector
myvector<std::string> str_vec;
str_vec.push_back("Hello");
str_vec.push_back("Template");
str_vec.myfunc(); // 输出: myvector size: 2
for (auto& str : str_vec)
{
std::cout << str << " "; // 输出: Hello Template
}
std::cout << std::endl;
return 0;
}
3.2 运行结果
编译并运行上述代码,输出如下:
myvector size: 3
10 20 30
myvector size: 2
Hello Template
四、非类型模板参数
类模板的参数不仅可以是类型(如typename T),还可以是普通的值,称为非类型模板参数。非类型模板参数必须是编译期常量,常见的类型有整数、指针和引用。
4.1 示例:固定大小的数组 myarray
我们实现一个固定大小的数组类模板myarray,数组的大小由非类型模板参数指定:
头文件 myarray.h
#ifndef __MYARRAY__
#define __MYARRAY__
#include <iostream>
template <typename T, int size = 10>
class myarray
{
public:
void myfunc();
private:
T arr[size];
};
template <typename T, int size>
void myarray<T, size>::myfunc()
{
std::cout << "Array size: " << size << std::endl;
}
#endif
4.2 使用示例
#include "myarray.h"
int main()
{
// 使用默认大小10
myarray<int> arr1;
arr1.myfunc(); // 输出: Array size: 10
// 指定大小为50
myarray<int, 50> arr2;
arr2.myfunc(); // 输出: Array size: 50
return 0;
}
4.3 非类型模板参数的限制
- 浮点型不能作为非类型模板参数:例如
double、float等类型不能作为非类型模板参数。 - 类类型不能作为非类型模板参数:自定义的类或结构体不能作为非类型模板参数。
五、总结
类模板是 C++ 中实现泛型编程的核心工具,它让我们可以编写与类型无关的代码,从而大幅提高代码的复用性和可维护性。通过本文的示例,完整地介绍了类模板的使用方法。
在实际开发中,标准库中的vector、list、map等容器都是类模板的典型应用。掌握类模板的使用,能够让我们更高效地编写通用、灵活的 C++ 代码。
浙公网安备 33010602011771号