C++ 类模板全面教程
类模板是C++中强大且灵活的特性,它允许我们创建可适用于多种数据类型的类。下面我将从基础到高级,系统性地介绍类模板。
一、类模板基本概念
1.1 什么是类模板
类模板是一种允许我们使用不同类型创建类的蓝图。就像函数模板可以生成针对不同类型参数的函数一样,类模板可以生成针对不同类型参数的类。
1.2 为什么需要类模板
假设你需要一个可以存储任何类型数据的栈,不使用模板就需要为每种类型分别创建类:
class IntStack { /* 实现int栈 */ };
class DoubleStack { /* 实现double栈 */ };
class StringStack { /* 实现string栈 */ };
使用类模板则可以定义一次,适应多种类型:
template <typename T>
class Stack { /* 通用栈实现 */ };
二、类模板基本语法
2.1 基本定义
template <typename T> // 或者 template <class T>
class MyClass {
// 类成员定义
};
2.2 示例:一个简单的Array类模板
template <typename T>
class Array {
private:
T* ptr;
int size;
public:
Array(int s) : size(s), ptr(new T[s]) {}
~Array() { delete[] ptr; }
T& operator[](int index) {
if (index >= size || index < 0) {
throw std::out_of_range("Index out of range");
}
return ptr[index];
}
int getSize() const { return size; }
};
2.3 使用类模板
Array<int> intArr(10); // 创建存储int的数组
Array<double> doubleArr(5); // 创建存储double的数组
三、类模板的定义与使用
3.1 多个类型参数
template <typename T, typename U>
class Pair {
public:
T first;
U second;
Pair(const T& f, const U& s) : first(f), second(s) {}
};
Pair<int, double> p1(1, 3.14);
Pair<std::string, bool> p2("success", true);
3.2 默认类型参数
template <typename T, typename U = bool>
class Pair {
public:
T first;
U second;
Pair(const T& f, const U& s) : first(f), second(s) {}
};
Pair<int, double> p1(1, 3.14);
Pair<std::string> p2("success", true); // 使用默认类型
3.3 默认非类型参数
template <typename T = int, int size = 10>
class Container {
T items[size];
// ...
};
Container<> defaultContainer; // 使用默认参数 T=int, size=10
Container<double, 20> customContainer; // 指定参数
3.4 成员函数模板
类内部可以定义成员函数模板:
template <typename T>
class Box {
T content;
public:
template <typename U>
void assign(const U& newContent) {
content = newContent; // 需要U能隐式转换为T
}
};
3.5 成员函数的类外实现
成员函数类外实现,模板声明必须和类模板一致,但无需给出默认参数。以为类名作用域需要指定类型,即必须用模板类型实例化,才能使用。
template <class T1, class T2>
class 类名{
T1 函数名(T2 a, T2 b);
}
// 注意类名作用域使用模板参数进行实例化。
template <class T1, class T2>
T1 类名<T1, T2>::函数名(T2 a, T2 b){
// 函数实现
}
3.6 类模板实例化
与函数模板不同,类模板只能显式实例化,不能隐式实例化。因为函数模板调时可以根据参数自动判断类型,而类模板调用时除了显式指定,没有任何参考依据。
template <class T>
class Complex{
T real;
T img;
}
// 使用类模板创建对象
Complex<int> c;
3.7 类模板作为函数参数
1. 普通函数
类模板在普通函数中使用必须实例化为一个具体的类:
template <class T>
class Complex{
T real;
T img;
}
Complex<double> add(Complex<double> a, Complex<double> b);
2. 函数模板
方法一:常用,用泛型类型实例化类模板作为函数参数类型。
template <class T>
class Complex{
T real;
T img;
}
template <class T>
Complex<T> add(Complex<T> a, Complex<T> b);
方法二:通用所有类,在使用时再指定类。
template <class T>
class Complex{
T real;
T img;
}
template <class T>
T add(T a, T b);
四、类模板特化
4.1 全特化
所有类型参数都必须具体化。
template <>
class Array<bool> {
private:
unsigned char* ptr;
int size;
public:
// 特化实现,按位存储bool以节省空间
Array(int s) : size(s), ptr(new unsigned char[(s+7)/8]) {}
~Array() { delete[] ptr; }
// 其他特化实现...
};
4.2 偏特化
只具体化部分类型参数。没有具体化的泛型类型在仍然在<>中。而类定义的实例化中可以使用泛型类型。
template <class T>
class 类名<T, string>{
}
五、类模板中的静态成员
类模板中的static成员对于每个模板实例化都是独立的:
template <typename T>
class Counter {
public:
static int count;
Counter() { ++count; }
~Counter() { --count; }
};
template <typename T>
int Counter<T>::count = 0;
Counter<int> c1, c2; // Counter<int>::count == 2
Counter<double> c3; // Counter<double>::count == 1
六、类模板继承
6.1 从类模板继承
template <typename T>
class Base {
// ...
};
template <typename T>
class Derived : public Base<T> {
// ...
};
6.2 继承关系中的名称查找
在模板继承中需要使用this->
或Base<T>::
来访问基类成员:
template <typename T>
class Derived : public Base<T> {
public:
void foo() {
this->baseMember(); // 使用this->访问基类成员
Base<T>::baseMember(); // 或使用完整限定名
}
};
七、类模板与友元
7.1 友元函数
template <typename T>
class MyClass {
T value;
// 声明友元函数
friend void printValue(const MyClass<T>& obj) {
std::cout << obj.value;
}
};
MyClass<int> obj;
printValue(obj); // 正确调用
7.2 友元类
template <typename T>
class FriendClass; // 前向声明
template <typename T>
class MyClass {
private:
T secret;
friend class FriendClass<T>; // 声明友元类
};
八、使用技巧与最佳实践
分离实现与声明:可以将类模板的声明放在头文件,定义放在另一个头文件(如
.tpp
或.ipp
),然后在声明文件末尾包含定义文件。防止模板膨胀:避免生成过多不必要的模板实例,可通过特化等方式优化。
明确错误信息:模板错误通常在实例化时才被发现,使用static_assert提供更友好的错误信息:
template <typename T>
class NumericArray {
static_assert(std::is_arithmetic<T>::value,
"NumericArray requires an arithmetic type");
// ...
};
总结
类模板特点
- 只能显式实例化,不能隐式实例化
- 可以指定默认类型
类模板是C++泛型编程的核心技术之一,通过类模板我们可以:
- 编写与类型无关的通用代码
- 减少代码重复,提高复用性
- 在编译时完成类型检查,确保类型安全
- 通过特化提供特定类型的优化实现
掌握类模板能显著提升你的C++编程能力,特别是在开发和维护大型库时。建议从简单例子开始,逐步尝试更复杂的应用场景。