C++初阶-模板初阶

目录

 

1.泛型编程

2.函数模板

2.1函数模板概念

2.2实现函数模板

2.3模板的原理

2.4函数模板的实例化

2.4.1隐式实例化

2.4.2显式初始化

2.5模板参数的匹配原则

3.类模板

3.1类模板定义格式

3.2类模板的实例化

4.总结



1.泛型编程

对广泛的类型法写代码,我们之前写的是一个专用的类型,如果想要用其他类型就要再写一个专用的函数写这个功能和实现方式与其他的差不多的代码,非常麻烦,如:

//交换两个整型
void Swap(int& a, int& b)
{int c = a;a = b;b = c;
}
//交换两个浮点型
void Swap(double& a, double& b)
{double c = a;a = b;b = c;
}

我们可以发现,如果我们把第一个函数的int 改为double就可以直接变为第二个函数了,这样写起来很费时间,而且如果我们这个函数只用几次的话,写了这个是完全没意义的,所以这有两个不好的地方:

(1)重载的函数仅仅是类型不同,代码复用率比较低,只要有新类型出现时,就需要用户自己增加对应的函数;

(2)代码的可维护性比较低,一个出错可能所有的重载均出错。

我们能否告诉编译器一个模子,让编译器根据不同的类型利用该模子来生成代码呢?

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

而这就需要我们来通过模板这个东西来实现,模板分为函数模板类模板

2.函数模板

2.1函数模板概念

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

2.2实现函数模板

模板的格式:template<typename T1,typename T2,……,typedname Tn>

其中T1、T2、……T3都是模板的参数,而这个template是关键字,typename是用来定义模板参数关键字,也可以使用class(切记不可用struct代替)。(到模板进阶(C++初阶最后的部分)会讲class与typename的区别)

如,我们之前实现的交换函数可以这样写:

#include<iostream>
using namespace std;
//需要几个类型就写几个参数
template<typename T>
void Swap(T& left, T& right)
{T temp = left;left = right;right = temp;
}
int main()
{//交换两个整型int a, b;cin >> a >> b;Swap(a, b);cout << a << " " << b << endl;//交换两个浮点型double c, d;cin >> c >> d;Swap(c, d);cout << c << " " << d << endl;//交换两个字符型char e, f;cin >> e >> f;Swap(e, f);cout << e << " " << f << endl;return 0;
}

运行结果如下:

如果我们实参不是一个类型,则会报错:

我们相较于之前写交换函数只是在函数定义前加了一行模板的定义而已,并且把类型换为模板了而已。

2.3模板的原理

函数模板是一个蓝图,它本身并不是函数,是编译器用使用方式产生特定具体类型函数的模具。
所以其实模板就是将本来应该我们做的重复的事情交给了编译器。只是看实参类型用模板去匹配而已,实际上生成的两个函数都不是一样的,编译器根据实参类型生成一个新函数,如果有不同的类型就再生成一个新函数,地址不同。如,我们之前实现的交换函数:

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

2.4函数模板的实例化

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

2.4.1隐式实例化

让编译器根据实参推演模板参数的实际类型。

和我之前实现的那个交换函数调用时一样的形式,int a,b;Swap(a,b);则通过自动识别出a和b都是int类型,这就是隐式实例化。

但是如果我们给的是不同的类型,通过识别出两个实参类型不同就会报错,这个时候我们要么就改实参为同一类型,要么就用显式实例化,要么就要一个类型强制类型转换为另一类型:

#include<iostream>
using namespace std;
template<class T>
T Add(const T& left, const T& right)
{return left + right;
}
int main()
{int a = 3, b = 2;double c = 3.024, d = 4.501;cout << Add(a, b) << endl;cout << Add(c, d) << endl;cout << Add(a, (int)c) << endl;cout << Add((double)a, c) << endl;return 0;
}

运行结果如下:

建议不要去运行之前的交换排序代码,因为它我测试过不仅显式实例化没有用处,强制类型转换也没有用处,至于原因感兴趣的可以去探讨一下。

当然这里涉及到精度丢失的问题,所以建议Add这类的函数还是加一个参数写成如下形式:

但是返回值你也要改一下,要么返回double类型要么返回int类型!

template<class T1,class T2>
T1 Add(const T1& left, const T2& right)
{return left + right;
}

这是一个演示的,如果之后学的更多了,也可能会有其他类型的返回值等等。

2.4.2显式初始化

我们之前的代码也可以改为如下形式,这样的形式也不用强制类型转换了,直接用<>里面的类型进行生成函数的操作。

#include<iostream>
using namespace std;
template<class T>
T Add(const T& left, const T& right)
{return left + right;
}
int main()
{int a = 2, b = 3;double c = 4.53, d = 2.54;//指定参数为int类型(形参)cout << Add<int>(a, c) << endl;//指定参数为double类型(形参)cout << Add<double>(b, d) << endl;return 0;
}

运行结果如下:

当然,运行时输出的会有一个提示:

也就是说double转换为int会有小数点后的数据丢失,所以建议把int转换为double类型!因为这样算出来的结果才更加准确。

当然不是所有的都可以用两种实例化的方式:

template<class T>
T* func(size_t n)
{return new T[n];
}

因为形参没有任何的能代表T的类型,也就是说不能通过类型推理来知道T的类型,所以就只能进行显式实例化了。

2.5模板参数的匹配原则

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

如:

// 专门处理int的加法函数
int Add(int left, int right)
{return left + right;
}// 通用加法函数
template<class T>
T Add(T left, T right)
{return left + right;
}void Test()
{Add(1, 2);       // 与非模板函数匹配,编译器不需要特化Add<int>(1, 2);  // 调用编译器特化的Add版本
}

若有int类型的,编译器会先用现成的(非模板),否则用模板的。但若我们不想用现成的我们就可以直接显式实例化。

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

如:

#include<iostream>
using namespace std;
// 专门处理int的加法函数
int Add(int left, int right)
{return left + right;
}// 通用加法函数
template<class T1, class T2>
T1 Add(T1 left, T2 right)
{return left + right;
}void Test()
{Add(1, 2);// 与非函数模板类型完全匹配,不需要函数模板实例化Add(1, 2.0);// 模板函数可以生成更加匹配的版本,编译器根据实参生成更加匹配的
}

只要非模板函数的形参类型与实参全部匹配就用非模板函数,但是如果是两个参数类型不一样的情况,发现模板函数用的不会使精度丢失,所以直接用了模板函数了。

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

3.类模板

3.1类模板定义格式

template<class T1, class T2, ..., class Tn>
class 类模板名
{
// 类内成员定义
};    

和之前的方式一样,当然,不是因为它是类就只能用class来定义参数,也可以用typename定义参数,只是现在没学到后面,不知道二者的区别而已。

我们可以把之前实现栈来改为这个方式:

#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;
};
// 模版不建议声明和定义分离到两个文件.h 和.cpp会出现链接错误,具体原因后面会讲
template<class T>
void Stack<T>::Push(const T& data)
{// 扩容_array[_size] = data;++_size;
}

我们定义这个栈的时候如果成员函数声明和定义分离时不能忘记加这一行模板了,否则会出现:

3.2类模板的实例化

Stack<int> st1;    // int
Stack<double> st2; // double

由于类模板不能通过隐式实例化来确定形参类型,所以必须用显式实例化,且注意:Stack是类名,Stack<int>是类型。

其次再补充一下之前的:

template<class T>
void Stack<T>::Push(const T& data)
{// 扩容_array[_size] = data;++_size;
}

模板参数不能是凭空出来的,故指定类域得加模板参数,否则T是用不了的,因为模板是给Stack类用的模板参数,因为模板参数是属于这个Stack类型,因为T可以为任意类型,若去掉,则本质上Push要实例化出多个,这样就不行了。

4.总结

这是模板初阶的东西,所以很多东西是没有讲解完全的,需要到模板进阶来讲清楚,但是模板进阶要很晚了,下面这张图片是C++初阶的全部内容,所以我们现在已经C++初阶过了一半了,之后难度会比较高(第8节开始),所以需要深入学习也需要持之以恒哦!

加油吧,相信自己能学会才是最好的安慰!喜欢的可以一键三连哦,下一节很简单,所以应该很快就发出来的!

 

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

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

相关文章

「Mac畅玩AIGC与多模态02」部署篇01 - 在 Mac 上部署 Ollama + Open WebUI

一、概述 本篇介绍如何在 macOS 环境下本地部署 Ollama 推理服务,并通过 Open WebUI 实现可视化交互界面。该流程无需 CUDA 或专用驱动,适用于 M 系列或 Intel 芯片的 Mac,便于快速测试本地大语言模型能力。 二、部署流程 1. 环境准备 安装 Homebrew(如尚未安装):/bin…

JavaScript 中 undefined 和 not defined 的区别

在 JavaScript 的调试过程中&#xff0c;你是否经常看到 undefined 却不知其来源&#xff1f;是否曾被 ReferenceError: xxx is not defined 的错误提示困扰&#xff1f;这两个看似相似的概念&#xff0c;实际上是 JavaScript 类型系统中最重要的分水岭。本文将带你拨开迷雾&am…

django admin AttributeError: ‘UserResorce‘ object has no attribute ‘ID‘

在 Django 中遇到 AttributeError: ‘UserResource’ object has no attribute ‘ID’ 这类错误通常是因为你在代码中尝试访问一个不存在的属性。在你的例子中&#xff0c;错误提示表明 UserResource 类中没有名为 ID 的属性。这可能是由以下几个原因造成的&#xff1a; 拼写错…

对鸿蒙 Next 系统“成熟论”的深度剖析-优雅草卓伊凡

对鸿蒙 Next 系统“成熟论”的深度剖析-优雅草卓伊凡 在科技飞速发展的当下&#xff0c;鸿蒙 Next 系统无疑成为了众多科技爱好者与行业人士关注的焦点。今日&#xff0c;卓伊凡便收到这样一个饶有趣味的问题&#xff1a;鸿蒙 Next 系统究竟需要多长时间才能完全成熟&#xff…

快速上手GO的net/http包,个人学习笔记

更多个人笔记&#xff1a;&#xff08;仅供参考&#xff0c;非盈利&#xff09; gitee&#xff1a; https://gitee.com/harryhack/it_note github&#xff1a; https://github.com/ZHLOVEYY/IT_note 针对GO中net/http包的学习笔记 基础快速了解 创建简单的GOHTTP服务 func …

AI-Browser适用于 ChatGPT、Gemini、Claude、DeepSeek、Grok的客户端开源应用程序,集成了 Monaco 编辑器。

一、软件介绍 文末提供程序和源码下载学习 AI-Browser适用于 ChatGPT、Gemini、Claude、DeepSeek、Grok、Felo、Cody、JENOVA、Phind、Perplexity、Genspark 和 Google AI Studio 的客户端应用程序&#xff0c;集成了 Monaco 编辑器。使用 Electron 构建的强大桌面应用程序&a…

Dify框架面试内容整理-Dify如何处理知识库的集成?

Dify 在知识库集成方面采用了“检索增强生成(RAG)”的技术架构,核心实现思路如下: 一、知识库集成的整体流程 Dify处理知识库集成通常包括以下关键步骤: 文档上传↓

Laravel 模型使用全局作用域和局部作用域

一. 需要解决什么问题 最近Laravel 项目中遇到一个需求&#xff0c;我有一个客户表&#xff0c;每个员工都有自己的客户&#xff0c;但是自己只能看自己的客户。 项目中&#xff0c;有很多功能需要查询客户列表&#xff0c;客户详情&#xff0c;查询客户入口很多&#xff0c;…

【Nova UI】十二、打造组件库之按钮组件(上):迈向功能构建的关键一步

序言 在上一篇文章中&#xff0c;我们深入探索了 icon 组件从测试到全局注册的全过程&#x1f3af;&#xff0c;成功为其在项目中稳定运行筑牢了根基。此刻&#xff0c;组件库的建设之旅仍在继续&#xff0c;我们将目光聚焦于另一个关键组件 —— 按钮组件。按钮作为用户与界面…

鸿蒙OSS文件(视频/图片)压缩上传组件-能够增删改查

一、鸿蒙实现处理-压缩上传整体代码处理逻辑 转沙箱压缩获取凭证并上传文件 文件准备&#xff08;拿到文件流&#xff09;获取上传凭证&#xff08;调接口1拿到file_name和upload_url&#xff09;执行文件上传&#xff08;向阶段2拿到的upload_url上传文件&#xff09;更新列表…

河道流量监测,雷达流量计赋能水安全智慧守护

在蜿蜒的河道之上&#xff0c;水流的脉搏始终与人类文明的兴衰紧密相连。从农田灌溉的水量调配到城市防洪的精准预警&#xff0c;从生态保护的水质溯源到水资源管理的决策&#xff0c;河道流量监测如同大地的 “血管检测”&#xff0c;是守护水安全的第一道防线。传统监测手段在…

CSS3 基础(边框效果)

一、边框效果 属性功能示例值说明border-radius创建圆角border-radius: 20px;设置元素的圆角半径&#xff0c;支持像素&#xff08;px&#xff09;或百分比&#xff08;%&#xff09;。值为 50% 时可变为圆形。box-shadow添加阴影box-shadow: 5px 5px 15px rgba(0, 0, 0, 0.5)…

零基础小白如何上岸数模国奖

零基础小白如何上岸数模国奖 我自己本人第一次参加数模国赛顺利上岸国奖&#xff0c;当然那段经历也是比较痛苦了&#xff0c;差不多也是从当年四月开始接触数学建模&#xff0c;第一次参加妈妈杯成绩并不理想&#xff0c;后面不断参加数模比赛进行模拟&#xff0c;最后顺利上岸…

SQL学习-常用函数

常见SQL函数使用 &#xff08;注意&#xff1a;不同的数据库类型使用的语法不同&#xff09; 以下是MySQL和PostgreSQL在实现替换、抽取、拼接、分列四个常见字符串操作功能时的核心区别总结&#xff0c;按功能分类对比&#xff1a; 1. 替换&#xff08;Replace&#xff09; …

rt-linux下的cgroup cpu的死锁bug

一、背景 rt-linux系统有其非常大的实时性的优势&#xff0c;但是与之俱来的是该系统上有一些天然的缺陷。由于rt-linux系统允许进程在内核态执行的逻辑里&#xff0c;在持锁期间&#xff0c;甚至持spinlock锁期间&#xff0c;都能被其他进程抢占。这一特性能带来实时性的好处…

java—12 kafka

目录 一、消息队列的优缺点 二、常用MQ 1. Kafka 2. RocketMQ 3. RabbitMQ 4. ActiveMQ 5. ZeroMQ 6. MQ选型对比 适用场景——从公司基础建设力量角度出发 适用场景——从业务场景角度出发 四、基本概念和操作 1. kafka常用术语 2. kafka常用指令 3. 单播消息&a…

14【模块学习】74HC595:使用学习

74HC595 1、74HC595简介2、代码演示2.1、驱动8位流水灯 3、74HC595级联3.1、驱动16位流水灯3.2、驱动8位数码管3.3、驱动8x8点阵屏幕3.4、8x8点阵屏幕滚动显示 1、74HC595简介 在51单片机中IO引脚资源十分的紧缺&#xff0c;所以常常需要使用75HC595芯片进行驱动那些需要占用多…

JAVA后端开发常用的LINUX命令总结

一、Linux常用命令大全&#xff08;2025年最新版&#xff09; 常用 Linux 命令 文件和目录管理&#xff1a; cd&#xff1a;用于切换当前工作目录&#xff0c;如cd /home/user。mkdir&#xff1a;创建新目录&#xff0c;mkdir -p /home/user/mydir可递归创建多级目录。pwd&am…

uniapp-商城-40-shop 购物车 选好了 进行订单确认4 配送方式3 地址编辑

前面说了配送 和地址页面 当地址页面为空或需要添加地址时&#xff0c;需要添加地址。 我的地址页面有个按钮 就是添加地址 点击 添加地址 按钮 后&#xff0c;就会跳转到地址添加的页面 1、添加地址页面 2、添加地址文件夹以及文件的创建 3、添加地址的代码 <template…

现场问题排查-postgresql某表索引损坏导致指定数据无法更新影响卷宗材料上传

问题现象 今天突然被拉进一个群&#xff0c;说某地区友商推送编目结果报错&#xff0c;在我们自己的卷宗系统上传材料也一直转圈&#xff0c;也删除不了案件卷宗&#xff0c;重置模板也没用&#xff0c;只有个别案件有问题。虽然这事儿不属于我负责&#xff0c;但还是抽时间给…