C++ 模板进阶

目录

一、非类型模板参数

二、模板的特化

1、函数模板特化

2、类模板特化

全特化

偏特化

3、例题

三、模板分离编译

1、定义

2、解决方法

3、模板总结


一、非类型模板参数

  • 模板参数分类类型形参与非类型形参。
  • 类型形参即:出现在模板参数列表中,跟在class或者typename之类的参数类型名称。
  • 非类型形参,就是用一个常量作为类(函数)模板的一个参数,在类(函数)模板中可将该参数当成常量来使用。
  • 浮点数、类对象以及字符串是不允许作为非类型模板参数的。
  • 非类型的模板参数必须在编译期就能确认结果。
template<class T, int N = 20>
class Array
{
public:private:T _a[N];
};
int main()
{Array<int, 10> a;Array<int, 15> b;return 0;
}

二、模板的特化

1、函数模板特化

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

template<class T>
bool Less(T left, T right)
{return left < right;
}int main()
{cout << Less(1, 2) << endl;   // 可以比较,结果正确Date d1(2022, 7, 7);Date d2(2022, 7, 8);cout << Less(d1, d2) << endl;  // 可以比较,结果正确Date* p1 = &d1;Date* p2 = &d2;cout << Less(p1, p2) << endl;  // 可以比较,结果错误int* p3 = new int(1);int* p4 = new int(2);cout << Less(p3, p4) << endl;  // 可以比较return 0;
}

Less 函数是一个通用的模板函数,用于比较两个值的大小。大多数情况下,它可以正常比较并返回正确的结果。然而,在特殊情况下,比如比较指针类型时,Less 函数的行为可能会出现错误。

为了解决这个问题,可以对模板进行特化。模板特化是在原模板的基础上,针对特定类型进行特殊化的实现方式。在这种情况下,可以对 Less 函数进行特化,以处理指针类型的比较。

通过对 Less 函数进行特化,当比较指针类型时,会比较指针所指向的对象的内容,而不是指针的地址。这样就可以得到正确的比较结果。 

函数模板的特化步骤:

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

这两个代码片段都是对 Less 函数进行特化,用于处理指针类型的比较。它们的区别在于特化的方式和适用范围。

  1. template<> bool Less<Date*>(Date* left, Date* right) { ... }: 这是一个显式的全特化,针对 Less 函数的模板参数为 Date* 的情况进行特化。它指定了具体的类型 Date*,并提供了特化的实现。这个特化只适用于 Date* 类型的指针比较。

  2. template<class T> bool Less(T* left, T* right) { ... }: 这是一个偏特化的部分特化,针对 Less 函数的模板参数为指针类型的情况进行特化。它使用了模板参数 T,表示任意类型的指针。这个特化适用于所有类型的指针比较。

总结来说,全特化是对特定类型进行特化,而部分特化是对一类类型进行特化。在给定的示例中,全特化 Less<Date*> 专门处理 Date* 类型的指针比较,而部分特化 Less<T*> 适用于所有类型的指针比较。选择使用哪种特化方式取决于具体的需求和使用场景。

注意:一般情况下如果函数模板遇到不能处理或者处理有误的类型,为了实现简单通常都是将该函数直接给出。
bool Less(Date* left, Date* right)
{return *left < *right;
}
  • 该种实现简单明了,代码的可读性高,容易书写,因为对于一些参数类型复杂的函数模板,特化时特别给出,因此函数模板不建议特化。 

2、类模板特化

全特化

全特化即是将模板参数列表中所有的参数都确定化。
#include <iostream>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;
};void TestVector()
{Data<int, int> d1;Data<int, char> d2;
}int main()
{TestVector();return 0;
}

偏特化

任何针对模版参数进一步进行条件限制设计的特化版本。
部分特化 :
将模板参数类表中的一部分参数特化。
// 将第二个参数特化为int
template <class T1>
class Data<T1, int>
{
public:Data() { cout << "Data<T1, int>" << endl; }
private:T1 _d1;int _d2;
};
参数更进一步的限制:
偏特化并不仅仅是指特化部分参数,而是针对模板参数更进一步的条件限制所设计出来的一个特化版本。
#include <iostream>template <class T1, class T2>
class Data<T1*, T2*>
{
public:Data() { std::cout << "Data<T1*, T2*>" << std::endl; }private:T1* _d1;T2* _d2;
};template <class T1, class T2>
class Data<T1&, T2&>
{
public:Data(const T1& d1, const T2& d2): _d1(d1), _d2(d2){std::cout << "Data<T1&, T2&>" << std::endl;}private:const T1& _d1;const T2& _d2;
};void test2()
{Data<double, int> d1;           // 调用基础的模板Data<int, double> d2;           // 调用基础的模板Data<int*, int*> d3;            // 调用特化的指针版本Data<int&, int&> d4(1, 2);      // 调用特化的引用版本
}int main()
{test2();return 0;
}
  1. 在这个示例中,定义了一个模板类 Data,它有两个模板参数 T1 和 T2。通过对 Data 类进行偏特化,针对特定的模板参数类型进行特殊化的实现。
  2. 首先,对 Data<T1*, T2*> 进行偏特化,用于处理指针类型的模板参数。在特化版本中,输出 "Data<T1*, T2*>"。
  3. 然后,对 Data<T1&, T2&> 进行偏特化,用于处理引用类型的模板参数。在特化版本中,输出 "Data<T1&, T2&>"。
  4. 在 test2 函数中,创建了不同类型的 Data 对象,包括基础的模板实例化和特化版本的实例化。根据不同的模板参数类型,会调用相应的模板实现或特化版本的实现。
  5. 这样,通过模板的偏特化,可以针对特定的模板参数类型提供不同的实现,以满足特定的需求。

3、例题

#include <iostream>
using namespace std;template<class T1, class T2>
class Data
{
public:Data() { cout << "Data<T1, T2>" << endl; }
private:T1 _d1;T2 _d2;
};template <class T1>
class Data<T1, int>
{
public:Data() { cout << "Data<T1, int>" << endl; }
private:T1 _d1;int _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, const T2& d2): _d1(d1), _d2(d2){cout << "Data<T1&, T2&>" << endl;}
private:const T1 & _d1;const T2 & _d2;
};int main()
{Data<double, int> d1; Data<int, double> d2; Data<int *, int*> d3; Data<int&, int&> d4(1, 2);return 0;
}

以下程序运行结果正确的是( )

A.Data<T1, T2> Data<T1, int> Data<T1*, T2*> Data<T1&, T2&>

B.Data<T1, int> Data<T1, T2> Data<T1&, T2&> Data<T1*, T2*>

C.Data<T1, int> Data<T1, T2> Data<T1*, T2*> Data<T1&, T2&>

D.Data<T1, T2> Data<T1, T2> Data<T1*, T2*> Data<T1&, T2&>

分析:

Data<double, int> d1; // 调用特化的int版本

Data<int, double> d2; // 调用基础的模板

Data<int *, int*> d3; // 调用特化的指针版本

Data<int&, int&> d4(1, 2); //调用特化的引用版本

故答案为: C 

#include <iostream>
using namespace std;template<typename Type>
Type Max(const Type &a, const Type &b)
{cout<<"This is Max<Type>"<<endl;return a > b ? a : b;
}template<>
int Max<int>(const int &a, const int &b)
{cout<<"This is Max<int>"<<endl;return a > b ? a : b;
}template<>
char Max<char>(const char &a, const char &b)
{cout<<"This is Max<char>"<<endl;return a > b ? a : b;
}int Max(const int &a, const int &b)
{cout<<"This is Max"<<endl;return a > b ? a : b;
} int main()
{Max(10,20);Max(12.34,23.45);Max('A','B');Max<int>(20,30);return 0;
}

以下程序运行结果正确的是( )

A.This is Max This is Max<Type> This is Max<char> This is Max<int>

B.This is Max<int> This is Max<Type> This is Max<char> This is Max<int>

C.This is Max This is Max<int> This is Max<char> This is Max<int>

D.This is Max This is Max<Type> This is Max<char> This is Max

分析:

在调用Max函数时,编译器会优先选择非模板函数。如果没有找到合适的非模板函数,编译器会再去查找模板函数。

Max(10,20);    //能够直接匹配int参数,调动非模板函数

Max(12.34,23.45); //double类型参数没有最佳匹配函数,此时只能调动模板函数

Max('A','B');   //能够直接匹配char参数,调动非模板函数

Max<int>(20,30); //由于直接实例化了函数,因此要调动模板函数,但是,由于进行函数的int特化,所以会调用特化版本的模板函数

故答案为: A

三、模板分离编译

1、定义

一个程序(项目)由若干个源文件共同实现,而每个源文件单独编译生成目标文件,最后将所有目标文件链接起来形成单一的可执行文件的过程称为分离编译模式。
// a.h
template<class T>
T Add(const T& left, const T& right);// a.cpp
template<class T>
T Add(const T & left, const T & right)
{return left + right;
}// main.cpp
#include "a.h"
int main()
{Add(1, 2);Add(1.0, 2.0);return 0;
}

  • 在给定的示例中,模板函数 Add 的声明和定义被分离到了不同的文件中。Add 函数的声明位于头文件 "a.h" 中,而定义位于源文件 "a.cpp" 中。
  • 然后,在主文件 "main.cpp" 中,通过包含 "a.h" 头文件来使用 Add 函数。 然而,这样的分离编译方式会导致编译错误。这是因为模板的实例化需要在编译时进行,而编译器在编译 "main.cpp" 时无法看到 "a.cpp" 中的模板定义,这是因为在C++中,模板的实例化是在编译时进行的。当编译器编译一个源文件时,它只能看到当前源文件中的代码以及通过包含的头文件中的代码。编译器无法直接访问其他源文件中的代码。
  • 在给定的示例中,"main.cpp" 文件包含了 "a.h" 头文件,其中包含了模板函数 `Add` 的声明。编译器可以看到这个声明,并知道 `Add` 是一个模板函数,但它无法看到模板函数的具体定义,因为定义位于另一个源文件 "a.cpp" 中。
  • 因此,当编译器在编译 "main.cpp" 时,它无法为 `Add` 函数生成特定类型的实例化代码,因为它没有模板定义的上下文。因此,编译器无法为 Add 函数生成特定类型的实例化代码。

2、解决方法

1. 将声明和定义放到一个文件 "xxx.hpp" 里面或者xxx.h其实也是可以的。推荐使用这种。
template<class T>
T Add(const T& left, const T& right);template<class T>
T Add(const T& left, const T& right)
{return left + right;
}

2. 模板定义的位置显式实例化。这种方法不实用,不推荐使用。

template
double Add<double>(const double& left, const double& right);template
int Add<int>(const int& left, const int& right);

3、模板总结

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

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

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

相关文章

662. 二叉树最大宽度

题目 给你一棵二叉树的根节点 root &#xff0c;返回树的 最大宽度 。 树的 最大宽度 是所有层中最大的 宽度 。 每一层的 宽度 被定义为该层最左和最右的非空节点&#xff08;即&#xff0c;两个端点&#xff09;之间的长度。将这个二叉树视作与满二叉树结构相同&#xff0…

【Python学习】Python学习5-条件语句

目录 【Python学习】Python学习5-条件语句 前言if语句if语句判断条件简单的语句组参考 文章所属专区 Python学习 前言 本章节主要说明Python的条件语句&#xff0c;Python条件语句是通过一条或多条语句的执行结果&#xff08;True或者False&#xff09;来决定执行的代码块。 …

日志服务管理和inode号

一、系统日志管理 1.1系统日志的介绍 在现实生活中&#xff0c;记录日志也非常重要&#xff0c;比如银行的转账记录&#xff0c;飞机上的黑盒子&#xff0c;那么将系统和应用发生的事件记录至日志中&#xff0c;以助于排错和分析使用 日志记录的内容包括&#xff1a; 历史事…

设计模式② :交给子类

文章目录 一、前言二、Template Method 模式1. 介绍2. 应用3. 总结 三、Factory Method 模式1. 介绍2. 应用3. 总结 参考内容 一、前言 有时候不想动脑子&#xff0c;就懒得看源码又不像浪费时间所以会看看书&#xff0c;但是又记不住&#xff0c;所以决定开始写"抄书&qu…

代码随想录算法训练营day6|242.有效的字母异位词、349.两个数组的交集、202.快乐数

哈希表理论基础 建议&#xff1a;大家要了解哈希表的内部实现原理&#xff0c;哈希函数&#xff0c;哈希碰撞&#xff0c;以及常见哈希表的区别&#xff0c;数组&#xff0c;set 和map。 什么时候想到用哈希法&#xff0c;当我们遇到了要快速判断一个元素是否出现集合里的时…

【React系列】React中的CSS

本文来自#React系列教程&#xff1a;https://mp.weixin.qq.com/mp/appmsgalbum?__bizMzg5MDAzNzkwNA&actiongetalbum&album_id1566025152667107329) 一. React中的css方案 1.1. react 中的 css 事实上&#xff0c;css 一直是 React 的痛点&#xff0c;也是被很多开发…

自动生成表结构screw

采用的组件 screw 操作流程&#xff1a; 1、新建springboot 项目 2、引入相关的依赖 <!-- screw核心 --><dependency><groupId>cn.smallbun.screw</groupId><artifactId>screw-core</artifactId><version>1.0.4</version><…

2023 年精选:每个 DevOps 团队都应该了解的 5 种微服务设计模式

微服务彻底改变了应用程序开发世界&#xff0c;将大型整体系统分解为更小、更易于管理的组件。这种架构风格的特点是独立、松散耦合的服务&#xff0c;带来了从可扩展性、模块化到更高的灵活性等众多优势。 DevOps 团队如何最好地利用这种方法来实现最高效率&#xff1f;答案在…

分布式基础概念

分布式基础概念 1 微服务 微服务架构风格&#xff0c;就像是把一个单独的应用程序开发为一套小服务&#xff0c;每个小服务运行在自己的进程中&#xff0c;并使用轻量级机制通信&#xff0c;通常是HTTP API。这些服务围绕业务能力来构建&#xff0c;并通过完全自动化部署机制…

ElasticSearch 集群搭建与状态监控cerebro

单机的elasticsearch做数据存储&#xff0c;必然面临两个问题:海量数据存储问题、单点故障问题。为了解决存储能力上上限问题就可以用到集群部署。 海量数据存储问题:将索引库从逻辑上拆分为N个分片(shard)&#xff0c;存储到多个节点单点故障问题:将分片数据在不同节点备份 (r…

[每周一更]-(第82期):选购NAS中重要角色RAID

网络附加存储&#xff08;NAS&#xff09;在现代数字生活中扮演着至关重要的角色&#xff0c;而对于NAS的选择中&#xff0c;关注RAID的重要性更是不可忽视的。 数据存储和安全越来越受关注&#xff1b; 为什么要使用NAS&#xff1f; 集中式存储&#xff1a; NAS提供了一个集中…

【计算机病毒传播模型】报告:区块链在车联网中的应用

区块链在车联网中的应用 写在最前面题目 - 26 车联网安全汇报演讲稿-删减2后&#xff0c;最终版&#xff08;1469字版本&#xff09;汇报演讲稿-删减1后&#xff08;2555字版本&#xff09;汇报演讲稿-删减前&#xff08;3677字版本&#xff09;1 概述1.1 车联网1.2 区块链1.3 …

Linux离线安装MySQL(rpm)

目录 下载安装包安装MySQL检测安装结果服务启停MySQL用户设置 下载安装包 下载地址&#xff1a;https://downloads.mysql.com/archives/community/ 下载全量包如&#xff1a;(mysql-8.1.0-1.el7.x86_64.rpm-bundle.tar) 解压&#xff1a;tar -xzvf mysql-8.1.0-1.el7.x86_64.…

Docker学习与应用(四)-容器数据卷

1、容器数据卷 1&#xff09;什么是容器数据卷 docker的理念回顾 将应用和环境打包成一个镜像&#xff01; 数据&#xff1f;如果数据都在容器中&#xff0c;那么我们容器删除&#xff0c;数据就会丢失&#xff01;需求&#xff1a;数据可以持久化 MySQL&#xff0c;容器删…

docker kingbase

docker kingbase run 命令 docker run -tid \ -e ENABLE_CIyes \ -e NEED_STARTyes \ -e DB_MODEoracle \ -e DB_USERkingbase \ -e DB_PASSWORD123456 \ --privileged \ -p 4321:54321 \ -v /home/admin/SoftWare/volume/kingbase/userdata/data:/home/kingbase/userdata/da…

IPv6路由协议---IPv6动态路由(OSPFv3-2)

OSPFv3特性 1.OSPFv3基于链路运行 OSPFv3不需要考虑是否配置在同一网段,只要在同一链路,就可以不配置IPv6全局地址而直接建立邻接关系来计算和传递路由信息。 OSPFv2版本是基于IP子网运行 (1)同一链路上的所有节点都必须处于同一个IP子网或网络内。 (2)邻居关系建立的…

Color Control

设计一个优秀的用户界面是一项艰巨的任务。特别是如果你想改变UI的颜色,调整所有元素可能需要花费大量时间。Color Control可以帮助你!在检查器中以可视化的方式将你的项目颜色定义为资源。Color Control为你提供了组件,当你编辑它们时,它们会自动更新你的UI元素。 颜色控制…

共识算法介绍

文章目录 共识算法Paxos 算法三种角色一致性提交算法prepare 阶段accept 阶段commit 阶段 CAP 定理BASE 理论Zookeeper 算法实现三类角色三个数据三种模式四种状态消息广播算法Leader选举算法 共识算法 Paxos 算法 Paxos 算法是莱斯利兰伯特(Leslie Lamport)1990 年提出的一种…

Nacos部署与应用指南

一、前言 Nacos&#xff08;前身为阿里巴巴的Nacos Config和Nacos Discovery&#xff09;是一款开源的分布式服务和配置管理平台&#xff0c;可以帮助开发者更轻松地构建、部署和管理微服务体系结构。本文将详细介绍如何部署Nacos以及如何在应用中使用它。 二、Nacos部署 2.1 …

机器人活动区域 - 华为OD统一考试

OD统一考试 题解: Java / Python / C++ 题目描述 现有一个机器人,可放置于 M x N 的网格中任意位置,每个网格包含一个非负整数编号,当相邻网格的数字编号差值的绝对值小于等于 1 时机器人可以在网格间移动。 问题: 求机器人可活动的最大范围对应的网格点数目。 说明: 网格…