📌 相关专栏
-
【C++ 专栏】
📌 相关文章推荐
很高兴你点开这篇文章✨
这里会持续更新更多有用的内容,关注我,一起慢慢变好呀
👍 点赞 ⭐ 收藏 💬 评论
文章目录
- 前言
- [1. 非类型模板参数](#1. 非类型模板参数)
-
- [1.1 基本概念](#1.1 基本概念)
- [1.2 使用限制](#1.2 使用限制)
- [1.3 非类型参数 vs 宏定义](#1.3 非类型参数 vs 宏定义)
- [2. array:非类型参数的典型应用](#2. array:非类型参数的典型应用)
-
- [2.1 array 与原生数组的对比](#2.1 array 与原生数组的对比)
- [2.2 传参时的区别](#2.2 传参时的区别)
- [2.3 越界检查对比](#2.3 越界检查对比)
- [3. 模板特化概述](#3. 模板特化概述)
-
- [3.1 为什么需要特化?](#3.1 为什么需要特化?)
- [3.2 特化的概念](#3.2 特化的概念)
- [4. 函数模板特化](#4. 函数模板特化)
-
- [4.1 特化的语法](#4.1 特化的语法)
- [4.2 特化的步骤](#4.2 特化的步骤)
- [4.2 特化的陷阱](#4.2 特化的陷阱)
- [4.3 函数模板特化的建议](#4.3 函数模板特化的建议)
- [5. 类模板全特化](#5. 类模板全特化)
-
- [5.1 全特化的语法](#5.1 全特化的语法)
- [5.2 优先级规则](#5.2 优先级规则)
- [6. 类模板偏特化](#6. 类模板偏特化)
-
- [6.1 部分参数特化](#6.1 部分参数特化)
- [6.2 匹配规则](#6.2 匹配规则)
- [7. 指针与引用的偏特化](#7. 指针与引用的偏特化)
-
- [7.1 指针类型的偏特化](#7.1 指针类型的偏特化)
- [7.2 引用类型的偏特化](#7.2 引用类型的偏特化)
- [7.3 指针偏特化的价值](#7.3 指针偏特化的价值)
- [8. 完整测试用例](#8. 完整测试用例)
-
- [8.1 非类型参数测试](#8.1 非类型参数测试)
- [8.2 函数模板特化测试](#8.2 函数模板特化测试)
- [8.3 类模板特化测试](#8.3 类模板特化测试)
- [9. 总结](#9. 总结)
- 本文代码链接
前言
在基础模板学习中,我们常用的模板参数是 类型形参(如 template)。但实际开发中,有时还需要给模板传递编译期常量(如固定数组大小、指定缓存容量),这时 非类型模板参数 就派上用场了。
- 此外,当通用模板逻辑对某些特殊类型(如指针)不适用时,就需要 模板特化 来为这些类型编写专属逻辑。本文将从这两个方向深入讲解 C++ 模板的进阶特性。
1. 非类型模板参数
1.1 基本概念
非类型模板参数是用编译期可确定的常量作为模板参数,在模板内部可以直接当常量使用。
cpp
// 固定数组大小的 Stack 类
template<class T, size_t N = 0> //:error C7592: "double"类型的非类型模板参数至少需要"std:c++20"
//:必须是整型(int、size_t等)
class Stack
{
private:
T _arr[N]; // N 是编译期常量,作为数组大小
};
1.2 使用限制
| 限制 | 说明 |
|---|---|
允许的类型 |
整型(int、size_t、char、bool 等) |
C++20 前 |
不支持 double、float 等浮点类型 |
C++20 起 |
支持 double(需指定 /std:c++20) |
cpp
template<class T, double N> //:error C7592: "double"类型的非类型模板参数至少需要"std:c++20",这里是C++17
//:必须是整型(int、size_t等)
class Stack {};
1.3 非类型参数 vs 宏定义
宏定义:所有数组大小都是 N,不能按需使用非类型参数:每个实例化可指定不同大小
cpp
#define N 10 // 宏定义:所有数组大小都是 10,不能按需使用
template<class T, size_t N = 0> // 非类型参数:每个实例化可指定不同大小
class Stack {};
Stack<int> st0; // N = 0
Stack<int, 5> st1; // N = 5
Stack<int, 10> st2; // N = 10(相当于实例化出不同的类)
优势 :非类型模板参数更加灵活,可以为不同实例指定不同大小,而宏定义是全局统一的。
2. array:非类型参数的典型应用
2.1 array 与原生数组的对比
std::array 是 C++11 引入的静态数组容器,使用非类型模板参数固定大小:
| 特性 | 原生数组 int arr10 | std::array<int, 10> |
|---|---|---|
内存位置 |
栈/全局 | 栈 |
大小信息 |
传参时退化丢失 | 保留大小信息 |
范围 for |
传参后不能用 | 始终可用 |
越界检查 |
仅警告(读不检查) | operator\[\] 断言检查 |
容器接口 |
无 | 有迭代器等 |
2.2 传参时的区别
原生数组传参:退化为指针,丢失大小信息- 不能使用范围 for(不知道大小)
array 传参:保留完整类型信息- 可以使用范围 for
cpp
// 原生数组传参:退化为指针,丢失大小信息
void func(int* a)
{
// ❌ 不能使用范围 for(不知道大小)
// for (auto e : a) { }
}
// array 传参:保留完整类型信息
void func(array<int, 10>& a)
{
// ✅ 可以使用范围 for
for (auto e : a)
{
cout << e << " ";
}
}
2.3 越界检查对比
普通数组:不检查越界读,输出随机垃圾值- 越界读,只报警告
array:重载 operator ,会进行断言检查- 越界断言(debug 模式)
cpp
void test1()
{
int a1[10];
array<int, 10> a2;
// 普通数组:不检查越界读,输出随机垃圾值
cout << a1[10] << endl; // 越界读,只会报警告
// array:重载 operator[],会进行断言检查
// cout << a2[10] << endl; // 越界断言(debug 模式)
}
3. 模板特化概述
3.1 为什么需要特化?
模板的核心是 通用,但遇到特殊类型时,通用逻辑可能失效:
- 比如用模板比较指针时,默认会比较"地址"而非指针"指向的内容" ------ 这时候就需要 "模板特化",为特殊类型写专属逻辑,即
特化是用来解决"特殊类型"适配问题的
cpp
template<class T>
bool Less(T left, T right)
{
return left < right;
}
// 问题:比较指针时,比较的是地址而非指向的值
int* p1 = &i1;
int* p2 = &i2;
cout << Less(p1, p2) << endl; // 比较地址,不是 11 < 10
3.2 特化的概念
特化为特殊类型编写专属逻辑,分为两类
特化:所有模板参数都被指定为具体类型偏特化(半特化):部分模板参数被特化,或添加指针/引用等限制
4. 函数模板特化
4.1 特化的语法
cpp
// 1. 基础函数模板
template<class T>
bool Less(const T& left, const T& right)
{
return left < right;
}
// 2. 全特化语法(不推荐,问题较多)
template<>
bool Less<int*>(int* const& left, int* const& right)
{
return *left < *right;
}
4.2 特化的步骤
-
必须要先有一个基础的函数模板 -
关键字 template 后面要接一对空的尖括号 :<> -
函数名后面跟一对尖括号,尖括号中指定需要特化的类型 -
函数形参表:
必须要和模板函数的"基础参数类型"完全相同(后面会进行详细讲解),如果不同编译器可能会报一些奇怪的错误。
4.2 特化的陷阱
函数模板特化容易出现问题,推荐使用普通函数重载:
cpp
// ❌ 错误:const 修饰的歧义
// bool Less<int*>(const int*& left, const int*& right) // 与模板不匹配
// ✅ 正确:普通函数重载(推荐)
bool Less(int* left, int* right)
{
return *left < *right;
}
// 处理 const int* 类型
bool Less(const int* left, const int* right)
{
return *left < *right;
}
4.3 函数模板特化的建议
核心规则 :当普通函数和模板函数都能匹配调用时,编译器会优先选择普通函数。因此,直接编写普通函数重载即可,无需使用函数模板特化。
5. 类模板全特化
5.1 全特化的语法
全特化是将类模板的所有参数都指定为具体类型 :
cpp
// 通用类模板
template<class T1, class T2>
class Data
{
public:
Data() { cout << "Data<T1, T2>" << endl; }
};
// 全特化:T1 = int, T2 = double
template<>
class Data<int, double>
{
public:
Data() { cout << "Data<int, double> 全特化" << endl; }
void func() {} // 特化版本可以新增方法
};
5.2 优先级规则
当同时存在全特化和偏特化时,编译器优先选择最匹配的版本:
cpp
void test3()
{
Data<int, int> d1; // 通用版本(无匹配特化)
Data<int, double> d2; // 全特化版本
Data<char, double> d3; // 偏特化版本(后面会讲)
}
6. 类模板偏特化
6.1 部分参数特化
偏特化可以只特化部分模板参数:
cpp
// 通用版本
template<class T1, class T2>
class Data {};
// 偏特化:只固定第二个参数为 double
template<class T1>
class Data<T1, double>
{
public:
Data() { cout << "Data<T1, double> 偏特化" << endl; }
void func() { cout << typeid(T1).name() << endl; }
};
6.2 匹配规则
cpp
Data<int, int> d1; // 通用版本(第二个参数不是 double)
Data<char, double> d3; // 偏特化版本(第二个参数是 double)
7. 指针与引用的偏特化
7.1 指针类型的偏特化
cpp
// 两个参数偏特化为指针类型
template<class T1, class T2>
class Data<T1*, T2*>
{
public:
Data()
{
cout << "Data<T1*, T2*> 偏特化--参数更进一步限制" << endl;
// 这里可以获取到原类型 T1、T2(而非指针类型)
T1 i = 0; // T1 是原类型
T1* pi = &i;
}
void func()
{
cout << typeid(T1).name() << endl; // 打印原类型名
cout << typeid(T2).name() << endl;
}
};
7.2 引用类型的偏特化
cpp
// 两个参数偏特化为引用类型
template<class T1, class T2>
class Data<T1&, T2&>
{
public:
Data()
{
cout << "Data<T1&, T2&> 偏特化--参数更进一步限制" << endl;
}
void func()
{
cout << typeid(T1).name() << endl; // 打印引用绑定的类型
cout << typeid(T2).name() << endl;
}
};
7.3 指针偏特化的价值
价值 :可以同时获取指针类型(T*)和原类型(T)
cpp
void test4()
{
// 匹配指针偏特化版本
Data<char*, double*> d4;
d4.func(); // 打印:char double(原类型,不是 char* double*)
// 匹配引用偏特化版本
Data<char&, double&> d5;
d5.func(); // 打印:char double
}
8. 完整测试用例
8.1 非类型参数测试
cpp
void test1()
{
Stack<int> st0; // N = 0(缺省值)
Stack<int, 5> st1; // N = 5
Stack<int, 10> st2; // N = 10(三个不同的类)
int a1[10];
array<int, 10> a2;
func(a1); // 传参退化,不能用范围 for
func(a2); // 保留类型,可以用范围 for
}
8.2 函数模板特化测试
cpp
void test2()
{
int i1 = 11, i2 = 10;
cout << Less(i1, i2) << endl; // 0(11 < 10?false)
int* p1 = &i1, * p2 = &i2;
cout << Less(p1, p2) << endl; // 0(比较指向的值)
const int* p3 = &i1, * p4 = &i2;
cout << Less(p3, p4) << endl; // 0(const 版本)
}
8.3 类模板特化测试
cpp
void test5()
{
Data2<int, int> d1; // 通用版本
Data2<char*, double*> d4; // 指针偏特化版本
d4.func(); // 打印:char double
Data2<char*, double> d6; // 部分偏特化(第二个参数 double)
d6.func(); // 打印:char
}
9. 总结
C++ 模板的进阶特性:
| 分类 | 核心内容 |
|---|---|
非类型模板参数 |
编译期常量作为模板参数,用于固定数组大小等 |
array 容器 |
非类型参数的典型应用,比原生数组更安全、更易用 |
函数模板特化 |
语法复杂,推荐使用普通函数重载替代 |
类模板全特化 |
所有参数指定具体类型 |
类模板偏特化 |
部分参数特化,或添加指针/引用限制 |
特化优先级 :普通函数(非模板) > 全特化 > 偏特化 > 通用模板
| 场景 | 推荐使用 |
|---|---|
固定大小容器 |
使用非类型模板参数 |
函数针对特殊类型处理 |
使用普通函数重载 |
类针对特殊类型处理 |
使用偏特化(更灵活) |
需要同时获取指针和原类型 |
使用指针偏特化 |
本文代码链接
https://gitee.com/ayidyy/cyvyan11/commit/03f32164959d87fa54e627b99cad2d246daed3a5
- 欢迎留言交流
- 期待你的评论与建议
- 留下你的想法吧

谢谢你看到这里呀
如果喜欢这篇内容,点个关注,下次更新不迷路✨
👍 点赞 ⭐ 收藏 💬 评论
