news 2026/10/3 16:20:57

C++模板进阶

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
C++模板进阶

如果没有了解过模板的朋友,就去看一下我的模板初阶文章:C++之模板初阶-CSDN博客

通过模板我们可以尝试去实现泛型编程,模板分为函数模板和类模板

那么下面我会跟大家介绍一下,模板进阶的知识

非类型模板参数

模板参数被分为:类型形参和非类型形参

类型形参:出现在模板参数列表中,跟在class或者typename之类的参数类型名称

非类型形参:就是用一个常量作为类(函数)模板的一个参数,在类(函数)模板中可将该参数当成常量来使用

我们先看一个例子,我们写个静态栈结构:

#define N 10 template<class T>//类型模板参数 class Stack { private: T _a[N]; size_t _top; }; int main() { Stack<int> st1;//大小为10 Stack<int> st2; }

我们想要去改变栈的大小就修改我们的宏就好了,如果我们有两个栈呢?一个栈想要10,另一个想要1000的大小,这样子我们就不能满足多个栈的需求了,除非我们在定义一个类模板,但是这样子会减少我们的效率,所以,在C++当中,模板有一个非类型的模板参数概念:

template<class T,size_t N> //T是类型模板参数,N是非类型模板参数,N是一个常量 class Stack { private: T _a[N]; size_t _top; }; int main() { Stack<int,100> st1;//100 Stack<int,20000> st2;//20000 }

我们这样子就可以通过传参来完成我们每个栈想要的大小需求了,在template<class T,size_t N>当中,T是类型模板参数,这里的N是非类型模板参数,这里的N是一个常量

那我们可以这样去实现传参嘛?

int main() { static int n; cin>>n; Stack<int,n> st;//error,非类型模板参数不能是变量 return 0; }

这样子在语法上不允许的,因为非类型传参不能是变量

在STL中的容器当中,C++11新增了一个array容器,array这个容器就是类似这样的结构,它使用了非类型的模板参数:

template<class T,size_t N> class Array { private: T _a[N]; }

array是一个大小固定的容器

但是array这个容器我们一般不建议去使用

因为函数调用会建议栈帧,数组过大,可能会造成栈溢出,用vector了话,空间不够就增容,比较灵活,增容是在堆区中开辟空间,而堆区时进行动态开辟的地方,它的空间比较大,知道需要的数据大小直接使用vector中的resize就好了,没必要使用array这个容器,所以我们可以知道C++11增加的array这个容器基本没有什么用,它的缺点大于他的优点

非类型模板参数缺省值

模板参数都可以给缺省值,模板参数给缺省值和函数参数给缺省值是完全相似的,可以全缺省,也可以半缺省(必须从右往左连续缺省)

比如:

//模板参数都可以给缺省值 //模板参数给缺省值和函数参数给缺省值是完全类似的 //可以全缺省 //也可以半缺省 -- 必须从右往左连续缺省 template<class T,size_t N = 10> class Array { private: T _a[N]; } int main() { Array<int> a1; Array<int,20> a2; return 0; }

注意:如果全都是缺省值的时候不能创建这样的对象:

Array a1;

全部都是缺省值,我们可以不传参数,但是我们知道Array是个模板,模板也是有类型的,我们需要这样:

Array<> a1;

注意:

1、浮点数、类对象以及字符串是不允许我们作为非类型模板参数的

template<class T,string s1> template<class T,double s1>

2、非类型的模板参数必须在编译期就能确认结果

模板的特化

概念

通常情况下,使用模板可以实现一些与类型无关的代码,但对于一些特殊类型的可能会得到一些错误的结果

template<class T> bool IsEqual(const T& left,const T& right) { return left==right; } int main() { cout<<IsEqual(1,2)<<endl; char p1[] = "hello"; char p2[] = "hello"; cout<<IsEqual(p1,p2)<<endl;//数组名是指针常量 return 0; }

这个模板可以用来比较整形,但是比较地址就会出现问题

此时可以使用模板的特化,可以针对某些类型进行特殊化去处理,我们可以这样写

bool IsEqual(const char*& left,const char*& right) { return strcmp(left,right)==0; } int main() { cout<<IsEqual(1,2)<<endl; char p1[] = "hello"; char p2[] = "hello"; cout<<IsEqual(p1,p2)<<endl;//数组名是指针常量 return 0; }

我们调式过后,发现这里不会进这个函数,因为数组名是指针常量,则这里的const修饰的是*left,是left指向的内容不能修改,而不是left不能修改,这里属于权限放大了

需要这样改,这样就可以进去了:

//模板的特化,针对某些类型进行特殊化处理 bool IsEqual(const char*& const left,const char*& const right) { return strcmp(left,right)==0; }
bool IsEqual(const char* left,const char* right) { return strcmp(left,right)==0; }

函数模板的特化

函数模板的特化步骤:

  1. 必须要先有一个基础的函数模板
  2. 关键字template后面接一对空的尖括号<>
  3. 函数名后跟一对尖括号,尖括号中指定需要特化的类型
  4. 函数形参表: 必须要和模板函数的基础参数类型完全相同,如果不同编译器可能会报一些奇怪的错误。
template<class T> void Swap(T& a,T& b) { //vector代价太大 T tmp = a; a = b; b = tmp; } int main() { int x = 1; int y = 2; Swap(x,y); vector<int> v1 = {1,2,3,4}; vector<int> v2 = {10,20,30,40}; Swap(v1,v2); return 0; }

我们交换的类型是vector时,此时用模板函数进行交换代价太大了,一次拷贝构造+两次赋值重载,所以我们可以优化一下:

//函数模板的特化 template<> void Swap<vector<int>>(vector<int>& a,vector<int>& b) { a.swap(b); }

当然也可以这样,利用模板的匹配原则,进行特殊化处理:

//模板的匹配原则,进行特殊化处理 void Swap(vector<int>& a,vector<int>& b) { a.swap(b); }

类模板的特化

全特化

全特化就是将模板列表中所有的参数都确定话

template<class T1,class T2> class Data { public: Data() { cout << "Data<T1,T2>"<<endl; } private: T1 _d1; T2 _d2; }; //全特化 template<> class Data<double, double> { public: Data() { cout << "Data<double,double>" << endl; } private: T1 _d1; T2 _d2; }; int main() { Data<int,int> d1; Data<double,double> d2; return 0; }

偏特化

偏特化:任何针对模板参数进一步进行条件限制设计的特化版本

偏特化有两种:

1、部分特化,将模板参数类表中的一部分参数特化

template<class T1,class T2> class Data { public: Data() { cout << "Data<T1,T2>"<<endl; } private: T1 _d1; T2 _d2; }; //偏特化或者半特化 template<class T1> class Data<T1,double> { public: Data() { cout << "Data<T1,double>" << endl; } }; int main() { Data<int,double> d2; Data<char,double> d1; return 0; }

2、参数更进一步的限制,偏特化并不是仅仅是指特化部分参数,而是针对模板参数更进一步的条件限制所设计出来的一个特化版本

//必须要存在原模板,在原模板的基础上去特化 template<class T1,class T2> class Date { public: Date() { cout << "Date<T1,T2>" << endl; } private: T1 _d1; T2 _d2; }; //偏特化,传的类型是指针 template<typename T1,typename T2> class Date<T1*, T2*> { public: Date() { cout << "Date<T1*,T2*>" << endl; } }; //传的是引用 template<typename T1, typename T2> class Date<T1&, T2&> { public: Date() { cout << "Date<T1&,T2&>" << endl; } }; //也可以半指针,半引用 template<typename T1, typename T2> class Date<T1&, T2*> { public: Date() { cout << "Date<T1&,T2*>" << endl; } }; int main() { Date<char*, char*> d5; Date<int*, double*>d6; Date<int&, double&>d7; Date<int&, double*>d8; return 0; }

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/2 23:10:04

IDM激活脚本终极指南:3种方法彻底解决30天试用限制

IDM激活脚本终极指南&#xff1a;3种方法彻底解决30天试用限制 【免费下载链接】IDM-Activation-Script IDM Activation & Trail Reset Script 项目地址: https://gitcode.com/gh_mirrors/id/IDM-Activation-Script 还在为Internet Download Manager的30天试用期到期…

作者头像 李华
网站建设 2026/10/1 11:30:06

本地运行更安全!科哥镜像保障图片数据不出内网

本地运行更安全&#xff01;科哥镜像保障图片数据不出内网 1. 引言&#xff1a;为什么本地抠图正在成为新趋势&#xff1f; 你有没有这样的经历&#xff1a;为了给产品图换背景&#xff0c;把一堆高清原图上传到某个在线抠图网站&#xff0c;心里却隐隐不安——这些包含品牌标…

作者头像 李华
网站建设 2026/10/1 1:43:11

[特殊字符]_网络IO性能优化:从TCP到HTTP的层层优化[20260121164341]

作为一名专注于网络性能优化的工程师&#xff0c;我在过去的项目中积累了丰富的网络IO优化经验。最近&#xff0c;我参与了一个对网络性能要求极高的项目——实时视频流平台。这个项目让我重新审视了Web框架在网络IO方面的表现。今天我要分享的是基于真实项目经验的网络IO性能优…

作者头像 李华
网站建设 2026/10/1 1:32:35

基于SpringBoot+Vue的Spring Boot可盈保险合同管理系统管理系统设计与实现【Java+MySQL+MyBatis完整源码】

摘要 随着保险行业的快速发展&#xff0c;传统的手工管理模式已无法满足现代企业对高效、精准和智能化管理的需求。保险合同管理系统作为保险业务的核心支撑平台&#xff0c;其信息化和自动化水平直接影响企业的运营效率和客户服务质量。当前许多保险公司仍依赖纸质合同或简单…

作者头像 李华
网站建设 2026/10/3 8:58:31

gradient_accumulation_steps=16的意义你知道吗?

gradient_accumulation_steps16的意义你知道吗&#xff1f; 在深度学习训练中&#xff0c;我们经常遇到显存不足的问题&#xff0c;尤其是在微调大模型时。你是否也曾在尝试微调 Qwen2.5-7B 这类参数量级的模型时&#xff0c;被 CUDA out of memory 的报错劝退&#xff1f;而当…

作者头像 李华