C++初阶(十三) 模板

一、非类型模板参数

  1. 模板参数分类类型形参与非类型形参。
  2. 类型形参即:出现在模板参数列表中,跟在class或者typename之类的参数类型名称
  3. 非类型形参,就是用一个常量作为类(函数)模板的一个参数,在类(函数)模板中可将该参数当成常量来使用。
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;


class Date
{
public:
	Date(int year = 1900, int month = 1, int day = 1)
		: _year(year)
		, _month(month)
		, _day(day)
	{}
	bool operator<(const Date& d)const
	{
		return (_year < d._year) ||
			(_year == d._year && _month < d._month) ||
			(_year == d._year && _month == d._month && _day < d._day);
	}
	bool operator>(const Date& d)const
	{
		return (_year > d._year) ||
			(_year == d._year && _month > d._month) ||
			(_year == d._year && _month == d._month && _day > d._day);
	}

	friend ostream& operator<<(ostream& _cout, const Date& d);
private:
	int _year;
	int _month;
	int _day;
};

ostream& operator<<(ostream& _cout, const Date& d)
{
	_cout << d._year << "-" << d._month << "-" << d._day;
	return _cout;
}

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

// //解决方法:
// //函数模板的特化
//template<>
//bool Less<Date*>(Date* left, Date* right)
//{
//	return *left < *right;
//}

bool Less(Date* left, Date* right)
{
	return *left < *right;
}

int main()
{
	cout << Less(1, 2) << endl;

	Date d1(2024, 2, 7);
	Date d2(2024, 2, 8);
	cout << Less(d1, d2) << endl; // 可以比较,结果正确

	Date* p1 = &d1;
	Date* p2 = &d2;
	cout << Less(p1, p2) << endl; 
	/*
	 可以比较,结果错
	 因为比较的其实是 p1 和 p2 两个指针变量的地址,
	而不是它们所指向的 Date 对象的值。
	*/

	Date* p3 = new Date(2024, 2, 7);
	Date* p4 = new Date(2024, 2, 8);
	cout << Less(p3, p4) << endl;  
		/*可以比较,结果错
		* p3 和 p4 是通过 new 运算符创建的 Date* 类型的指针,
		分别指向了两个动态分配的 Date 对象。
		当你调用 Less(p3, p4) 时,实际上是在比较这两个指针的地址,
		而不是它们所指向的对象的值。
由于这两个指针指向的是动态分配的对象,
它们的地址是不确定的,因此比较结果错误。
		*/
	return 0;
}

注意:

  • 1. 浮点数、类对象以及字符串是不允许作为非类型模板参数的。
  • 2. 非类型的模板参数必须在编译期就能确认结果。 

 二、模板的特化

2.1 概念

在原模板类的基础上,针对特殊类型所进行特殊化的实现方式

模板特化中分为函数模板特化与类模板特化。

2.2  函数模板特化

函数模板的特化步骤:

  • 1. 必须要先有一个基础的函数模板
  • 2. 关键字template后面接一对空的尖括号<>
  • 3. 函数名后跟一对尖括号,尖括号中指定需要特化的类型
  • 4. 函数形参表: 必须要和模板函数的基础参数类型完全相同,如果不同编译器可能会报一些奇怪的错误。
// 函数模板 -- 参数匹配
template<class T>
bool Less(T left, T right) {
    return left < right;
}
// 对Less函数模板进行特化
template<>
bool Less<Date*>(Date* left, Date* right) {
    return *left < *right;
}
int main() {
    cout << Less(1, 2) << endl;
    Date d1(2024, 2, 7);
    Date d2(2024, 2, 8);
    cout << Less(d1, d2) << endl;
    Date* p1 = &d1;
    Date* p2 = &d2;
    cout << Less(p1, p2) << endl; // 调用特化之后的版本,而不走模板生成了
    return 0;
}

 

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

函数模板的特化可以用于为特定类型提供定制的实现,从而覆盖默认的模板实现。尽管函数模板特化在某些情况下是有用的,但通常情况下,不建议频繁使用函数模板的特化。以下是一些原因:

  1. 代码复杂性增加:函数模板的特化会导致代码的维护和理解变得更加困难。每个特化版本都需要单独编写和维护,如果有多个特化版本,代码的复杂性将大大增加。

  2. 可读性和可维护性下降:特化版本的实现可能与原始模板实现不一致,这会使代码更难以理解和调试。同时,当需求变化时,特化版本可能需要相应地更新,这会增加维护的负担。

  3. 可移植性受限:特化版本可能依赖于特定的编译器行为或库支持,这会导致代码在不同的编译环境中的行为不一致。

  4. 概念耦合度增加:函数模板的特化可能会导致代码之间的紧密耦合,使得代码更难以重用和扩展。

2.3 类模板特化

2.3.1全特化

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

#include <iostream>
using namespace std;

// 定义模板类 Data
template<class T1, class T2>
class Data {
  public:
    Data() {
        cout << "Data<T1, T2>" << endl;
    }
  private:
    T1 _d1;
    T2 _d2;
};

// 对 Data 类进行全特化,特化为 Data<int, char>
template<>
class Data<int, char> {
  public:
    Data() {
        cout << "Data<int, char>" << endl;
    }
  private:
    int _d1;
    char _d2;
};

// 创建 Data<int, int> 和 Data<int, char> 两个对象实例
void TestVector() {
    Data<int, int> d1; 
    Data<int, char> d2; 
}

int main() {
    TestVector();
    return 0;
}

 

因为Data<int, int> 和 Data<int, char>是不同类型的对象,所以它们的构造函数会被分别调用。对于Data<int, int>,会调用原始模板的构造函数,输出 "Data<T1, T2>";而对于 Data<int, char>,会调用全特化的构造函数,输出 "Data<int, char>"。

 2.3.2 偏特化

偏特化:任何针对模版参数进一步进行条件限制设计的特化版本。比如对于以下原模板类:

template<class T1, class T2>
class Data {
  public:
    Data() {
        cout << "Data<T1, T2>" << endl;
    }
  private:
    T1 _d1;
    T2 _d2;
};

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

  • 部分特化 将模板参数类表中的一部分参数特化。​
// 将第二个参数特化为int
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;
};
void test2() {
    Data<double, int> d1; // 调用特化的int版本
    Data<int, double> d2; // 调用基础的模板 
    Data<int*, int*> d3; // 调用特化的指针版本
    Data<int&, int&> d4(1, 2); // 调用特化的引用版本
}

int main() {
    test2(); 
    return 0;
}

 2.3.3 类模板特化应用示例

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


#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

class Date {
public:
    int year;
    int month;
    int day;

    Date(int year, int month, int day) : year(year), month(month), day(day) {}

    bool operator<(const Date& other) const {
        // 按年、月、日升序排序
        if (this->year == other.year) {
            if (this->month == other.month) {
                return this->day < other.day;
            }
            return this->month < other.month;
        }
        return this->year < other.year;
    }
};

template<class T>
struct Less {
    bool operator()(const T& x, const T& y) const {
        // 通过指针间接比较对象,用于排序
        return x < y;
    }
};

int main() {
    Date d1(2024, 7, 7);
    Date d2(2024, 7, 6);
    Date d3(2024, 7, 8);
    vector<Date> v1;
    v1.push_back(d1);
    v1.push_back(d2);
    v1.push_back(d3);

    // 对 vector v1 中的 Date 对象进行排序
    sort(v1.begin(), v1.end());

    // 输出排序后的结果,结果是日期升序
    for (const auto& date : v1) {
        cout << date.year << "-" << date.month << "-" << date.day << endl;
    }

    cout << "---------------------" << endl;
    vector<Date*> v2;
    v2.push_back(&d1);
    v2.push_back(&d2);
    v2.push_back(&d3);

    // 对存储 Date 指针的 vector v2 中的对象进行排序
    sort(v2.begin(), v2.end(), Less<Date*>());

    // 结果错误日期还不是升序,而v2中放的地址是升序
 // 此处需要在排序过程中,让sort比较v2中存放地址指向的日期对象
 // 但是走Less模板,sort在排序时实际比较的是v2中指针的地址,因此无法达到预期

    // 输出排序后的结果
    for (const auto& ptr : v2) {
        cout << (*ptr).year << "-" << (*ptr).month << "-" << (*ptr).day << endl;
    }

    return 0;
}

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

// 对Less类模板按照指针方式特化
template<>
struct Less<Date*> {
    bool operator()(Date* x, Date* y) const {
        return *x < *y;
    }
};

三 、模板的分离编译

3.1 什么是分离编译

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

 3.2 模板的分离编译

假如有以下场景,模板的声明与定义分离开,在头文件中进行声明,源文件中完成定义:

// 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;
}

 

 

3.3 解决方法

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

// main.cpp
#include "a.hpp"

int main()
{
    Add(1, 2);
    Add(1.0, 2.0);

    return 0;
}

.hpp是一种常见的用于包含C++头文件的命名约定,和.h头文件没有本质上的区别,只是在命名上有所不同。.hpp通常用于包含C++中的类定义、模板定义等,以区分于传统的C语言风格的.h头文件。 

  •  2. 模板定义的位置显式实例化。显式实例化是一种在编译器中明确指定模板函数或类的实例化的方法。它可以用于将模板代码编译成特定类型的函数或类,以提高编译和链接的效率。这种方法不实用,主要是因为它会导致模板的定义与实例化分离,增加了代码的复杂性和维护难度。不推荐使用。
// 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;
}

// 显式实例化
template int Add<int>(const int& left, const int& right);
template double Add<double>(const double& left, const double& right);

// main.cpp
#include "a.h"

int main()
{
    Add(1, 2);
    Add(1.0, 2.0);

    return 0;
}

 四、模板总结

优点:

  1. 代码重用: 使用模板可以编写通用的代码,节省资源,更快的迭代开发,C++的标准模板库(STL)因此而产生,适用于多种数据类型,避免了为每种数据类型都编写相似的代码的重复劳动。
  2. 类型安全: 模板可以提供编译期间的类型检查,从而在一定程度上提高了代码的类型安全性。
  3. 性能优化: 通过编译器根据实际使用情况生成具体的代码,可以针对特定的数据类型进行优化,提高程序的性能。
  4. 灵活性: 可以轻松地扩展模板代码以适应新的数据类型或需求,提高了代码的灵活性和可扩展性。

缺点:

  1. 编译时间: 使用模板会增加编译时间,因为模板代码通常需要在每个使用处进行实例化,导致编译时间增加。
  2. 可读性: 模板代码可能会影响代码的可读性,特别是在模板特化和元编程等高级用法中,代码可能变得晦涩难懂。
  3. 链接错误信息: 当模板代码出现问题时,可能会导致编译器产生复杂或晦涩的错误信息,对调试造成困难。​ ​

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Vue练习4:插槽

预览 <Layout><template #left><div class"left">左边栏区域&#xff0c;宽度适应内容&#xff0c;溢出隐藏</div></template><template #main><div class"main">中间区域</div></template><te…

MySQL性能分析1——查看频次

1、查看执行频次 查看当前数据库的INSERT,UPDATE,DELETE,SELECT的访问频次&#xff0c;得到当前数据库是以插入&#xff0c;更新和删除为主还是以查询为主&#xff0c;如果是以插入&#xff0c;更新和删除为主的话&#xff0c;那么优化比重可以轻一点儿。 语法&#xff1a; …

Shiro-07-Authorization 授权

授权 [外链图片转存中…(img-Hy85ZiSz-1708262588217)] 授权&#xff0c;也称为访问控制&#xff0c;是管理对资源访问的过程。 换句话说&#xff0c;控制谁有权访问应用程序中的内容。 授权检查的示例包括&#xff1a;是否允许用户查看此网页&#xff0c;编辑此数据&#…

MyBatisPlus速成

文章目录 MyBatisPlus1&#xff0c;MyBatisPlus入门案例与简介1.1 入门案例步骤1:创建数据库及表步骤2:创建SpringBoot工程步骤3:勾选配置使用技术步骤4:pom.xml补全依赖步骤5:添加MP的相关配置信息步骤6:根据数据库表创建实体类步骤7:创建Dao接口步骤8:编写引导类步骤9:编写测…

沁恒CH32V30X学习笔记03--64位systick

systick CH32F2x 系列产品Cortex-M3 内核自带了一个 24 位自减型计数器(SysTick timer)。支持 HCLK 或 HCLK/8 作为时基,具有较高优先级别(6)。一般可用于操作系统的时基。 CH32V3x 系列产品内核自带了一个 64 位加减计数器(SysTick),支持 HCLK 或者 HCLK/8 作为时基,…

云原生之容器编排实践-基于CentOS7搭建三个节点的Kubernetes集群

背景 前面采用 minikube 作为 Kubernetes 环境来体验学习 Kubernetes 基本概念与操作&#xff0c;这样避免了初学者在裸金属主机上搭建 Kubernetes 集群的复杂度&#xff0c;但是随着产品功能的逐渐完善&#xff0c;我们需要过渡到生产环境中的 K8S 集群模式&#xff1b;而在实…

汽车金融市场研究:预计2029年将达到482亿美元

汽车金融公司作为汽车流通产业链的重要一环&#xff0c;认真贯彻落实国家有关政策&#xff0c;采取多种措施助力汽车产业发展&#xff0c;为促进推动汽车消费、助力畅通汽车产业链、支持稳定宏观经济大盘发挥了积极作用。 益于国内疫情得到有效控制&#xff0c;我国经济持续稳定…

羊大师:羊奶的不同口味带来了什么不同效果

羊大师&#xff1a;羊奶的不同口味带来了什么不同效果 羊奶的口味不同主要是因为其成分和加工方式的差异。尽管口味的变化可能会影响人们对羊奶的喜好程度&#xff0c;但总体而言&#xff0c;不同口味的羊奶在营养价值上并没有明显的差别。 然而&#xff0c;有些品牌的羊奶会添…

【数据结构】图的存储与遍历

图的概念 图是由顶点集合及顶点间的关系组成的一种数据结构&#xff1a;G (V&#xff0c; E) 图分为有向图和无向图 在有向图中&#xff0c;顶点对<x, y>是有序的&#xff0c;顶点对<x&#xff0c;y>称为顶点x到顶点y的一条边(弧)&#xff0c;<x, y>和&l…

聊聊xinput1_3.dll丢失的几种修复方式,xinput1_3.dll文件的作用和重要性

xinput1_3.dll丢失是一个常见且令人困扰的问题。xinput1_3.dll是一个重要的动态链接库文件&#xff0c;用于支持在Windows操作系统上运行的许多游戏和应用程序。当这个文件丢失或损坏时&#xff0c;用户可能会面临无法启动游戏或应用程序的困境。接下来就和大家聊聊xinput1_3.d…

18. 【Linux教程】vim 编辑器

前面小节介绍如何创建文件、移动文件、删除文件&#xff0c;但之前都没有介绍如何修改文件内容&#xff0c;本小节介绍如何使用 vim 编辑器对文件内容进行修改&#xff0c;另外介绍 vim 编辑器的安装和使用。 1. vim 编辑器简介 vim 编辑器是由 vi 发展而来的文本编辑器。它的…

【qt创建线程两种方式】

QT使用线程的两种方式 1.案例进度条 案例解析&#xff1a; 如图由组件一个进度条和三个按钮组成&#xff0c;当点击开始的时候进度条由0%到100%&#xff0c;点击暂停&#xff0c;进度条保持之前进度&#xff0c;再次点击暂停变为继续&#xff0c;点击停止按钮进度条停止。 案…

03_uartLinux内核模块

01_basicLinux内核模块-CSDN博客文章浏览阅读23次。环境IDubuntuMakefilemodules:clean:basic.creturn 0;运行效果。https://blog.csdn.net/m0_37132481/article/details/136157384?csdn_share_tail%7B%22type%22%3A%22blog%22%2C%22rType%22%3A%22article%22%2C%22rId%22%3A%…

禁止电子邮箱地址登录WordPress后台的插件No Login by Email Address

WordPress 4.5及之后的版本增加了使用注册用户的电子邮件地址代替用户名登录的功能&#xff0c;但是大多数个人站长的管理员邮箱地址都是固定&#xff0c;而且到其他站点进行评论留言也是同一个邮箱地址&#xff0c;很容易给一些别有用心的可乘之机&#xff0c;所以禁止WordPre…

备战蓝桥杯---数学之矩阵快速幂基础

我们先不妨看一道题&#xff1a; 看见n的数据范围就知道直接按以前的递归写肯定狗带&#xff0c;那我们有什么其他的方法吗&#xff1f; 下面是分析&#xff1a; 我们就拿斐波那契数列试试手吧&#xff1a; 下面是AC代码&#xff0c;可以当作模板记&#xff1a; #include<b…

二叉树(5)——链式二叉树

续上篇&#xff0c;我们继续讲解链式二叉树第K层的节点个数和查找值为x的节点的代码实现。 1 二叉树第K层的节点个数 思路分析 若为空&#xff0c;返回0不为空&#xff0c;且k1&#xff0c;返回1不为空&#xff0c;且k>1&#xff0c;返回左子树的k-1层右子树的k-1层 代码实…

十大经典排序算法之一--------------堆排序(java详解)

一.堆排序基本介绍&#xff1a; 堆排序是利用堆这种数据结构而设计的一种排序算法&#xff0c;堆排序是一种选择排序&#xff0c;它的最坏&#xff0c;最好&#xff0c;平均时间复杂度均为O(nlogn)&#xff0c;它也是不稳定排序。堆是具有以下性质的完全二叉树&#xff1a;每个…

深度学习-分类任务---经典网络

文章目录 经典网络1 LeNet51.1 模型结构1.2 模型结构1.3 模型特性 2 AlexNet2.1 模型介绍2.2 模型结构2.3 模型解读2.4 模型特性 3 可视化ZFNet-转置卷积3.1 基本的思想及其过程3.2 卷积与转置卷积3.3 卷积可视化3.4 ZFNet和AlexNet比较 4 VGGNet4.1 模型结构4.2 模型特点 5 Ne…

MySQL数据库基础(九):SQL约束

文章目录 SQL约束 一、主键约束 二、非空约束 三、唯一约束 四、默认值约束 五、外键约束&#xff08;了解&#xff09; 六、总结 SQL约束 一、主键约束 PRIMARY KEY 约束唯一标识数据库表中的每条记录。主键必须包含唯一的值。主键列不能包含 NULL 值。每个表都应该有…

全国今日油价一键查询API:轻松了解油价新闻

导语&#xff1a; 随着能源需求的增长&#xff0c;油价成为全球经济的重要指标之一。了解油价的动态变化对于企业和个人来说都至关重要。本文介绍了一款全国今日油价一键查询的API接口&#xff0c;通过该接口可以轻松获取全国各省汽油和柴油的最新价格&#xff0c;并结合油价新…