【C++】进阶模板

非类型模板参数

模板参数分类类型形参与非类型形参。

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

例如,先来搞一下静态数组:

//静态数组
namespace flash     //下面代码中的array会和std中的array冲突,因此加个命名空间
{template<class T, size_t N = 10>   //N代表的就是元素个数,而不是一个特定的类型,可以有缺省值。class array{private:T _a[N];};
}void test_template()
{flash::array<int, 10> a1;flash::array<double, 66>  a2;
}

这里需要注意以下两点:

  1. 浮点数、类对象以及字符串是不允许作为非类型模板参数的,只能是整形家族类型,而且传参时必须传常量。
  2. 非类型的模板参数必须在编译期就能确认结果。

在这里插入图片描述
array这个容器:一旦定义就固定大小,是一个静态数组,支持迭代器、[ ]、不支持插入(因为数组大小是固定的,不能扩容),没有构造函数,不能初始化。

这里就能看出这个容器还是不咋地的,因为我们学C时就已经有了数组这个东西,二者功能基本一样的,而且数组定义的时候还更简便一点。

array 的最大优点还是在于越界检查是非常屌的。

在这里插入图片描述

  • 对于a1而言:a1的[ ]为函数调用,就是重载的[ ]。其会在[ ]内部检查所访问的下标是否超过了其内部的size,那么如果越界了是一定能检查到的。但是普通的数组就不是了。
  • 对于a2而言:a2为指针,其检查是抽检。而且只针对越界写,越界读不做检查。

在这里插入图片描述

模板的特化

模板的特化也称为模板的特殊化。就是当模板参数传参时,有一种类型所执行的功能不是我们本来模板函数或模板类中所想要的功能时,就要对这个类型进行特殊化处理。
例如我们对Data类

class Date
{friend ostream& operator<<(ostream& out, const Date& date);
public:Date(int year = 2023, int month = 5, int day = 7){_year = year;_month = month;_day = day;}Date(const Date& date){cout << "Date(const Date& date)" << endl;_year = date._year;_month = date._month;_day = date._day;}void show() const;void show();Date& operator=(const Date& date);Date operator++(int);Date& operator++();int GetMonthDay(int year, int month);Date& operator+=(int day);Date operator+(int day);Date& operator-=(int day);Date operator-(int day);bool operator < (const Date& date);bool operator <= (const Date& date);bool operator > (const Date& date);bool operator >= (const Date& date);bool operator!=(const Date& date);bool operator==(const Date& date);int operator-(const Date& date);//ostream& operator<<(ostream& out) const;private:int _year;int _month;int _day;void Date::show() const
{cout << "void Date::show() const" << endl;cout << _year << "/" << _month << "/" << _day << endl;
}void Date::show()
{cout << "void Date::show()" << endl;cout << _year << "/" << _month << "/" << _day << endl;
}bool Date::operator==(const Date& date)
{return _year == date._year&& _month == date._month&& _day == date._day;
}int Date::GetMonthDay(int year, int month)
{int arr[13] = { 0, 31, 28, 31, 30,31,30,31,31,30,31,30,31 };if (month == 2 && (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)){return 29;}return arr[month];
}Date& Date::operator=(const Date& date)
{cout << "Date & Date::operator=(const Date & date)" << endl;if (!(*this == date)){_year = date._year;_month = date._month;_day = date._day;}return *this;
}Date& Date::operator+=(int day)
{if (day < 0){*this -= -day;}_day += day;while (_day > GetMonthDay(_year, _month)){_day -= GetMonthDay(_year, _month);_month++;if (_month == 13){_year++;_month = 1;}}return *this;
}Date Date::operator++(int)
{Date tmp = *this;*this += 1;//+=也是重载的函数,等会再讲return tmp;
}Date& Date::operator++()
{*this += 1;//+=也是重载的函数,等会再讲return *this;
}Date Date::operator+(int day)
{Date tmp = *this;tmp += day;return tmp;
}Date& Date::operator-=(int day)
{if (day < 0){*this += -day;}_day -= day;while (_day <= 0){--_month;if (_month == 0){_month = 12;--_year;}_day += GetMonthDay(_year, _month);}return *this;
}Date Date::operator-(int day)
{Date tmp = *this;tmp -= day;return tmp;
}bool Date::operator < (const Date& date)
{if (_year < date._year){return true;}else if (_year == date._year && _month < date._month){return true;}else if (_year == date._year && _month == date._month && _day < date. _day){return true;}return false;
}bool Date::operator <= (const Date& date)
{if (*this < date || *this == date)return true;return false;
}bool Date::operator > (const Date& date)
{if (!(*this <= date))return true;return false;
}bool Date::operator >= (const Date& date)
{if (!(*this < date))return true;return false;
}bool Date::operator!=(const Date& date)
{if (!(*this == date))return true;return false;
}int Date::operator-(const Date& date)
{int flag = 1;int n = 0;Date max = *this;Date min = date;if (max < min){max = date;min = *this;flag = -1;}while (min != max){++min;++n;}return n * flag;
}
};inline ostream& operator<<(ostream& out,const Date& date)
{out << date._year << "_" << date._month << "_" << date._day;return out;
}

这里写一个less模板函数,用来比较两个数据的大小。

// 函数模板 -- 参数匹配
template<class T>
bool Less(T left, T right)
{return left < right;
}
void Date_test()
{std::cout << Less(1, 2) << std::endl; // 可以比较,结果正确Date d1(2023, 11, 23);Date d2(2023, 11, 13);std::cout << Less(d1, d2) << std::endl; // 可以比较,结果正确Date* p1 = &d1;Date* p2 = &d2;std::cout << Less(p1, p2) << std::endl; // 不可以比较,结果不一定正确
}

这里想用Date来比较两个Date的大小,结果却失败了。因为这里实例化对象的时候实例化出来的是Date的对象,p1和p2比较的时候是纯指针的比较,并没有调用Date对象的 < 重载。所以就出问题了。

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

函数模板特化

函数模板的特化步骤:

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

一般情况下如果函数模板遇到不能处理或者处理有误的类型,为了实现简单通常都是将该函数直接给
出。

bool Less(Date* left, Date* right)
{
return *left < *right;
}

该种实现简单明了,代码的可读性高,容易书写,因为对于一些参数类型复杂的函数模板,特化时特别给出,因此函数模板不建议特化。

类模板特化

先定义如下类Less:

template<class T>
struct Less
{bool operator()(const T& x, const T& y) const{return x < y;}
};

当我们进行同样的比较的时候也会出现上面函数模板中的问题。

void test_classtemplate()
{Less<Date> Lessfunc1;Less<Date*> Lessfunc2;Date d1(2023, 11, 23);Date d2(2023, 11, 13);std::cout << Lessfunc1(d1, d2) << std::endl; //可以比较,结果正确Date* p1 = &d1;Date* p2 = &d2;std::cout << Lessfunc2(p1, p2) << std::endl; //不可以比较,结果不一定正确
}

想要解决的话也是特化就行。但是类模板的特化和函数模板的特化有些不一样。

template<>
struct Less<Date*>
{bool operator()(Date* x, Date* y){return *x < *y;}
};

通过观察上述程序的结果发现,对于日期对象可以直接比较大小,并且结果是正确的。但是如果待排序元素是指针,结果就不一定正确。因为:比较方式是最终按照Less模板中方式比较,所以只会比较指针,而不是比较指针指向空间中内容,此时可以使用类版本特化来处理上述问题。

类模板特化分类

类模板特化可分为 :

  • 全特化:全特化即是将模板参数列表中所有的参数都确定化。
  • 偏特化:任何针对模版参数进一步进行条件限制设计的特化版本。
//普通模板
template<class T1, class T2>
class Data
{
public:Data(){std::cout << "Data(T1, T2)" << std::endl;}
private:T1 _d1;T2 _d2;
};//全特化
template<>
class Data<int, char>
{
public:Data(){std::cout << "Data<int, char>" << std::endl;}
private:int _d1;char _d2;
};//偏特化1:部分偏特化
// 将第二个参数特化为int
template <class T1>
class Data<T1, int>
{
public:Data() {cout<<"Data<T1, int>" <<endl;}
private:T1 _d1;int _d2;
};
//这里只要第二个参数为int就走的是这个模板的特化。根特化不沾边的类型,就走的是原模版。//偏特化2:参数更进一步的限制
//偏特化并不仅仅是指特化部分参数,而是针对模板参数更进一步的条件限制所设计出来的一个特化版本。
//两个参数偏特化为指针类型
template <typename T1, typename T2>
class Data <T1*, T2*>
{
public:Data() {cout<<"Data<T1*, T2*>" <<endl;}
private:T1 _d1;T2 _d2;
};
//两个参数偏特化为引用类型
template <typename T1, typename T2>
class Data <T1&, T2&>
{
public:Data(){cout<<"Data<T1&, T2&>" <<endl;}
private:T1 _d1;T2 _d2;
};
//指针引用混着来
template <typename T1, typename T2>
class Data <T1&, T2*>
{
public:Data(){cout<<"Data<T1&, T2*>" <<endl;}
private:T1 _d1;T2 _d2;
};

这里体现的就是有更匹配的就走更匹配的,没有匹配的就将就下。

模板分离编译

首先解释一下什么是分离编译:

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

在之前模板初阶提到过,模板不要分离编译(声明和定义放在不同文件中),因为出错了很麻烦,这里用一下模拟实现的vector,将其push_back()函数进行声明定义分离。会发现出现了链接报错:

在这里插入图片描述
原因是:

一个vector.h
一个vector.cpp
一个test.cpp

cpp文件都引了h,当我们编译了之后,两个.cpp生成对应的.o文件。
就是 vector.o 和 test.o

在test.cpp中,引了vector.h,有test_template函数进行实例化,也就是里面flash::vector v;这条语句,将类模板进行了实例化。能够生成一份对应int的代码。此时.h中的构造、size、析构等函数也就实例化了。所以这些函数的地址在编译阶段就能够确定,那么test.o中是包含有这些成员函数的地址信息的,但是.h中只有push_back和insert的声明,故这两个函数的地址没法确定,只能确定函数名,所以test.o中缺少了这两个函数的函数地址信息。

在vector.cpp中,引了vector.h,而且.cpp文件中还有两个函数模板,但是该文件中没有任何的语句能够将其中的模板参数T实例化,那么push_back和insert 这两个函数也就没法实例化,也就是说编译器无法确定这两个函数的地址,vector.h中虽也有这两个函数的声明,但是都没什么用,函数地址无法确定,最终形成的符号表中只有函数名,没有对应的地址,但是编译器是能够编译通过的,因为编译器眼里还有最后链接的时也能确定函数的地址,所以最终形成的vector.o中也没有这两个函数的地址。

最终链接阶段,两个.o文件中都没有这两个函数的地址,所以这两个函数的地址都没法确定,而且我们还使用到了push_back,但是push_back并没有实例化,所以就出现了链接错误。

解决方法:

  1. 将声明和定义放到一个文件 “xxx.hpp” 里面或者xxx.h其实也是可以的。推荐使用这种。
  2. 模板定义的位置显式实例化。这种方法不实用,不推荐使用。

模板总结

【优点】

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

【缺陷】

  1. 模板会导致代码膨胀问题,也会导致编译时间变长 。
  2. 出现模板编译错误时,错误信息非常凌乱,不易定位错误。

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

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

相关文章

Android 14 Beta 1

Android 14的第一个 Beta 版&#xff0c;围绕隐私、安全、性能、开发人员生产力和用户定制等核心主题构建&#xff0c;同时继续改进平板电脑、可折叠设备等大屏幕设备的体验。我们一直在完善 Android 14 的功能和稳定性方面取得稳步进展&#xff0c;现在是时候向开发者和早期采…

论文笔记——BiFormer

Title: BiFormer: Vision Transformer with Bi-Level Routing AttentionPaper: https://arxiv.org/pdf/2303.08810.pdfCode: https://github.com/rayleizhu/BiFormer 一、前言 众所周知&#xff0c;Transformer相比于CNNs的一大核心优势便是借助自注意力机制的优势捕捉长距离…

基于STM32的外部中断(EXTI)在嵌入式系统中的应用

外部中断&#xff08;External Interrupt&#xff0c;EXTI&#xff09;是STM32嵌入式系统中常见且重要的功能之一。它允许外部事件&#xff08;例如按键按下、传感器触发等&#xff09;通过适当的引脚触发中断&#xff0c;从而应用于各种嵌入式系统中。在STM32微控制器中&#…

Vulkan渲染引擎开发教程 一、开发环境搭建

一 安装 Vulkan SDK Vulkan SDK 就是我们要搞的图形接口 首先到官网下载SDK并安装 https://vulkan.lunarg.com/sdk/home 二 安装 GLFW 窗口库 GLFW是个跨平台的小型窗口库&#xff0c;也就是显示窗口&#xff0c;图形的载体 去主页下载并安装&#xff0c;https://www.glfw.…

【自然语言处理(NLP)实战】LSTM网络实现中文文本情感分析(手把手与教学超详细)

目录 引言&#xff1a; 1.所有文件展示&#xff1a; 1.中文停用词数据&#xff08;hit_stopwords.txt)来源于&#xff1a; 2.其中data数据集为chinese_text_cnn-master.zip提取出的文件。点击链接进入github&#xff0c;点击Code、Download ZIP即可下载。 2.安装依赖库&am…

Spring 事务和事务传播机制

一、再谈事务 到这里 JavaEE 的学习基本是已经接近了尾声&#xff0c;相信大家对事务已然有了一些理解。当然这里我们还是简单的说明一下&#xff1a; 事务就是将一组操作封装成一个执行单元&#xff0c;要么全部成功&#xff0c;要么全部失败。比较典型的应用场景是转账&…

Vite - 配置 - 文件路径别名的配置

为什么要配置别名 别名的配置&#xff0c;主要作用是为了缩短代码中的导入路径。例如有如下的项目目录&#xff1a; project-name| -- src| -- a| --b| --c| --d| --e| -- abc.png| -- index.html| -- main.js如果想在 main.js 文件中使用 abc.png ,则使用的路径是 &#xff1…

二元分类模型评估方法

文章目录 前言一、混淆矩阵二、准确率三、精确率&召回率四、F1分数五、ROC 曲线六、AUC&#xff08;曲线下面积&#xff09;七、P-R曲线类别不平衡问题中如何选择PR与ROC 八、 Python 实现代码混淆矩阵、命中率、覆盖率、F1值ROC曲线、AUC面积 指标 公式 意义 真正例 (TP)被…

DNS1(Bind软件)

名词解释 1、DNS&#xff08;Domain Name System&#xff09; DNS即域名系统&#xff0c;它是一个分层的分布式数据库&#xff0c;存储着IP地址与主机名的映射 2、域和域名 域为一个标签&#xff0c;而有多个标签域构成的称为域名。例如hostname.example.com&#xff0c;其…

gRPC 的原理 介绍带你从头了解gRPC

gRPC 的原理 什么是gRPC gRPC的官方介绍是&#xff1a;gRPC是一个现代的、高性能、开源的和语言无关的通用 RPC 框架&#xff0c;基于 HTTP2 协议设计&#xff0c;序列化使用PB(Protocol Buffer)&#xff0c;PB 是一种语言无关的高性能序列化框架&#xff0c;基于 HTTP2PB 保…

Sentinel 流控规则

Sentinel 是面向分布式、多语言异构化服务架构的流量治理组件&#xff0c;主要以流量为切入点&#xff0c;从流量路由、流量控制、流量整形、熔断降级、系统自适应过载保护、热点流量防护等多个维度来帮助开发者保障微服务的稳定性。 SpringbootDubboNacos 集成 Sentinel&…

实用技巧:在C和cURL中设置代理服务器爬取www.ifeng.com视频

概述&#xff1a; 网络爬虫技术作为一种自动获取互联网数据的方法&#xff0c;在搜索引擎、数据分析、网站监测等领域发挥着重要作用。然而&#xff0c;面对反爬虫机制、网络阻塞、IP封禁等挑战&#xff0c;设置代理服务器成为解决方案之一。代理服务器能够隐藏爬虫的真实IP地…

Word中NoteExpress不显示的问题

首先确认我们以及安装了word插件 我们打开word却没有。此时我们打开&#xff1a;文件->选项->加载项 我们发现被禁用了 选择【禁用项目】&#xff08;如果没有&#xff0c;试一试【缓慢且禁用的加载项】&#xff09;&#xff0c;点击转到 选择启用 如果没有禁用且没有出…

C++加持让python程序插上翅膀——利用pybind11进行c++和python联合编程示例

目录 0、前言1、安装 pybind11库c侧python侧 2、C引入bybind11vs增加相关依赖及设置cpp中添加头文件及导出模块cpp中添加numpy相关数据结构的接收和返回编译生成dll后改成导出模块同名文件的.pyd 3、python调用c4、C引入bybind11 0、前言 在当今的计算机视觉和机器学习领域&am…

CSDN每日一题学习训练——Python版(简化路径,不同的二叉搜索树)

版本说明 当前版本号[20231116]。 版本修改说明20231116初版 目录 文章目录 版本说明目录简化路径题目解题思路代码思路参考代码 不同的二叉搜索树题目解题思路代码思路参考代码 简化路径 题目 给你一个字符串 path &#xff0c;表示指向某一文件或目录的 Unix 风格 绝对路…

Mysql数据库 16.SQL语言 数据库事务

一、数据库事务 数据库事务介绍——要么全部成功要么全部失败 我们把完成特定的业务的多个数据库DML操作步骤称之为一个事务 事务——就是完成同一个业务的多个DML操作 例&#xff1a; 数据库事务四大特性 原子性&#xff08;A&#xff09;&#xff1a;一个事务中的多个D…

(三)什么是Vite——Vite 主体流程(运行npm run dev后发生了什么?)

vite分享ppt&#xff0c;感兴趣的可以下载&#xff1a; ​​​​​​​Vite分享、原理介绍ppt 什么是vite系列目录&#xff1a; &#xff08;一&#xff09;什么是Vite——vite介绍与使用-CSDN博客 &#xff08;二&#xff09;什么是Vite——Vite 和 Webpack 区别&#xff0…

vscode 配置 lua

https://luabinaries.sourceforge.net/ 官网链接 主要分为4个步骤 下载压缩包&#xff0c;然后解压配置系统环境变量配置vscode的插件测试 这里你可以选择用户变量或者系统环境变量都行。 不推荐空格的原因是 再配置插件的时候含空格的路径 会出错&#xff0c;原因是空格会断…

linux 网络 cat /proc/net/dev 查看测试网络丢包情况

可以通过 cat /proc/net/dev 查看测试网络丢包情况&#xff0c;drop关键字&#xff0c;查看所有网卡的丢包情况 还可以看其他数据&#xff0c; /proc/net/下面有如下文件

性能测试 —— Jmeter接口处理不低于200次/秒-场景

需求&#xff1a;期望某个接口系统的处理能力不低于200次/秒&#xff0c;如何设计&#xff1f; ①这个场景是看服务器对某个接口的TPS值是否能大于等于200&#xff0c;就可以了&#xff1b; ②系统处理能力&#xff1a;说的就是我们性能测试中的TPS&#xff1b; ③只要设计一…