模板初阶

目录

1. 泛型编程

2. 函数模板

2.1 函数模板概念

2.2 函数模板格式

2.3 函数模板的原理

2.4 函数模板的实例化

2.5 模板参数的匹配原则

3. 类模板

3.1 类模板的定义格式

3.2 类模板的实例化


1. 泛型编程

如何实现一个通用的交换函数呢?

void Swap(int& left, int& right)
{int temp = left;left = right;right = temp;
}
void Swap(double& left, double& right)
{double temp = left;left = right;right = temp;
}
void Swap(char& left, char& right)
{char temp = left;left = right;right = temp;
}
//.......

使用函数重载虽然可以实现,但是有以下几个不好的地方:

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

那么这个使用我们就可以告诉编译器一个摸具,让编译器根据不同的类型利用该模子来生成代码。

泛型编程:编写与类型无关的通用代码,是代码复用的一种手段。模板是泛型编程的基础。 

2. 函数模板

2.1 函数模板概念

函数模板代表一个函数家族,该函数模板与类型无关,在使用时被参数化,根据实参类型产生函数的特定类型版本。

2.2 函数模板格式

template<typename T1,typename T2,.....,typename Tn>

返回值类型 函数名(参数列表){}

#include <iostream>
using namespace std;
//函数模板
//泛型编程
template<typename T>//这里一般写T,因为T是type的缩写
//typename说明T是类型的名称
//template<class T>//那么typename也可以被修改为class。
//typename和class在这个地方没有区别,但是在以后的地方会有区别。
void Swap(T& x, T& y)
{T tmp = x;x = y;y = tmp;
}
int main()
{int x = 1, y = 0;double m = 1.1, n = 2.2;Swap(x, y);Swap(m, n);cout << "x:" << x << " y:" << y << endl;cout << "m:" << m << " n:" << n << endl;return 0;
}

运行结果:

 注意:typename是用来定义模板参数关键字的,也可以使用class(切记:不能使用struct代替class)

这里的交换int类型的Swap函数和交换double类型的Swap函数调用的不是一个函数,函数调用要建立栈帧,这个tmp的大小都不一样,一个是int类型4个字节,另一个是double类型8个字节,所有不可能是调用同一个函数,虽然调试的时候是调用函数模板,但是底层还是调用两个函数,我们还是从汇编层可以看出来的。

 第一个掉调了Swap<int>这个函数,后四位是14F6,第二个调用了Swap<double>这个函数,后四位是14E7,这两个函数都是模板生成的,所有我们就得看看模板的原理。

2.3 函数模板的原理

函数模板是一个蓝图,它本身并不是函数,是编译器用使用方式产生特定具体类型函数的摸具。所有其实模板就是将本来应该我们做的重复的事情交给了编译器。

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

 如果实参部分的两个类型不一样的话这里就会报错, 将实参部分的x传递给模板的x,将T推导为int类型,又将n传给函数模板中的y,将y推导为double类型,所有编译器在编译的时候要生成一个形参为double类型,另一个形参为int类型,这样是生成不了的,所有我们必须将模板参数的设置成两个,模板参数可以是一个,也可以是多个。多个模板参数之间用逗号分割。

#include <iostream>
using namespace std;
//函数模板
//泛型编程
template<typename T>//这里一般写T,因为T是type的缩写
//typename说明T是类型的名称
//template<class T>//那么typename也可以被修改为class。
//typename和class在这个地方没有区别,但是在以后的地方会有区别。
void Swap(T& x, T& y)
{T tmp = x;x = y;y = tmp;
}
//模板参数跟函数参数很相似
//不同的是模板参数用尖括号,函数参数用圆括号
//模板参数前面是关键字,函数参数前面是类型
//模板参数后面是类型,具体什么类型不重要,函数参数后面是变量。
template<class T1, class T2>
void Swap(T1& x, T2& y)
{T1 tmp = x;x = y;y = tmp;
}
int main()
{int x = 1, y = 0;Swap(x, y);double m = 1.1, n = 2.2;Swap(m, n);char a = 'a', b = 'b';Swap(a, b);Swap(x, n);return 0;
}

匹配不上第一个函数模板那么就匹配第二个函数模板,第一个和第二个是可以同时存在的,它们实例化出的函数构成重载。

当然这个里面会存在数据丢失的问题,int给double,double给int这种操作存在数据丢失。 上面的函数模板你用哪些就生成哪些,比如没有用float,那么就不会生成float类型的Swap函数。

模板是在编译的时候生成实例化函数。编译器编译成指令的时候就没有模板这个概念了,就只要函数了,跟直接定义是一样的。

2.4 函数模板的实例化

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

1. 隐式实例化:让编译器根据实参推演模板参数的实际类型 

#include <iostream>
using namespace std;
template<class T>
T Add(const T& left, const T& right)
{return left + right;
}
int main()
{int a1 = 10, a2 = 20;double d1 = 10.1, d2 = 20.1;//自动推导类型 - 隐式实例化Add(a1, a2);Add(d1, d2);/*隐式实例化就是根据需要的类型去推导这个T,推出这个T的类型去生成。生成对应的函数。*///Add(a1, d1);/*如上语句,那么模板参数只有一个,但是一个传int,一个传double的时候应该怎么办呢?1.写两个模板参数*///2.强转cout << Add(a1, (int)d1) << endl;cout << Add((double)a1, d1) << endl;//3.显示实例化return 0;
}

2. 显示实例化:在函数名后的<>中指定模板参数的实际类型

#include <iostream>
using namespace std;
template<class T>
T Add(const T& left, const T& right)
{return left + right;
}
int main()
{int a1 = 10, a2 = 20;double d1 = 10.1, d2 = 20.1;//3.不推导 - 显示实例化cout << Add<int>(a1,d1) << endl;cout << Add<double>(a1, d1) << endl;return 0;
}

一般来说不用强转和显示实例化来解决问题,都是写两个模板函数。

那么显示实例化主要解决什么问题呢?

#include <iostream>
using namespace std;
template<class T>
T Add(const T& left,const T& right)
{return left + right;
}
template<class T>
T* Func(size_t n)//这里的参数没有带模板参数T
{return new T[n];
}
int main()
{//以前实参传给形参是直接可以推导T的类型,但是Func这个函数不一样//这个T是什么也不直到,这样的场景就得显示实例化Func<int>(10);// - 显示实例化Func<double>(20);// - 显示实例化return 0;
}

如果类型不匹配,编译器会尝试进行隐式类型转换,如果无法转换成功编译器将会报错。

 2.5 模板参数的匹配原则

1. 一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这个非模板函数。

#include <iostream>
using namespace std;
// 专门处理int的加法函数
int Add(int left, int right)
{return left + right;
}
// 通用加法函数
template<class T>
T Add(T left, T right)
{return (left + right)*5;
}
//这个普通函数和模板是可以同时存在的。
int main()
{cout << Add(1, 2) << endl;//优先调用普通函数,有现成的调用现成的,cout << Add<int>(1, 2) << endl;//就想调模板函数可以显示实例化cout << Add(1.1, 2.2) << endl;//参数不匹配肯定也是调模板函数return 0;
}

2. 对于非模板函数和同名函数模板,如果其他条件都相同,在调动时会优先调用非模板函数而不会从该模板产生出一个例子。如果模板函数可以产生一个具有更好匹配的函数,那么将选择模板。

#include <iostream>
using namespace std;
// 专门处理int的加法函数
int Add(int left, int right)
{return left + right;
}
// 通用加法函数
template<class T>
T Add(T left, T right)
{return (left + right)*5;
}
// 通用加法函数
template<class T1, class T2>
T1 Add(T1 left, T2 right)
{return (left + right) * 10;
}
int main()
{cout << Add(1, 2) << endl;//调现成的Add函数cout << Add<int>(1, 2) << endl;//显示实例化,调函数模板cout << Add(1.1, 2.2) << endl;//调模板参数是一个的函数模板//实参是同一个类型就会先调用同一个类型,更匹配一些。cout << Add(1, 2.2) << endl;//调模板参数是两个的函数模板//编译器调用的原则是调最匹配的。/*第一个现成的int类型Add函数和第二个显示实例化生成的int类型的Add函数不会发生冲突。因为int类型的Add函数和显示实例化生成的int类型的Add函数有点相似,但是函数名不一样,函数名不一样在允许同时存在。*/return 0;
}

从汇编我们可以看到,显示实例化生成的int类型的Add函数是Add<int> 。

3. 模板函数不允许自动类型转换,但普通函数可以进行自动类型转换 

库里面有现成的swap函数,所有我们在写代码的过程中就不需要自己写交换函数了。

#include <iostream>
using namespace std;
int main()
{int a = 1, b = 2;swap(a, b);cout << "a:" << a << endl;cout << "b:" << b << endl;return 0;
}

3. 类模板

3.1 类模板的定义格式

//类模板的定义格式
template<class T1, class T2, ..., class Tn>
class 类模板名
{// 类内成员定义
};
#include<iostream>
using namespace std;//typedef int STDataType;
using STDataType = int;//C++11更喜欢用using替代typedefclass Stack
{
public:Stack(size_t capacity = 4){_array = new STDataType[capacity];_capacity = capacity;_size = 0;}void Push(const STDataType& data);
private:STDataType* _array;size_t _capacity;size_t _size;
};

这样写的话总感觉不用类模板也是可以的,想用哪个类型就using中替换即可,但是并不是这样的。假设我要创建两个栈,一个栈存int类型的数据,另一个栈存double类型的数据,但是这个using或者typedef只能定义一个,解决不了问题。

所有我们还得用模板 - 用这个模板可以生成存放各种类型的栈,具体T是什么就看自己需要这个栈存什么数据了,暂时还是不知道的。

#include<iostream>
using namespace std;
//类模版 - 有的地方叫模板类,是不准确的,主要还是模板,实例化生成类
//而且不能传参实例化,也就是说不能隐式实例化,必须显示实例化。
template<typename T>
class Stack
{
public:Stack(size_t capacity = 4){_array = new T[capacity];_capacity = capacity;_size = 0;}void Push(const T& data);
private:T* _array;size_t _capacity;size_t _size;
};
int main()
{Stack<int> st1;Stack<double> st2;//Stack<int>和Stack<double>不是同一个类型,而是给一个模板实例化成int,生成//一个存放int类型的栈,把里面的T都替换为int。Stack<double>就是把里面的T全部//替换成double,也就是说生成了两个类,return 0;
}

模板不建议声明和定义分离到两个文件.h和.cpp会出现链接错误。如果同一个文件声明和定义可以这样写。

#include<iostream>
using namespace std;
//类模版 - 有的地方叫模板类,是不准确的,主要还是模板,实例化生成类
//而且不能传参实例化,也就是说不能隐式实例化,必须显示实例化。
template<typename T>
class Stack
{
public:Stack(size_t capacity = 4){_array = new T[capacity];_capacity = capacity;_size = 0;}void Push(const T& data);
private:T* _array;size_t _capacity;size_t _size;
};
//以前我们声明和定义是类里面声明,类外面定义,类外面定义指定类域就可以了
//但是在类模板中这样写编不过,
//void Stack::Push(const T& data)
//{
//	// 扩容
//	_array[_size] = data;
//	++_size;
//}
//因为类模板上面的模板参数是给类模板用的,也就是说只有在类里面可以用T,函数也是一样
//如果想用就需要再次声明模板函数,而且类模板这里不能用类名去代替类型
template <class T>
void Stack<T>::Push(const T& data)
{// 扩容_array[_size] = data;++_size;
}
int main()
{Stack<int> st1;Stack<double> st2;//Stack<int>和Stack<double>不是同一个类型,而是给一个模板实例化成int,生成//一个存放int类型的栈,把里面的T都替换为int。Stack<double>就是把里面的T全部//替换成double,也就是说生成了两个类,return 0;
}

3.2 类模板的实例化

类模板实例化与函数模板实例化不同,类模板实例化需要在类模板名字后跟<>,然后将实例化的类型放在<>中即可,类模板名字不是正真的类,而实例化的结果才是正真的类。

int main()
{//Stack是类名,Stack<int>是类型Stack<int> st1;//intStack<double> st2;//doubleStack<char> st3;//charreturn 0;
}

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

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

相关文章

API - Math,System,Runtime,BigDecimal,日期时间,Arrays...

01 Math类 【注意】&#xff1a;round在四舍五入时&#xff0c;只会四舍五入小数点后第一位&#xff0c;例如&#xff1a;4.499&#xff0c;的结果是4. 02 System类 【解释】&#xff1a;currentTimeMillis返回的是从1970-1-1 0:0:0开始到此刻的毫秒值&#xff0c;所以返回值才…

Moshi: a speech-text foundation model for real time dialogue

视频号 挺神奇的东西 整下来 kyutai-labs/moshi (github.com) git clone https://github.com/kyutai-labs/moshi.git 在线体验 moshi.chat 结束后 点击Download audio Download video 可以下载音频与视频 &#xff08;不过是webm格式&#xff09; 发行版 已上传至资源 小…

【C++篇】C++类与对象深度解析(四):初始化列表、类型转换与static成员详解

文章目录 C类与对象超详细入门指南前言1. 初始化列表——再谈构造函数1.1 初始化成员变量的方式1.1.1 构造函数内部赋值 vs 初始化列表1.1.2 两者的区别1.1.3 为什么要使用初始化列表1.1.4 示例 1.2 初始化列表的语法1.2.1 示例&#xff1a; 1.3 引用成员变量、const成员变量的…

【图灵完备 Turing Complete】游戏经验攻略分享 Part.4 处理器架构

比较有难度的一个部分。 运算单元ALU&#xff0c;其实就是通过OP选择计算方式&#xff0c;然后选通某个计算&#xff0c;之后输出。每个计算逐个实现就行了。 下面是一个优化占地面积的ALU&#xff0c;变得紧凑了一点。 下面是一个简单的OP选通原理线路。判断是立即数寻址&…

【C++】关键字auto详解

&#x1f984;个人主页:小米里的大麦-CSDN博客 &#x1f38f;所属专栏:C_小米里的大麦的博客-CSDN博客 &#x1f381;代码托管:C: 探索C编程精髓&#xff0c;打造高效代码仓库 (gitee.com) ⚙️操作环境:Visual Studio 2022 目录 一、前言 二、类型别名思考 三、auto简介 四…

学习笔记——RegNet:Designing Network Design Spaces

RegNet&#xff1a;Designing Network Design Spaces RegNet&#xff1a;设计一个网络设计空间 论文地址&#xff1a; https://arxiv.org/pdf/2003.13678 1、前言 在这项工作中&#xff0c;作者提出了一种新的网络设计范例。 作者的目标是帮助增进对网络设计的理解并发现跨设置…

2024年华为杯数学建模研赛(C题) 建模解析| 磁芯损耗建模 | 小鹿学长带队指引全代码文章与思路

我是鹿鹿学长&#xff0c;就读于上海交通大学&#xff0c;截至目前已经帮2000人完成了建模与思路的构建的处理了&#xff5e; 本篇文章是鹿鹿学长经过深度思考&#xff0c;独辟蹊径&#xff0c;实现综合建模。独创复杂系统视角&#xff0c;帮助你解决研赛的难关呀。 完整内容可…

C语言中易混淆概念的关键字

最快的关键字---- register register&#xff1a; 这个关键字请求编译器尽可能的将变量存在 CPU 内部寄存器中而不是通过内 存寻址访问以提高效率。注意是尽可能&#xff0c;不是绝对。你想想&#xff0c;一个 CPU 的寄存器也就那么 几个或几十个&#xff0c;你要是定义了很多很…

Kyutai开源实时语音对话模型Moshi

新闻 法国人工智能实验室Kyutai在巴黎举行的一次活动上推出了能够进行自然交互的对话式人工智能助手Moshi&#xff0c;并计划将其作为开源技术发布。Kyutai表示&#xff0c;Moshi是首款可公开访问的人工智能助手&#xff0c;可实现实时对话&#xff0c;有别于OpenAI的GPT-4o&a…

互联网广告产品基础知识

一 计价与效果 广告产品如何估算收入&#xff1f; 一种是从需求侧计算&#xff1a;按照广告主数量进行拟合&#xff1b;一种是从供给侧计算&#xff1a;按照曝光量和千次曝光单价进行拟合。 需求侧 从需求侧&#xff0c;也就是广告主侧&#xff0c;来计算广告产品的总收入&…

构建高可用和高防御力的云服务架构:从DDoS高防到PolarDB

引言 随着互联网技术的飞速发展&#xff0c;网络环境已经成为我们日常生活和商业活动中不可或缺的一部分。然而&#xff0c;这种依赖也带来了新的挑战&#xff0c;尤其是在网络安全领域。其中&#xff0c;分布式拒绝服务&#xff08;DDoS&#xff09;攻击因其破坏性强、难以防…

vite 使用飞行器仪表示例

这里写自定义目录标题 环境vue代码效果图 环境 jquery npm install -S jqueryjQuery-Flight-Indicators 将img、css、js拷贝到vite工程目录中 打开 jquery.flightindicators.js&#xff0c;在文件开头加上import jQuery from "jquery"; vue代码 <template>&…

深度学习经典模型之BERT(上)

BERT(Bidirectional Encoder Representations from Transformers)是一个双向transformer编码器的言表示模型。来自论文&#xff1a;BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding 。由Google公司的研发&#xff0c;BERT的出现使得我们能够…

MySQL篇(SQL优化)(持续更新迭代)

目录 一、插入数据&#xff1a;Insert 1. 优化方案一&#xff1a;批量插入数据 2. 优化方案二&#xff1a;手动控制事务 3. 优化方案三&#xff1a;主键顺序插入&#xff0c;性能要高于乱序插入 4. 大批量插入数据 5. 案例 5.1. 创建表结构 5.2. 设置参数 5.3. load加载…

IDAE中Quarkus框架(3.13版本)开发、调试、部署、打包等

code-with-quarkus code-with-quarkus 是使用官网生成的demo项目 这个项目使用Quarkus&#xff08;使用3.13.0版本&#xff0c;该版本支持JDK21&#xff09;&#xff0c;超音速亚原子Java框架。官网地址: https://quarkus.io/. 环境要求 OS: Windows 10.0 jdk 11 maven 3.9…

单元测试、集成测试、系统测试有什么不同?

单元测试、集成测试和系统测试是软件测试开发中不可或缺的部分。 单元测试&#xff1a; 范围&#xff1a;单元测试是对软件中最小的可测试单元的测试&#xff0c;通常是函数、方法或类。 目的&#xff1a;它的目标是验证每个单独的单元是否按照预期工作&#xff0c;以增加代码…

数据转换器——佛朗哥Chater 1

【注:本文基于《数据转换器》一书进行学习、总结编撰,适合新手小白进行学习】 目录 1.1 理想的数据转换器 1.2 采样 1.2.1 欠采样 1.2.2 采样时间的抖动(A/D转换的第一个精度限制) 1.3 幅度的量化 1.3.1 量化噪声(基本限制) 1.3.2 量化噪声的性质 1.4 KT/C噪声(…

Qt (19)【Qt 线程安全 | 互斥锁QMutex QMutexLocker | 条件变量 | 信号量】

阅读导航 引言一、互斥锁1. QMutex&#xff08;1&#xff09;基本概念&#xff08;2&#xff09;使用示例基本需求⭕thread.h⭕thread.cpp⭕widget.h⭕widget.cpp 2. QMutexLocker&#xff08;1&#xff09;基本概念&#xff08;2&#xff09;使用示例 3. QReadWriteLocker、QR…

【Linux】简易日志系统

目录 一、概念 二、可变参数 三、日志系统 一、概念 一个正在运行的程序或系统就像一个哑巴&#xff0c;一旦开始运行我们很难知晓其内部的运行状态。 但有时在程序运行过程中&#xff0c;我们想知道其内部不同时刻的运行结果如何&#xff0c;这时一个日志系统可以有效的帮…

软考无损连接判断

如何判断是否为无损连接&#xff0c;要看能否还原回最开始的关系模式 最开始的关系模式 U{A&#xff0c;B&#xff0c;C} 函数连接 F{A -> B}&#xff0c;这个函数连接的意思就是A可以推导出B 首先从P1开始判断&#xff0c;{ AB&#xff0c;BC } C不能通过函数依赖推导出来…