C++模版初阶

前言

在本文我们将学习模版的基础知识点,了解泛型编程。

一、泛型编程

1、引入

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

我们先看一段代码,如下:

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

从表面看使用函数重载似乎可以实现要求,但是函数重载有一下缺点:

  1. 重载的函数仅仅是参数类型不同,代码复用率比较低,只要有新类型出现时,就需要用户自己增加对应的函数。
  2. 函数重载最麻烦的是,在编写程序的时候,我们就要确定可能要Swap的所有类型。如果希望能在用户提供的类型上使用此函数,这种策略就失效了。
  3. 代码的可维护性比较低,一个出错可能所有的重载均出错。

所以C++增加了模板

模板就类似活字印刷术,如我们需要打印不同颜色的文档,只需通过打印机(模板)填充不同颜色的墨水(类型),就可以获得不同颜色的文档(生成具体类型的代码)。

模板是C++中泛型编程的基础

2、泛型编程

百度百科

  • 泛型编程一般指泛型。
  • 泛型程序设计是程序设计语言的一种风格或范式。
  • 泛型允许程序员在强类型程序设计语言中编写代码时使用一些以后才指定的类型(即编写程序时不确定类型),在实例化时作为参数指明这些类型。
  • 注:各种程序设计语言和编译器、运行环境对泛型的支持均不一样。

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

一个模板就是一个创建类或函数的蓝图或者说公式。

在这里插入图片描述

当我们使用泛型函数时,我们需要提供足够的信息将蓝图转换为特定的类或函数这种转换发生在编译时。下面我们将学习怎么定义模板。

二、函数模板

1、函数模板的概念

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

2、函数模板格式

template< typename T1, typename T2,……, typename Tn>//模板参数

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

tip:

  • 模板参数以关键字template开始,template翻译成中文就是模板的意思。
  • 模板参数列表:模板参数列表是一个逗号分隔的一个或多个模板参数的列表,用<>包围起来。
  • 模板参数表示在类或函数定义中用到的类型或值。当使用模板时,我们(隐式地或显式地)指定模板实参,将其绑定到模板参数上。
  • 模板参数表示的实际类型在编译时根据模板的使用情况来确定。
  • 模板参数针对的是广泛的类型,不是一个具体的类型。
  • 注意:在模板定义中,模板参数列表不能为空。

代码示例:写一个通用的交换函数

template<typename T>//模板参数 —— 类型
void Swap(T& a, T& b)
{T temp = a;a = b;b = temp;
}int main()
{double d1 = 2.0;double d2 = 5.0;Swap(d1, d2);//使用Swap模板int i1 = 10;int i2 = 20;Swap(i1, i2);char a = '0';char b = '9';Swap(a, b);return 0;
}

F10调试观察,是否都能完成交换:

在这里插入图片描述

因为模板参数T表示的实际在编译时根据Swap的使用情况来确定,所以Swap是一个通用函数模板。

3、模板类型参数&模板参数作用域

(1)模板类型参数

类型参数前必须使用关键字class或typename

template< typename T1,class T2>

tip:

  • 在模板参数列表中,这两个关键字的含义相同,可以互相使用。一个模板参数列表中也可以同时使用这两个关键字。
  • 一般,我们可以将类型参数看作类型说明符,就像内置类型或类类型说明符一样使用。即类型参数可以用来指定返回类型或函数的参数类型,以及函数体内用于变量声明或类型转换。
  • 我们通常将类型参数命名为T(取自type的首字母),但实际上我们可以使用任何名字。

(2)模板参数作用域

模板参数遵循普通的作用域规则。

一个模板参数名的可用范围是在其声明之后,至模板定义结束之前。

在这里插入图片描述

tip:每一个模板定义都以template开始,后跟一个模板参数列表。

4、函数模板的原理(实例化函数模板)

问题:这三个Swap的使用,调用的是模板吗?

答案是:不是,当我们调用一个函数模板时,编译器(通常)用函数实参来为我们推断模板的实参。即当我们调用Swap时,编译器使用函数实参的类型来确定绑定到模板参数T的类型。

tip:

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

在这里插入图片描述
tip:

  • 在编译器编译阶段,对于模板函数的使用,编译器需要根据传入的实参类型来推演出的模板参数来为我们实例化一个对应类型的函数以供调用。
  • 例如:当用double类型使用函数模板时,编译器通过对实参类型的推演,将T确定为double类型,然后实例化一份专门处理double类型的代码。
  • 用不同类型的参数使用函数模板时, 称为函数模板的实例化
  • 这些编译器生成的对应函数通常被称为模板的实例

5、函数模板实例化的两种方式

用不同类型的参数使用模板时,称为函数模板的实例化。

模板参数实例化分为:隐式实例化和显式实例化

(1)隐式实例化

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

template<class T>
T Add(const T& a, const T& b)
{return a + b;
}int main()
{int a1 = 10, a2 = 20;double d1 = 1.1, d2 = 2.2;//隐式实例化:让编译器根据实参推演模板参数的实际类型cout << Add(a1, a2) << endl;cout << Add(d1, d2) << endl;return 0;
}

一个模板类型参数可以用作多个函数形参的类型。

例如:我们的Add函数接收两个const T&参数,两个实参是相同类型,编译器可以推演模板参数的实际类型,如果传两个不同类型的实参,编译器还可以推演吗?

template<class T>
T Add(const T& a, const T& b)
{return a + b;
}int main()
{int a = 3;double b = 3.5;/** Add(a, b)不能通过编译,* 在编译阶段,编译器需要根据传入实参类型推演模板参数* 通过实参a将T推演为int,通过实参b将T推演为double,但模板参数列表中只有一个T,* 编译器无法确定此处到底该将T确定为int或者double类型而报错* * 简单说,就是模板参数T不明确* * 注意:在模板中,编译器一般不会进行类型转换操作,因为一旦转化出问题,编译器就需要背锅*///cout << Add(a, b) << endl;//解决方案://①如果还是希望编译器根据实参推演模板参数,将函数模板定义为两个类型参数//②用户自己来将实参强制转换//③显式实例化return 0;
}

tip:

  • 模板类型参数与类型转换
    1. 编译器通常不是对实参进行类型转换,而是生成一个新的模板实例。
    2. 在模板中,编译器一般不会对实参进行类型转换操作,因为一旦转化出问题,编译器就需要背锅。
    3. 特殊:将实参传递给带模板类型的函数形参时,能自动应用的类型转换只有const转换(转换为常量)及数组或函数到指针的转换(注:需要函数形参不是引用类型)。
  • 如果函数参数类型不是模板参数,则对实参进行正常的类型转换。

(2)显式实例化

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

使用场景:在某些情况下,编译器无法推断出模板实参的类型。例如:

  1. 用户自己控制模板的实例化。
  2. 函数返回类型与参数列表中任何类型都不相同等等。

代码示例1:

template<class T1, class T2, class T3>
//编译器无法推断T1,它未出现在函数参数列表
T1 Sum(const T2& a, const T3& b)
{return  a + b;
}int main()
{int a = 10;double b = 9.9;//T1是显式指定的,T2和T3是从函数实参类型推演而来的cout << Sum<int>(a, b) << endl;cout << Sum<double>(a, b) << endl;return 0;
}

tip:
在这里插入图片描述

  • 显式模板实参按由左至右的顺序与对应的模板参数匹配。
  • 第一个模板实参与第一个模板参数匹配,第二个实参与第二个参数匹配,依此类推。
  • 注:只有尾部(最右)参数的显式模板实参才可以忽略,但是前提是它们可以从函数参数推断出来。

代码示例2:

template<class T>
void func(const T& a, const T& b)
{cout << a << " " << b << endl;
}int main()
{int num1 = 10;double num2 = 3.14;//在模板中,编译器一般不会对实参做类型转换,所以传递给func的实参必须是同一类型的,否则编译报错,模板参数T不明确func<int>(num1, num2);//T被显式指定为int,因此num2被转换为intfunc<double>(num1, num2);return 0;
}

tip:

  • 在模板中编译器一般不会对实参做类型转换,因为一旦转化出问题,编译器就需要背锅。
  • 对于模板类型参数已经显式指定了的函数实参,可以进行正常的类型转换。

总结:当编译器无法推断模板实参的类型时,使用显式实例化。

6、模板参数的匹配原则

  1. 一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这个非模板函数。
// 专门处理int的加法函数
int Add(int left, int right)
{cout << "int Add(int left, int right)" << endl;return left + right;
}// 通用加法函数
template<class T>
T Add(T left, T right)
{cout << "T Add(T left, T right)" << endl;return left + right;
}int main()
{Add(1, 2); // 与非模板函数匹配,编译器不需要特化Add<int>(1, 2); // 调用编译器特化的Add版本return 0;
}

运行结果:

在这里插入图片描述

  1. 对于非模板函数和同名函数模板,如果其他条件都相同,在调用时会优先调用非模板函数而不会从模板产生一个实例。如果模板可以产生一个具有更好匹配的函数,那么会选择模板。
// 专门处理int的加法函数
int Add(int left, int right)
{cout << "int Add(int left, int right)" << endl;return left + right;
}// 通用加法函数
template<class T1, class T2>
T1 Add(T1 left, T2 right)
{cout << "T1 Add(T1 left, T2 right)" << endl;return left + right;
}int main()
{Add(1, 2); // 与非函数模板类型完全匹配,不需要函数模板实例化Add(1, 2.0); // 模板函数可以生成更加匹配的版本,编译器根据实参生成更加匹配的Add函数return 0;
}

运行结果:

在这里插入图片描述

三、类模板

1、引入

思考:在同一个程序中,定义两个栈,分别存储int和double数据,我们怎么实现了?

typedef int DataType;
class StackInt
{
public:StackInt(size_t capacity = 3):_array(new DataType[capacity]), _capacity(capacity), _top(0){}// 其他方法...~StackInt(){if (_array){delete[] _array;_array = NULL;_capacity = 0;_top = 0;}}private:DataType* _array;int _capacity;int _top;
};

在C语言中,我们使用typedef重命名栈中存储数据的类型

我们已经有了一个存储int的栈,要想再有一个存储double的栈,我们直接在CV一份栈代码,只需typedef类型即可。

存储不同数据的栈,唯一的差异仅仅是类型不同,那能不能和函数模板一样,我们定义一个栈的模板,让编译器去帮我们实例化出对应的类。

类模板是用来生成类的蓝图。

2、类模板的定义格式

定义类模板:类似函数模板,类模板以关键字template开始,后跟模板参数列表。

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

代码示例:

template<class T>
class Stack
{
public:Stack(size_t capacity = 3):_array(new T[capacity]), _capacity(capacity), _top(0){}// 其他方法...~Stack(){if (_array){delete[] _array;_array = NULL;_capacity = 0;_top = 0;}}private:T* _array;int _capacity;int _top;
};int main()
{Stack<int> st1;//存储intStack<double> st2;//存储doublereturn 0;
}

tip:

  • 与函数模板不一样的是,编译器不能为类模板推断模板参数类型。
  • 使用类模板,我们必须显式实例化!

3、类模板的实例化

使用类模板时,我们必须显式实例化——在类模板名字后跟<>,然后将实例化的类型放在<>中即可。

类模板名字不是真正的类,而实例化的结果才是真正的类。

所以类模板的类名与类型不一样——类名:Stack,类型:Stack< T >(如果类模板实例化了就是具体的类型了)

tip:

  • 一个类模板的每一个实例都形成一个独立的类。

4、类模板的成员函数

  • 类模板成员函数的定义
    1. 与其他任何类一样,我们即可以在类模板内部,也可以在类模板外部为其定义成员函数。
    2. 定义在类模板内的成员函数被隐式声明为内联函数。
    3. 一般为了代码的可读性,代码长的定义在外部,代码短的定义在内部。
  • 在外部定义时:
    1. 类模板的每一个实例都有其自己版本的成员函数。因此,类模板的成员函数具有和模板相同的模板参数。因而,定义在类模板之外的成员函数必须以关键字template开始,后接类模板参数列表。
    2. 在类外定义一个成员时,必须说明成员属于哪个类。而且,从一个模板生成的类的名字中必须包含其模板实参。
template<class T>
class Stack
{
public:Stack(size_t capacity = 3):_array(new T[capacity]), _capacity(capacity), _top(0){}// 其他方法...//将析构定义在类外~Stack();private:T* _array;int _capacity;int _top;
};template<class T>
Stack<T>::~Stack ()
{if (_array){delete[] _array;_array = NULL;_capacity = 0;_top = 0;}
}

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

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

相关文章

机器学习 | 如何利用集成学习提高机器学习的性能?

目录 初识集成学习 Bagging与随机森林 Otto Group Product(实操) Boosting集成原理 初识集成学习 集成学习&#xff08;Ensemble Learning&#xff09;是一种通过组合多个基本模型来提高预测准确性和泛化能力的机器学习方法。它通过将多个模型的预测结果进行整合或投票来做…

【Java程序设计】【C00243】基于Springboot的社区医院管理系统(有论文)

基于Springboot的社区医院管理系统&#xff08;有论文&#xff09; 项目简介项目获取开发环境项目技术运行截图 项目简介 这是一个基于Springboot的社区医院管理服务系统 本系统分为系统功能模块、管理员功能模块、用户功能模块以及医生功能模块。 系统功能模块&#xff1a;社…

ElementUI Form:Checkbox 多选框

ElementUI安装与使用指南 Checkbox 多选框 点击下载learnelementuispringboot项目源码 效果图 el-checkbox.vue &#xff08;Checkbox 多选框&#xff09;页面效果图 项目里el-checkbox.vue代码 <script> const cityOptions [上海, 北京, 广州, 深圳] export def…

JProfiler for Mac:提升性能和诊断问题的终极工具

在当今的高性能计算和多线程应用中&#xff0c;性能优化和问题诊断是至关重要的。JProfiler for Mac 是一个强大的性能分析工具&#xff0c;旨在帮助开发者更好地理解其应用程序的运行情况&#xff0c;提升性能并快速诊断问题。 JProfiler for Mac 的主要特点包括&#xff1a;…

2024/2/3

一&#xff0e;选择题 1、适宜采用inline定义函数情况是&#xff08;C&#xff09; A. 函数体含有循环语句 B. 函数体含有递归语句‘、考科一 ’ C. 函数代码少、频繁调用 D. 函数代码多、不常调用 2、假定一个函数为A(int i4, int j0) {;}, 则执行“A (1);”语句后&#xff0c…

机器学习复习(2)——线性回归SGD优化算法

目录 线性回归代码 线性回归理论 SGD算法 手撕线性回归算法 模型初始化 定义模型主体部分 定义线性回归模型训练过程 数据demo准备 模型训练与权重参数 定义线性回归预测函数 定义R2系数计算 可视化展示 预测结果 训练过程 sklearn进行机器学习 线性回归代码…

电商小程序01需求分析

目录 1 电商用例分析2 功能架构3 原型开发3.1 首页3.2 店铺页面3.3 配货单3.4 配货单有货3.5 我的应用3.6 商品详情3.7 订单确认3.8 收货地址3.9 店铺详情3.10 店铺分类3.11 商品分类 总结 低代码学习的时候最高效的方法就是带着问题去学习&#xff0c;一般可以先从电商小程序开…

【大数据】Flink SQL 语法篇(三):窗口聚合(TUMBLE、HOP、SESSION、CUMULATE)

Flink SQL 语法篇&#xff08;三&#xff09;&#xff1a;窗口聚合 1.滚动窗口&#xff08;TUMBLE&#xff09;1.1 Group Window Aggregation 方案&#xff08;支持 Batch / Streaming 任务&#xff09;1.2 Windowing TVF 方案&#xff08;1.13 只支持 Streaming 任务&#xff…

配置实例—交换机VLAN聚合配置实例

一、组网需求 某公司拥有多个部门且位于同一网段&#xff0c;为了提升业务安全性&#xff0c;将不同部门的用户划分到不同VLAN中。现由于业务需要&#xff0c;不同部门间的用户需要互通。如图1所示&#xff0c;VLAN2和VLAN3为不同部门&#xff0c;现需要实现不同VLAN间的用户可…

浪漫的通讯录(顺序表篇)

本篇会加入个人的所谓‘鱼式疯言’ ❤️❤️❤️鱼式疯言:❤️❤️❤️此疯言非彼疯言 而是理解过并总结出来通俗易懂的大白话, 我会尽可能的在每个概念后插入鱼式疯言,帮助大家理解的. &#x1f92d;&#x1f92d;&#x1f92d;可能说的不是那么严谨.但小编初心是能让更多人能…

代码随想录算法训练营第39天 | 62.不同路径 + 63.不同路径 II

今日任务 62.不同路径 63. 不同路径 II 62.不同路径 - Medium 题目链接&#xff1a;力扣&#xff08;LeetCode&#xff09;官网 - 全球极客挚爱的技术成长平台 一个机器人位于一个 m x n 网格的左上角 &#xff08;起始点在下图中标记为 “Start” &#xff09;。 机器人每次只…

flutter如何实现省市区选择器

前言 当我们需要用户填写地址时&#xff0c;稳妥的做法是让用户通过“滚轮”来滑动选择省份&#xff0c;市&#xff0c;区&#xff0c;此文采用flutter的第三方库来实现这一功能&#xff0c;比调用高德地图api简单一些。 流程 选择库 这里我选择了一个最近更新且支持中国的…

Acwing 141 周赛 解题报告 | 珂学家 | 逆序数+奇偶性分析

前言 整体评价 很普通的一场比赛&#xff0c;t2思维题&#xff0c;初做时愣了下&#xff0c;幸好反应过来了。t3猜猜乐&#xff0c;感觉和逆序数有关&#xff0c;和奇偶性有关。不过要注意int溢出。 欢迎关注: 珂朵莉的天空之城 A. 客人数量 题型: 签到 累加和即可 import…

Three.js学习3:第一个Three.js页面

一、一图看懂Three.js 坐标 这个没什么好说的&#xff0c;只是需要注意颜色。在 Three.js 提供的编辑器中&#xff0c;各种物体的坐标也这样的色彩&#xff1a; 红色&#xff1a;x 轴 绿色&#xff1a;y 轴 蓝色&#xff1a;z 轴 Three.js 提供的编辑器可以在本地 Three.js …

常用git指令

一.安装配置git&&利用SSH完成Git与GitHub的绑定 1.参考知乎网址&#xff1a;还不会使用 GitHub &#xff1f; GitHub 教程来了&#xff01;万字图文详解 二.在git上更新仓库步骤 1.在新建文件夹下&#xff0c;右键选择“git bash here” 2.把项目下载到本地&#xf…

AI应用开发-git开源项目的一些问题及镜像解决办法

AI应用开发相关目录 本专栏包括AI应用开发相关内容分享&#xff0c;包括不限于AI算法部署实施细节、AI应用后端分析服务相关概念及开发技巧、AI应用后端应用服务相关概念及开发技巧、AI应用前端实现路径及开发技巧 适用于具备一定算法及Python使用基础的人群 AI应用开发流程概…

微信小程序(三十一)本地同步存储API

注释很详细&#xff0c;直接上代码 上一篇 新增内容&#xff1a; 1.存储数据 2.读取数据 3.删除数据 4.清空数据 源码&#xff1a; index.wxml <!-- 列表渲染基础写法&#xff0c;不明白的看上一篇 --> <view class"students"><view class"item…

使用 git 将本地文件上传到 gitee 远程仓库中,推送失败

项目场景&#xff1a; 背景&#xff1a; 使用 git 想要push 本地文件 到 另一个远程仓库&#xff0c;执行 git push origin master后此时报错 问题描述 问题&#xff1a; git push 本地文件 到 另一个远程仓库时&#xff0c;运行 git push origin master ,push文件失败&…

老版本labelme如何不保存imagedata

我的版本是3.16&#xff0c;默认英文且不带取消保存imagedata的选项。 最简单粗暴的方法就是在json文件保存时把传递过来的imagedata数据设定为None&#xff0c;方法如下&#xff1a; 找到labelme的源文件&#xff0c;例如&#xff1a;D:\conda\envs\deeplab\Lib\site-packages…

vue 适配大屏 页面 整体缩放

正常应该放在app.vue 里面。我这里因为用到element-ui 弹框无法缩放&#xff0c;所以加在body上面 (function (doc, win) {var docEl doc.documentElement,resizeEvt orientationchange in window ? orientationchange : resize,recalc function () {var clientWidth docE…