• c++ - 第12节 - 模板进阶


    目录

    1.非类型模板参数

    2.模板的特化

    2.1.概念

    2.2.函数模板特化

    2.3.类模板特化

    2.3.1.全特化

    2.3.2.偏特化

    2.3.3.类模板特化应用示例


    1.非类型模板参数

    模板参数分为类型形参与非类型形参
    类型形参(可以认为是虚拟类型):出现在模板参数列表中,跟在class或者typename之类的参数类型名称。
    非类型形参(可以认为是常量):用一个常量作为类(函数)模板的一个参数,在类(函数)模板中可将该参数当成常量来使用。
    注:
     1.模板的非类型形参可以认为是常量,在模板中不能被修改
    2.浮点数、类对象以及字符串是不允许作为非类型模板参数的(非类型模板参数一般使用的都是整型,例如int、unsigned int、char)
    3.非类型的模板参数必须在编译期就能确认结果 

    容器array(c++11新增的容器)是一个固定大小的顺序容器,相当于定长数组。如下图所示,容器array类模板的官方库声明中,就有非类型模板参数N,N就是开辟array容器的元素个数。

    array与vector相比:容器array的所有功能使用vector都可以实现。与vector有一点不同的是array里面存的就是全部的数据,数据全部存在栈里,而vector里面其实只有几个指针,vector里面的数据都存在堆里。实际中栈空间很小堆空间很大,所以vector更优。

    array与原生数组相比:array其实就是封装过的原生数组,二者的区别是array对于越界的读写都可以检查出来(operator[ ]能严格检查越界),而原生数组是抽查不一定能检测出来。

    总结:array对比原生数组还是有一些越界检查的优势的,但是实际中我们统一直接用vector更好。

    如下图一所示代码定义一个静态栈,这样的代码有一个缺陷就是如果同时定义多个栈,这些栈的大小都是一样的。我们可以使用非类型模板参数来解决这个缺陷,灵活的控制每一个静态栈的大小。

      


    2.模板的特化

    2.1.概念

    通常情况下,使用模板可以实现一些与类型无关的代码,但对于一些特殊类型的可能会得到一些错误的结果,需要特殊处理,如下图所示第一二个结果是我们所想的,但是第三个和我们预期的结果不同,其实第三个结果值是随机的,第三个这里比较的是p1和p2指针指向地址的大小,这里应该给Less函数传的是*p1和*p2,才是我们预期的结果。

    但是如果一定要让你传p1和p2给Less函数,而且要实现预期的逻辑和结果,就需要用到模板的特化,即在原模板类的基础上,针对特殊类型所进行特殊化的实现方式。模板特化中分为函数模板特化与类模板特化。

    2.2.函数模板特化

    函数模板的特化步骤:
    1. 必须要先有一个基础的函数模板
    2. 关键字template后面接一对空的尖括号<>
    3. 函数名后跟一对尖括号,尖括号中指定需要特化的类型
    4. 函数形参表: 必须要和模板函数的基础参数类型完全相同,如果不同编译器可能会报一些奇怪的错误 
    注:其实我们也可以直接写一个现成的Less函数,这个Less函数只负责处理Date*这种类型的,这样也是可以的,如下图所示,这里Less函数模板、Less函数模板特化、Less函数是可以同时存在的,Less(p1,p2)调用的是现成的Less函数。
    总结:
    1.函数模板基本不用特化方式处理,直接写一个具体类型的函数更好
    2.虽然函数模板可以用上面的方式直接写一个现成的函数来处理,不需要进行特化,但是类模板不能去写一个类来处理,因为函数可以重载但是类不能重载。

    2.3.类模板特化

    2.3.1.全特化

    全特化:将模板参数列表中所有的参数都确定化,如下图所示

    注:上面的全特化版本的功能简单的说就是,只要Data模板第一个参数是int第二个参数是char就走我

    2.3.2.偏特化

    偏特化:任何针对模版参数进一步进行条件限制设计的特化版本。
    偏特化有两种表现方式:
    第一种:部分特化,将模板参数类表中的一部分参数特化,如下图所示。
    注:上面的这种偏特化版本的功能简单的说就是,只要Data模板第二个参数是char就走我

    第二种:参数更进一步的限制,偏特化并不仅仅是指特化部分参数,而是针对模板参数更进一步的条件限制所设计出来的一个特化版本,如下图所示。

    注:

    1.上面的第二种偏特化版本写了指针和引用两个版本,指针版本的功能简单的说就是,只要Data模板第一个参数是指针第二个参数是指针就走我,引用版本的功能简单的说就是,只要Data模板第一个参数是引用第二个参数是引用就走我

    2.上面的d9是int*和int类型,所有的特化都不匹配,因此走的是原本的Data模板

    2.3.3.类模板特化应用示例

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

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

    但是对于上面这种特化版本,如果排序的是上图所示的v3对象,无法匹配template<>  struct Less特化版本,那么如果匹配的是原模版,原模版按照地址进行比较,还是有问题。我们可以使用偏特化,如上图template  struct Less特化版本,这样只要是指针都可以匹配这个特化版本,问题得到解决。

    迭代器的萃取技术:

    前面我们讲过,官方库中的反向迭代器reverse_iterator类模板参数只有一个iterator迭代器参数,这样传参对于自定义类型的迭代器我们使用iterator迭代器内嵌类型来解决普通反向迭代器和const反向迭代器的问题(即operator*和operator->返回值的问题),但是对于vector和string这种迭代器是一个原生指针的情况,我们没有解决,官方库中的解决办法是使用迭代器的萃取技术,这里我们讲解一下官方库中是如何使用迭代器的萃取技术解决这一问题的。

    如下图一所示,reverse_iterator类模板参数iterator对于vector和string容器传过来的是T*类型,对于list等容器,传过来的是类似__list_iterator类型,__list_iterator类型是自定义类型,里面可以内嵌类型,T*是原生指针,里面没有内嵌类型,这就是问题的关键,官方库中为了解决这一问题定义了一个iterator_traits萃取类,然后对于T*和const T*的两种类型进行了特化,如下图二所示。这两个特化其实就是为了vector和string两个原生指针迭代器准备的。

    如果是自定义类型的迭代器:在reverse_iterator类模板中,iterator_traits匹配的是iterator_traits类模板,将迭代器Iterator传给iterator_traits萃取类模板参数,在萃取类中迭代器Iterator类型传过来后,得到迭代器里面的这五个内置类型并类型重定义成iterator_category、value_type、difference_type、pointer、reference,然后在reverse_iterator类模板中再将这五个类型类型重定义成iterator_category、value_type、difference_type、pointer、reference,这样在reverse_iterator类模板中就拿到了指针和引用pointer、reference,如下图所示。

    注:对比我们之前自定义类型迭代器的实现,我们的实现是在reverse_iterator类模板中直接将迭代器Iterator里面的五个内置类型类型重定义成iterator_category、value_type、difference_type、pointer、reference,进而拿到指针和引用,源代码的实现是用iterator_traits萃取类又套了一层,将iterator传给萃取类,在萃取类中得到五个内置类型,然后再拿回reverse_iterator类中进行类型重定义。

    如果是原生指针类型的迭代器:在reverse_iterator类模板中,迭代器Iterator其实就是T*或const T*,iterator_traits匹配的是template    struct iterator_traits或template    struct iterator_traits,将迭代器Iterator传给iterator_traits的这两个特化中对应的版本,特化版本中将T*、T&或const T*、const T&类型重定义成pointer和reference,pointer和reference和其他三个类型再被reverse_iterator类模板获取,在reverse_iterator中类型重定义成iterator_category、value_type、difference_type、pointer、reference,这样在reverse_iterator类模板中就拿到了指针和引用pointer、reference,如下图所示。

  • 相关阅读:
    栈的基础函数介绍及用法
    Maven——maven核心概念
    【勇敢饭饭,不怕刷题之链表】两个链表的操作
    php表单提交并自动发送邮件给某个邮箱(示例源码下载)
    企业电子招标采购系统源码Spring Boot + Mybatis + Redis + Layui + 前后端分离 构建企业电子招采平台之立项流程图
    python项目实战——银行取款机系统(七)
    华为软件开发实习生笔试2021.3.31(三)
    学习笔记:吴恩达ChatGPT提示工程
    0. 吴恩达深度学习笔记完整版
    吲哚菁绿ICG标记海藻酸钠|ICG-海藻酸钠|alginate-Indocyaninegreen
  • 原文地址:https://blog.csdn.net/qq_45113223/article/details/127968720