【C++ 学习⑮】- 模板进阶

news2024/11/24 4:56:54

目录

一、必须使用 typename 的场景

二、非类型模板参数

三、模板的特化

3.1 - 函数模板特化

3.2 - 类模板特化

3.2.1 - 全特化

3.2.2 - 偏特化

四、类模板分离式编译

4.1 - 分离编译的概念

4.2 - 类模板分离式的问题

4.3 - 解决方案



一、必须使用 typename 的场景

问题:当我们定义了一个函数模板 Print,用于输出容器中的所有元素,编译却不能通过?

template<class Container>
void Print(const Container& ctnr)
{
    Container::const_iterator it = ctnr.begin();
    while (it != ctnr.end())
    {
        cout << *it << " ";
        ++it;
    }
    cout << endl;
}

发生编译错误是因为在模板实例化之前,编译器无法确定 Container::const_iterator 是嵌套类型,还是静态成员变量,亦或是静态成员函数

因此,需要使用 typename 关键字直接告诉编译器 Container::const_iterator 是一个类型

typename Container::const_iterator it = ctnr.begin();

除此之外,还有另一种解决办法,即

auto it = ctnr.begin();

其他应用场景

template <class T, class Container = vector<T>,
  class Compare = less<typename Container::value_type> > class priority_queue;


二、非类型模板参数

对于函数模板和类模板,模板参数并不局限于类型,普通值也可以作为模板参数,即非类型模板参数(Nontype Template Parameters)

例如 C++11 标准中新增的序列容器 array,它以类模板的形式定义在 <array> 头文件中,并位于 std 命名空间中。

template <class T, size_t T > class array;

其中 N 就是非类型的类模板参数,用于指定容器的大小。

需要注意的是,非类型模板参数有一定的限制,浮点数、类对象以及字符串是不允许作为非类型模板参数的


三、模板的特化

通常情况下,使用模板可以实现与类型无关的代码,但对于一些特殊类型,可能会得到非预期的结果,此时就需要对模板进行特化,即在原模板的基础上,针对特殊类型做特殊化处理

模板的特化有时也被称为模板的具体化,可分为函数模板特化和类模板特化

3.1 - 函数模板特化

#include <iostream>
using namespace std;
​
// 函数模板
template<class T>
T Max(const T x, const T y)
{
    return x > y ? x : y;
}
​
// 对 Max 函数模板进行特化
template<>
const char* Max<const char*>(const char* x, const char* y)
{
    return strcmp(x, y) > 0 ? x : y;
}
​
int main()
{
    cout << Max(10, 20) << endl;  // 20
    cout << Max('a', 'z') << endl;  // z
    cout << Max("abcdefg", "hijk");  // hijk
    return 0;
}

如果不对 Max 函数模板进行特化,那么在比较字符串 "abcdefg""hijk" 的大小时,比较的是两个字符串的起始地址的大小,而不是字符串的内容

除了定义函数模板的特化版本,还可以直接给出对应的普通函数,即:

const char* Max(const char* x, const char* y)
{
    return strcmp(x, y) > 0 ? x : y;
}

程序运行的结果和使用函数模板特化相同,但是如果使用普通函数,那么不管是否发生函数调用,都会在目标文件中生成该函数的二进制代码,而如果使用模板函数的特化版本,除非发生函数调用,否则不会在目标文件中生成特化模板函数的二进制代码,这符合函数模板的 "惰性实例化" 准则

3.2 - 类模板特化

3.2.1 - 全特化

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

下面是一个用于比较的类模板,里面可以有多种用于比较的函数,以 IsEqual 为例。

#include <cstdlib>
#include <iostream>
using namespace std;
​
template<class T>
class Compare
{
public:
    static bool IsEqual(const T& lhs, const T& rhs)
    {
        return lhs == rhs;
    }
};
​
// specialize for double
template<>
class Compare<double>
{
public:
    static bool IsEqual(const double& lhs, const double& rhs)
    {
        return abs(lhs - rhs) < 10e-6;
    }
};
​
int main()
{
    double epsilon = 0.001;
    double d1 = 2.234;
    double d2 = 2.235;
    if (Compare<double>::IsEqual(d2 - d1, epsilon))
        cout << "equal" << endl;
    else
        cout << "unequal" << endl;
    return 0;
}

因为浮点数在计算机中的存储并不总是精确的,所以在判断两个浮点数是否相等时,应该采用比较两数之差的绝对值是否小于一个很小的数字来确定是否相等的方法

3.2.2 - 偏特化

偏特化有以下两种表现方式:

  1. 部分特化,即将模板参数的一部分参数特化

    #include <iostream>
    using namespace std;
    ​
    template<class T1, class T2>
    class A
    {
    public:
        A() { cout << "A<T1, T2>" << endl; }
    private:
        T1 _i;
        T2 _j;
    };
    ​
    // 部分特化
    template<class T1>
    class A<T1, int>
    {
    public:
        A() { cout << "A<T1, int>" << endl; }
    private:
        T1 _i;
        int _j;
    };
    ​
    int main()
    {
        A<double, double> a1;
        // A<T1, T2>
        A<double, int> a2;
        // A<T1, int>
        return 0;
    }
  2. 对参数做更进一步的限制

    例一

    template<class T>
    class Compare<T*>
    {
    public:
        static bool IsEqual(const T* lhs, const T* rhs)
        {
            return Compare<T>::IsEqual(*lhs, *rhs);
        }
    };

    这种特化不是一种绝对的特化,它只是对类型做了某些限定,但仍然保留了其一定的模板性,这种特化给我们提供了极大的方便,如这里,我们就不再需要对 int*、float*、double* 等等类型分别做特化了

    例二

    template<class T>
    class Compare<vector<T>>
    {
        static bool IsEqual(const vector<T>& lhs, const vector<T>& rhs)
        {
            if (lhs.size() != rhs.size())
                return false;
    ​
            for (size_t i = 0; i < lhs.size(); ++i)
            {
                if (lhs[i] != rhs[i])
                    return false;
            }
            return true;
        }
    };

    这里把 IsEqual 的参数限定为一种 vector 类型,但具体是 vector<int> 还是 vector<float>,我们可以不关心,因为对于这两种类型,我们的处理方式都是一样的


四、类模板分离式编译

4.1 - 分离编译的概念

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

4.2 - 类模板分离式的问题

A.h

#pragma once
​
template<class T>
class A
{
public:
    void f();  // 声明
};

A.cpp

#include "A.h"
​
// 定义
template<class T>
void A<T>::f()
{
    // ... ...
}

main.cpp

#include "A.h"
​
int main()
{
    A<int> a;
    a.f();
    return 0;
}

这是因为 A.cpp 中的函数模板代码不能直接编译成二进制代码,其中需要一个实例化的过程,所以最终出现的链接错误

4.3 - 解决方案

  1. 在模板定义的位置显示实例化,即

    #include "A.h"
    ​
    template class A<int>;  // 显示实例化
    ​
    // 定义
    template<class T>
    void A<T>::f()
    {
        // ... ...
    }

    但是这种方法不实用,不推荐使用,推荐使用下面那种方法

  2. 将声明和定义放在一个 .hpp 或者 .h 文件中,即

    #pragma once
    ​
    template<class T>
    class A
    {
    public:
        void f();  // 声明
    };
    ​
    // 定义
    template<class T>
    void A<T>::f()
    {
        // ... ...
    }

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

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

相关文章

shell 基础3

在第一行后面追加内容 在第3行后面追加内容 在每行前面加 在第四行前面加入 -i表示添加在文本中 在每个22后面加 $a 在文件最后一行追加 匹配到每个包含22的行&#xff0c;并在之前加 把第7行整行替换 将所有匹配22的行替换 删除第5行 隔行删除&#xff0c;删除奇数行 删除偶数…

live555server环境搭建

live555环境搭建详解&#xff08;ubuntu18.04&#xff09; 1.环境依赖 openssl可选安不安 安装&#xff08;选择好版本&#xff09; sudo apt-get update sudo apt-get install openssl sudo apt-get install libssl-dev使用头文件是否可用时编译测试时记得链接&#xff08…

【C++入门到精通】C++入门 —— priority_queue(STL)优先队列

阅读导航 前言一、priority_queue简介1. 概念2. 特点 二、priority_queue使用1. 基本操作2. 底层结构 三、priority_queue模拟实现⭕ C代码⭕priority_queue中的仿函数 总结温馨提示 前言 ⭕文章绑定了VS平台下std::priority_queue的源码&#xff0c;大家可以下载了解一下&…

C#,数值计算——Ridders的多项式外推方法的计算方法与源程序

using System; namespace Legalsoft.Truffer { /// <summary> /// 通过Ridders的多项式外推方法返回函数func在点x处的导数。 /// 输入值h作为估计的初始步长&#xff1b;它不需要很小&#xff0c;而是应为x上的增量&#xff0c; /// 在此增量上func将发…

在CMD中找不到Bootrec/fixboot元素怎么办?

当您尝试在CMD中执行Bootrec/fixboot以修复Windows 7/8.1/8/10/11和Windows Server 2012等操作系统中的系统启动问题时&#xff0c;通常会遇到Bootrec/fixboot参数无效的情况。这类启动问题可能是由磁盘克隆、系统迁移、Windows更新、MBR和GPT转换等引起的。当一个问题仍然存在…

YOLO目标检测——矿石数据集图片下载分享

矿石图片&#xff0c;其中训练集包括“玄武岩”、“花岗岩”、“大理石”、“石英岩”、“煤”、“石灰石”、“砂岩”七种矿石图片。测试集包括24张相应的七种矿石图像。 数据集点击下载&#xff1a; 矿石数据集4500图片数据说明.rar

OpenGL学习路程(一)

Hello啊各位&#xff0c;鸽了挺长时间没更新&#xff0c;其实是博主找到新乐子了。 如标题所说&#xff0c;我正在学习OpenGL。 现在已经成功的调用显卡画出了一个三角形&#xff0c;这虽然不是什么大的成就&#xff0c;但已经让我很兴奋了。 我不打算在这里写出我配置openg…

Pyqt5-开源工具分解功能(文本拖拽)

开源第四篇:功能实现之拖拽功能与配置文件。 写这个功能的初衷,是因为,每次调试我都要手动敲命令,太麻烦了,想偷个懒,所以直接给这功能加上了,顺便衍生出了另一个想法,配置文件自动填写相关数据。 先看个简单的拖拽功能: 很明显吧,还是比较便捷的。所以我们本章,就在…

TextView加粗字体太粗

解决方法如下&#xff1a; import android.content.Context; import android.graphics.Canvas; import android.graphics.Paint; import android.util.AttributeSet;import androidx.annotation.Nullable; import androidx.appcompat.widget.AppCompatTextView;/*** 自定义加粗…

【真人语音】讯飞星火个人声音训练及导出工具V0.2.exe

【项目背景】 小编一直在尝试着短视频技术&#xff0c;在读文案的时候经常会读错&#xff1b;所以&#xff0c;只能用微软或者剪映的文本转语音软件。 很早之前在Github上也看到过真人人声训练的开源代码&#xff0c;尝试过一番之后&#xff0c;也是以失败告终&#xff1b;就…

若依项目的运行详细步骤

目录 一、项目的解读与获取 二、项目的运行 后端步骤(ruoyi-admin) &#xff08;一&#xff09;导入若依的2个SQL文件 版本建议 &#xff08;二&#xff09;Redis的配置 &#xff08;三&#xff09;启动后端 前端步骤(ruoyi-ui) 版本建议 &#xff08;一&#xff09;…

深入了解Git:介绍及常用命令指南

当今软件开发领域中&#xff0c;版本控制是一个至关重要的概念&#xff0c;而Git作为最流行的分布式版本控制系统&#xff0c;发挥着不可替代的作用。本文将介绍Git的基本概念以及常用命令&#xff0c;帮助你更好地理解和使用这一强大的工具。 Git简介 Git是一种分布式版本管…

结构化知识管理-20张思维导图为例子

思维导图&#xff0c;是结构化思考最高效的工具。 以中心主题为核心&#xff0c;通过各级主题的组合来呈现信息。 思维导图的可视化图形表达&#xff0c;让我们可以更加便于理解和记忆。各主题间相互连接的形式&#xff0c;展现了信息间的相关性。促进我们综合性的思考。思维导…

飞机打方块(三)特殊按钮制作

一、特殊按钮容器 1.新建PropController脚本&#xff0c;并绑定新建的特殊按钮容器节点 GameController.ts property({ type: cc.Node, displayName: "特殊按钮节点", tooltip: "特殊按钮节点&#xff0c;分别为全消无敌和菜单" })special_btn: cc.Node …

三种生成树(STP,RSTP,MSTP)的基本配置(自我理解)

目录 一、为什么要使用生成树&#xff08;STP)&#xff1a; 二、由于设备冗余而导致的问题&#xff1a; 广播风暴&#xff1a; 三、802.1D生成树基本配置 四、802.1D生成树实验 实验拓扑&#xff1a; 实验配置&#xff1a; 配置完成后&#xff0c;在SW8上观察现象&…

[Docker] Windows 下基于WSL2 安装

Docker 必须部署在 Linux 内核的系统上。如果其他系统想部署 Docker 就必须安装一个虚拟 Linux 环境。 1. 开启虚拟化 进入系统BIOS&#xff08;AMD 为 SVM&#xff1b;Intel 为 Intel-vt&#xff09;改为启用(enable) 2. 开启WSL 系统设置->应用->程序和功能->…

前端 -- 基础 HTML基本语法 结构标签介绍

HTML 语法规范 基本语法概述 : HTML 标签是由尖括号包围的关键词&#xff0c;示例 &#xff1a; <html> HTML 标签通常是成对出现的&#xff0c;例如 <html > 和 < /html > &#xff0c; 我们称为双标签。 标签对中的第一个标签是 开始标签&#xff0c;…

docker搭建私有镜像harbor

docker安装搭建私有仓库 Harbor harbor用于存储和分布docker镜像企业级registry服务器的harbor使用的是官方的docker registry(v2命名是distribution)服务去完成。 安装harhor 启动harbor 6.

【算法日志】动态规划刷题:整数拆分,n节点的BST数量(day35)

代码随想录刷题60Day 目录 前言 整数拆分 N个数节点的二叉搜索树数量 前言 今天问题的难点在于从问题中抽象出dp数组和状态转移方程。 整数拆分 int integerBreak1(int n) {vector<int> dp(n 1, 0);dp[1] 1;for (int i 2; i < n; i)for (int j 1; j < i; j…

工厂模式并不难理解

文章目录 工厂模式简单工厂模式简单工厂模式使用的场景 工厂方法模式工厂方法模式使用场景 抽象工厂模式抽象工厂模式使用场景 工厂模式 功能&#xff1a;将对象的创建交给工厂&#xff0c;我们只需要告诉工厂我们要什么对象就可以得到该对象。 目的&#xff1a;实现创建对象…