【C++指南】模板 深度解析
💓 博客主页:倔强的石头的CSDN主页
📝Gitee主页:倔强的石头的gitee主页
⏩ 文章专栏:《C++指南》
期待您的关注
目录
1. 引言
2. 模板的基本概念
3. 函数模板
3.1 定义和语法
3.2 函数模板实例化
3.3 隐式实例化
3.4 显式实例化
3.5 模板函数的匹配规则
4. 类模板
4.1 定义和语法
4.2 成员函数的定义
4.3 模板参数的默认值
5. 模板的高级用法
5.1 模板特化
5.2 模板模板参数
6. 实战案例
6.1 函数模板示例
6.2 类模板示例
7. 常见问题与注意事项
8. 结语
9. 参考文献
1. 引言
C++模板是一种强大的泛型编程工具,它允许程序员编写独立于具体类型的代码。
泛型编程:编写与类型无关的通用代码,是代码复用的一种手段。模板是泛型编程的基础。
通过模板,我们可以创建通用的函数和类,这些函数和类可以适用于多种数据类型,从而提高代码的复用性和灵活性。
本文将详细介绍C++模板的基本概念和使用方法。
2. 模板的基本概念
模板是C++的一种特性,用于创建函数或类的通用形式,这些形式可以应用于多种数据类型。
模板允许开发者编写一次代码,然后用不同的类型实例化,从而生成具体的函数或类。
3. 函数模板
3.1 定义和语法
函数模板的定义使用关键字template
,后跟模板参数列表,然后是函数声明或定义。模板参数列表通常包含一个或多个类型参数。
template<typename T1, typename T2,......,typename Tn>
返回值类型 函数名(参数列表){}
template <typename T>
T max(T a, T b) {
return (a > b) ? a : b;
}
注意:
typename是用来定义模板参数关键字,也可以使用class(切记:不能使用struct代替class)
3.2 函数模板实例化
用不同类型的参数使用函数模板时,称为函数模板的实例化。
模板参数实例化分为:隐式实例化 和显式实例化。
3.3 隐式实例化
当调用函数模板时,编译器会根据传递的参数类型自动推导出模板参数的具体类型。
这种实例化,称为隐式实例化,如下方示例:
#include<iostream>
template <typename T>
T max(T a, T b) {
return (a > b) ? a : b;
}
int main() {
int x = 3, y = 4;
double a = 1.1, b = 2.2;
int maxInt = max(x, y); // T 被推导为 int
double maxDouble = max(a, b); // T 被推导为 double
std::cout << "Max of " << x << " and " << y << " is " << maxInt << std::endl;
std::cout << "Max of " << a << " and " << b << " is " << maxDouble << std::endl;
return 0;
}
要注意的是:如果传递的参数不能让编译器正确推导出实例化的函数,就会报错
如下方示例:
#include<iostream>
template <typename T>
T max(T a, T b) {
return (a > b) ? a : b;
}
int main() {
int x = 3, y = 4;
double a = 1.1, b = 2.2;
int maxInt = max(x, a); // 编译报错
std::cout << "Max of " << x << " and " << y << " is " << maxInt << std::endl;
return 0;
}
注意:在模板中,编译器一般不会进行类型转换操作,因为一旦转化出问题,编译器就需要 背黑锅
此时有两种处理方式:1. 用户自己来强制转化 2. 使用显式实例化
int maxInt = max(x, (int)a); //强制类型转换
3.4 显式实例化
显式指定模板参数的类型,可以使用尖括号<>
来指定。
int main() {
int x = 3, y = 4;
double a = 1.1, b = 2.2;
int maxInt = max<int>(x, y); // 显式指定 T 为 int
double maxDouble = max<double>(a, b); // 显式指定 T 为 double
std::cout << "Max of " << x << " and " << y << " is " << maxInt << std::endl;
std::cout << "Max of " << a << " and " << b << " is " << maxDouble << std::endl;
return 0;
}
3.5 模板函数的匹配规则
- 一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这 个非模板函数。对于非模板函数和同名函数模板,如果其他条件都相同,在调动时会优先调用非模板函数而 不会从该模板产生出一个实例。普通函数的优先级高于模板函数
- 如果模板可以产生一个具有更好匹配的函数, 那么将选择模板
// 专门处理int的加法函数
int Add(int left, int right)
{
return left + right;
}
// 通用加法函数
template<class T>
T Add(T left, T right)
{
return left + right;
}
void Test()
{
Add(1, 2); // 与非模板函数匹配,编译器不需要特化
Add<int>(1, 2); // 调用编译器特化的Add版本
}
4. 类模板
4.1 定义和语法
类模板的定义类似于函数模板,使用关键字template
,后跟模板参数列表,然后是类的定义。
template<class T1, class T2, ..., class Tn>
class 类模板名
{
// 类内成员定义
};
示例代码:
template <typename T>
class Stack {
private:
T* items; // 数组存储元素
int top; // 栈顶索引
int size; // 栈的最大容量
public:
Stack(int sz) : size(sz), top(-1) {
items = new T[size];
}
~Stack() {
delete[] items;
}
void push(const T& item);
T pop();
bool isEmpty() const;
};
4.2 成员函数的定义
类模板的成员函数可以在类内定义,也可以在类外定义。
注意:模版不建议声明和定义分离到两个文件.h 和.cpp会出现链接错误
// 在类内定义
template <typename T>
class Stack {
public:
void push(const T& item) {
if (top < size - 1) {
items[++top] = item;
} else {
std::cout << "Stack overflow" << std::endl;
}
}
T pop() {
if (top >= 0) {
return items[top--];
} else {
std::cout << "Stack underflow" << std::endl;
return T(); // 返回默认构造的对象
}
}
bool isEmpty() const {
return top == -1;
}
};
// 在类外定义
template <typename T>
void Stack<T>::push(const T& item) {
if (top < size - 1) {
items[++top] = item;
} else {
std::cout << "Stack overflow" << std::endl;
}
}
template <typename T>
T Stack<T>::pop() {
if (top >= 0) {
return items[top--];
} else {
std::cout << "Stack underflow" << std::endl;
return T(); // 返回默认构造的对象
}
}
template <typename T>
bool Stack<T>::isEmpty() const {
return top == -1;
}
4.3 模板参数的默认值
模板参数可以有默认值,这样在实例化时可以省略某些参数。
template <typename T, int Size = 100>
class Stack {
private:
T* items; // 数组存储元素
int top; // 栈顶索引
int size; // 栈的最大容量
public:
Stack() : size(Size), top(-1) {
items = new T[size];
}
~Stack() {
delete[] items;
}
void push(const T& item);
T pop();
bool isEmpty() const;
};
注意:
模板参数可以是类型参数(如 typename T
)或非类型参数(如 int N
)。
非类型参数必须是编译时常量表达式,并且其类型通常是整型、枚举类型、指针类型或引用类型(指向对象的引用)
这里的 int Size = 100
实际上是一个非类型模板参数,它有一个默认值。这意味着当实例化这个模板类时,如果不提供第二个参数,Size
将默认为100。但是,请注意,这个参数是模板级别的,而不是对象级别的。也就是说,一旦模板被实例化(例如 Stack<int>
或 Stack<int, 200>
),该实例的 Size
值就是固定的,不能在运行时改变
5. 模板的高级用法
5.1 模板特化
模板特化允许为特定的类型提供特殊的实现。
// 全特化
template <>
class Stack<char> {
private:
char* items; // 数组存储元素
int top; // 栈顶索引
int size; // 栈的最大容量
public:
Stack(int sz) : size(sz), top(-1) {
items = new char[size];
}
~Stack() {
delete[] items;
}
void push(char item);
char pop();
bool isEmpty() const;
};
// 部分特化(仅限类模板)
template <typename T, int Size>
class Stack<T*, Size> {
private:
T** items; // 数组存储元素
int top; // 栈顶索引
int size; // 栈的最大容量
public:
Stack() : size(Size), top(-1) {
items = new T*[size];
}
~Stack() {
delete[] items;
}
void push(T* item);
T* pop();
bool isEmpty() const;
};
5.2 模板模板参数
模板模板参数允许将一个模板作为另一个模板的参数。
template <template <typename, int> class Container, typename T, int Size>
class MyClass {
Container<T, Size> container;
public:
void add(const T& item) {
container.push(item);
}
T remove() {
return container.pop();
}
};
6. 实战案例
6.1 函数模板示例
编写一个通用的数组排序函数。
#include <algorithm>
template <typename T>
void sortArray(T arr[], int size) {
std::sort(arr, arr + size);
}
int main() {
int intArr[] = {5, 2, 8, 1, 9};
double doubleArr[] = {3.1, 2.2, 1.1, 5.5, 4.4};
sortArray(intArr, 5);
sortArray(doubleArr, 5);
for (int i : intArr) {
std::cout << i << " ";
}
std::cout << std::endl;
for (double d : doubleArr) {
std::cout << d << " ";
}
std::cout << std::endl;
return 0;
}
6.2 类模板示例
编写一个通用的链表类。
template <typename T>
class LinkedList {
private:
struct Node {
T data;
Node* next;
Node(T val) : data(val), next(nullptr) {}
};
Node* head;
public:
LinkedList() : head(nullptr) {}
~LinkedList() {
clear();
}
void insert(T value);
void remove(T value);
bool contains(T value) const;
void clear();
};
template <typename T>
void LinkedList<T>::insert(T value) {
Node* newNode = new Node(value);
if (!head) {
head = newNode;
} else {
Node* current = head;
while (current->next) {
current = current->next;
}
current->next = newNode;
}
}
template <typename T>
void LinkedList<T>::remove(T value) {
Node* current = head;
Node* previous = nullptr;
while (current) {
if (current->data == value) {
if (previous) {
previous->next = current->next;
} else {
head = current->next;
}
delete current;
return;
}
previous = current;
current = current->next;
}
}
template <typename T>
bool LinkedList<T>::contains(T value) const {
Node* current = head;
while (current) {
if (current->data == value) {
return true;
}
current = current->next;
}
return false;
}
template <typename T>
void LinkedList<T>::clear() {
Node* current = head;
while (current) {
Node* next = current->next;
delete current;
current = next;
}
head = nullptr;
}
int main() {
LinkedList<int> list;
list.insert(1);
list.insert(2);
list.insert(3);
std::cout << "Contains 2: " << list.contains(2) << std::endl;
list.remove(2);
std::cout << "Contains 2: " << list.contains(2) << std::endl;
return 0;
}
7. 常见问题与注意事项
- 编译时间增加:复杂的模板可能会显著增加编译时间。
- 错误信息复杂:模板错误可能导致难以理解的错误消息。
- 代码膨胀:模板的过度使用可能会导致目标文件中出现大量重复的代码。
8. 结语
C++模板是实现泛型编程的重要手段,它不仅增强了代码的复用性和可维护性,还提高了程序的执行效率。通过本文的学习,希望读者能够掌握C++模板的基本概念和使用方法,并在实际编程中灵活运用。
9. 参考文献
- Stroustrup, B. (2013). The C++ Programming Language. 4th Edition.
- Josuttis, N. M. (2012). The C++ Standard Library: A Tutorial and Reference. 2nd Edition.
- C++ Standard Library Documentation
希望这篇文章对你有所帮助,祝你在学习C++模板的过程中取得更大的进步!