[C++]: 模板进阶

标题:[C++]: 模板进阶

@水墨不写bug



目录

一、非类型模板参数

(1)、非类型模板参数简介 

 (2)、非类型模板参数实例

二、模板的特化

(1)函数模板特化

(2)类模板特化

三、模板的分离编译


正文开始:

一、非类型模板参数


(1)、非类型模板参数简介 

        在模板初阶中,我们讲解了一般我们使用模板的做法:

//函数模板
template<class T1,class T2>
void func(T1 t1,T2,t2)
{//......
}//类模板
template<class T>
class A
{
public://......private:T t;
}

        模板在如下场景中的使用会让你感到更加方便:

        如果我们要实现一个栈,在通常情况下我们可能会选择实现一个静态的栈,它的大小是固定的,比如:

typedef N 100template<class T>
class stack
{private:T _data[N];
}

         但是我们发现这样实现的栈的局限性很大,因为一旦确定它的大小,就无法改变了。如果我们想要在实例化的时候能够自己手动确定它的大小,就需要用到非类型模板参数;

        模板参数的类型分为:类型模板形参与非类型模板形参。

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

非类型模板形参:就是用一个常量作为类(函数)模板的一个参数,在实例化的时候确定,在模板内部可以将该参数作为常量来使用。

在使用非类型模板形参之后,我们可以这样定义模板:


template<class T, int N>
class stack
{
public:private:T st[N];
};int main()
{stack<int, 100> st1;stack<int, 10> st2;return 0;
}

        其中,第二个模板参数 是一个整形常量,这个常量值在类实例化的时候确定。这样一来,就可以在创建栈的时候定义它的大小。

注意: 

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

        2.非类型模板参数必须在编译时就能确定。

        但是,在C++20及以后,浮点数可以作为类的非类型模板参数 。


 (2)、非类型模板参数实例

        STL中有一种容器,array

        在C++11及以后,它就是一种使用非类型模板参数的容器:

         array就是数组,但是它是一种封装后的一种数组;对于一般的数组,越界检查是部分的抽查,是通过编译器内部对比数组边界外的小范围内是否被改变来检测实现的;

        如果我们只读,检测不出来: 


int main()
{int a[10] = { 1,2,3,4,5,6,7,8,9,10};a[10];a[11];a[12];return 0;
}

         如果我们越界写入一个值,就会被检测出来;甚至编译都无法通过:

        但是当我们在数组外距离边界比较远的地方越界写入时,就可能不会被检测出来:

但是array解决了这个问题,因为array是一个类,它可以通过在类内部实现对  [ ]  的重载来进行严格的越界检查,也就是通过assert()来进行检查。

在使用容器array时越界访问,在越界读时会被检测出来: 

#include<array>
using namespace std;
int main()
{array<int, 10> arr = {1,2,3,4,5,6,7,8,9,10};arr[10];return 0;
}

         

越界写时也可以检测出来:

#include<array>
using namespace std;
int main()
{array<int, 10> arr = {1,2,3,4,5,6,7,8,9,10};arr[10] = 1;return 0;
}

 


二、模板的特化

        我们曾将实现了一个比较大小的函数模板:

template<class T>
bool Less(T left, T right)
{
return left < right;
}

这个模板在大多数情况下都可以正常使用,不会出错,比如对int(整形家族),Date(重载了比较运算符的自定义类型等)都可以正常使用:

class Date
{
public:Date(int year = 0, int month = 0, int day = 0);bool operator<(Date d) const;private:int _year = -1;int _month = -1;int _day = -1;
};template<class T>
bool Less( T left, T right)
{return left < right;
}int main()
{int a = 1;int b = 2;cout << ::Less<int>(a, b) << endl;cout << ::Less<double>(9.2, 8.2) << endl;Date d1(2022, 1, 1);Date d2(2023, 1, 1);cout << ::Less<Date>(d1, d2) << " ";return 0;
}

但是对于一种特殊情况,Less函数就会出现问题了:

 


int main()
{Date d1(2022, 1, 1);Date d2(2023, 1, 1);cout << ::Less<Date*>(&d1, &d2) << " ";return 0;
}

运行结果会随着d1和d2的实例化顺序而不同:

        究其原因,是函数模板实例化出的函数是根据d1和d2的地址大小比较的而不是d1和d2本身。

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


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

(1)函数模板特化

1. 必须要先有一个基础的函数模板
2. 关键字template后面接一对空的尖括号<>
3. 函数名后跟一对尖括号,尖括号中指定需要特化的类型
4. 函数形参表: 必须要和模板函数的基础参数类型完全相同!


        我们就以上面的less函数模板特化出Date类函数为例,进行模板特化:

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()
{Date d2(2023, 1, 1);Date d1(2022, 1, 1);cout << ::Less<Date*>(&d1, &d2) << " ";return 0;
}

        这样我们就完成了对函数模板的特化,这时,函数就会根据d1和d2大小来比较,而不是根据d1和d2的地址来比较。这样函数的结果就不会因为d1和d2的实例化顺序不同而产生差异了。


           但是,我们在实现一个函数的时候,传参传自定义类型消耗太大,于是就需要传引用,既然传引用就要防止对象被改变,需要加上const,如下:

template<class T>
bool Less(const T& left,const T& right)
{return left < right;
}

当你试图对这个函数模板进行特化的时候,就会发现意想不到的问题:刚开始学习特化,有一个误区:那就是将原模板的类型直接替换到特化后的函数内,比如对上面这个函数进行特化<Date*>,可能你会这样写:


template<>
bool Less<Date*>(const Date*& pleft,const Date* & pright)
{return *pleft < *pright;
}

        但是,这是错误的写法;你会发现编译都无法通过,这就是违背了“4. 函数形参表: 必须要和模板函数的基础参数类型完全相同!”的这一条规则。

        仅仅对于语法来说,对于less模板,const修饰的两个变量本身不能修改,特化的Date*版本,两个参数是指针类型,要与模板保持一致,就需要const修饰变量本身,即const修饰指针本身。

        并且const在*之前,修饰指针的内容;const在*之后,修饰指针本身。那么这样写才是正确的:


template<>
bool Less<Date*>(Date* const& pleft, Date* const& pright)
{return *pleft < *pright;
}

        这一点需要非常慎重,特别注意!


        同时你可能会发现:

        const修饰指针本身,但是我们还可以通过指针解引用改变其内容,这不是我们希望的,这也就要求:当你在使用函数模板的时候,需要对特化出来的函数一清二楚,并不能说你试着特化一下,看一下特化出来的东西是不是想要的,这个不是概率问题。

(2)类模板特化

        类模板的特化分为全特化和偏特化。

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


template<class T1,class T2>
class Data
{
private:T1 t1;T2 t2;
};
template<>
class Data<char, int>
{
private:char t1;int t2;
};


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


对参数的进一步限制可以是对参数部分特化:

// 将第二个参数特化为int
template <class T1>
class Data<T1, int>
{private:T1 _d1;int _d2;
};

也可以是对参数类型的进一步限制:

//两个参数偏特化为指针类型
template <typename T1, typename T2>
class Data <T1*, T2*>
{private:T1 _d1;T2 _d2;
};
//两个参数偏特化为引用类型
template <typename T1, typename T2>
class Data <T1&, T2&>
{private:const T1 & _d1;const T2 & _d2;
};

        对于上述两种偏特化类型,可能会有一个误区:

        我们直接避开这个误区不谈,直接将正确的思想;其实,上述两个类名后面的特化参数只是一个标记,编译器会根据这个标记来匹配特化的类,而不会由于特化参数的写法而改变原本传入的参数类型:

比如:

template<class T1,class T2>
class Data
{
public:Data(){cout << "原模板" << 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(const T1& d1 = 0, const T2& d2 = 0): _d1(d1), _d2(d2){cout << "Data<T1&, T2&>" << endl;}private:const T1& _d1;const T2& _d2;
};
int main()
{Data<int, int> d1;Data<int*, double*> d2;Data<int&, int&> d3;return 0;
}

        指针偏特化,传入<int*,double*>,参数T1,T2分别就是int*,double*,不会因为类名后面的<T1*, T2*>而将T1,T2改变为int,double。

        引用偏特化也是类似的。

三、模板的分离编译

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

        当我们在使用模板时,如果使用分离编译模式,会导致链接错误。

由于模板在编译时不会实例化出对应的类或者函数,自然没有相应的地址。所以在链接时,编译器在找这个类或者函数的地址时,会找不到,所以报错。

解决方法:

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

模板总结
【优点】
        1. 模板复用了代码,节省资源,更快的迭代开发,C++的标准模板库(STL)因此而产生
        2. 增强了代码的灵活性
【缺陷】
        1. 模板会导致代码膨胀问题,也会导致编译时间变长
        2. 出现模板编译错误时,错误信息非常凌乱,不易定位错误
 


完~

未经作者同意禁止转载 

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

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

免费的SSL证书能使用吗

SSL证书为网站提供数据安全加密&#xff0c;保护数据传输&#xff0c;提升用户信任。 现在免费的SSL证书还能使用吗&#xff1f;答案是肯定的。个人博客、个人的网站目前使用免费SSL证书的居多&#xff0c;另外一些单位在网站上线前&#xff0c;也会使用免费SSL证书对网站进行…

不容错过!手把手教你开启微信通话自动录音功能!(含手机端和电脑端)

文章目录 📖 介绍 📖🏡 演示环境 🏡📒 微信自动录音 📒📝 方法一📝 方法二📝 电脑端自动录音📝 注意事项⚓️ 相关链接 ⚓️📖 介绍 📖 在商务沟通或重要对话中,通话录音功能可以帮助我们记录关键信息,避免遗漏,同时也是证据保存的一种手段。虽然微…

自定义在线活动报名表单小程序源码系统 源代码+搭建部署教程 可二次定制开发

系统概述 在数字化时代&#xff0c;线上活动成为连接用户与组织的重要桥梁。为了高效地管理活动报名流程&#xff0c;一款灵活、易用的在线活动报名表单小程序显得尤为重要。本文旨在为开发者提供一套全面的解决方案&#xff0c;包括自定义在线活动报名表单小程序的源代码分析…

关于解决双屏幕鼠标移动方向问题

1.点开设置》系统》屏幕 2.分清屏幕标识&#xff0c;一般笔记本为1 3.点击要移动的屏幕&#xff0c;然后按住鼠标左键不方进行移动 感谢您的浏览&#xff0c;希望可以帮到您&#xff01;

【SpringCloud】概述 -- 微服务入门

在Java的整个学习过程中&#xff0c;大家势必会听见一些什么分布式-微服务、高并发、高可用这些专业术语&#xff0c;给人的感觉很高级&#xff0c;有一种高深莫测的感觉。可以看一下这篇博客对这些技术架构的演变有一个初步的认识: 服务端⾼并发分布式结构演进之路-CSDN博客文…

昇思MindSpore学习笔记6-04计算机视觉--Shufflenet图像分类

摘要&#xff1a; 记录MindSpore AI框架使用ShuffleNet网络对CIFAR-10数据集进行分类的过程、步骤和方法。包括环境准备、下载数据集、数据集加载和预处理、构建模型、模型训练、模型评估、模型测试等。 一、概念 1.ShuffleNet网络 旷视科技提出的CNN模型 应用在移动端 通…

【LLM之Agent】ReAct论文阅读笔记

研究背景 论文介绍了 “ReAct” 范式&#xff0c;该范式旨在融合推理和行动的功能&#xff0c;通过让大型语言模型&#xff08;LLMs&#xff09;生成既包括言语推理轨迹又包括行动序列的输出&#xff0c;解决多种语言推理和决策任务。这种方法允许模型在与外部环境&#xff08…

Ubuntu22.04.4 LTS系统/安装Anaconda【GPU版】

安装过程 1.wget命令行下载 下载Anaconda并保存文件至本地指定目录 wget -c https://repo.anaconda.com/archive/Anaconda3-2023.09-0-Linux-x86_64.sh -P ~/Downloads/anaconda3 查看是否下载好了 2.安装Anaconda 2.1 bash命令安装 bash后面是anaconda3下载好的路径 bash …

补光灯LED照明 2.7V4.2V5V升60V80V100V升压恒流芯片IC-H6902B

H6902B升压恒流芯片IC确实是一款为LED照明应用设计的稳定且可靠的解决方案。这款芯片具有以下几个显著特点&#xff1a; 高效率&#xff1a;效率高达95%以上&#xff0c;这意味着在驱动LED灯时&#xff0c;电源到LED的能量转换效率非常高&#xff0c;减少了能量损失&#xff0…

untiy 在菜单栏添加自定义按钮 点击按钮弹出一个Unity窗口,并在窗口里添加属性

using System.Collections.Generic; using UnityEditor; using UnityEngine; using UnityEngine.Rendering.PostProcessing;public class AutoGenerateWindow : EditorWindow //这是定义一个窗口 {public string subjecttName "科目名字";//科目的名字public GameOb…

【JavaWeb】登录校验-会话技术(三)过滤器Filter与拦截器Interceptor、异常处理

过滤器Filter 什么是Filter&#xff1f; Filter表示过滤器&#xff0c;是 JavaWeb三大组件(Servlet、Filter、Listener)之一。过滤器可以把对资源的请求拦截下来&#xff0c;从而实现一些特殊的功能 使用了过滤器之后&#xff0c;要想访问web服务器上的资源&#xff0c;必须先…

在线PS新功能:一键抠图轻松搞定

Photoshop&#xff0c;设计界的巨头软件&#xff0c;是多少设计小白入门的噩梦。Photoshop下载难度大&#xff0c;Photoshop收费贵&#xff0c;PS暂存盘已满&#xff0c;PS操作难度大...为了减少设计师被Photoshop压制&#xff0c;设计工具市场不断升级发展&#xff0c;即时设计…

大连网站制作需要注意哪些问题

在制作大连网站时&#xff0c;需要注意以下几个问题&#xff1a; 1. 目标受众&#xff1a;首先要明确网站的目标受众是谁&#xff0c;根据受众的特点和需求来设计网站的内容和结构。比如&#xff0c;如果目标受众是年轻人&#xff0c;网站的设计风格可以更加时尚和前卫&#xf…

昇思MindSpore学习笔记6-05计算机视觉--SSD目标检测

摘要&#xff1a; 记录MindSpore AI框架使用SSD目标检测算法对图像内容识别的过程、步骤和方法。包括环境准备、下载数据集、数据采样、数据集加载和预处理、构建模型、损失函数、模型训练、模型评估等。 一、概念 1.模型简介 SSD目标检测算法 Single Shot MultiBox Detecto…

Java的数学学习系统-计算机毕业设计源码 56236

目录 摘要 1 绪论 1.1 选题背景与意义 1.2国内外研究现状 1.3论文结构与章节安排 2系统分析 2.1 可行性分析 2.1.1技术可行性 2.1.2经济可行性 2.1.3操作可行性 2.2 系统流程分析 2.2.1系统开发流程 2.2.2 用户登录流程 2.2.3 系统操作流程 2.2.4 添加信息流程 …

前端javascript中的排序算法之冒泡排序

冒泡排序&#xff08;Bubble Sort&#xff09;基本思想&#xff1a; 经过多次迭代&#xff0c;通过相邻元素之间的比较与交换&#xff0c;使值较小的元素逐步从后面移到前面&#xff0c;值较大的元素从前面移到后面。 大数据往上冒泡&#xff0c;小数据往下沉&#xff0c;也就是…

【企业级监控】源码部署Zabbix与监控主机

Zabbix企业级分布式监控 文章目录 Zabbix企业级分布式监控资源列表基础环境一、LNMP环境搭建&#xff08;在zbx主机上&#xff09;1.1、配置Yum仓库1.1.1、下载阿里云的仓库文件1.2.2、安装PHP7的仓库1.2.3、生成Mariadb10.11的仓库文件1.2.4、快速重建Yum缓存 1.2、安装PHP7.4…

Python函数语法详解(与C++对比学习)

一、Python函数的形式 def function_name (参数, ...) -> return value_type:# 函数体return value# 看具体需求# 如果没有return语句&#xff0c;函数执行完毕后也会返回结果# 只是结果为None。return None可以简写为return 二、函数名 这里可以与C中进行类比&#xff0c…

强化学习总结(有具体代码实现)

文章目录 第一部分 强化学习基础第1章 强化学习概述1.1 强化学习概念1.2 强化学习的环境1.3 强化学习的目标1.4 强化学习的数据 第2章 多臂老虎机问题&#xff08;MAB问题&#xff09;2.1 问题描述2.1.1 问题定义2.1.2 形式化描述2.1.3 累积懊悔2.1.4 估计期望奖励 2.2 解决方法…

windows10 +VS2019环境下的PCL安装和配置

今天想做点云重建&#xff0c;千篇一律&#xff0c;PCL少不了。一路跑下来觉得PCL的安装和环境配置还挺麻烦的&#xff0c;比OpenCV真的麻烦很多&#xff0c;有点不想写详细安装和配置过程了&#xff0c;偷个懒&#xff0c;就转载一下大佬的文章吧&#xff0c;下面的博主们已经…