C++(模板进阶)


目录

前言:

本章学习目标:

1.非类型模版参数 

1.1使用方法 

1.2注意事项

1.3 实际引用

2.模版特化 

 2.1概念

2.2函数模板特化

 2.3类模板特化

2.3.1全特化

2.3.2偏特化 

 3.模版分离编译

​编辑 3.1失败原因

​编辑 3.2解决方案

4 总结 



前言:

本章节是在学习完STL之后,对高阶模版进行的总结,模板给泛型编程注入了灵魂,模板提高了程序的灵活性,模板包括:非类型模版参数、全特化、偏特化等,同时本文还会对模板声明和定义不能分离的问题做出介绍。

本章学习目标:

1. 非类型模板参数
2. 类模板的特化
3. 模板的分离编译

1.非类型模版参数 

模板参数分类 :类型形参与非类型形参。
类型形参即:    出现在模板参数列表中,跟在class或者typename之类的参数类型名称。
非类型形参:    就是用一个常量作为类(函数)模板的一个参数,在类(函数)模板中可将该参数当成常量来使用。

1.1使用方法 

非类型模版参数 既然是使用常量作为参数,那么浮点数、类对象以及字符串是不允许作为非类型模版参数的,   非类型模版参数必须在编译期就能确认结果。

利用非类型模版参数创建一个可以自由调整大小的整形数组代码如下:

#include <assert.h>
using namespace std;template <size_t N>
class arr
{
public:int& operator[](size_t pos){assert(pos >= 0 && pos < N);return _arr[pos];}size_t size() const{return N;}private:int _arr[N];
};
int main()
{arr<5> a1;arr<10> a2;arr<15> a3;cout <<a1.size() << endl;cout << a2.size() << endl;cout << a3.size() << endl;return 0;
}

定义一个模板类型的静态数组

template <class T ,size_t N>
class arr
{
public:T& operator[](size_t pos){assert(pos >= 0 && pos < N);return _arr[pos];}size_t size() const{return N;}private:int _arr[N];
};
int main()
{arr<int,5> a1;arr<double,10> a2;arr<char,15> a3;cout <<a1.size()<< typeid(a1).name() << endl;cout << a2.size() << typeid(a2).name()<< endl;cout << a3.size() << typeid(a3).name()<< endl;return 0;
}

1.2注意事项

 非类型模板参数要求类型为 整型家族,其他类型是不被编译器通过的,如果我们使用非整形的家族成员就会报错,代码如下:

//浮点型,非标准
template<class T, double N>
class arr 
{/*……*/ 
};

 整形家族:char     short      int      bool         long           longlong

1.3 实际引用

 在 C++11 标准中,引入了一个新容器 array,它就使用了 非类型模板参数,为一个真正意义上的 泛型数组,这个数组是用来对标传统数组的。

注意: 部分老编译器可能不支持使用此容器

新引入arry非类型模版参数的代码 如下:

#include <iostream>
#include <cassert>
#include <array>  //引入头文件using namespace std;int main()
{int arrOld[10] = { 0 };	//传统数组array<int, 10> arrNew;	//新标准中的数组//与传统数组一样,新数组并没有进行初始化//新数组对于越界读、写检查更为严格 优点arrOld[15];	//老数组越界读,未报错arrNew[15];	//新数组则会报错arrOld[12] = 0;	//老数组越界写,不报错,出现严重的内存问题arrNew[12] = 10;	//新数组严格检查return 0;
}

array 是泛型编程思想中的产物,支持了许多 STL 容器的功能,比如 迭代器 和 运算符重载 等实用功能,最主要的改进是 严格检查越界行为。

需要提醒的是:arry能做到严格的越界检查 得益于 []的重载,对下标进行了严格检查。

2.模版特化 

 2.1概念

通常情况下,使用模板可以实现一些与类型无关的代码,但对于一些特殊类型的可能会得到一些错误的结,需要特殊处理,比如:使用 日期类对象指针 构建优先级队列后,若不编写对应的仿函数,则比较结果会变为未定义 代码如下:

// 函数模板 -- 参数匹配
template<class T>
bool Less(T left, T right)
{return left < right;
}
int main()
{cout << Less(1, 2) << endl; // 可以比较,结果正确Date d1(2022, 7, 7);Date d2(2022, 7, 8);cout << Less(d1, d2) << endl; // 可以比较,结果正确Date* p1 = &d1;Date* p2 = &d2;cout << Less(p1, p2) << endl; // 可以比较,结果错误return 0;
}

造成每次结果不一样是因为 我们每次都日期类对象比较的时候 系统每次分配的地址都是随机的,我们对地址进行比较是不符合实际情况的。 

此时,就 需要对模板进行特化。即:在原模板类的基础上,针对特殊类型所进行特殊化的实现方式。模板特 化中分为 函数模板特化 类模板特化

2.2函数模板特化

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

上述日期进行比较的函数模版进行特化之后,代码如下:

// 函数模板 -- 参数匹配
template<class T>
bool Less(T left, T right)
{return left < right;
}
// 对Less函数模板进行特化
template<>
bool Less<Date*>(Date* left, Date* right)
{return *left < *right;
}
int main()
{cout << Less(1, 2) << endl;Date d1(2022, 7, 7);Date d2(2022, 7, 8);cout << Less(d1, d2) << endl;Date* p1 = &d1;Date* p2 = &d2;cout << Less(p1, p2) << endl; // 调用特化之后的版本,而不走模板生成了return 0;
}

 2.3类模板特化

 模板特化主要用在类模板中,它可以在泛型思想之上解决大部分特殊问题,并且类模板特化还可以分为:全特化和偏特化,适用于不同场景

后面用的日期类举例比较多,先把日期类放出来

class Date
{
public:Date(int year = 1970, int month = 1, int day = 1): _year(year), _month(month), _day(day){}bool operator<(const Date& d)const{return (_year < d._year) ||(_year == d._year && _month < d._month) ||(_year == d._year && _month == d._month && _day < d._day);}bool operator>(const Date& d)const{return (_year > d._year) ||(_year == d._year && _month > d._month) ||(_year == d._year && _month == d._month && _day > d._day);}private:int _year;int _month;int _day;
};

2.3.1全特化

 全特化指 将所有的模板参数特化为具体类型,将模板全特化后,调用时,会优先选择更为匹配的模板类

//原模板
template<class T1, class T2>
class Test
{
public:Test(const T1& t1, const T2& t2):_t1(t1),_t2(t2){cout << "template<class T1, class T2>" << endl;}private:T1 _t1;T2 _t2;
};//全特化后的模板
template<>
class Test<int, char>
{
public:Test(const int& t1, const char& t2):_t1(t1), _t2(t2){cout << "template<>" << endl;}private:int _t1;char _t2;
};int main()
{Test<int, int> T1(1, 2);Test<int, char> T2(20, 'c');return 0;
}

对模板进行全特化处理后,实际调用时,会优先选择已经特化并且类型符合的模板。

2.3.2偏特化 

 偏特化,指 将泛型范围进一步限制,可以限制为某种类型的指针,也可以限制为具体类型

//原模板---两个模板参数
template<class T1, class T2>
class Test
{
public:Test(){cout << "class Test" << endl;}
};//偏特化之一:限制为某种类型
template<class T1>
class Test<T1, int>
{
public:Test(){cout << "class Test<T, int>" << endl;}
};//偏特化之二:限制为不同的具体类型
template<class T>
class Test<T*, T*>
{
public:Test(){cout << "class Test<T*, T*>" << endl;}
};int main()
{Test<double, double> t1;Test<char, int> t2;Test<Date*, Date*> t3;return 0;
}

 

偏特化(尤其是限制为某种类型)在 泛型思想 和 特殊情况 之间做了折中处理,使得 限制范围式的偏特化 也可以实现 泛型

  • 比如偏特化为 T*,那么传 int*char*Date* 都是可行的

应用实例:

 有如下专门用来按照小于比较的类模板 :Less

#include<vector>
#include <algorithm>
template<class T>
struct Less
{bool operator()(const T& x, const T& y) const{return x < y;}
};
int main()
{Date d1(2022, 7, 7);Date d2(2022, 7, 6);Date d3(2022, 7, 8);vector<Date> v1;v1.push_back(d1);
v1.push_back(d2);v1.push_back(d3);// 可以直接排序,结果是日期升序sort(v1.begin(), v1.end(), Less<Date>());vector<Date*> v2;v2.push_back(&d1);v2.push_back(&d2);v2.push_back(&d3);// 可以直接排序,结果错误日期还不是升序,而v2中放的地址是升序// 此处需要在排序过程中,让sort比较v2中存放地址指向的日期对象// 但是走Less模板,sort在排序时实际比较的是v2中指针的地址,因此无法达到预期sort(v2.begin(), v2.end(), Less<Date*>());return 0;
}
通过观察上述程序的结果发现,对于日期对象可以直接排序,并且结果是正确的。但是如果待排序元素是指 针,结果就不一定正确。因为: sort 最终按照 Less模板中方式比较,所以只会比较指针,而不是比较指针指 向空间中内容,此时可以使用类版本特化来处理上述问题
// 对Less类模板按照指针方式特化
template<>
struct Less<Date*>
{bool operator()(Date* x, Date* y) const{return *x < *y;}
};

 3.模版分离编译

 

早在 模板初阶 中,我们就已经知道了 模板不能进行分离编译,会引发链接问题

 3.1失败原因

 声明与定义分离后,在进行链接时,无法在符号表中找到目标地址进行跳转,因此链接错误

下面是 模板声明与定义写在同一个文件中时,具体的汇编代码执行步骤

test.h 

#pragma once//声明
template<class T>
T add(const T x, const T y);//定义
template<class T>
T add(const T x, const T y)
{return x + y;
}

 main.cpp

#include <iostream>
#include "Test.h"using namespace std;int main()
{add(1, 2);return 0;
}

 

 声明与定义在同一个文件中时,可以直接找到函数的地址

编译器 生成可执行文件的四个步骤:

  1. 预处理:头文件展开、宏替换、条件编译、删除注释,生成纯净的C代码
  2. 编译:语法 / 词法 / 语义 分析、符号汇总,生成汇编代码
  3. 汇编:生成符号表,生成二进制指令
  4. 链接:合并段表,将符号表进行合并和重定位,生成可执行程序

当模板的 声明 与 定义 分离时,因为是 【泛型】,所以编译器无法确定函数原型,即 无法生成函数,也就无法获得函数地址,在符号表中进行函数链接时,必然失败 

 3.2解决方案

 解决方法有两种:

  1. 在函数定义时进行模板特化,编译时生成地址以进行链接
  2. 模板的声明和定义不要分离,直接写在同一个文件中
//定义
//解决方法一:模板特化(不推荐,如果类型多的话,需要特化很多份)
template<>
int add(const int x, const int y)
{return x + y;
}

 

//定义
//解决方法二:声明和定义写在同一个文件中
template<class T>
T add(const T x, const T y)
{return x + y;
}

这也就解释了为什么涉及 模板 的类,其中的函数声明和定义会写在同一个文件中 (.h),著名的 STL 库中的代码的声明和定义都是在一个 .h 文件中

为了让别人一眼就看出来头文件中包含了 声明 与 定义,可以将头文件后缀改为 .hpp,著名的 Boost 库中就有这样的命名方式 

 

4 总结 

 

模板是 STL 的基础支撑,假若没有模板、没有泛型编程思想,那么恐怕 "STL" 会变得非常大

模板的优点:

模板复用了代码,节省资源,更快的迭代开发,C++的标准模板库(STL)因此而产生
增强了代码的灵活性
模板的缺点:

模板会导致代码膨胀问题,也会导致编译时间变长
出现模板编译错误时,错误信息非常凌乱,不易定位错误位置
总之,模板 是一把双刃剑,既有优点,也有缺点,只有把它用好了,才能使代码 更灵活、更优雅

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

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

相关文章

【C++】类和对象——构造函数和析构函数

今天要学习两个特殊的函数&#xff0c;分别是构造函数和析构函数&#xff0c;它们究竟有什么用呢&#xff1f; 比如说&#xff0c;我们先写一个简单的日期的类 class Date { public:void Init() {_year 1;_month 1;_day 1;}void Print() {cout << _year << &qu…

Sentinel 分布式系统

Sentinel 是一种分布式系统的流量防卫兵和熔断器&#xff0c;由阿里巴巴开发并开源。它的主要目标是保护分布式系统中的稳定性和可用性&#xff0c;防止因高并发或异常流量而导致的系统崩溃。下面是 Sentinel 的原理和使用教程的概要&#xff1a; Sentinel 的原理&#xff1a;…

如何去开发一个springboot starter

如何去开发一个springboot starter 我们在平时用 Java 开发的时候&#xff0c;在 pom.xml 文件中引入一个依赖就可以很方便的使用了&#xff0c;但是你们知道这是如何实现的吗。 现在我们就来解决这一个问题&#xff01; 创建 SpringBoot 项目 首先我们要做的就是把你想要给别…

css3

基础 <!DOCTYPE html> <html><head><meta charset"utf-8"><title>style</title><!-- link&#xff08;外部样式&#xff09;和style&#xff08;内部样式&#xff09;优先级相同&#xff0c;重复写会覆盖 --><link re…

面试题-9

1.如何封装一个组件 1.使用Vue.extend()创建一个组件 2.使用Vue.components()方法注册组件 3.如果子组件需要数据,可以在props中接收定义 4.子组件修改好数据,要把数据传递给父组件&#xff0c;可以用emit()方法 原则: 把功能拆开 尽量让组件原子化,一个组件做一件事情 …

centos7安装MySQL—以MySQL5.7.30为例

centos7安装MySQL—以MySQL5.7.30为例 本文以MySQL5.7.30为例。 官网下载 进入MySQL官网&#xff1a;https://www.mysql.com/ 点击DOWNLOADS 点击链接&#xff1b; 点击如上链接&#xff1a; 选择对应版本&#xff1a; 点击下载。 安装 将下载后的安装包上传到/usr/local下…

CTF靶场搭建及Web赛题制作与终端docker环境部署

♥ ♡ ♥ ♡ ♥ ♡ ♥ ♡ ♥ ♡ ♥ ♡ ♥ ♡ ♥ ♡ ♥ ♡ ♥ ♡ ♥ ♡ ♥ ♡ ♥ ♡ ♥ ♡ ♥ ♡ ♥ ♡ ♥ ♡ ♥ ♡ ♥ ♡ ♥ ♡ ♥ ♡ ♥ ♡ ♥ ♡ ♥ 写在前面 ╔═══════════════════════════════════════════════════…

使用ChatGPT创建Makefile构建系统:使用Make运行Docker

使用ChatGPT创建Makefile构建系统&#xff1a;使用Make运行Docker 芯语芯愿&#xff08;知乎/纷传/CSDN/&#xff09;&#xff1b;小石头的芯语芯愿&#xff08;微信公众号&#xff09; 开发高效现代的构建系统对于满足开发周期需求至关重要。原先&#xff0c;嵌入式开发者一…

Unity 场景烘培 ——LensFlare镜头光晕(三)

提示&#xff1a;文章有错误的地方&#xff0c;还望诸位大神指出&#xff01; 文章目录 前言一、镜头光晕 (Lens Flares)是什么&#xff1f;二、使用Lens Flares组件总结 前言 一般情况下都会忽略的东西&#xff0c;镜头光晕。理论上不加镜头光晕&#xff0c;也不会有什么影响…

vue3的两个提示[Vue warn]: 关于组件渲染和函数外部使用

1. [Vue warn]: inject() can only be used inside setup() or functional components. 这个消息是提示我们&#xff0c;需要将引入的方法作为一个变量使用。以vue-store为例&#xff0c;如果我们按照如下的方式使用&#xff1a; import UseUserStore from ../../store/module…

数据治理之考评环节

考评的流程&#xff08;批处理&#xff09; 周期调度&#xff0c;每天一次&#xff1a;采集hive, hdfs元数据存放到mysql中的dga库的metainfo表手动通过管理页面补充辅助信息指标考评 读取要考评的表的元数据及辅助信息读取要考评的指标对每张表的每个指标逐个进行考评保存考评…

RabbitMQ快速入门(简单收发消息)

文章目录 前言一、数据隔离1.用户管理2.virtual host 二、控制台收发1.交换机2.队列3.绑定 三、编程式收发1.依赖和配置2.收发信息 总结 前言 1.了解数据隔离 2.RabbitMQ控制台收发信息 3.SpringBoot整合RabbitMQ收发信息 一、数据隔离 1.用户管理 点击Admin选项卡&#xff0…

mmdet全教程

官方给的文档一言难尽&#xff0c;网上的教程又没有从大纲到源码的完整解读&#xff0c;计划年后开个系列记录一下

依赖库:Ceres-solver-2.0.0安装

依赖库&#xff1a;Ceres-solver-2.0.0安装 前言安装ceres-solver-2.0.0验证 前言 Ceres Solver是谷歌开源的C非线性优化库&#xff0c;能够解决有约束或无约束条件下的非线性最小二乘问题。2010年之后大量的运用在谷歌的产品开发中&#xff0c;尤其在谷歌开源的cartographer中…

图像分类单张图片预测准确率达到百分之百

在图像分类任务中&#xff0c;针对单个图片得到100%的准确率是有可能但极其罕见的&#xff0c;并且不代表模型在整个测试集上也能达到100%的准确率。   针对单个图片获得100%准确率的情况可能包括以下几种情形&#xff1a; 图片本身特殊性: 如果测试集中的某张图片在训练集中…

【python基础(1)】变量和简单数据类型

文章目录 一. 变量的命名和使用二. 字符串1. 修改字符串的大小写2. 在字符串中使用变量3. 使用制表符或换行符来添加空白4. 删除空白 三. 数1. 整数2. 浮点数3. 整数和浮点数4. 数中的下划线5. 同时给多个变量赋值6. 常量 三. 注释四. Python之禅 一. 变量的命名和使用 变量规…

各种LLM数据集包括SFT数据集

各种LLM数据集包括SFT数据集 数集介绍和 hf上的名字对话数据生成方法交通领域数据集SFT 的解释数集介绍和 hf上的名字 通用预训练数据集 SFT datasets SFT 数据集 50万条中文ChatGPT指令Belle数据集:BelleGroup/train_0.5M_CN 100万条中文ChatGPT指令Belle数据集:BelleGrou…

C++学习 --stack

目录 1&#xff0c; 什么是stack 2&#xff0c; 创建stack 2-1&#xff0c; 标准数据类型 2-2&#xff0c; 自定义数据类型 2-3&#xff0c; 其他创建方式 3&#xff0c; 操作stack 3-1&#xff0c; 赋值 3-2&#xff0c; 插入元素(push) 3-3&#xff0c; 查询元素 3…