C++模板总结

文章目录

  • 写在前面
  • 1. 函数模板
    • 1.1 函数模板的概念
    • 1.2 函数模板的原理
    • 1.3 函数模板的实例化
    • 1.4 函数模板的实例化模板参数的匹配原则
  • 2. 类模板
  • 3. 非类型模板参数
  • 4. 模板的特化
    • 4.1 概念
    • 4.2 函数模板特化
    • 4.3 类模板特化
  • 5. 模板分离编译
  • 6. 总结

写在前面

进入C++以后,C++支持了函数重载也就是在同一作用域中可以存在功能类似的同名函数,例如swap函数来完成int,double,char等数据类型的交换。

void swap(int& e1, int& e2)
{int tmp = e1;e1 = e2;e2 = tmp;
}
void swap(double& e1, double& e2)
{double tmp = e1;e1 = e2;e2 = tmp;
}
void swap(char& e1, char& e2)
{char tmp = e1;e1 = e2;e2 = tmp;
}

但是函数重载面临的问题是:

  1. 重载的函数仅仅是类型不同,功能是类似,所以代码复用率比较低,而且一旦有新类型出现,就需要用户自己 增加对应的函数。
  2. 代码的可维护性比较低,一个出错可能所有的重载均出错

如果在C++中,能够存在这样一个模具,通过给这个模具中填充不同材料(类型),来获得不同材料的铸件(即生成具体类型的代码)。类似于活字印刷术,模板可以帮助我们快速生成我们所需的代码。
在这里插入图片描述

巧的是前人早已将树栽好,我们只需在此乘凉。
泛型编程:编写与类型无关的通用代码,是代码复用的一种手段。模板是泛型编程的基础
在这里插入图片描述

1. 函数模板

1.1 函数模板的概念

  1. 函数模板概念:函数模板代表了一个函数家族,该函数模板与类型无关,在使用时通过传递不同的类型参数来实例化不同类型的函数。这种机制使得代码更加简洁和通用,减少了重复代码的编写
  2. 函数模板格式:定义一个函数模板时,需要使用关键字 template或者class(切记:不能使用struct代替class),后跟一个模板参数列表。
    template<typename T1, typename T2,…,typename Tn>
    返回值类型 函数名(参数列表){}

    以下是一个简单的例子,展示了如何使用模板来创建一个通用的函数模板,它实现了两个数值的交换:
//template <typename T>
template <class T>
void swap(T& a, T& b) 
{T temp = a;a = b;b = temp;
}

1.2 函数模板的原理

大家都知道,瓦特改良蒸汽机,人类开始了工业革命,解放了生产力。机器生产淘汰掉了很多手工产品。本质是什么,重复的工作交给了机器去完成。有人给出了论调:懒人创造世界。马云曾经说过:"懒不是傻懒,如果你想少干,就要想出懒的方法。要懒出风格,懒出境界"。而函数模板是一个蓝图,它本身并不是函数,是编译器根据使用方式产生特定具体类型函数的模具。所以模板就是将本来应该我们做的重复的事情交给了编译器。

在编译器编译阶段,对于模板函数的使用,编译器需要根据传入的实参类型来推演生成对应类型的函数以供调用。比如:当用double类型使用函数模板时,编译器通过对实参类型的推演,将T确定为double类型,然后产生一份专门处理double类型的代码。通过下面例子来理解一下:

#include <iostream>
using namespace std;
//函数模板
template<class T>
void Swap(T& e1, T& e2)
{T tmp = e1;e1 = e2;e2 = tmp;
}int main()
{int a = 10, b = 20;double x = 10, y = 20;Swap(a, b);Swap(x, y);return 0;
}

在这里插入图片描述
通过汇编代码再看一下上面的两次Swap函数调用,也能看出调用的函数不是同一个,因为它们call的地址都不同。
在这里插入图片描述

1.3 函数模板的实例化

用不同类型的参数使用函数模板时,称为函数模板的实例化。模板参数实例化分为:隐式实例化和显示实例化

  1. 隐式实例化:让编译器根据实参推演模板参数的实际类型。
    例如上面的Swap(a, b) 和 Swap(x, y),编译器根据传递给模板函数的参数自动推导出模板参数的类型。实际上,在大多数情况下编译器都能够推导出模板参数的类型,但在下面这种情况,编译器推导不出来模板参数的具体类型,导致编译错误:
    在这里插入图片描述
    这是因为当调用 Add(e1, e2) 时,e1 的类型是 int,而 e2 的类型是 double,这种情况下编译器无法自动推导出模板参数 T,因为 int 和 double 是不同的类型。
    解决办法一:自己来强制转化,使得e1和e2类型相同。
    在这里插入图片描述
    解决办法二:显示实例化,下面就来介绍一下什么是显示实例化。

  2. 显示实例化:在函数名后的<>中指定模板参数的实际类型。
    在调用 Add<int> 时,将 e2 转换为 int 类型。
    在调用 Add<double> 时,将 e1 转换为 double 类型。
    通过显式类型转换,确保传递给模板函数的参数类型一致,从而避免编译错误。
    在这里插入图片描述

1.4 函数模板的实例化模板参数的匹配原则

  1. 一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这个非模板函数。
    在这里插入图片描述
  2. 对于非模板函数和同名函数模板,如果其他条件都相同,在调动时会优先调用非模板函数而不会从该模板产生出一个实例。如果模板可以产生一个具有更好匹配的函数, 那么将选择模板。
    在这里插入图片描述
  3. 模板函数不允许自动类型转换,但普通函数可以进行自动类型转换。
    在C++中,模板函数不会进行隐式类型转换来匹配参数类型,而普通函数会进行隐式类型转换。如果模板函数不能完全匹配参数类型,编译器将尝试调用非模板函数,这时可能会发生隐式类型转换。在这里插入图片描述

2. 类模板

  1. 类模板的定义格式:
    template<class T1, class T2, …, class Tn>
    class 类模板名
    {
    // 类内成员定义
    };

    这里,typename 是一个关键字,用于指定 T 是一个模板参数。你也可以使用 class 关键字来代替 typename,它们在这里是等价的。
    在类模板中,如果要在类外定义成员函数,则需要在定义成员函数时提供模板参数列表。这是为了让编译器知道这些函数是属于哪个模板类的实例。下面的例子,展示了如何在类外定义类模板的成员函数:
// 类模板定义
template <class T>
class MyClass 
{
private:T data;
public:MyClass(T d);       // 构造函数声明void Print();     // 成员函数声明
};// 在类外定义构造函数
template <class T>
MyClass<T>::MyClass(T d) : data(d) {}// 在类外定义成员函数
template <class T>
void MyClass<T>::Print() 
{cout << data << endl;
}
  1. 类模板的实例化
    类模板实例化与函数模板实例化不同,类模板实例化需要在类模板名字后跟<>,然后将实例化的类型放在<>中即可,类模板名字不是真正的类,而实例化的结果才是真正的类。实例化类模板意味着根据模板定义创建具体的类,这些具体类使用特定的数据类型。
int main()
{MyClass<int> m1;MyClass<double> m1;return 0;
}

3. 非类型模板参数

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

#include <iostream>
#include <assert.h>using namespace std;
namespace zzb
{template<class T, size_t N = 10>class array{public:T& operator[](size_t index){assert(index >= 0 && index < _size);return _nums[index];}const T& operator[](int index) const{assert(index >= 0 && index < _size);return _nums[index];}size_t size() const{return _size;}size_t empty()const {return 0 == _size;}private:T _nums[N];size_t _size = N;};
}int main()
{zzb::array<int, 10> nums;return 0;
}

ps:浮点数、类对象以及字符串是不允许作为非类型模板参数的,也就说只有整数类型(包括枚举)可以作为非类型模板参数
在这里插入图片描述
而且非类型模板参数必须在编译期确定,这意味着它们的值或大小必须在编译时就能确定,而不能依赖于运行时的计算或输入。这样做是为了在编译期间能够生成对应的代码,以便在程序运行时能够直接使用这些参数值

4. 模板的特化

4.1 概念

模板的特化是允许我们为特定类型提供定制的实现,通常情况下使用模板可以实现一些与类型无关的代码,但对于一些特殊类型的可能会得到一些错误的结果,需要特殊处理,在这些情况下是非常有用的。
比如:我们定义一个通用的模板函数来进行小于比较。

// 通用模板函数,用于比较两个值
template <typename T>
bool Less(const T& a, const T& b) 
{return a < b;
}

当我们调用函数模板来比较两个整数时,发现可以正常比较。
在这里插入图片描述
当传参传过去的是指针时,它是按照指针的大小来比较的,不是按照指针指向的内容来比比较的,不符合我们的预期。
在这里插入图片描述
可以看到,Less绝对多数情况下都可以正常比较,但是在特殊场景下就得到错误的结果。上述示例中,x是小于y的,但是Less内部并没有比较a和b指向的对象内容,而按照地址的大小比较的,这就无法达到预期而错误。此时,就需要对模板进行特化。即:在原模板类的基础上,针对特殊类型所进行特殊化的实现方式。模板特化中分为函数模板特化与类模板特化。

4.2 函数模板特化

函数模板的特化步骤:
1.必须要先有一个基础的函数模板,它可以处理大多数情况。

// 通用模板函数,用于比较两个值
template <typename T>
bool Less(const T& a, const T& b) 
{return a < b;
}

2.关键字template后面接一对空的尖括号<>。
3.函数名后跟一对尖括号,尖括号中指定需要特化的类型。
4.函数形参表: 必须要和模板函数的基础参数类型完全相同,如果不同编译器可能会报一些奇怪的错误。

//函数模板的特化
template <>
bool Less<int*>(const int*& a, const int*& b) 
{return *a < *b;
}

此时传参再传过去指针时,它就是按照指针指向的内容来比较的,符合我们的预期。这是因为特化模板在模板实例化时会优先于通用模板
在这里插入图片描述
我们可以看出上面的特化版本看着特别奇怪 const 写在int* 后面,因此一般情况下如果函数模板遇到不能处理或者处理有误的类型,为了实现简单通常都是将该函数直接给出。

bool Less(const int* a, const int* b)
{return *a < *b;
}

该种实现简单明了,代码的可读性高,容易书写,因为对于一些参数类型复杂的函数模板,特化时特别给出,因此函数模板不建议特化

4.3 类模板特化

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

template<class T>
struct Less
{bool operator()(const T& e1, const T& e2){return e1 < e2;}
};

调用sort函数排序一个整数数组,发下可以直接排序。
在这里插入图片描述
调用sort函数排序一个整型指针数组,可以直接排序,但是结果错误。
在这里插入图片描述
同理,这里也需要提供int*的特化版本,而特化分为全特化与偏特化。

1.全特化:将模板参数列表中所有的参数都确定化。

template<>
struct Less<int*>
{bool operator()(int* e1, int* e2){return *e1 < *e2;}
};

上面提供的就是int*的全特化版本,模板参数列表中所有的参数都确定化了,此时运行程序,结果正确。
在这里插入图片描述
2.偏特化:任何针对模版参数进一步进行条件限制设计的特化版本。
偏特化有以下两种表现方式

  • 参数更进一步的限制:偏特化并不仅仅是指特化部分参数,而是针对模板参数更进一步的条件限制所设计出来的一个特化版本。
template<class T>
struct Less<T*>
{bool operator()(T* e1, T* e2){return *e1 < *e2;}
};

上面提供的就是T的偏特化版本,对模板参数列表中的参数进行了更进一步的条件限制,不仅能匹配int类型的,是能匹配所有类型的指针。
此时运行程序,结果也是正确的。
在这里插入图片描述

  • 偏特化的另一种表现方式是部分特化:将模板参数类表中的一部分参数特化。
    例如有如下类模板:
template <typename T1, typename T2>
class MyClass 
{
public:void print(){cout << "MyClass <T1, T2>" << endl;}
};

对上面类进行部分特化:

template <typename T1>
class MyClass<T1, int>
{
public:void print(){cout << "MyClass <T1, int>" << endl;}
};

MyClass<T1, int> 是对第二个模板参数 T2 为 int 的情况进行的特化,在main 函数中,分别调用了基础模板和部分特化模板,可以看到编译器根据传入的类型选择了合适的模板版本。
在这里插入图片描述

5. 模板分离编译

分离编译:一个程序(项目)由若干个源文件共同实现,而每个源文件单独编译生成目标文件,最后将所有目标文件链接起来形成单一的可执行文件的过程称为分离编译模式。 关于编译链接的相关知识可以看下之前的写的文章:详解C语言的编译与链接,这里不再赘述。
而模板分离编译的分离编译是将模板的声明和定义分离到不同的文件中,通常是声明放到xxx.h,定义放到xxx.cpp。
假如有以下场景,模板的声明与定义分离开,在头文件中进行声明,源文件中完成定义:
1.我们首先在头文件中声明函数模板,但不包括具体的实现。

//Swap.h
template<class T>
void Swap(T& e1, T& e2);

2.然后创建一个单独的实现文件,包含模板的完整定义。


//Swap.cpp
#include "Swap.h"
template<class T>
void Swap(T& e1, T& e2)
{T tmp = e1;e1 = e2;e2 = tmp;
}

3.在使用模板的源文件test.cpp中,包含头文件Swap.h,然后调用模板Swap来完成两个数的交换。

//test.cpp
#include <iostream>
#include "Swap.h"
using namespace std;int main()
{int x = 10, y = 20;cout << "交换前 x: " << x << " " << "y: " << y << endl;Swap(x, y);cout << "交换后 x: " << x << " " << "y: " << y << endl;return 0;
}

运行程序,发现报如下错误:
在这里插入图片描述
这是因为,一个C/C++程序要变成一个可执行程序,要经过如下过程:
在这里插入图片描述
而编译器对工程中的源文件是单独编译的,因此:
在这里插入图片描述

对于上面的问题有如下两个解决办法:
1.在模板定义的位置显式实例化(不推荐)
在这里插入图片描述
这种办法存在一定的弊端,当再有两个double类型的变量调用函数模板完成交换时,又会报错。
在这里插入图片描述
要想解决这个错误,就只能在模板定义的地方再去实例化一份Swap<double>的出来。因此每当出现一个新类型去调用这个模板的时候,都需要去模板定义的地方去显示实例化一份出来。这种显式实例化方式只适用于我们能预先知道所需类型的情况且这在泛型编程中并不常见,下面来介绍另一种解决方式。
2. 将声明和定义放到同一个文件 “xxx.hpp” 里面或者xxx.h(推荐)

//Swap.h
template<class T>
void Swap(T& e1, T& e2)
{T tmp = e1;e1 = e2;e2 = tmp;
}

这也就意味着,当在test.cpp中包含Swap.h的以后,在test.cpp中可以找到函数模板的完整定义,因此可以根据需求实例化出任意需要的函数,就不会报链接错误了。

在这里插入图片描述

6. 总结

关于模板的优缺点总结如下:
优点:
1.代码复用:通过模板,可以编写通用的代码,而不需要为每个数据类型编写单独的代码,实现了代码的高效复用。模板的使用加速了开发过程,因为可以更容易地引入新的数据类型,而无需修改大量现有代码。C++ 标准模板库(STL)的产生就是模板技术的重要应用之一,提供了大量高效的容器、算法和迭代器,极大地提高了开发效率。
增强代码的灵活性:
2.泛型编程:模板允许编写与类型无关的代码,可以处理不同类型的数据,增强了代码的通用性和灵活性。
缺陷:
1.代码膨胀:模板的实例化会导致生成多个实例代码,可能导致二进制文件变大,尤其是在大型项目中。模板实例化需要更多的编译时间,特别是当模板被广泛使用时,编译时间会显著增加。
2. 模板编译错误信息复杂:模板编译错误信息通常非常复杂且冗长,不易理解和调试,定位错误可能需要更多的时间和经验。

至此,本片文章就结束了,若本篇内容对您有所帮助,请三连点赞,关注,收藏支持下。

创作不易,白嫖不好,各位的支持和认可,就是我创作的最大动力,我们下篇文章见!

如果本篇博客有任何错误,请批评指教,不胜感激 !!!
在这里插入图片描述

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

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

相关文章

智能小车——初步想法

需要参考轮趣的智能小车自己搭建一台智能机器人&#xff0c;这里从底层控制开始逐步搭建。 控制模式 之后要自行搭建智能小车&#xff0c;所以将轮趣的底盘代码进行学习&#xff0c;根据开发手册先大致过一遍需要的内容。 有做很多个控制方法&#xff0c;包括了手柄、串口、…

Linux下常见压缩文件tar.xz、tar.bz2、tar.gz的区别和详解

文章目录 tar.xz tar.bz2 tar.gz 的区别三种文件的解压方式tar.xz的解压三种压缩文件的创建方式 tar.xz tar.bz2 tar.gz 的区别 这三个文件扩展名都表示压缩后的档案文件&#xff0c;但它们使用不同的压缩算法。 tar.xz: tar 代表 Tape Archive&#xff0c;它是一种将多个文件…

【若依管理系统】注意事项

1.前端字段必填 rules: {sceneName: [{ required: true, message: "场景名称不能为空", trigger: "blur" }],orderNum: [{ required: true, message: "显示排序不能为空", trigger: "blur" }], }, 2.IDEA&#xff0c;默认以debug模式…

Agents 要点

一、Agents概念 人类是这个星球上最强大的 Agent。Agent是一个能感知并自主地采取行动的实体&#xff0c;这里的自主性极其关键&#xff0c;Agent要能够实现设定的目标&#xff0c;其中包括具备学习和获取知识的能力以提高自身性能。 关键点&#xff1a;感知环境、自主决策、具…

element plus 实现跨页面+跨tab栏多选

文章目录 element plus 层面数据层面 菜鸟好久没写博客了&#xff0c;主要是没遇见什么很难的问题&#xff0c;今天碰见了一个没有思路的问题&#xff0c;解决后立马来和大家伙分享了&#xff01; 菜鸟今天要实现一个需求&#xff0c;就是&#xff1a;实现跨页面跨 tab栏 多选…

CVE-2024-27292:Docassemble任意文件读取漏洞复现 [附POC]

文章目录 CVE-2024-27292&#xff1a;Docassemble任意文件读取漏洞复现 [附POC]0x01 前言0x02 漏洞描述0x03 影响版本0x04 漏洞环境0x05 漏洞复现1.访问漏洞环境2.构造POC3.复现 0x06 修复建议 CVE-2024-27292&#xff1a;Docassemble任意文件读取漏洞复现 [附POC] 0x01 前言 …

冒泡排序与其C语言通用连续类型排序代码

冒泡排序与其C语言通用连续类型排序代码 冒泡排序冒泡排序为交换排序的一种&#xff1a;动图展示&#xff1a;冒泡排序的特性总结&#xff1a;冒泡排序排整型数据参考代码&#xff08;VS2022C语言环境&#xff09;&#xff1a; 冒泡排序C语言通用连续类型排序代码对比较的方式更…

法律行业守护神:知识库+AI大模型,解锁企业知识全周期管理

在法律行业中&#xff0c;搭建一个有效的知识库并进行企业知识全生命周期管理确实是一项不小的挑战。法律环境的复杂性和不断变化的法规要求企业必须持续更新和维护其知识库&#xff0c;以确保所有信息的准确性和实时性。 这种系统化的信息管理不仅有助于提高律师和法律顾问的…

打卡第9天-----字符串

我在自学的时候,看了卡尔的算法公开课了,有些题目我就照葫芦画瓢写了一遍js代码,差不多都写出来了,有暴力解法,有卡尔推荐的思路和方法。话不多说,直接上题上代码吧: 一、翻转字符串里的单词 leetcode题目链接:151. 反转字符串中的单词 题目描述: 给你一个字符串 s…

5个自动化面试题,助你过关斩将!

面试时&#xff0c;自动化是软件测试高频面试内容&#xff0c;通过学习和准备面试题&#xff0c;你会对可能遇到的问题有所准备&#xff0c;从而减轻面试时的紧张感&#xff0c;让你在面试中稳操胜券&#xff01; 今天&#xff0c;分享一些在面试中可能会遇到的自动化测试面试…

大学生暑假“三下乡”社会实践工作新闻投稿指南请查收!

近年来&#xff0c;大学生暑期“三下乡”社会实践工作方兴未艾&#xff0c;越来越多的大学生通过参与“三下乡”实践工作&#xff0c;走出校园&#xff0c;深入基层&#xff0c;体验农村生活&#xff0c;服务农民&#xff0c;促进农村经济社会发展&#xff0c;实现了理论与实践…

算能科技,致力于成为全球领先的通用算力供应商

算能致力于成为全球领先的定制算力提供商&#xff0c;专注于RISC-V、TPU处理器等算力产品的研发和推广应用。公司遵循全面开源开放的生态理念&#xff0c;携手行业伙伴推动RISC-V高性能通用计算产业落地&#xff1b;打造覆盖“云、边、端”的全场景产品矩阵&#xff0c;为数据中…

【eNSP模拟实验】三层交换机实现VLAN通信

实验需求 让PC1和PC2能够互相通讯&#xff0c;其中PC1在vlan10中&#xff0c;PC2在vlan20中。 实验操作 首先把PC1和PC2都配置好ip&#xff0c;配置好之后&#xff0c;点击右下角的应用 然后&#xff0c;在S2交换机&#xff08;S3700&#xff09;上做如下配置 #进入系统 <…

【Redis】Redis十大类型

文章目录 前言一、string字符串类型二、List列表类型三、 Hash表四、 Set集合五、 ZSet有序集合六、 GEO地理空间七、 HyperLogLog基数统计八、Bitmap位图九、bitfield位域十、 Stream流10.1 队列指令10.2 消费组指令10.3 ACK机制 前言 redis是k-v键值对进行存储&#xff0c;k…

【最经典的79个】软件测试面试题(内含答案)提前备战“金九银十”

001.软件的生命周期(prdctrm) 计划阶段(planning)-〉需求分析(requirement)-〉设计阶段(design)-〉编码(coding)->测试(testing)->运行与维护(running maintrnacne) 测试用例 用例编号 测试项目 测试标题 重要级别 预置条件 输入数据 执行步骤 预期结果 0002.问&…

“论软件维护方法及其应用”写作框架,软考高级论文,系统架构设计师论文

论文真题 软件维护是指在软件交付使用后&#xff0c;直至软件被淘汰的整个时间范围内&#xff0c;为了改正错误或满足 新的需求而修改软件的活动。在软件系统运行过程中&#xff0c;软件需要维护的原因是多种多样的&#xff0c; 根据维护的原因不同&#xff0c;可以将软件维护…

CVE-2024-34351 漏洞复现

CVE-2024-34351&#xff0c;由Next.js异步函数createRedirectRenderResult导致的SSRF。 影响版本&#xff1a;13.4.0< Next.js < 14.1.1 参考文章&#xff1a; Next.js Server-Side Request Forgery in Server Actions CVE-2024-34351 GitHub Advisory Database Gi…

RK3568平台开发系列讲解(内存篇)Linux进程内存的消耗统计

🚀返回专栏总目录 文章目录 一、VSS(Virtual Set Size)二、RSS(Resident Set Size)三、PSS(Proportional Set Size)四、USS(Unique Set Size)五、其他工具Linux 提供了多种进程内存占用的度量指标, 它们反映了不同的内存使用特征: VSS 反映进程虚拟内存总需求, 包括未…

Nature Communications|柔性无感智能隐形眼镜(柔性传感/可穿戴电子/柔性电子)

南京大学徐飞(Fei Xu)、陆延青(Yanqing Lu)、陈烨(Ye Chen)和江苏省人民医院袁松涛(Songtao Yuan)团队,在《Nature Communications》上发布了一篇题为“Frequency-encoded eye tracking smart contact lens for human–machine interaction”的论文。论文内容如下: 一、 摘…

一起了解开发表单设计器的几大优势

实现提质、降本、增效的办公效率&#xff0c;可以随时来了解低代码技术平台、开发表单设计器。它们可视化操作界面、更灵活、好维护的优势特点&#xff0c;使得其在激烈的市场竞争中拥有更多强劲的市场竞争力&#xff0c;是提升办公效率的理想武器。今天&#xff0c;小编就向大…