C++笔记:模板

模板

为什么要学习模板编程

        在学习模板之前,一定要有算法及数据结构的基础,以及重载,封装,多态,继承的基础知识,不然会出现看不懂,或者学会了没办法使用。

        为什么C++会有模板,来看下面的代码。

        add()第一版
#include <iostream>
#include <string>
using namespace std;int add(int a, int b) {return a + b;
}double add(double a, double b) {return a + b;
}string add(string a, string b) {return a + b;
}int main() {cout << add(1, 2) << endl;cout << add(2.1, 3.3) << endl;string a = "hello", b = "world";cout << add(a, b) << endl;return 0;
}

        当我们使用add函数时,不同的类型要去重载实现不同参数的add函数,那么有多少种相同类型进行相加,那么我们就要重载实现多少种add函数,那么就对于我们程序员来说这种方法就很麻烦,那么模板编程就可以帮我们避免这种麻烦。来看下面这段代码:

        add()第二版
#include <iostream>
#include <string>
using namespace std;template<typename T>
T add(T a, T b) {return a + b;
}int main() {cout << add(1, 2) << endl;cout << add(2.1, 3.3) << endl;string a = "hello", b = "world";cout << add(a, b) << endl;return 0;
}

        可以发现第一版我实现了3种add函数,而第二版我只实现了一种add函数,直接少写了很多重复的逻辑代码,这就是为什么需要学习模板编程。

模板编程(泛型编程)

程序 = 算法 + 数据结构

数据结构:可以存储任意类型

算法:能够操作存储任意类型数据的数据结构

        例如vector容器它是能够存储任意类型的顺序表,sort函数可以对任意类型的顺序表进行排序并且还可以自定义排序规则,而这两个例子都是通过模板编程进行实现。

        模板对于我们程序员来说是一种工具,而这种工具我们可以在程序设计中把任意类型进行抽象出来。

模板函数

        例如文章开头的例子,我不知道add中需要传入的参数是什么,那么也不知道具体的返回值是什么,那么我们就需要利用模板编程进行抽象出来一个模板函数,进行可以对任意类型进行处理的函数。

        那么我拿第二版的add函数进行继续探索模板函数,现在我有一个需求是

cout << add(1, 1.2) << endl

        传入的参数是不同的,那么我该如何设计,如下:

#include <iostream>
#include <string>
using namespace std;//template是用来引入模板参数的关键字
//typename先理解为定义一个任意类型的变量
template<typename T, typename U>
T add(T a, U b) {return a + b;
}int main() {cout << add(1, 2) << endl;cout << add(2.1, 3.3) << endl;string a = "hello", b = "world";cout << add(a, b) << endl;cout << add(1, 1.2) << endl;// 结果 2cout << add(1.2, 1) << endl;// 结果 2.2return 0;
}

        那我就加两个模板任意参数就可以了,这样就可以不发生报错了,但是又有一个问题了,我,我把add(1, 1.2)中的参数进行调换了位置,他的结果会不一样,因为我的返回值是T类型,那么对应的就是第一个参数的类型,如果第一个参数是1那么返回值类型就是int,第一个参数是1.2那么返回值类型就是float。

        那么这里会引入一个新的关键字decltype

        decltype:

        这里我们用到的是第二点,也就是判断复杂表达式的结果是什么类型。

#include <iostream>
#include <string>
using namespace std;//template是用来引入模板参数的关键字
//typename先理解为定义一个任意类型的变量
template<typename T, typename U>
//T和U是任意类型
//不管什么类型都有一个默认构造
decltype(T() + U()) add(T a, U b) {return a + b;
}int main() {//int也有可以当作类来使用//所以任意类型都有构造函数int num = int(1);cout << add(1, 2) << endl;cout << add(2.1, 3.3) << endl;string a = "hello", b = "world";cout << add(a, b) << endl;cout << add(1, 1.2) << endl;cout << add(1.2, 1) << endl;return 0;
}

        问题又来了,如果我传入T类型,而这个T类型默认构造被删除了,那这个代码是会发生报错的,那么如何去解决呢,那么这里又引出了一个新的概念返回值后置:

        这里会用到一个新的关键字auto

        

#include <iostream>
#include <string>
using namespace std;//template是用来引入模板参数的关键字
//typename先理解为定义一个任意类型的变量
template<typename T, typename U>
//在原来返回值的位置写一个auto关键字
//auto是用来推到后置返回值的类型
//因为-> 在参数列表后面,根据代码的执行顺序,那么a和b就可以进行使用
//所以可以用decltype进行判断a + b的返回类型
//然后传给auto进行推断,最后确定返回值类型
auto add(T a, U b) -> decltype(a + b){return a + b;
}int main() {//int也有可以当作类来使用//所以任意类型都有构造函数int num = int(1);cout << add(1, 2) << endl;cout << add(2.1, 3.3) << endl;string a = "hello", b = "world";cout << add(a, b) << endl;cout << add(1, 1.2) << endl;cout << add(1.2, 1) << endl;return 0;
}
        模板类:

        在下面这份代码中,我利用模板实现了一个模板类,而这个模板类是一个简单对于数组的实现:

#include <iostream>
#include <cstdlib>
#include <ctime>
using namespace std;//利用模板创建一个模板类
template<typename T>
class A {
public : A(int n = 10) : n(n) {this->arr = new T[n];}T &operator[](int ind) {if (ind < 0 || ind > n) return __end;return arr[ind];}void rand_arr() {for (int i = 0; i < n; i++) {int x = rand() % 100;arr[i] = x - (T)x / 10;}return ;}~A() {delete arr;}//在声明友元函数时,也要加上模板的关键字引入和模板参数template<typename U>friend ostream &operator<<(ostream &out, const A<U> &obj);
private :T *arr;int n;T __end;
};
//重载输出时也需要利用到模板编程
template<typename T>
ostream &operator<<(ostream &out, const A<T> &obj) {for (int i = 0; i < obj.n; i++) {cout << obj.arr[i] << " ";}return out;
}int main() {srand(time(0));//通过模板类创建对象时//需要确定模板类型中的模板参数类型A<int> a;a.rand_arr();A<double> b;b.rand_arr();cout << a << endl;cout << b << endl;return 0;
}

        认识了大概的模板类进行如何使用我们继续往下探索:

        模板特化与偏特化

        模板特化

        现在假如我对于add函数进行使用时,我需要对int类型特殊处理,也就是返回值结果需要加2,那么就需要用到模板函数的特化:

#include <iostream>
#include <string>
using namespace std;//template是用来引入模板参数的关键字
//typename先理解为定义一个任意类型的变量
template<typename T, typename U>
//在原来返回值的位置写一个auto关键字
//auto是用来推到后置返回值的类型
//因为-> 在参数列表后面,根据代码的执行顺序,那么a和b就可以进行使用
//所以可以用decltype进行判断a + b的返回类型
//然后传给auto进行推断,最后确定返回值类型
auto add(T a, U b) -> decltype(a + b){return a + b;
}//由于是特化,那么我们就确定了参数
//就不需要传入模板参数了,但是也得需要template关键字引入
template<> 
int add(int a, int b) {return a + b + 2;  
}int main() {//int也有可以当作类来使用//所以任意类型都有构造函数int num = int(1);cout << add(1, 2) << endl; //那么这里的结果应该是 1 + 2 + 2 = 5cout << add(2.1, 3.3) << endl;string a = "hello", b = "world";cout << add(a, b) << endl;cout << add(1, 1.2) << endl;cout << add(1.2, 1) << endl;return 0;
}
        模板偏特化

        如下我传入的参数是指针类型时,我该如何进行处理

int a = 10, b = 20;
cout << add(&a, &b) << endl;

        这里我们就会用到偏特化:

#include <iostream>
#include <string>
using namespace std;//template是用来引入模板参数的关键字
//typename先理解为定义一个任意类型的变量
template<typename T, typename U>
//在原来返回值的位置写一个auto关键字
//auto是用来推到后置返回值的类型
//因为-> 在参数列表后面,根据代码的执行顺序,那么a和b就可以进行使用
//所以可以用decltype进行判断a + b的返回类型
//然后传给auto进行推断,最后确定返回值类型
auto add(T a, U b) -> decltype(a + b){return a + b;
}//由于是特化,那么我们就确定了参数
//就不需要传入模板参数了,但是也得需要template关键字引入
template<> 
int add(int a, int b) {return a + b + 2;  
}//模板偏特化版本
//当传入的参数是指针类型时的版本
template<typename T, typename U>
auto add(T *a, U *b) -> decltype(*a + *b) {cout << "this is piantehua" << endl;return *a + *b;
}int main() {//int也有可以当作类来使用//所以任意类型都有构造函数int num = int(1);cout << add(1, 2) << endl; //那么这里的结果应该是 1 + 2 + 2 = 5cout << add(2.1, 3.3) << endl;string a = "hello", b = "world";cout << add(a, b) << endl;cout << add(1, 1.2) << endl;cout << add(1.2, 1) << endl;int c = 10, d = 20;cout << add(&c, &d) << endl;return 0;
}
可变参数模板
typename

现在来说一下typename的作用:

typename的作用就是,声明后面的表达式是一个类型。

        可变参模板函数        

        现在我要实现一个函数叫做print,他可以打印所有的参数,并且参数的个数是任意的:
        

#include<iostream>
using namespace std;
//递归出口,打印最后一个参数
//也就是偏特化版本,只有一个参数时的print
template<typename T>
void print(T a) {cout << a << endl;return ;
}//一个模板参数代表当前层数的参数的类型, ARGS代表后续跟着的参数的类型
template<typename T, typename ...ARGS>
void print(T a, ARGS ...args) {//打印当前层的的第一个参数cout << a << " ";//递归到下一层,去打印下一个参数print(args...);return ;
}int main() {int a = 10;print(a, 12.3, "hello", '0', "gg");return 0;
}

        那么最终只有一个参数时,会调用的print的偏特化版本只有一个参数时,进行递归结束。

        可变参模板类

        下面实现一个类,这个类模板的参数个数是不定的,并且演示了如何获取每一层中分别对应的变参类型:

#include<iostream>
using namespace std;//template引入当前类型中的第一个参数T
//然后引入变参列表ARGS
template<typename T, typename ...ARGS>
class ARG {
public ://将T类型重命名为getTtypedef T getT;//将下个一个类重命名为next_Ttypedef ARG<ARGS...> next_T;
};
//类的递归出口,只有一个参数时的模板类
template<typename T> 
class ARG<T> {
public :typedef T getT;
};int main() {//取到第一层中的intARG<int, double, long long, float>::getT a;//取到第二层中的doubleARG<int, double, long long, float>::next_T::getT b;//取到第三层中的long longARG<int, double, long long, float>::next_T::next_T::getT c;//取到第四层中的floatARG<int, double, long long, float>::next_T::next_T::next_T::getT e;cout << sizeof(a) << endl;cout << sizeof(b) << endl;cout << sizeof(c) << endl;cout << sizeof(e) << endl;return 0;
}

        下面通过上面的代码,在提出一个需求:

        之前我取最后一层的类型需要这样去取,那如果我有10个,100个参数,就需要去写,

n - 1个next_T吗,所以我需要进行迭代更新一下:

ARG<int, double, float, char>::next_T::next_T::next_T::getT e;

        改完之后:

ARG<3,int, double, float, char>::getT e;

        数字3就代表我要取的对应的类型,那么如何实现看下面代码:

#include<iostream>
using namespace std;//基础模板声明
//因为进行偏特化处理时或者特化处理时需要基础模板
template<int n, typename ...ARGS>
class ARG_imag;//偏特化模板递归
template<int n, typename T, typename ...Rest>
class ARG_imag<n, T, Rest...>{
public ://进行递归,直到找到需要的层数typedef typename ARG_imag<n - 1, Rest...>::thisT thisT;
};//偏特化模板递归出口
//当n等于0时,说明到达需求层数,进行递归结束
template<typename T, typename ...Rest>  
class ARG_imag<0, T, Rest...> {
public :typedef T thisT;
};//进行封装
//用户调用的是ARG
template<int n, typename ...ARGS>
class ARG {
public :typedef typename ARG_imag<n, ARGS...>::thisT getT;
};int main() {//取到第一层中的intARG<0, int, double, float, char>::getT a = 123;//取到第二层中的doubleARG<1, int, double, float, char>::getT b = 12.3;//取到第三层中的floatARG<2, int, double, float, char>::getT c = 123.3;//取到第四层中的charARG<3, int, double, float, char>::getT e = 'c';cout << "sizeof(a) = " << sizeof(a) << " a = " << a << endl;cout << "sizeof(b) = " << sizeof(b) << " b = " << b << endl;cout << "sizeof(c) = " << sizeof(c) << " c = " << c << endl;cout << "sizeof(e) = " << sizeof(e) << " e = " << e << endl;return 0;
}
          模板中的引用重叠

        

        C++11 标准引入了 "引用折叠" 规则,这个规则定义了在模板实例化过程中,不同类型的引用组合如何被折叠成最终的引用类型。引用折叠规则如下:

  • T & & -> T &
  • T & && -> T &
  • T && & -> T &
  • T && && -> T &&

        也就是说传入的类型是右值引用,并且参数中也是右值引用,T类型才是右值引用,否则是左值引用。

        下面带入代码演示:
 

#include<iostream>
using namespace std;#define TEST(func, n) {\printf("%s(%s) ", #func, #n);\func(n);\
}template<typename T>
void func(T &&a) {//假如T为int & 那么a的类型就为int & &&然后通过折叠得到为int &if (is_same<T &, decltype(a)>::value) {cout << " is left" << endl;//假如T为int && 那么a的类型就为int&& &&然后通过折叠得到为int &&} else if (is_same<T &&, decltype(a)>::value) {cout << " is right" << endl;} else {cout << " is a type" << endl;}return ;
}int main() {int n = 123; int& l = n;int&& r = 123;TEST(func, n); //n为左值, T类型就为int &TEST(func, l); //l为左值, T类型就为int &TEST(func, r); //r为左值, T类型就为int &TEST(func, 123); //123为右值, T类型就为int &&TEST(func, move(n)); //move(n)为右值, T类型就为int &&return 0;
}

        那么又有新问题出现了,传入的类型为引用的类型,那么该如何取获取他的类型呢,如下:

        

std::remove_reference 是一个类型特征工具,它能从一个类型中去除引用,并返回无引用的类型。例如:

  • 对于 int&std::remove_reference<int&>::typeint

  • 对于 int&&std::remove_reference<int&&>::type 也是 int

#include<iostream>
using namespace std;template<typename T>
void func(T &&t) {//通过remove_reference,去掉T的引用获取到他的类型typedef typename remove_reference<T>::type a;if (is_same<a, int>::value) cout << "a type is int" << endl;if (is_same<a, char>::value) cout << "a type is char" << endl;if (is_same<a, double>::value) cout << "a type is double" << endl;if (is_same<a, float>::value) cout << "a type is float" << endl;if (is_same<a, string>::value) cout << "a type is string" << endl;
}int main() {int a;string str = "hello";func(a);func(str);func('a');func(3.14);return 0;
}

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

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

相关文章

JVM性能优化案例:减少对象频繁创建

JVM性能优化案例&#xff1a;减少对象频繁创建 案例背景 某金融应用系统在处理大量并发交易时&#xff0c;响应时间过长&#xff0c;并且有时出现内存溢出&#xff08;OutOfMemoryError&#xff09;的问题。经过分析&#xff0c;发现问题主要出在频繁的对象创建和较差的内存管…

git的ssh安装,windows通过rsa生成密钥认证问题解决

1 windows下载 官网下载可能出现下载太慢的情况&#xff0c;Git官网下载地址为&#xff1a;官网&#xff0c;推荐官网下载&#xff0c;如无法下载&#xff0c;可移步至CSDN&#xff0c;csdn下载地址&#xff1a;https://download.csdn.net/download/m0_46309087/12428308 2 Gi…

Perl 语言学习进阶

一、如何深入 要深入学习Perl语言的库和框架&#xff0c;可以按照以下步骤进行&#xff1a; 了解Perl的核心模块&#xff1a;Perl有许多核心模块&#xff0c;它们提供了许多常用的功能。了解这些模块的功能和用法是深入学习Perl的第一步。一些常用的核心模块包括&#xff1a;S…

如何在 Windows 10/11 上编辑 PDF [4 种简单方法]

PDF 在大多数设备上都易于查看&#xff0c;但由于其设计用于查看&#xff0c;因此编辑起来可能比较棘手。编辑 PDF 可能比编辑 Microsoft Office 文档更具挑战性。 不用担心&#xff0c;我们已经为你做好了准备。无论你是想添加、删除还是插入文本或图片&#xff0c;你都可以使…

Coze+Discord:打造你的免费AI助手(教您如何免费使用GPT-4o/Gemini等最新最强的大模型/Discord如何正确连接Coze)

文章目录 📖 介绍 📖🏡 演示环境 🏡📒 文章内容 📒📝 准备Discord📝 准备Coze🔌 连接💡 测试效果⚓️ 相关链接 ⚓️📖 介绍 📖 你是否想免费使用GPT-4o/Gemini等最新最强的大模型,但又不想花费高昂的费用?本文将教你如何通过Coze搭建Bot,并将其转发…

【AI绘画】Stable Diffusion 3开源

Open Release of Stable Diffusion 3 Medium 主要内容 Stable Diffusion 3是Stability AI目前为止最先进的文本转图像开放源代码算法。 这款模型的小巧设计使其完美适合用于消费级PC和笔记本电脑&#xff0c;以及企业级图形处理单元上运行。它已经满足了标准化的文字转图像模…

AI办公自动化:批量合并多个Excel表格的数据并汇总

工作任务&#xff1a; 有多个表格 把里面的月流量数据都合并到一张表中&#xff1a; 在chatgpt中输入提示词&#xff1a; 你是一个Python编程专家&#xff0c;要完成一个Python脚本编写任务&#xff0c;具体步骤如下&#xff1a; 打开文件夹&#xff1a;F:\AI自媒体内容\AI行…

计算机视觉全系列实战教程:(九)图像滤波操作

1.图像滤波的概述 (1)Why (为什么要进行图像滤波) 去噪&#xff1a;去除图像在获取、传输等过程中的各种噪音干扰提取特征&#xff1a;使用特定的图像滤波器提取图像特定特征 (2)What (什么是图像滤波) 使用滤波核对图像进行卷积运算或非线性运算&#xff0c;以达到去噪或提…

11.2 Go 常用包介绍

&#x1f49d;&#x1f49d;&#x1f49d;欢迎莅临我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:「stormsha的主页」…

使用‘消除’技术绕过LLM的安全机制,不用训练就可以创建自己的nsfw模型

开源的大模型在理解和遵循指令方面都表现十分出色。但是这些模型都有审查的机制&#xff0c;在获得被认为是有害的输入的时候会拒绝执行指令&#xff0c;例如会返回“As an AI assistant, I cannot help you.”。这个安全功能对于防止误用至关重要&#xff0c;但它限制了模型的…

化学品危险性分类鉴定报告 危化品危险性分类

一、化学品危险性分类报告&#xff1a; 按照国务院令 第591号 《危险化学品安全管理条例》、原十部委公告 2015年 第5号 《危险化学品目录&#xff08;2015版&#xff09;》、原安监总局令 第60号《化学品物理危险性鉴定与分类管理办法》和原安监总局令 第53号《危险化学品登记…

IBM Spectrum LSF Process Manager 在共享分布式计算环境中运行和管理业务关键工作流程

亮点 ● 快速创建复杂的分布式工作流 ● 开发可重复的最佳实践 ● 自信地运行关键工作流程 ● 提高流程可靠性 IBM Spectrum LSF Process Manager 使您能够设计和自动化计算或分析流程&#xff0c; 捕获和保护可重复的最佳实践。 使用直观的图形界面&#xff0c;您可以轻松记录…

【漏洞复现】飞企互联-FE企业运营管理平台 treeXml.jsp SQL注入漏洞

0x01 产品简介 飞企互联-FE企业运营管理平台是一个基于云计算、智能化、大数据、物联网、移动互联网等技术支撑的云工作台。这个平台可以连接人、链接端、联通内外&#xff0c;支持企业B2B、C2B与020等核心需求&#xff0c;为不同行业客户的互联网转型提供支持。其特色在于提供…

【十大排序算法】基数排序

数字犹如无数繁星&#xff0c;基数排序如晨曦的指引&#xff0c;将混沌序列织就成和谐的序曲。 文章目录 一、基数排序二、发展历史三、处理流程四、算法实现五、算法特性六、小结推荐阅读 一、基数排序 基数排序是一种非比较性的排序算法&#xff0c;它根据元素的位数来对元…

极限网关助力好未来 Elasticsearch 容器化升级

极限网关在好未来的最佳实践案例&#xff0c;轻松扛住日增百 TB 数据的流量&#xff0c;助力 ES 从物理机到云原生架构的改造&#xff0c;实现了流控、请求分析、安全管理、无缝迁移等场景。一次完美的客户体验~ 背景 物理机架构时代 2022 年&#xff0c;好未来整个日志 Elas…

教学辅助系统的设计

管理员账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;教师管理&#xff0c;作业管理&#xff0c;学生管理&#xff0c;管理员管理&#xff0c;作业提交管理&#xff0c;教学视频管理 教室账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0…

React+TS前台项目实战(九)-- 全局常用组件弹窗Dialog封装

文章目录 前言Dialog公共弹窗组件1. 功能分析2. 代码详细注释3. 使用方式4. 效果展示 总结 前言 今天这篇主要讲全局公共弹窗Dialog组件封装&#xff0c;将用到上篇封装的模态框Modal组件。有时在前台项目中&#xff0c;偶尔要用到一两个常用的组件&#xff0c;如 弹窗&#x…

HTTP 概述

HTTP 概述 HTTP 是一种用于获取资源&#xff08;如 HTML 文档&#xff09;的协议。 它是 Web 上任何数据交换的基础&#xff0c;它是一种客户端-服务器协议&#xff0c;这意味着请求由接收方&#xff08;通常是 Web 浏览器&#xff09;发起。 一个完整的文档是从获取的不同子文…

2024全国大学生信息安全竞赛(ciscn)半决赛东北赛区Pwn题解

前言 今年Ciscn华东北赛区半决赛的时间比较晚&#xff0c;找东北赛区的师傅要了一份半决赛Pwn题。 听说好像有5个Pwn题&#xff0c;但是只拿到了4个。如果有师傅有剩下那一个欢迎私信我。 拿到手的4个除了最后一个vmJS&#xff0c;还是挺简单的。都是格式化字符串、栈溢出和…

构建 LLM 应用为什么需要文本加载器,langchain 中如何使用文本加载器?

构建 LLM 应用为什么需要文本加载器&#xff0c;langchain 中如何使用文本加载器&#xff1f; 上一篇文章中 [使用langchain搭建本地知识库系统(新) 我们构建一个 RAG 的本地应用&#xff0c;我们使用到了网页的文本加载器用来动态获取网页的数据。 在不同的应用场景中需要使…