目录
[一 非类型模版参数:](#一 非类型模版参数:)
[1.1 什么是非类型模版参数:](#1.1 什么是非类型模版参数:)
[1.1.1 定义:](#1.1.1 定义:)
[1.2 非类型模版参数需注意:](#1.2 非类型模版参数需注意:)
[1.3 非类型模板参数的使用场景:](#1.3 非类型模板参数的使用场景:)
[二 模板的特化:](#二 模板的特化:)
[2.1 定义:](#2.1 定义:)
[2.1.1 模板特化的分类:](#2.1.1 模板特化的分类:)
[2.2 函数模板特化:](#2.2 函数模板特化:)
[2.3 类模板特化:](#2.3 类模板特化:)
[2.3.1 类模板全特化:](#2.3.1 类模板全特化:)
[2.3.2 类模板偏特化:](#2.3.2 类模板偏特化:)
[2.3.3 对指针进行排序的类模板特化:](#2.3.3 对指针进行排序的类模板特化:)
[三 模板的分离编译:](#三 模板的分离编译:)
[3.1 什么是模板的分离编译?](#3.1 什么是模板的分离编译?)
[3.2 分离编译中的问题:](#3.2 分离编译中的问题:)
[3.3 示例:模板的声明和定义分离](#3.3 示例:模板的声明和定义分离)
[3.4 解决模板分离编译问题:](#3.4 解决模板分离编译问题:)
[3.4.1 将模板的声明和定义放在同一个头文件中:](#3.4.1 将模板的声明和定义放在同一个头文件中:)
[3.4.2 显式实例化模板:](#3.4.2 显式实例化模板:)
[四 模板总结:](#四 模板总结:)
[4.1 优点:](#4.1 优点:)
[4.2 缺点:](#4.2 缺点:)
一 非类型模版参数:
1.1 什么是非类型模版参数:
在模板编程中,除了类型参数(如class T 或 typename T)外,还可以使用非类型模板参数。非类型模板参数可以是常量,例如整数、枚举、指针等,它们在编译期间是已知的值。
1.1.1 定义:
cpp
template<class T, size_t N>
class Array
{
public:
T& operator[](size_t index)
{
return _array[index];
}
const T& operator[](size_t index) const
{
return _array[index];
}
size_t size() const
{
return N;
}
private:
T _array[N];
};
在这个例子中,N 是一个非类型模板参数,表示数组的大小,它必须在编译时已知。
1.2 非类型模版参数需注意:
1.允许的类型 :非类型模板参数可以是整型、枚举、指针或者引用类型,但浮点数、类对象和字符串不允许作为非类型模板参数。
2.编译期确认:非类型模板参数必须在编译期确认。这意味着它的值在编译时必须是一个常量表达式。
1.3 非类型模板参数的使用场景:
非类型模板参数最常用于需要对某些固定值进行编译期优化的场景。例如,在实现容器类时,可以通过非类型模板参数来指定容器的大小,从而在编译时确定内存分配的规模。
二 模板的特化:
2.1 定义:
模板特化是指在模板的基础上,针对某些特定的类型提供专门的实现。当模板的默认实现无法满足某些特定类型的需求时,就可以通过特化来处理。例如,针对指针类型的特殊处理。
2.1.1 模板特化的分类:
模板特化分为两种:
全特化:对模板中的所有参数进行特化。
偏特化:仅对模板中的部分参数进行特化或进一步限制。
2.2 函数模板特化:
示例:
cpp
template<class T>
bool Less(T left, T right)
{
return left < right;
}
// 针对指针类型的特化
template<>
bool Less<Date*>(Date* left, Date* right)
{
return *left < *right;
}
int main() {
Date d1(2022, 7, 7);
Date d2(2022, 7, 8);
std::cout << Less(d1, d2) << std::endl; // 正常比较日期
Date* p1 = &d1;
Date* p2 = &d2;
std::cout << Less(p1, p2) << std::endl; // 使用特化版本,比较指针指向的内容
return 0;
}
在这个例子中,函数 Less
针对 Date*
指针类型进行了特化,以正确处理指针类型的比较。
2.3 类模板特化:
2.3.1 类模板全特化:
全特化指的是对模板中的所有参数进行特化,适用于某些特定类型,完全替代原始的模板实现。
cpp
template<class T1, class T2>
class Data
{
public:
Data() { std::cout << "Data<T1, T2>" << std::endl; }
};
template<>
class Data<int, char>
{
public:
Data() { std::cout << "Data<int, char>" << std::endl; }
};
int main()
{
Data<int, int> d1; // 使用原始模板版本
Data<int, char> d2; // 使用全特化版本
}
在这个例子中,Data<int, char> 这个类型的对象会调用全特化的版本,输出 "Data<int, char>"。
2.3.2 类模板偏特化:
偏特化允许对模板的一部分参数进行特化,而不需要对全部参数进行特化。它使得模板能够更灵活地处理复杂的类型组合。
部分参数的偏特化:
cpp
template<class T1, class T2>
class Data
{
public:
Data() { std::cout << "Data<T1, T2>" << std::endl; }
};
// 偏特化版本,将第二个模板参数特化为int
template<class T1>
class Data<T1, int>
{
public:
Data() { std::cout << "Data<T1, int>" << std::endl; }
};
int main()
{
Data<int, char> d1; // 调用原始模板
Data<int, int> d2; // 调用偏特化版本
}
在这里,Data<int, int> 将调用偏特化版本,而 Data<int, char> 将调用原始模板版本。
指针类型的偏特化:
cpp
template<class T1, class T2>
class Data
{
public:
Data() { std::cout << "Data<T1, T2>" << std::endl; }
};
// 偏特化版本,将两个参数特化为指针类型
template<class T1, class T2>
class Data<T1*, T2*>
{
public:
Data() { std::cout << "Data<T1*, T2*>" << std::endl; }
};
int main()
{
Data<int, int> d1; // 调用原始模板
Data<int*, int*> d2; // 调用指针类型偏特化版本
}
在这个例子中,Data<T1*, T2*> 将调用偏特化的指针版本,输出 "Data<T1*, T2*>"。
2.3.3 对指针进行排序的类模板特化:
cpp
#include <vector>
#include <algorithm>
template<class T>
struct Less
{
bool operator()(const T& x, const T& y) const
{
return x < y;
}
};
// 针对指针类型进行特化
template<>
struct Less<Date*>
{
bool operator()(Date* x, Date* y) const
{
return *x < *y;
}
};
int main()
{
Date d1(2025, 7, 29);
Date d2(2025, 7, 30);
Date d3(2025, 7, 31);
// 排序日期对象
std::vector<Date> v1 = {d1, d2, d3};
std::sort(v1.begin(), v1.end(), Less<Date>());
// 正确排序
// 排序指针
std::vector<Date*> v2 = {&d1, &d2, &d3};
std::sort(v2.begin(), v2.end(), Less<Date*>());
// 使用特化版本,按指针指向的日期排序
return 0;
}
三 模板的分离编译:
3.1 什么是模板的分离编译?
一个程序(项目)由若干个源文件共同实现,而每个源文件单独编译生成目标文件,最后将所有 目标文件链接起来形成单一的可执行文件的过程称为分离编译模式。
3.2 分离编译中的问题:
在模板的分离编译中,模板的声明和定义分离时会产生编译或链接错误。这是因为模板的实例化是由编译器根据实际使用的类型生成的代码,如果在模板的定义和使用之间缺乏可见性,编译器无法正确地实例化模板。
3.3 示例:模板的声明和定义分离
cpp
// a.h
template<class T>
T Add(const T& left, const T& right);
// a.cpp
template<class T>
T Add(const T& left, const T& right)
{
return left + right;
}
// main.cpp
#include "a.h"
int main()
{
Add(1, 2); // 使用模板函数
Add(1.0, 2.0); // 使用模板函数
return 0;
}
在这种情况下,由于模板的定义和使用是分离的,编译器在不同编译单元中无法找到模板的定义,从而导致链接错误。
3.4 解决模板分离编译问题:
3.4.1 将模板的声明和定义放在同一个头文件中:
将模板的定义和声明都放在头文件中,使得所有使用模板的编译单元都可以访问到模板的定义。
3.4.2 显式实例化模板:
通过显式实例化,将模板的具体实现放在 .cpp 文件中。这样,编译器能够在实例化时找到模板的定义。
这两种方法都能有效避免模板分离编译带来的问题,推荐将模板的定义和声明放在同一个文件中,通常使用
.hpp
或.h
文件格式。
四 模板总结:
模板编程在C++中是一种非常强大的工具,通过泛型编程、模板特化和非类型模板参数等技术,可以编写高效、灵活的代码。模板编程的优缺点总结如下:
4.1 优点:
- 代码复用:模板能够极大提高代码的复用性,减少重复代码的编写。
- 灵活性:可以根据不同的数据类型生成特定的代码,增强了程序的适应性。
- STL基础:C++的标准模板库(STL)就是基于模板技术构建的,它为容器、算法和迭代器提供了高度泛型化的接口。
4.2 缺点:
- 代码膨胀:模板实例化时会生成不同版本的代码,可能导致二进制文件变大。
- 编译时间变长:由于模板的编译期实例化,可能会导致编译时间增加。
- 调试困难:模板编译错误信息往往非常复杂,难以阅读和调试。
以上就是**【C++篇】模版进阶** 的全部内容,欢迎指正~ 🌹🌹🌹
码文不易,还请多多关注支持,这是我持续创作的最大动力!