跳到主要内容

C++ 模板基础

什么是模板?

模板是C++中一种强大的特性,它允许程序员编写通用的、与类型无关的代码。换句话说,你可以编写一次代码,然后对不同的数据类型重复使用,而不需要为每种类型都编写一个新函数或类。模板是C++泛型编程的基础。

备注

模板本身不是函数或类,而是编译器用来生成函数或类的"配方"。

模板的类型

在C++中,主要有两种类型的模板:

  1. 函数模板
  2. 类模板

我们将详细介绍这两种类型的模板。

函数模板

函数模板允许你创建一个函数,该函数可以接受任何类型的参数,而不需要为每种类型都编写一个新函数。

函数模板的基本语法

cpp
template <typename T>
T functionName(T parameter) {
// 函数体
return parameter;
}

这里,typename 关键字指示 T 是一个类型参数。你也可以使用关键字 class 代替 typename

函数模板示例:求两个数的最大值

cpp
#include <iostream>
using namespace std;

// 函数模板定义
template <typename T>
T findMax(T a, T b) {
return (a > b) ? a : b;
}

int main() {
// 使用整数类型
cout << "最大整数: " << findMax(10, 20) << endl;

// 使用浮点类型
cout << "最大浮点数: " << findMax(10.5, 20.7) << endl;

// 使用字符类型
cout << "字典序较大的字符: " << findMax('a', 'b') << endl;

return 0;
}

输出:

最大整数: 20
最大浮点数: 20.7
字典序较大的字符: b

函数模板的实例化

当你使用函数模板时,编译器会根据你传递的参数类型生成对应的函数实例。这个过程称为模板实例化。

例如,当你调用 findMax(10, 20) 时,编译器会生成 int findMax(int a, int b) 的实例。

多个类型参数的函数模板

函数模板可以有多个类型参数:

cpp
template <typename T1, typename T2>
void printValues(T1 a, T2 b) {
cout << "a = " << a << ", b = " << b << endl;
}

你可以这样使用它:

cpp
printValues(10, "Hello");  // a = 10, b = Hello
printValues(3.14, 42); // a = 3.14, b = 42

类模板

类模板允许你定义一个通用类,该类可以适用于任何类型。

类模板的基本语法

cpp
template <typename T>
class ClassName {
private:
T member;
public:
void setMember(T value) {
member = value;
}
T getMember() {
return member;
}
};

类模板示例:通用数组类

cpp
#include <iostream>
using namespace std;

template <typename T>
class Array {
private:
T* elements;
int size;
public:
// 构造函数
Array(int s) {
size = s;
elements = new T[size];
}

// 析构函数
~Array() {
delete[] elements;
}

// 设置元素
void setElement(int index, T value) {
if (index >= 0 && index < size) {
elements[index] = value;
}
}

// 获取元素
T getElement(int index) {
if (index >= 0 && index < size) {
return elements[index];
}
// 这里简化处理,实际应该抛出异常
return T();
}

// 获取数组大小
int getSize() {
return size;
}
};

int main() {
// 创建一个整数数组
Array<int> intArray(5);
for (int i = 0; i < 5; i++) {
intArray.setElement(i, i * 10);
}

cout << "整数数组的元素:" << endl;
for (int i = 0; i < intArray.getSize(); i++) {
cout << intArray.getElement(i) << " ";
}
cout << endl;

// 创建一个字符数组
Array<char> charArray(5);
charArray.setElement(0, 'H');
charArray.setElement(1, 'e');
charArray.setElement(2, 'l');
charArray.setElement(3, 'l');
charArray.setElement(4, 'o');

cout << "字符数组的元素:" << endl;
for (int i = 0; i < charArray.getSize(); i++) {
cout << charArray.getElement(i);
}
cout << endl;

return 0;
}

输出:

整数数组的元素:
0 10 20 30 40
字符数组的元素:
Hello

类模板的实例化

类似于函数模板,类模板在使用时也需要进行实例化。不同的是,必须在创建对象时明确指定模板参数类型:

cpp
Array<int> intArray(5);      // 创建整数数组
Array<string> strArray(3); // 创建字符串数组

类模板与继承

类模板可以作为基类被继承,也可以继承其他类:

cpp
template <typename T>
class Base {
// 基类的实现
};

template <typename T>
class Derived : public Base<T> {
// 派生类的实现
};

模板参数

默认模板参数

与函数参数一样,模板参数也可以有默认值:

cpp
template <typename T = int>
class Container {
private:
T element;
public:
void setElement(T val) {
element = val;
}

T getElement() {
return element;
}
};

int main() {
Container<> c; // 使用默认类型 int
c.setElement(42);
cout << c.getElement() << endl; // 输出: 42

Container<double> d; // 使用 double 类型
d.setElement(3.14);
cout << d.getElement() << endl; // 输出: 3.14

return 0;
}

非类型模板参数

模板参数不限于类型,也可以是常量表达式:

cpp
template <typename T, int Size>
class StaticArray {
private:
T elements[Size];
public:
void setElement(int index, T value) {
if (index >= 0 && index < Size) {
elements[index] = value;
}
}

T getElement(int index) {
if (index >= 0 && index < Size) {
return elements[index];
}
return T();
}

int getSize() {
return Size;
}
};

int main() {
StaticArray<int, 5> arr;
for (int i = 0; i < arr.getSize(); i++) {
arr.setElement(i, i * i);
}

for (int i = 0; i < arr.getSize(); i++) {
cout << arr.getElement(i) << " ";
}
cout << endl; // 输出: 0 1 4 9 16

return 0;
}

模板特化

模板特化允许你为特定的类型提供特定的实现。

函数模板特化

cpp
#include <iostream>
#include <string>
using namespace std;

// 通用模板
template <typename T>
void print(T value) {
cout << "通用版本: " << value << endl;
}

// 针对 char* 的特化
template <>
void print<char*>(char* value) {
cout << "字符串特化版本: " << value << endl;
}

int main() {
int i = 42;
double d = 3.14;
char str[] = "Hello";

print(i); // 调用通用版本
print(d); // 调用通用版本
print(str); // 调用特化版本

return 0;
}

输出:

通用版本: 42
通用版本: 3.14
字符串特化版本: Hello

类模板特化

cpp
#include <iostream>
#include <string>
using namespace std;

// 通用模板
template <typename T>
class Container {
public:
void display() {
cout << "通用容器" << endl;
}
};

// 针对 string 类型的特化
template <>
class Container<string> {
public:
void display() {
cout << "字符串容器" << endl;
}
};

int main() {
Container<int> intContainer;
Container<double> doubleContainer;
Container<string> stringContainer;

intContainer.display(); // 输出: 通用容器
doubleContainer.display(); // 输出: 通用容器
stringContainer.display(); // 输出: 字符串容器

return 0;
}

实际应用场景

1. 容器类

模板最常见的应用是在容器类中,如STL中的vectorlistmap等。

cpp
#include <iostream>
#include <vector>
#include <string>
using namespace std;

int main() {
// 整型向量
vector<int> intVector;
intVector.push_back(10);
intVector.push_back(20);
intVector.push_back(30);

cout << "整型向量中的元素:" << endl;
for (int i : intVector) {
cout << i << " ";
}
cout << endl;

// 字符串向量
vector<string> strVector;
strVector.push_back("Hello");
strVector.push_back("World");

cout << "字符串向量中的元素:" << endl;
for (const string& s : strVector) {
cout << s << " ";
}
cout << endl;

return 0;
}

2. 智能指针

模板在智能指针的实现中也起着重要作用,如std::shared_ptrstd::unique_ptr

cpp
#include <iostream>
#include <memory>
using namespace std;

class Resource {
public:
Resource() { cout << "Resource 被创建" << endl; }
~Resource() { cout << "Resource 被销毁" << endl; }
void doSomething() { cout << "Resource 正在执行某项操作" << endl; }
};

int main() {
cout << "使用智能指针:" << endl;
{
shared_ptr<Resource> res1 = make_shared<Resource>();
res1->doSomething();

{
shared_ptr<Resource> res2 = res1; // 共享所有权
cout << "引用计数: " << res1.use_count() << endl; // 输出: 2
}

cout << "内部作用域结束后的引用计数: " << res1.use_count() << endl; // 输出: 1
}
cout << "外部作用域结束" << endl;

return 0;
}

3. 算法函数

模板在算法函数中的应用,例如排序算法:

cpp
#include <iostream>
using namespace std;

// 冒泡排序模板函数
template <typename T>
void bubbleSort(T arr[], int size) {
for (int i = 0; i < size - 1; i++) {
for (int j = 0; j < size - i - 1; j++) {
if (arr[j] > arr[j + 1]) {
T temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}

// 打印数组的模板函数
template <typename T>
void printArray(T arr[], int size) {
for (int i = 0; i < size; i++) {
cout << arr[i] << " ";
}
cout << endl;
}

int main() {
// 整型数组排序
int intArr[] = {5, 2, 9, 1, 7, 6};
int intSize = sizeof(intArr) / sizeof(int);

cout << "排序前的整型数组: ";
printArray(intArr, intSize);

bubbleSort(intArr, intSize);

cout << "排序后的整型数组: ";
printArray(intArr, intSize);

// 双精度数组排序
double doubleArr[] = {3.14, 1.1, 5.5, 0.3, 2.7};
int doubleSize = sizeof(doubleArr) / sizeof(double);

cout << "排序前的浮点数组: ";
printArray(doubleArr, doubleSize);

bubbleSort(doubleArr, doubleSize);

cout << "排序后的浮点数组: ";
printArray(doubleArr, doubleSize);

return 0;
}

输出:

排序前的整型数组: 5 2 9 1 7 6 
排序后的整型数组: 1 2 5 6 7 9
排序前的浮点数组: 3.14 1.1 5.5 0.3 2.7
排序后的浮点数组: 0.3 1.1 2.7 3.14 5.5

模板的注意事项

  1. 编译错误:模板错误通常在实例化时才会被检测到,这可能导致复杂难懂的错误消息。

  2. 代码膨胀:过度使用模板可能导致代码膨胀,因为每个不同类型的实例化都会生成新的代码。

  3. 调试困难:模板代码通常更难调试,因为需要追踪特定类型的实例化。

  4. 编译时间:大量使用模板可能会导致编译时间增加。

总结

C++模板是实现泛型编程的关键特性,它允许我们编写与类型无关的代码,提高代码的重用性和灵活性。主要包括:

  1. 函数模板:创建适用于不同类型的函数
  2. 类模板:创建适用于不同类型的类
  3. 模板参数:可以是类型参数,也可以是非类型参数
  4. 模板特化:为特定类型提供特定实现

模板在标准库、容器、算法、智能指针等多个领域有广泛应用。掌握模板的基础知识对深入学习C++至关重要。

练习

  1. 创建一个函数模板 swap,用于交换两个相同类型的变量值。

  2. 编写一个通用的 min 函数模板,返回两个值中的较小值。

  3. 创建一个类模板 Pair,它可以存储两个不同类型的值。

  4. 实现一个类模板 Stack,包含 pushpopisEmpty 操作。

  5. 为上面的 Stack 模板,编写一个针对 char* 类型的特化版本。

学习资源