首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >【c++】模板进阶

【c++】模板进阶

作者头像
mosheng
发布2026-01-14 18:32:03
发布2026-01-14 18:32:03
1460
举报
文章被收录于专栏:c++c++

hello~ 很高兴见到大家! 这次带来的是C++中关于模板进阶这部分的一些知识点,如果对你有所帮助的话,可否留下你宝贵的三连呢? 个 人 主 页: 默|笙

一、非类型模板参数

1.1 非类型模板参数

模板参数分为类型模板参数和非类型模板参数。之前我们在模板初阶里学到的就是类型模板参数,现在我们来认识非类型模板参数。

  1. 类型形参是跟在 class 或 typename 后面的参数类型的名称,而非类型形参则是用一个常量(类型已固定)作为类(函数)模板的一个参数。非类型的模板参数必须在编译期就能确认结果。
  2. 这个常量以目前的编译器只能支持整形,如 size_t,int,char等。一般使用的是 size_t。
代码语言:javascript
复制
//#define N 10

template<class T, size_t N>
class Stack
{
private:
	T _a[N];//vector的内存是在堆上申请,它是动态的,如果我们需要一个静态栈的话
	size_t top;
	size_t capacity;
};

int main()
{
	Stack<int, 10> st1;
	Stack<int, 1000> st2;
	return 0;
}
  1. 如果我们使用c语言里的宏定义来控制一个静态栈的大小,那么每一个栈的大小都是固定的,为10个 T 类型所占字节;但如果我们使用非类型模板参数,我们就可以在创建栈的时候主动为它赋予一个适合的大小。
  2. 栈所占内存相比堆是非常少的,只有 1-8MB左右,而堆通常为GB级别,所以在创建静态栈等里面的一些静态数组的时候,内存不要申请过大,否则会发生栈溢出的现象。
在这里插入图片描述
在这里插入图片描述

1.2 array

  1. 相比于vector这种根据需要申请内存,只能访问已初始化的位置;静态数组则是先申请好内存,它能够访问范围内的所有位置。
  2. array是一个封装了固定大小数组的容器,使用方法跟上面的自定义栈差不多。
  3. 使用array需要包头文件array。
  4. 不会初始化数据,数据还是随机值。
代码语言:javascript
复制
array<int, 10> a1;
int a2[10];
  1. 上面两者有什么不同?都不会初始化数据,都是申请一个10个int类型大小的静态数组。不同之处在于:array有严格的数组越界检查,而下面的越界检查是一种抽查(抽查相邻位置),而且只查写而不查读。
代码语言:javascript
复制
a1.fill(1);//可以使用fill进行初始化

二、模板的特化

模板的特化是在已有模板的基础之上另外进行改造,我们把这个基础模板叫做主模板,这个根据情况改造之后的模板就是特化模板

  1. 主模板它适用于大多数类型,而特化模板只适用于特定类型,使用特化模板的就不会使用主模板,特化模板的优先级更高
  2. 可以把主模板看作一个咖啡配方,这个咖啡配方适合大多数人;然后特化模板就是根据一些人的口味对咖啡配方进行微调,让这个咖啡的口味更适合这类人。而这类人有了更爱喝的咖啡之后自然就不会想去喝原来的咖啡了。

2.1 函数模板特化

  1. 函数模板是只有全特化,什么是全特化可以在下文进行了解。
  2. 函数模板特化格式:
代码语言:javascript
复制
template<>//表明这是一个特化版本
返回类型 主模板函数名<特化的类型>(主模板格式+特化类型 形参)
{
	特化内容
}
  1. 看一个例子加深理解:
代码语言:javascript
复制
//主模板
template<class T>
void Less(const T& left, const T& right)
{
	cout << "Less(const T& left, const T& right)" << endl;
}

//函数模板特化
//特化模板
template<>
void Less<double*>(double* const & left, double* const & right)
{
	cout << "Less<double*>(double* const & left, double* const & right)" << endl;
}
int main()
{
	
	Less(1, 2);
	double n1 = 1.1, n2 = 2.2;
	Less(&n1, &n2);
	return 0;
}

执行结果:

在这里插入图片描述
在这里插入图片描述
  1. 对于double*类型,它调用的是特化后的模板,而不是原模板,正所谓有了更爱喝更适合的咖啡之后就不会去喝原来的咖啡了。
  2. 一个易错点:double* const & left 里的const 要在 * 的右边。这是因为 const 修饰的应该是(double*类型)引用本身,而不是指针所指向的值。否则特化不上。

  1. const 在 * 的左边,修饰指针所指向的内容,指针所指向的内容不能更改。
  2. const 在 * 的右边,修饰指针本身,指针本身不能够修改。

2.2 类模板特化

1. 全特化

全特化就是把模板里的模板参数全部给特化掉,换成具体的类型。比如上面的函数模板特化就是这样。

例子:

代码语言:javascript
复制
//主类模板
template<class T1, class T2>
class Date
{
public:
	Date()
	{
		cout << "Date<T1, T2>" << endl;
	}
private:
	T1 _d1;
	T2 _d2;
};

//特化类模板
template<>
class Date<int, char>
{
public:
	Date()
	{
		cout << "Date<int, char>" << endl;
	}
private:
	int _d1;
	char _d2;
};


int main()
{
	Date<int, int> d1;
	Date<int, char> d2;
	return 0;
}

执行结果:

在这里插入图片描述
在这里插入图片描述
  1. template<>绝对不能漏掉。
2. 偏特化

偏特化有两种,一种是模板参数只有一部分换成具体的类型,一种是不把它们换成具体类型,而是模式特化(比如添加修饰符*)。

2.1 部分参数特化(部分参数换成具体类型)
代码语言:javascript
复制
//主类模板
template<class T1, class T2>
class Data
{
public:
	Data()
	{
		cout << "Date<T1, T2>" << endl;
	}
private:
	T1 _d1;
	T2 _d2;
};

//第一类部分参数特化
//特化第二个模板参数
template<class T1>
class Data<T1, char>
{
public:
	Data()
	{
		cout << "Date<T1, char>" << endl;
	}
private:
	T1 _d1;
	char _d2;
};
//特化第一个模板参数
template<class T2>
class Data<char, T2>
{
public:
	Data()
	{
		cout << "Date<T2, char>" << endl;
	}
private:
	char _d1;
	T2 _d2;
};
  1. 特化模板声明的时候要把没有特化的模板参数放在template<>里面,像是template<class T1>与template<class T2>。
  2. 类名后面跟参数模式,特化的把特化后的具体类型写上,没有进行特化的写原来的模板参数名。
  3. 格式:
代码语言:javascript
复制
template <未特化的模板参数列表>
class 类名<部分特化的参数模式> {
    // 类实现
};
2.2 模式特化
代码语言:javascript
复制
//主类模板
template<class T1, class T2>
class Data
{
public:
	Data()
	{
		cout << "Date<T1, T2>" << endl;
	}
private:
	T1 _d1;
	T2 _d2;
};

//模式特化
template<class T1, class T2>
class Data<T1*, T2*>
{
public:
	Data()
	{
		cout << "Data<T1*, T2*>" << endl;
	}
private:
	T1* _d1;
	T2* _d2;
};
  1. 模式特化不同于全特化和部分参数特化,它的特化不是具体的类型,而是给原来的模板参数加上一些限制如*,所以template里面不能漏掉模板参数。
  2. 类名后面的参数模式,原来的模板参数加上修饰符如*就行。
  3. 格式:
代码语言:javascript
复制
template <typename 模板参数>  
class 主模板名<修饰后的模板参数> {  // 在尖括号中添加修饰符
    // 类实现
};
  1. 两种特化可以混合使用,但需要遵守各自的规则,如:
代码语言:javascript
复制
//主模板省略
//混合使用
template<class T1>
class Data<T1*, int>
{
public:
	Data()
	{
		cout << "Data<T1*, int>" << endl;
	}
private:
	T1* _d1;
	int _d2;
};

2.3 优先顺序

  1. 对于函数:

  1. 优先匹配普通非模板函数
  2. 其次匹配函数模板特化/重载。
  3. 最后匹配函数主模板。
  4. 对于类:全特化 > 偏特化 > 主模板。普通类不参与模板匹配。

三、模板声明和定义一般为什么不分离?

  1. 这主要是因为编译器的编译机制,导致模板无法正常实例化,流程:预处理->编译->汇编->链接,实例化在编译阶段。
在这里插入图片描述
在这里插入图片描述
  1. 倘若我们实现模板声明和定义的分离且不做其他处理(显式实例化,之后会讲到),包含Func.h用来测试的test.cpp文件只能拿到声明,拿不到定义,那么模板的实例化不能在test.cpp文件里完成。包含Func.h的Func.cpp空有定义,却不知道要实例成什么样子,同样无法完成实例化。
  2. 报链接错误:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
  1. 那么我们将声明和定义放到同一个文件.h或.hpp里面就不会出现这种问题了。还有一种办法就是显式实例化,Func.cpp既然不知道要实例化成什么样子,那我们就告诉它。
  1. 但是模板的显式实例化对于每一个类型都要告诉它,非常不方便而且我们告诉它的实例它都会进行实例化不管用不用得上。所以一般都是将声明和定义放在同一个文件,简短的模板函数成为内联,内容长的可能分离,但分离也是在同一个文件里进行分离。

今天的分享就到此结束啦,如果对读者朋友们有所帮助的话,可否留下宝贵的三连呢~~ 让我们共同努力, 一起走下去!

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2026-01-13,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、非类型模板参数
    • 1.1 非类型模板参数
    • 1.2 array
  • 二、模板的特化
    • 2.1 函数模板特化
    • 2.2 类模板特化
      • 1. 全特化
      • 2. 偏特化
    • 2.3 优先顺序
  • 三、模板声明和定义一般为什么不分离?
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档