C++是一种功能强大的编程语言,它支持面向对象编程和泛型编程。泛型编程的核心概念之一就是模板(Template)。模板允许程序员编写可以在多种数据类型上工作的通用代码,从而提高代码的复用性和可维护性。本文将详细介绍C++中的模板概念,包括函数模板和类模板,并通过实例代码帮助读者更好地理解模板的使用。
一、模板的基本概念
1.什么是模板?
模板是C++中的一种机制,允许程序员编写可以在多种数据类型上工作的代码。通过使用模板,程序员可以定义函数或类,而无需指定具体的类型。编译器会在编译时根据实际使用的数据类型生成相应的代码。
2.为什么使用模板?
- 代码复用:模板允许编写通用的代码,避免重复编写类似的代码。
- 类型安全:模板在编译时进行类型检查,确保类型安全。
- 性能优化:模板在编译时生成具体的代码,避免了运行时的类型转换开销。
二、函数模板
1.定义函数模板
函数模板的定义类似于普通函数,但在函数名前加上template关键字,并指定模板参数列表。模板参数列表通常使用尖括号<>括起来,可以包含一个或多个模板参数。
示例:简单的函数模板
#include <iostream>
// 定义一个函数模板,用于交换两个变量的值
template <typename T>
void swap(T &a, T &b) {
T temp = a;
a = b;
b = temp;
}
int main() {
int x = 5, y = 10;
std::cout << "Before swap: x = " << x << ", y = " << y << std::endl;
swap(x, y);
std::cout << "After swap: x = " << x << ", y = " << y << std::endl;
double a = 3.14, b = 2.71;
std::cout << "Before swap: a = " << a << ", b = " << b << std::endl;
swap(a, b);
std::cout << "After swap: a = " << a << ", b = " << b << std::endl;
return 0;
}
在这个示例中,swap函数模板可以用于交换任意类型的两个变量的值。编译器会根据实际使用的数据类型生成相应的代码。
2.多个模板参数
函数模板可以有多个模板参数,每个模板参数之间用逗号分隔。
示例:带有多个模板参数的函数模板
#include <iostream>
// 定义一个函数模板,用于比较两个变量的大小
template <typename T1, typename T2>
bool compare(T1 a, T2 b) {
return a < b;
}
int main() {
int x = 5;
double y = 3.14;
std::cout << "x < y: " << compare(x, y) << std::endl;
char c = 'A';
std::string s = "Hello";
std::cout << "c < s: " << compare(c, s) << std::endl;
return 0;
}
在这个示例中,compare函数模板可以比较不同类型的两个变量。编译器会根据实际使用的数据类型生成相应的代码。
3.默认模板参数
从C++11开始,函数模板可以有默认模板参数。
示例:带有默认模板参数的函数模板
#include <iostream>
// 定义一个函数模板,带有默认模板参数
template <typename T = int>
void print(T value) {
std::cout << value << std::endl;
}
int main() {
print(5); // 使用默认模板参数 int
print(3.14); // 自动推导模板参数为 double
print("Hello"); // 自动推导模板参数为 const char*
return 0;
}
在这个示例中,print函数模板有一个默认模板参数int。如果没有显式指定模板参数,编译器会自动推导模板参数。
4.特化函数模板
有时,我们需要为特定的数据类型提供不同的实现。这时可以使用函数模板特化。
示例:函数模板特化
#include <iostream>
#include <string>
// 定义一个通用的函数模板
template <typename T>
void print(T value) {
std::cout << "Generic template: " << value << std::endl;
}
// 定义一个特化的函数模板,用于处理 std::string 类型
template <>
void print<std::string>(std::string value) {
std::cout << "Specialized template for std::string: " << value << std::endl;
}
int main() {
print(5); // 使用通用模板
print(3.14); // 使用通用模板
print("Hello"); // 使用通用模板
print(std::string("World")); // 使用特化模板
return 0;
}
在这个示例中,print函数模板有一个通用的实现和一个特化的实现,用于处理std::string类型。
三、类模板
1.定义类模板
类模板的定义类似于普通类,但在类名前加上template关键字,并指定模板参数列表。
示例:简单的类模板
#include <iostream>
// 定义一个类模板,用于表示一个简单的栈
template <typename T>
class Stack {
private:
T* data;
size_t capacity;
size_t top;
public:
Stack(size_t cap) : capacity(cap), top(0) {
data = new T[capacity];
}
~Stack() {
delete[] data;
}
void push(const T& value) {
if (top >= capacity) {
std::cerr << "Stack overflow!" << std::endl;
return;
}
data[top++] = value;
}
T pop() {
if (top == 0) {
std::cerr << "Stack underflow!" << std::endl;
return T();
}
return data[--top];
}
bool isEmpty() const {
return top == 0;
}
};
int main() {
Stack<int> intStack(5);
intStack.push(1);
intStack.push(2);
intStack.push(3);
while (!intStack.isEmpty()) {
std::cout << intStack.pop() << std::endl;
}
Stack<double> doubleStack(5);
doubleStack.push(3.14);
doubleStack.push(2.71);
doubleStack.push(1.41);
while (!doubleStack.isEmpty()) {
std::cout << doubleStack.pop() << std::endl;
}
return 0;
}
在这个示例中,Stack类模板可以用于表示不同类型的栈。编译器会根据实际使用的数据类型生成相应的代码。
2.多个模板参数
类模板可以有多个模板参数,每个模板参数之间用逗号分隔。
示例:带有多个模板参数的类模板
#include <iostream>
// 定义一个类模板,用于表示一个简单的矩阵
template <typename T, size_t Rows, size_t Cols>
class Matrix {
private:
T data[Rows][Cols];
public:
void set(size_t row, size_t col, const T& value) {
if (row >= Rows || col >= Cols) {
std::cerr << "Index out of bounds!" << std::endl;
return;
}
data[row][col] = value;
}
T get(size_t row, size_t col) const {
if (row >= Rows || col >= Cols) {
std::cerr << "Index out of bounds!" << std::endl;
return T();
}
return data[row][col];
}
};
int main() {
Matrix<int, 2, 3> intMatrix;
intMatrix.set(0, 0, 1);
intMatrix.set(0, 1, 2);
intMatrix.set(0, 2, 3);
intMatrix.set(1, 0, 4);
intMatrix.set(1, 1, 5);
intMatrix.set(1, 2, 6);
for (size_t i = 0; i < 2; ++i) {
for (size_t j = 0; j < 3; ++j) {
std::cout << intMatrix.get(i, j) << " ";
}
std::cout << std::endl;
}
return 0;
}
在这个示例中,Matrix类模板可以用于表示不同类型的矩阵,并且可以指定矩阵的行数和列数。编译器会根据实际使用的数据类型和维度生成相应的代码。
3.默认模板参数
从C++11开始,类模板可以有默认模板参数。
示例:带有默认模板参数的类模板
#include <iostream>
// 定义一个类模板,带有默认模板参数
template <typename T = int, size_t Size = 10>
class Array {
private:
T data[Size];
public:
void set(size_t index, const T& value) {
if (index >= Size) {
std::cerr << "Index out of bounds!" << std::endl;
return;
}
data[index] = value;
}
T get(size_t index) const {
if (index >= Size) {
std::cerr << "Index out of bounds!" << std::endl;
return T();
}
return data[index];
}
};
int main() {
Array<> defaultArray;
defaultArray.set(0, 1);
defaultArray.set(1, 2);
defaultArray.set(2, 3);
for (size_t i = 0; i < 10; ++i) {
std::cout << defaultArray.get(i) << " ";
}
std::cout << std::endl;
Array<double, 5> doubleArray;
doubleArray.set(0, 3.14);
doubleArray.set(1, 2.71);
doubleArray.set(2, 1.41);
for (size_t i = 0; i < 5; ++i) {
std::cout << doubleArray.get(i) << " ";
}
std::cout << std::endl;
return 0;
}
在这个示例中,Array类模板有一个默认模板参数int和10。如果没有显式指定模板参数,编译器会使用默认模板参数。
4.特化类模板
有时,我们需要为特定的数据类型提供不同的实现。这时可以使用类模板特化。
示例:类模板特化
#include <iostream>
#include <string>
// 定义一个通用的类模板
template <typename T>
class Container {
public:
void add(const T& value) {
std::cout << "Adding generic type: " << value << std::endl;
}
};
// 定义一个特化的类模板,用于处理 std::string 类型
template <>
class Container<std::string> {
public:
void add(const std::string& value) {
std::cout << "Adding specialized type: " << value << std::endl;
}
};
int main() {
Container<int> intContainer;
intContainer.add(5);
Container<double> doubleContainer;
doubleContainer.add(3.14);
Container<std::string> stringContainer;
stringContainer.add("Hello");
return 0;
}
在这个示例中,Container类模板有一个通用的实现和一个特化的实现,用于处理std::string类型。
5.成员函数模板
类模板的成员函数也可以是模板。
示例:成员函数模板
#include <iostream>
// 定义一个类模板
template <typename T>
class Box {
private:
T content;
public:
Box(const T& value) : content(value) {}
// 成员函数模板
template <typename U>
void print(U prefix) const {
std::cout << prefix << ": " << content << std::endl;
}
};
int main() {
Box<int> intBox(5);
intBox.print("Integer content");
Box<double> doubleBox(3.14);
doubleBox.print("Double content");
Box<std::string> stringBox("Hello");
stringBox.print("String content");
return 0;
}
在这个示例中,Box类模板有一个成员函数模板print,它可以接受不同类型的前缀参数。
四、模板的高级特性
1.可变参数模板
从C++11开始,模板支持可变参数模板,允许函数模板和类模板接受任意数量的参数。
示例:可变参数模板
#include <iostream>
// 基本情况
void print() {
std::cout << std::endl;
}
// 递归情况
template <typename T, typename... Args>
void print(T first, Args... args) {
std::cout << first << " ";
print(args...);
}
int main() {
print(1, 2.5, "Hello", 'A');
return 0;
}
在这个示例中,print函数模板可以接受任意数量的参数,并逐个打印出来。
2.模板别名
从C++11开始,可以使用using关键字定义模板别名。
示例:模板别名
#include <iostream>
#include <vector>
// 定义一个模板别名
template <typename T>
using Vec = std::vector<T>;
int main() {
Vec<int> intVec = {1, 2, 3, 4, 5};
for (int num : intVec) {
std::cout << num << " ";
}
std::cout << std::endl;
Vec<std::string> stringVec = {"Hello", "World"};
for (const std::string& str : stringVec) {
std::cout << str << " ";
}
std::cout << std::endl;
return 0;
}
在这个示例中,Vec是一个模板别名,用于简化std::vector的使用。
3.模板元编程
模板元编程是指在编译时使用模板进行计算和生成代码的技术。模板元编程可以用于实现复杂的算法和数据结构。
示例:模板元编程
#include <iostream>
// 定义一个模板元函数,用于计算阶乘
template <int N>
struct Factorial {
static const int value = N * Factorial<N - 1>::value;
};
// 基本情况
template <>
struct Factorial<0> {
static const int value = 1;
};
int main() {
std::cout << "Factorial of 5: " << Factorial<5>::value << std::endl;
std::cout << "Factorial of 10: " << Factorial<10>::value << std::endl;
return 0;
}
在这个示例中,Factorial是一个模板元函数,用于计算阶乘。编译器在编译时会生成相应的代码。