【C++】模板2.0

最近学习了一些模板的知识,速写本博客作为学习笔记,若有兴趣,欢迎垂阅读!

 1.非类型模板参数

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

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

ps:

  • 浮点数(C++20之前)、类对象以及字符串是不允许作为非类型模板参数的。
  • 非类型的模板参数必须在编译期就能确认结果。
namespace hd
{// 定义一个模板类型的静态数组template<class T, size_t N = 10>//N就是非类型模板参数class array{public:T& operator[](size_t index) { return _array[index]; }const T& operator[](size_t index)const { return _array[index]; }size_t size()const { return _size; }bool empty()const { return 0 == _size; }private:T _array[N];size_t _size;};
}int main()
{hd::array<int> ia;hd::array<char, 6> ca;return 0;
}

看到这个栗子,类模板参数N就是非类型模板参数。


库里面也有使用非类型模板参数的栗子,比如类模板array 的设计就使用了非类型模板参数,看到N就是这个类模板的非类型模板参数:

array也是一个容器,底层其实就是一个静态数组,关于其接口有兴趣的话可以自行去查阅。不过这个容器比较鸡肋吧,因为vector似乎更香。

当然,这个容器也有其优点:

  • 普通数组对于越界访问的检查是一种抽查,越界访问了未必检查得出来。但array对于越界访问一查一个准,也许其底层实现加了断言吧,例如其成员函数operator[]完全可以加断言检查是否越界。
  • 本容器对象一旦实例化,就不支持动态调整大小,因为其空间是静态开辟的静态数组。但是其空间是在栈区开辟的,而vector的空间是在堆区开辟的,是动态开辟的。

看到当非类型模板参数有缺省值的一些情况,实例化对象代码写法:

template<class T = int, size_t N = 10>
class a
{T _arr[N];
};
template<int N = 10>
class b
{int _arr[N];
};int main()
{a<> ap;b<> bp;return 0;
}

 C++20以后也支持如下写法:

template<class T = int, size_t N = 10>
class a
{T _arr[N];
};
template<int N = 10>
class b
{int _arr[N];
};int main()
{a ap1;a<char> ap2;b bp;return 0;
}

2.模板的特化 

2.1.模板特化的概念 

通常情况下,使用模板可以实现一些与类型无关的代码,但对于一些特殊类型的可能会得到一些 错误的结果,需要特殊处理,比如:实现了一个专门用来进行小于比较的函数模板

#include <iostream>
using namespace std;class Time
{int _h;int _m;int _s;
public:Time(int h, int m, int s):_h(h) ,_m(m) ,_s(s){}bool operator<(const Time& t)const{if (_h != t._h) return _h < t._h;else if (_m != t._m) return _m < t._m;return _s < t._s;}
};//专门比较小于的函数模板
template <class T>
bool Less(const T& t1, const T& t2)
{return t1 < t2;
}int main()
{Time t1(22, 22, 22);Time t2(11, 11, 11);cout << Less(t1, t2) << endl;//结果正确cout << Less(&t1, &t2) << endl;//结果错误return 0;
}

 可以看到,Less绝对多数情况下都可以正常比较,但是在特殊场景下就得到错误的结果。上述示 例中,&t2指向的t2显然小于&t1指向的t1对象,但是Less内部并没有比较&t2和&t1指向的对象内 容,而比较的是&t1和&t2本身的值,这就无法达到预期而错误。

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

2.2.函数模板特化

 函数模板的特化步骤:

1. 必须要先有一个基础的函数模板

2. 关键字template后面接一对空的尖括号<>

3. 函数名后跟一对尖括号,尖括号中指定需要特化的类型

4. 函数形参表: 必须要和模板函数的基础参数类型完全相同,如果不同编译器可能会报一些奇 怪的错误。

#include <iostream>
using namespace std;class Time
{int _h;int _m;int _s;
public:Time(int h, int m, int s):_h(h) ,_m(m) ,_s(s){}bool operator<(const Time& t)const{if (_h != t._h) return _h < t._h;else if (_m != t._m) return _m < t._m;return _s < t._s;}
};//专门比较小于的函数模板
template <class T>
bool Less(const T& t1, const T& t2)//注意这里const修饰的是引用,而不是修饰类型
{return t1 < t2;
}//Less函数模板的特化
template<>
bool Less<Time*>(Time* const & t1, Time* const & t2)
{return *t1 < *t2;
}int main()
{Time t1(22, 22, 22);Time t2(11, 11, 11);cout << Less(t1, t2) << endl;//走模板生成cout << Less(&t1, &t2) << endl;//调用特化之后的版本,而不走模板生成了return 0;
}


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

#include <iostream>
using namespace std;class Time
{int _h;int _m;int _s;
public:Time(int h, int m, int s):_h(h), _m(m), _s(s){}bool operator<(const Time& t)const{if (_h != t._h) return _h < t._h;else if (_m != t._m) return _m < t._m;return _s < t._s;}
};//专门比较小于的函数模板
template <class T>
bool Less(const T& t1, const T& t2)//注意这里const修饰的是引用,而不是修饰类型
{return t1 < t2;
}//现成函数(非函数模板特化)
bool Less(Time* t1, Time* t2)
{return *t1 < *t2;
}int main()
{Time t1(22, 22, 22);Time t2(11, 11, 11);cout << Less(t1, t2) << endl;//走模板生成cout << Less(&t1, &t2) << endl;//调用现成函数return 0;
}

直接将类型是Time*类型比较的函数给出,不是香喷喷吗?何必走函数模板特化呢?所以函数模板不建议特化。

2.3.类模板特化 

 类模板特化分为全特化和半特化(偏特化)。

2.3.1.全特化

#include <iostream>
using namespace std;
template<class T1, class T2>
class Data
{
public:Data() { cout << "Data<T1, T2>" << endl; }
private:T1 _d1;T2 _d2;
};//类模板全特化,当第1个模板参数为int且第2个模板参数为char时,调用
template<>
class Data<int, char>
{
public:Data() { cout << "Data<int, char>" << endl; }
private:int _d1;char _d2;
};
int main()
{Data<int, int> d1;Data<int, char> d2;return 0;
}

 2.3.2.半特化(偏特化)

 偏特化:任何针对模版参数进一步进行条件限制设计的特化版本。偏特化有以下两种表现方式:


  • 部分特化

将模板参数类表中的一部分参数特化。

#include <iostream>
using namespace std;
template<class T1, class T2>
class Data
{
public:Data() { cout << "Data<T1, T2>" << endl; }
private:T1 _d1;T2 _d2;
};//类模板半特化,只要第2个类模板参数为int,调用
template<class T1>
class Data<T1, int>
{
public:Data() { cout << "Data<T1, int>" << endl; }
private:T1 _d1;int _d2;
};
int main()
{Data<int, int> d1;Data<int, char> d2;return 0;
}


    • 参数更进一步的限制

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

    #include <iostream>
    using namespace std;
    template<class T1, class T2>
    class Data
    {
    public:Data() { cout << "Data<T1, T2>" << endl; }
    private:T1 _d1;T2 _d2;
    };//类模板半特化,两个参数偏特化为指针类型,只要2个类模板参数为指针,调用
    template<class T1, class T2>
    class Data<T1* , T2*>
    {
    public:Data() { cout << "Data<T1* , T2*>" << endl; }
    private:T1 _d1;T2 _d2;
    };
    int main()
    {Data<int, int> d1;Data<int*, char*> d2;return 0;
    }

    3.模板分离编译 

     3.1.什么是分离编译

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

    3.2.模板的分离编译

    模板是不推荐分离编译的,也就是说模板不推荐声明和定义分离到不同文件下,如果分离了会出现链接问题。例如:

    一个工程,有3个文件,分别是a.h、a.cpp、test.cpp。

    a.h:

    #pragma once//a类模板声明
    template <class T>
    class a
    {T _tmp;
    public:a(const T& tmp = T());const T& get_tmp();
    };//Add函数模板声明
    template<class T>
    T Add(const T& n1, const T& n2);

    a.cpp:

    #include "a.h"//a类模板定义
    template<class T>
    const T& a<T>::get_tmp()
    {return _tmp;
    }template<class T>
    a<T>::a(const T& tmp):_tmp(tmp)
    {}//Add函数模板定义
    template<class T>
    T Add(const T& n1, const T& n2)
    {return n1 + n2;
    }

     test.cpp:

    #include <iostream>
    using namespace std;
    #include "a.h"int main()
    {a<int> x(10);cout << x.get_tmp() << endl;cout << Add(1, 2) << endl;return 0;
    }
    

    这3个文件中有2个模板,声明和定义都分离了。编译会出现问题:


    如何解决? 

     2个办法:

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

    a.cpp:

    #include "a.h"//a类模板定义
    template<class T>
    const T& a<T>::get_tmp()
    {return _tmp;
    }template<class T>
    a<T>::a(const T& tmp):_tmp(tmp)
    {}//Add函数模板定义
    template<class T>
    T Add(const T& n1, const T& n2)
    {return n1 + n2;
    }//显示实例化
    template
    class a<int>;//显示实例化
    template
    int Add(const int& n1, const int& n2);


     模板声明和定义分离到不同文件下,且不在模板定义的位置显示实例化的话,会出现链接错误的原因的话,感兴趣可以自己去查阅哈,我就不介绍了。

    感谢阅读,欢迎斧正!

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

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

    相关文章

    目标检测中的损失函数(二) | BIoU RIoU α-IoU

    BIoU来自发表在2018年CVPR上的文章&#xff1a;《Improving Object Localization With Fitness NMS and Bounded IoU Loss》 论文针对现有目标检测方法只关注“足够好”的定位&#xff0c;而非“最优”的框&#xff0c;提出了一种考虑定位质量的NMS策略和BIoU loss。 这里不赘…

    如何在 Amazon EC2 上部署 Java(Spring Boot 版)

    让我们学习如何将 Java Spring Boot Web 服务器部署到 Amazon EC2。每月只需 3 美元。 使用 Azure&#xff0c;您可能不知道要花费多少钱。 Spring Boot 项目示例 在本教程中&#xff0c;我们将重点介绍如何将 Java Spring Boot 服务器部署到 Amazon EC2&#xff0c;因此我们不…

    Git常用命令分类汇总

    Git常用命令分类汇总 一、基础操作 初始化仓库git init添加文件到暂存区git add file_name # 添加单个文件 git add . # 添加所有修改提交更改git commit -m "提交描述"查看仓库状态git status二、分支管理 创建/切换分支git branch branch_name …

    mysql——基础知识

    关键字大小写不敏感 查看表结构中的 desc describe 描述 降序中的 desc descend 1. 数据库的操作 1. 创建数据库 create database 数据库名;为防止创建的数据库重复 CREATE DATABASE IF NOT EXISTS 数据库名;手动设置数据库采用的字符集 character set 字符集名;chars…

    Redis 哨兵与集群脑裂问题详解及解决方案

    Redis 哨兵与集群脑裂问题详解及解决方案 本文将深入探讨Redis在哨兵模式和集群模式下可能出现的脑裂问题&#xff0c;包括其发生场景、原因以及有效的解决策略。同时&#xff0c;我们还将提供相应的代码示例和配置方案来帮助读者理解和实施。 一、脑裂问题概述 脑裂&#x…

    国内网络设备厂商名单(List of Domestic Network Equipment Manufacturers)

    国内网络设备厂商名单 运维工程师必须广泛熟悉国内外各大厂商的设备&#xff0c;深入掌握其应用场景、功能特点及优势。这不仅有助于在故障排查时迅速定位问题&#xff0c;还能在系统设计、优化与升级中做出更合理的决策。对设备特性的精准把握&#xff0c;能够显著提升运维效…

    2、SpringAI接入ChatGPT与微服务整合

    2、SpringAI接入ChatGPT与微服务整合 小薛博客AI 大模型资料 1、SpringAI简介 https://spring.io/projects/spring-ai Spring AI是一个人工智能工程的应用框架。其目标是将Spring生态系统的设计原则&#xff08;如可移植性和模块化设计&#xff09;应用于人工智能领域&#…

    基于ubuntu24.10安装NACOS2.5.1的简介

    基于ubuntu24.10安装NACOS2.5.1的简介 官方网站地址&#xff1a; https://nacos.io 可访问nacos站点 https://nacos.io/zh-cn/ 2025年04月记录发布 V2.5.1 版本 一、环境预准备 64 bit JDK 1.8&#xff1b; sudo apt update sudo apt install openjdk-8-jdk sudo apt upda…

    神经网络:从基础到应用,开启智能时代的大门

    在当今数字化时代&#xff0c;神经网络已经成为人工智能领域最热门的技术之一。从语音识别到图像分类&#xff0c;从自然语言处理到自动驾驶&#xff0c;神经网络的应用无处不在。它不仅改变了我们的生活方式&#xff0c;还为各个行业带来了前所未有的变革。本文将带你深入了解…

    [k8s实战]Containerd 1.7.2 离线安装与配置全指南(生产级优化)

    [k8s实战]Containerd 1.7.2 离线安装与配置全指南&#xff08;生产级优化&#xff09; 摘要&#xff1a;本文详细讲解在无外网环境下部署 Containerd 1.7.2 容器运行时的完整流程&#xff0c;涵盖二进制包安装、私有镜像仓库配置、Systemd服务集成等关键步骤&#xff0c;并提供…

    【CPU】结合RISC-V CPU架构回答中断系统的7个问题(个人草稿)

    结合RISC-V CPU架构对中断系统七个关键问题的详细解析&#xff0c;按照由浅入深的结构进行说明&#xff1a; 一、中断请求机制&#xff08;问题①&#xff09; 硬件基础&#xff1a; RISC-V通过CLINT&#xff08;Core Local Interrupter&#xff09;和PLIC&#xff08;Platfor…

    [密码学实战]国密算法面试题解析及应用

    以下是密码学领域常见的面试题及其详细解析,涵盖基础理论、算法实现与应用场景,帮助系统化备战技术面试 一、基础概念类 1. 密码学的主要目标是什么? 答案: 确保数据的机密性(加密防止窃听)、完整性(哈希校验防篡改)、认证性(数字签名验证身份)和不可否认性(签名防…

    Spring Boot 实现 Excel 导出功能(支持前端下载 + 文件流)

    &#x1f9e0; 一、为什么用 EasyExcel&#xff1f; 在 Java 开发中&#xff0c;操作 Excel 的框架主要有&#xff1a; Apache POI&#xff08;经典但慢、内存占用大&#xff09; JXL&#xff08;老旧不维护&#xff09; Alibaba EasyExcel&#xff08;阿里出品&#xff0c;…

    【论文速递】2025年06周 (Robotics/Embodied AI/LLM)

    目录 SMOLLM2&#xff1a;当Smol变得大 - 以数据为中心的小语言模型英文摘要中文摘要 OmniHuman-1&#xff1a;重新考虑一阶段的人类动画模型的扩展英文摘要中文摘要 S1&#xff1a;简单的测试时间缩放英文摘要中文摘要 直接对齐算法间的差异日渐模糊英文摘要中文摘要 VideoJAM…

    学习深度学习是否要先学习机器学习?工程师的路径选择策略

    深度学习与机器学习的关系&#xff0c;如同摩天大楼与地基——前者是后者的高阶延伸&#xff0c;但能否绕过地基直接造楼&#xff1f;本文从技术本质、学习曲线、应用场景三个维度剖析这一关键问题。 一、技术血脉的承继关系 概念体系同源&#xff1a; 损失函数、梯度下降、过拟…

    开始放飞之先搞个VSCode

    文章目录 开始放飞之先搞个VSCode重要提醒安装VSCode下载MinGW-w64回到VSCode中去VSCode原生调试键盘问题遗留问题参考文献 开始放飞之先搞个VSCode 突然发现自己的新台式机上面连个像样的编程环境都没有&#xff0c;全是游戏了&#xff01;&#xff01;&#xff01;&#xff…

    【2025“华中杯”大学生数学建模挑战赛】选题分析 A题 详细解题思路

    目录 2025“华中杯”大学生数学建模挑战赛选题分析A题&#xff1a;晶硅片产销策略优化B题&#xff1a;校园共享单车的调度与维护问题C题&#xff1a;就业状态分析与预测D题&#xff1a;患者院内转运不良事件的分析与预测 A 题 晶硅片产销策略优化问题 1&#xff1a;月利润计算模…

    YOLO11改进,尺度动态损失函数Scale-based Dynamic Loss,减少标签不准确对损失函数稳定性的影响

    在目标检测领域,标签噪声与尺度敏感问题始终是制约模型性能提升的"阿喀琉斯之踵"。2025年CVPR最佳论文提出的尺度动态损失函数(Scale-based Dynamic Loss, SDL),通过构建自适应损失调节机制,不仅实现了对YOLOv11检测精度的指数级提升,更重新定义了损失函数的设…

    缓存 --- 内存缓存 or 分布式缓存

    缓存 --- 内存缓存 or 分布式缓存 内存缓存&#xff08;In-Memory Cache&#xff09;分布式缓存&#xff08;Distributed Cache&#xff09;内存缓存 vs 分布式缓存 内存缓存和分布式缓存是两种常见的缓存策略&#xff0c;它们在存储位置、访问速度和适用场景上有所不同。下面分…

    Python+CoppeliaSim+ZMQ remote API控制机器人跳舞

    这是一个使用Python和CoppeliaSim&#xff08;V-REP&#xff09;控制ASTI人型机器人进行舞蹈动作的演示项目。 项目描述 本项目展示了如何使用Python通过ZeroMQ远程API与CoppeliaSim仿真环境进行交互&#xff0c;控制ASTI人型机器人执行预定义的舞蹈动作序列。项目包含完整的机…