C++ 模板 (第 16 节课)
主要内容:
1. 模板 - 实现泛型编程
用途: 模板为我们提供了泛型编程的能力,允许我们编写可以处理多种数据类型的代码,而无需为每种类型都写一遍代码。利用模板,我们可以定义一个通用的算法或数据结构,然后在编译时根据实际需要,将模板中的类型参数替换成具体的类型,从而生成针对特定类型的代码。
关键点:
- 类型参数: 使用
typename
或class
关键字声明类型参数,例如typename T
。 - 模板实例化: 编译器在遇到使用模板的代码时,会根据实际传入的类型参数生成具体的代码,这个过程称为模板实例化。
- 函数查找优先级: 编译器在寻找可调用函数时,会优先寻找完全匹配的函数,如果没有找到,才会尝试使用模板生成匹配的函数。
代码示例:
#include <iostream>
template <typename T>
T max(T a, T b) {
return (a > b) ? a : b;
}
int main() {
int a = 5, b = 10;
std::cout << "Max of " << a << " and " << b << " is: " << max(a, b) << std::endl;
double c = 3.14, d = 2.71;
std::cout << "Max of " << c << " and " << d << " is: " << max(c, d) << std::endl;
return 0;
}
讲解: 这个例子定义了一个通用的 max
函数模板,可以比较任意两种类型的值并返回较大的值。在 main
函数中,我们分别使用 int
和 double
类型调用 max
函数,编译器会自动生成两个不同版本的 max
函数,分别处理 int
和 double
类型。
2. 函数模板
格式:
template<typename T1, typename T2, ..., typename Tn>
函数返回类型 函数名称 (参数列表) {
// 函数执行代码
// 模板类型可以用在函数的任何位置,比如参数列表中的参数类型,返回值类型,执行部分
}
关键点:
- 声明: 使用
template
关键字和尖括号<>
声明模板参数列表。 - 参数列表: 可以有多个模板参数,用逗号分隔。
- 实例化: 编译器根据函数调用时的参数类型,自动进行模板实例化。
代码示例:
#include <iostream>
template <typename T>
T add(T a, T b) {
return a + b;
}
int main() {
int x = 5, y = 10;
std::cout << "Sum of " << x << " and " << y << " is: " << add(x, y) << std::endl;
double p = 3.14, q = 2.71;
std::cout << "Sum of " << p << " and " << q << " is: " << add(p, q) << std::endl;
return 0;
}
讲解: 这个例子定义了一个通用的 add
函数模板,可以将任意两种相同类型的数值相加。编译器会根据调用 add
函数时传入的参数类型,自动生成相应的函数实例。
3. 类模板
格式:
template<typename T1, typename T2, ..., typename Tn>
class 自定义类型名 {
// 成员列表 (成员变量, 成员函数)
};
关键点:
- 声明: 与函数模板类似,使用
template
关键字和尖括号<>
声明模板参数列表。 - 定义: 类模板的成员函数一般需要在类外定义,并在定义时也要带上模板参数列表。
- 实例化: 通过指定具体的类型参数来实例化类模板,例如
MyClass<int> myObject;
。
代码示例:
#include <iostream>
template <typename T>
class MyArray {
private:
T* data;
int size;
public:
MyArray(int size) : size(size) {
data = new T[size];
}
~MyArray() {
delete[] data;
}
void set(int index, T value) {
data[index] = value;
}
T get(int index) {
return data[index];
}
};
int main() {
MyArray<int> intArray(5);
intArray.set(0, 10);
std::cout << "Value at index 0: " << intArray.get(0) << std::endl;
MyArray<double> doubleArray(3);
doubleArray.set(1, 3.14);
std::cout << "Value at index 1: " << doubleArray.get(1) << std::endl;
return 0;
}
讲解: 这个例子定义了一个 MyArray
类模板,可以创建一个存储任意类型的数组。在 main
函数中,我们分别实例化了 MyArray<int>
和 MyArray<double>
,创建了存储 int
类型和 double
类型的数组。
4. 成员函数模板
关键点:
- 定义: 类模板的成员函数也可以是模板函数,这意味着一个成员函数可以处理多种类型的参数。
- 声明: 在类模板内部声明成员函数模板时,需要再次使用
template
关键字和尖括号<>
声明模板参数列表。 - 实例化: 在调用成员函数模板时,编译器会根据传入的参数类型自动进行实例化。
代码示例:
#include <iostream>
template <typename T>
class MyContainer {
private:
T value;
public:
MyContainer(T value) : value(value) {}
template <typename U>
void printWith(U prefix) {
std::cout << prefix << ": " << value << std::endl;
}
};
int main() {
MyContainer<int> intContainer(10);
intContainer.printWith("Integer");
MyContainer<double> doubleContainer(3.14);
doubleContainer.printWith("Double");
doubleContainer.printWith(123); // 可以传入不同类型的参数
return 0;
}
讲解: 这个例子定义了一个 MyContainer
类模板,其中包含一个成员函数模板 printWith
。这个函数可以接受任意类型的参数 prefix
,并将其与容器的值一起打印出来。在 main
函数中,我们分别使用字符串和整数作为 prefix
参数调用 printWith
函数,展示了成员函数模板的灵活性。
作业:
定义一个模板函数sum,使得它可以对于任何数组类型求和 (int, double, std::string),并在 main 函数中调用。
再写相同功能的非模板函数,各自都运行 1000000 次调用,观察两者的用时表现
int add(int a, int b)