C++ 模版进阶

目录

前言

1. 非类型模版参数

1.1 概念与讲解

1.2  array容器

2. 模版的特化

2.1 概念

2.2 函数模版特化

2.3 类模版特化

2.3.1 全特化

2.3.2 偏特化

3.模版的编译分离

3.1 什么是分离编译

3.2 模版的分离编译

3.3 解决方法

4. 模版总结

总结


前言

本篇文章主要讲解的是模版进阶的内容,其中有模版更深入的应用,内容丰富,干货多多!


1. 非类型模版参数

1.1 概念与讲解

模版参数分类类型形参非类型形参

类型形参即,出现在模版参数列表中,跟在class或者typename后面的参数类型名称。

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

我们来看下面的场景。

  • 我们要完成一个静态的栈,一般可以使用宏来定义N,需要存储多少数据,修改宏的大小即可。如果在main函数中我们想让第一个栈存储10个数据,第二个栈存储100数据。
  • 此时,宏的弊端就体现出来。因为N只能表示一个数值,为了满足上面的要求,将N定义为100。可是第一个栈只需要存储10个数据,这样就会造成空间上的浪费。
#define N 100//静态的栈
template<class T>
class Stack
{
private:T a[N];int top;
};int main()
{Stack<int> st1;  //10Stack<int> st2;  //100return 0;
}

为了解决上面出现的问题,我们可以使用非类型模版参数。

//静态的栈
template<class T, size_t N>
class Stack
{
private:T a[N];int top;
};int main()
{Stack<int, 10> st1;  //10Stack<int, 100> st2;  //100return 0;
}

  • 非类型模版参数还可以给缺省值,类似于函数参数。
  • 非类型模版参数是个常量,不可以修改。
//静态的栈
template<class T, size_t N = 10>
class Stack
{
public:void func(){N++;//不可以修改N}private:T a[N];int top;
};int main()
{Stack<int> st1;  //10Stack<int, 100> st2;  //100st1.func();//会报错return 0;
}

  • C++20之前的版本只允许整型类型做非类型模版参数。
  • C++20之后的版本支持所有内置类型做非类型模版参数,但是不支持自定义类型参数做非类型模版参数
template<double X, string str>
class Unkonwn
{};int main()
{Unkonwn<1.1, "xxxxx"> un;return 0;
}

1.2  array容器

非类型模版参数既然可以解决一些场景下的问题,在STL中的容器有没有使用的呢?array容器就是用非类型模版参数。相当于一个定长数组,进行了封装。

#include <array>
int main()
{array<int, 10> aa1;cout << sizeof(aa1) << endl;return 0;
}

相比于之前的数组,array容器有什么优势呢?

  • 之前的定长数组,检查越界方面使用的是抽查机制,即检查超出数组下标一两位内存空间是否发生改变,如果超出数组下标太多,可能检查不到,并且检查的成本很大。只有你进行写的操作可以检查出来,如果进行读操作,打印出来时,无法检查出来。
  • array容器是自定义类型,下标方括号访问是运算符重载,可以对方括号内的参数进行限制,不管是写还是读操作,都能检查出来。
#include <array>
int main()
{//严格的越界检查array<int, 10> aa1;aa1[10] = 10;cout << aa1[11] << endl;int aa2[10];aa2[10] = 10; //大多数编译器检查的出来aa2[14] = 10; //一般的编译器检查不出来,除非是新版的cout << aa2[15] << endl; //检查不出来return 0;
}

2. 模版的特化

2.1 概念

通常情况下,使用模版可以实现一些与类型无关的代码,但对于一些特殊类型的肯呢个会得到一些错误的结果,需要进行特殊处理。如下面的场景,写一个用来进行小于比较的函数模版。

class Date
{
public:Date(int year = 1900, int month = 1, int day = 1):_year(year),_month(month),_day(day){}bool operator<(const Date& d) const{if (_year < d._year)return true;elseif (_year == d._year)if (_month < d._month)return true;elseif (_month == d._month)return _day < d._day;return false;}
private:int _year;int _month;int _day;
};// 函数模板 -- 参数匹配
template<class T>
bool Less(T left, T right)
{return left < right;
}int main()
{cout << Less(1, 2) << endl;// 可以比较Date d1(2024, 7, 8);Date d2(2024, 7, 5);cout << Less(d1, d2) << endl; // 可以比较Date* p1 = &d1;Date* p2 = &d2;cout << Less(p1, p2) << endl; // 可以比较,但是结果错误return 0;
}

运行结果如下:

如上述代码所示,Less函数使用模版后对内置类型和自定义类型都可以进行比较操作,但是Date*日期指针类型返回的结果是跟Date类型结果不同,因为p1表示d1日期类变量的地址,p2表示d2日期类变量的地址,只有对p1和p2进行解引用时,才是指向d1和d2两个变量的内容,所以指针相关类型比较特殊。

在原模版类的基础上,针对特殊类型所进行特殊的实现方式。模版特化中分为函数模版特化类模版特化

2.2 函数模版特化

函数模版特化要求:

  1. 必须要先有一个基础的函数模版。
  2. 关键字template后面接一对空的尖括号<>。
  3. 函数名后跟一对尖括号,尖括号中指定需要特化的类型。
  4. 函数形参必须要和原模版函数的基础参数类型完全相同,需要注意的是,如果不同编译器可能会报一些奇怪的错误

一开始用Less函数举例子,指出对于指针类型无法比较,我们可以使用函数模版来解决。

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()
{cout << Less(1, 2) << endl;Date d1(2024, 7, 8);Date d2(2024, 7, 5);cout << Less(d1, d2) << endl; Date* p1 = &d1;Date* p2 = &d2;cout << Less(p1, p2) << endl; //调用特化函数模版,进行比较return 0;
}

运行结果如下:

不过使用模版函数会有许多坑。我们一般对函数使用模版时,函数参数会对模版使用引用,如果这个函数不进行修改操作,会在前面加上const修饰,防止该变量被修改。

  • 如下面代码所示,一般人写函数模版的特化,会写成第一种。
  • 这是错误的,因为T此时是Date*指针类型,const T& left中的const修饰的是left,且left本身存储的是变量的地址,所以是指针变量本身不能改变。如果写成第一种const加在Date*前,修饰的是指针指向的内容,跟原函数模版的函数参数类型不同,编译时会报错。
  • 第二种函数模版的特化才是正确的写法,const放在*符号之后,表示修饰left存储变量的地址。
// 函数模板 -- 参数匹配
template<class T>
bool Less(const T& left, const T& right)
{return left < right;
}//1.
template<>
bool Less<Date*>(const Date* & left, const Date* & right)
{return *left < *right;
}//2.
template<>
bool Less<Date*>(Date* const & left, Date* const & right)
{return *left < *right;
}

2.3 类模版特化

类模版的特化分为全特化偏特化

2.3.1 全特化

全特化即是将模版参数列表中所有的参数都确定化。其中的格式跟函数模版特化类似

template<class T1, class T2>
class Data
{
public:Data() { cout << "Data<T1, T2> -原模版" << endl; }
private:T1 _d1;T2 _d2;
};template<>
class Data<int, char>
{
public:Data() { cout << "Data<int, char> -全特化" << endl; }
};void Test1()
{Data<int, double> d1; //走第一个构造函数Data<int, char> d2; //走特化的构造函数
}

运行结果如下:

2.3.2 偏特化

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

  • 部分特化:将模版参数表中的一部分参数特化。
//特化第二个参数int
template<class T1>
class Data<T1, int>
{
public:Data(){cout << "Data<T1, int> -偏特化" << endl;}
};

  • 进一步限制参数类型。偏特化并不仅仅是指特化部分参数,而是针对模版参数更进一步的条件限制所设计出来的一个特化版本。
// 限定模版类型
template<typename T1, typename T2>
class Data<T1*, T2*>
{
public:Data(){cout << "Data<T1*, T2*> -偏特化" << endl;}
};void Test2()
{Data<int, double> d1;Data<int, char> d2;Data<int, int> d3;Data<int*, double*> d4;Data<int**, char*> d5;
}

运行结果如下:

再看下面的代码,先对第二种偏特化的构造函数做一些修改,其中typeid(T1).name( )是获取T1的类型名称,下面一条也是。

// 限定模版类型
template<typename T1, typename T2>
class Data<T1*, T2*>
{
public:Data(){cout << typeid(T1).name() << endl;cout << typeid(T2).name() << endl;cout << "Data<T1*, T2*> -偏特化" << endl;}
};void Test3()
{Data<int*, double*> d4;Data<int**, char*> d5;
}

运行结果如下,T1和T2分表是原来*符号前的类型,而不是原来的指针类型。

3.模版的编译分离

3.1 什么是分离编译

一个程序(项目)有若干个源文件共同实现,而每个源文件单独编译生成目标文件,最后将所有目标文件链接起来形成单一的可执行文件的过程称为分离编译模式。

3.2 模版的分离编译

下面的代码是模版函数和普通函数分离编译的代码,看看运行的结果如何。建立两个文件,分别是Func.h和Func.cpp。

  • Func.h文件存放函数的声明。
//Func.h//模版函数
template<class T>
T Add(const T& left, const T& right);//普通函数
void func();
  • Func.cpp文件存放函数的定义。
//Func.cpp
#include "Func.h"
//模版函数
template<class T>
T Add(const T& left, const T& right)
{cout << "T Add(const T& left, const T& right)" << endl;return left + right;
}//普通函数
void func()
{cout << "void func()" << endl;
}

写两个测试函数,运行程序看结果。

//Test.cpp
#include "Func.h"
void test1()
{Add(1, 2);     Add(1.0, 2.0); 
}void test2()
{func();
}int main()
{test1();test2();return 0;
}

运行test1函数,报连接错误,说有无法解析的外部符号。

运行test2函数,结果如下,没有问题。

普通函数分离编译可以正常运行,模版函数分离编译后运行会报链接错误,这是为什么呢?

  • C/C++程序要运行起来,都要经过这几个步骤:预处理—>编译—>汇编—>链接
  • 在预处理阶段,所有的.h文件都要在包含的位置展开。在编译过程中,将Func.cpp和Test.cpp文件编译成目标文件,分别是Func.o和Test.o。
  • 并且会生成一个符号表,符号表中有函数名经过不同平台规则的变化成新的名称,并且会在函数的定义中,存放函数地址,方便链接。
  • 但是模版函数在定义的部分,不知道要使用什么类型实例化,就不会将Add函数的地址存放在符号表中,那么就会出现上面报的连接错误,无法解析的符号。

 

 

3.3 解决方法

有两种解决方法:

  1. 将声明和定义放在同一个.hpp或者.h文件中
  2. 在模版函数定义的位置显示实例化。

如下面的代码所示,不过这种方法很少用。因为当你使用到这个函数其他类型的话,还需要再添加,十分麻烦。

#include "Func.h"template<class T>
T Add(const T& left, const T& right)
{cout << "T Add(const T& left, const T& right)" << endl;return left + right;
}//显示实例化
template
int Add(const int& left, const int& right);template
double Add(const double& left, const double& right);

4. 模版总结

模版的优点:

  1. 模版复用代码,节省资源,提高开发效率。
  2. 增强代码的灵活性。

缺点:

  1. 模版会导致代码膨胀问题,使得编译时间变长。
  2. 出现模版编译错误时,错误信息非常凌乱,难以定位错误进行纠正。


总结

通过这篇文章,对于模版的使用有了更深入的了解,如果还有某些地方不够熟悉,可以自己动手敲敲代码。

创作不易,希望这篇文章能给你带来启发和帮助,如果喜欢这篇文章,请留下你的三连,你的支持的我最大的动力!!!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/42447.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

包/final/权限修饰符/代码块

包package 1、包的作用 包用来管理不同的类。 2、包名 包名要全部小写&#xff0c;一般是域名反写&#xff0c;如com.liu。在Java中&#xff0c;java解释器会将package中的.解释为目录分隔符/&#xff0c;也就是说该文件的目录结构为&#xff1a;...com/liu/... 3、全类名…

1.pwn的汇编基础(提及第一个溢出:整数溢出)

汇编掌握程度 能看懂就行&#xff0c;绝大多数情况不需要真正的编程(shellcode题除外) 其实有时候也不需要读汇编&#xff0c;ida F5 通常都是分析gadget&#xff0c;知道怎么用&#xff0c; 调试程序也不需要分析每一条汇编指令&#xff0c;单步执行然后查看寄存器状态即可 但…

实现多数相加,但是传的参不固定

一、情景 一般实现的加法和减法等简单的相加减函数的话。一般都是写好固定传的参数。比如&#xff1a; function add(a,b) {return a b;} 这是固定的传入俩个&#xff0c;如果是三个呢&#xff0c;有人说当然好办&#xff01; 这样写不就行了&#xff01; function add(a…

vue中自定义设置多语言(包括使用vue-i18n),并且运行js脚本自动生成多语言文件

在项目中需要进行多个国家语言的切换时&#xff0c;可以用到下面方法其中一个 一、自定义设置多语言 方法一: 可以自己编写一个设置多语言文件 在项目新建js文件&#xff0c;命名为&#xff1a;language.js&#xff0c;代码如下 // language.js 文档 let languagePage {CN…

聊一下Maven打包的问题(jar要发布)

文章目录 一、问题和现象二、解决方法&#xff08;1&#xff09;方法一、maven-jar-pluginmaven-dependency-plugin&#xff08;2&#xff09;方法二、maven-assembly-plugin 一、问题和现象 现在的开发一直都是用spring boot&#xff0c;突然有一天&#xff0c;要自己开发一个…

Django之项目开发(二)

目录 一、安装和使用uWSGI 1.1、安装 1.2、配置文件 1.3、启动与停止uwsgi 二、安装nginx 三、Nginx 配置uWSGI 四、Nginx配置静态文件 五、Nginx配置负载均衡 一、安装和使用uWSGI uWSGI 是一个 Web 服务器,可以用来部署 Python Web 应用。它是一个高性能的通用的 We…

味蕾与理解:应对自闭症儿童挑食的策略与理解

在星贝育园自闭症康复学校&#xff0c;我们深知饮食习惯对孩子们的成长至关重要&#xff0c;而自闭症儿童的挑食问题往往比同龄儿童更为突出&#xff0c;给家长和照顾者带来了额外的挑战。今天&#xff0c;作为这里的老师&#xff0c;我想与大家分享一些应对自闭症儿童挑食的策…

(南京观海微电子)——电阻应用及选取

什么是电阻&#xff1f; 电阻是描述导体导电性能的物理量&#xff0c;用R表示。 电阻由导体两端的电压U与通过导体的电流I的比值来定义&#xff0c;即&#xff1a; 所以&#xff0c;当导体两端的电压一定时&#xff0c;电阻愈大&#xff0c;通过的电流就愈小&#xff1b;反之&…

鸿蒙应用实践:利用扣子API开发起床文案生成器

前言 扣子是一个新一代 AI 应用开发平台&#xff0c;无需编程基础即可快速搭建基于大模型的 Bot&#xff0c;并发布到各个渠道。平台优势包括无限拓展的能力集&#xff08;内置和自定义插件&#xff09;、丰富的数据源&#xff08;支持多种数据格式和上传方式&#xff09;、持…

[Unity入门01] Unity基本操作

参考的傅老师的教程学了一下Unity的基础操作&#xff1a; [傅老師/Unity教學] Unity3D基礎入門 [華梵大學] 遊戲引擎應用基礎(Unity版本) Class#01 移动&#xff1a;鼠标中键旋转&#xff1a;鼠标右键放大&#xff1a;鼠标滚轮飞行模式&#xff1a;右键WASDQEFocus模式&…

算法设计与分析 实验5 并查集法求图论桥问题

目录 一、实验目的 二、问题描述 三、实验要求 四、实验内容 &#xff08;一&#xff09;基准算法 &#xff08;二&#xff09;高效算法 五、实验结论 一、实验目的 1. 掌握图的连通性。 2. 掌握并查集的基本原理和应用。 二、问题描述 在图论中&#xff0c;一条边被称…

基于Android Studio订餐管理项目

目录 项目介绍 图片展示 运行环境 获取方式 项目介绍 能够实现登录&#xff0c;注册、首页、订餐、购物车&#xff0c;我的。 用户注册后&#xff0c;登陆客户端即可完成订餐、浏览菜谱等功能&#xff0c;点餐&#xff0c;加入购物车&#xff0c;结算&#xff0c;以及删减…

【学习笔记】操作系统--万字长文

计算机操作系统 文章目录 计算机操作系统引言 操作系统基本概念第一章 引论目标和作用操作系统发展历程单道批处理系统多道批处理系统分时系统实时系统 基本特征并发共享虚拟异步性&#xff08;不确定性&#xff09; 操作系统主要功能处理机管理内存管理设备管理文件管理 第二章…

工程化:Commitlint / 规范化Git提交消息格式

一、理解Commitlint Commitlint是一个用于规范化Git提交消息格式的工具。它基于Node.js&#xff0c;通过一系列的规则来检查Git提交信息的格式&#xff0c;确保它们遵循预定义的标准。 1.1、Commitlint的核心功能 代码规则检查&#xff1a;Commitlint基于代码规则进行检查&a…

汇聚荣拼多多电商的技巧有哪些?

在电商平台上&#xff0c;汇聚荣拼多多以其独特的商业模式和创新的营销策略吸引了大量消费者。那么&#xff0c;如何在这样一个竞争激烈的平台上脱颖而出&#xff0c;成为销售佼佼者呢?本文将深入探讨汇聚荣拼多多电商的成功技巧。 一、精准定位目标客户群体 首先&#xff0c;…

绝区肆--2024 年AI安全状况

前言 随着人工智能系统变得越来越强大和普及&#xff0c;与之相关的安全问题也越来越多。让我们来看看 2024 年人工智能安全的现状——评估威胁、分析漏洞、审查有前景的防御策略&#xff0c;并推测这一关键领域的未来可能如何。 主要的人工智能安全威胁 人工智能系统和应用程…

C-11 三角剖分的调研

C-11 三角剖分算法 三角剖分就是将输入的多边形&#xff0c;分割成一系列互不重叠的三角形&#xff0c;其重要性就在这不多赘述。这个是一个别人总结的链接&#xff1a;http://vterrain.org/Implementation/Libs/triangulate.html 图片链接&#xff1a;http://www-cgrl.cs.m…

基于CentOS Stream 9平台搭建MinIO以及开机自启

1. 官网 https://min.io/download?licenseagpl&platformlinux 1.1 下载二进制包 指定目录下载 cd /opt/coisini/ wget https://dl.min.io/server/minio/release/linux-amd64/minio1.2 文件赋权 chmod x /opt/coisini/minio1.3 创建Minio存储数据目录&#xff1a; mkdi…

springboot校园安全通事件报告小程序-计算机毕业设计源码02445

Springboot 校园安全通事件报告小程序系统 摘 要 随着中国经济的飞速增长&#xff0c;消费者的智能化水平不断提高&#xff0c;许多智能手机和相关的软件正在得到更多的关注和支持。其中&#xff0c;校园安全通事件报告小程序系统更是深得消费者的喜爱&#xff0c;它的出现极大…

《Programming from the Ground Up》阅读笔记:p19-p48

《Programming from the Ground Up》学习第2天&#xff0c;p19-p48总结&#xff0c;总计30页。 一、技术总结 1.object file p20, An object file is code that is in the machine’s language, but has not been completely put together。 之前在很多地方都看到object fi…