C++初阶(十三) 模板

news2025/1/20 19:10:03

一、非类型模板参数

  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. 链接错误信息: 当模板代码出现问题时,可能会导致编译器产生复杂或晦涩的错误信息,对调试造成困难。​ ​

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

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

相关文章

mysql 2-18

加密与解密函数 其他函数 聚合函数 三者效率 GROUP BY HAVING WHERE和HAVING的区别 子查询 单行子查询和多行子查询 单行比较操作符 多行比较操作符 把平均工资生成的结果当成一个新表 相关子查询 EXISTS 一条数据的存储过程 标识符命名规则 创建数据库 MYSQL的数据类型 创建表…

人工智能专题:2024亚太地区生成式人工智能应用与监管报告

今天分享的是人工智能系列深度研究报告&#xff1a;《人工智能专题&#xff1a;2024亚太地区生成式人工智能应用与监管报告》。 &#xff08;报告出品方&#xff1a;德勤&#xff09; 报告共计&#xff1a;20页 来源&#xff1a;人工智能学派 知识更新&#xff1a;了解传统…

按键控制LED和光敏传感器控制蜂鸣器

按键控制LED 把两个按键分别接在PB11、PB1上面&#xff0c;两个LED接在PA1和PA2上面 main.c#include "stm32f10x.h" // Device header #include "Delay.h" #include "LED.h" #include "Key.h"uint8_t keynum; //全局…

激光跟踪仪|6D跟踪仪测量大尺寸空间姿态

标题理解激光跟踪仪的工作原理与应用 激光跟踪仪基于激光干涉和测距原理&#xff0c;通过发射和接收激光束来实现对目标物体的跟踪和测量。它是将激光照射到接触测量目标物的目标&#xff08;使用反射器等&#xff09;上&#xff0c;然后经目标反射的激光返回发光源&#xff0…

【Java】图解 JVM 垃圾回收(一):GC 判断策略、引用类型、垃圾回收算法

图解 JVM 垃圾回收&#xff08;一&#xff09; 1.前言1.1 什么是垃圾1.2 内存溢出和内存泄漏 2.垃圾回收的定义与重要性3.GC 判断策略3.1 引用计数算法3.2 可达性分析算法 4.引用类型5.垃圾回收算法5.1 标记-复制&#xff08;Copying&#xff09;5.2 标记-清除&#xff08;Mark…

算法学习系列(三十五):贪心(杂)

目录 引言一、合并果子&#xff08;Huffman树&#xff09;二、排队打水&#xff08;排序不等式&#xff09;三、货仓选址&#xff08;绝对值不等式&#xff09;四、耍杂技的牛&#xff08;推公式&#xff09; 引言 上一篇文章也说过了这个贪心问题没有一个规范的套路和模板&am…

Spring 事务原理总结七

今天是二零二四年二月十八&#xff0c;农历正月初九。同时今天也是农历新年假期后的第一个工作日。我的内心既兴奋&#xff0c;又担忧&#xff0c;更急躁。兴奋是因为假期后的第一个工作日工作轻松&#xff1b;担忧是因为经过了这么长时间&#xff0c;我依旧没搞明白Spring事务…

物奇平台DRC动态范围控制修改方法

物奇平台DRC动态范围控制修改 是否需要申请加入数字音频系统研究开发交流答疑群(课题组)?可加我微信hezkz17, 本群提供音频技术答疑服务,+群赠送语音信号处理降噪算法,蓝牙耳机音频,DSP音频项目核心开发资料, 音频 DRC 是指动态范围控制(Dyna

Pytest测试技巧之Fixture:模块化管理测试数据!

在 Pytest 测试中&#xff0c;有效管理测试数据是提高测试质量和可维护性的关键。本文将深入探讨 Pytest 中的 Fixture&#xff0c;特别是如何利用 Fixture 实现测试数据的模块化管理&#xff0c;以提高测试用例的清晰度和可复用性。 什么是Fixture&#xff1f; 在 Pytest 中…

Java入门教程:介绍、优势、发展历史以及Hello World程序示例

Java入门教学 java语言介绍 Java是由Sun Microsystems公司(已被Oracle公司收购)于1995年5月推出的Java面向对象程序设计语言和Java平台的总称。由James Gosling和同事们共同研发&#xff0c;并在1995年正式推出。 Java分为三个体系&#xff1a; JavaSE&#xff08;J2SE&…

【PX4-AutoPilot教程-源码】移植PX4固件到自制NuttX操作系统飞控板的方法

移植PX4固件到自制NuttX操作系统飞控板的方法 找到使用相同&#xff08;或型号相似&#xff09;CPU类型的现有目标并进行复制飞控板的配置文件夹结构firmware.prototype文件default.px4board文件bootloader.px4board文件nuttx-config/bootloader/defconfig文件nuttx-config/nsh…

Code Composer Studio (CCS) - Licensing Information

Code Composer Studio [CCS] - Licensing Information 1. Help -> Code Composer Studio Licensing Information2. Upgrade3. Specify a license fileReferences 1. Help -> Code Composer Studio Licensing Information 2. Upgrade ​​​ 3. Specify a license file …

Paper - CombFold: Predicting structures of large protein assemblies 环境配置

欢迎关注我的CSDN&#xff1a;https://spike.blog.csdn.net/ 本文地址&#xff1a;https://spike.blog.csdn.net/article/details/136153329 CombFold&#xff1a; GitHub&#xff1a;https://github.com/dina-lab3D/CombFoldPaper&#xff1a;predicting structures of large…

详解C语言10大字符串函数【超详细建议点赞收藏】

目录 1. strlen----求字符串长度1.1 函数介绍1.2 函数使用1.3 模拟实现 2. strcpy----字符串拷贝2.1 函数介绍2.2 函数使用3.3 模拟实现 3. strcat----字符串追加3.1 函数介绍3.2 函数使用3.3 模拟实现 4. strcmp----字符串比较4.1 函数介绍4.2 函数使用 5. strncpy----长度受限…

一、直方图相关学习

1、灰度直方图 1.1 基本概念和作用 表示图像中每个灰度级别的像素数量。用于分析图像的亮度分布情况。 1.2 代码示例 参数介绍 hist cv2.calcHist(images, channels, mask, histSize, ranges, hist, accumulate)-images&#xff1a;输入图像的列表。对于灰度图像&#xff0…

APP端网络测试与弱网模拟

当前APP网络环境比较复杂&#xff0c;网络制式有2G、3G、4G网络&#xff0c;还有越来越多的公共Wi-Fi。不同的网络环境和网络制式的差异&#xff0c;都会对用户使用app造成一定影响。另外&#xff0c;当前app使用场景多变&#xff0c;如进地铁、上公交、进电梯等&#xff0c;使…

基于Springboot的新能源充电系统(有报告)。Javaee项目,springboot项目。

演示视频&#xff1a; 基于Springboot的新能源充电系统&#xff08;有报告&#xff09;。Javaee项目&#xff0c;springboot项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层体系结构&a…

AtCoder Regular Contest 172(仅A题)

A - Chocolate 给N个人分巧克力&#xff0c;分的大小是2^Ai * 2^Ai&#xff0c;给你一个大小为H*W的巧克力&#xff0c;问能不能给N个人都分到要求的巧克力。 假设蓝色是h*w的大巧克力&#xff0c;红色的是要分出来的巧克力&#xff08;四个角落都一样&#xff0c;这里用左上…

VMware虚拟机安装CentOS7

对于系统开发来说&#xff0c;开发者时常会需要涉及到不同的操作系统&#xff0c;比如Windows系统、Mac系统、Linux系统、Chrome OS系统、UNIX操作系统等。由于在同一台计算机上安装多个系统会占据我们大量的存储空间&#xff0c;所以虚拟机概念应运而生。本篇将介绍如何下载安…

innoDB page页结构详解

Page是整个InnoDB存储的最基本构件,也是InnoDB磁盘管理的最小单位,与数据库相关的所有内容都存储在这种Page结构里。 Page分为几种类型,常见的页类型有数据页(B+tree Node)Undo页(Undo Log Page)系统页(System Page) 事务数据页(Transaction System Page)等 Page 各…