C++模板使用

文章目录

目录

文章目录

前言

一、交换函数(泛型编程)

二、函数模板

        2.1 函数模板概念

        2.2函数模板格式

        2.3使用方法

2.4 函数模板的原理

2.4.1库中的swap

2.5 函数模板的实例化

2.6 模板参数的匹配原则

三、类模板

        3.1 类模板的定义格式

3.2类模板声明和定义分离


前言

        C语言阶段要实现不同类型的交换函数swap,需要重复写很多代码,需要造很多的轮子。为了提高程序员写代码的效率,C++出现了泛型编程的概念,模板应运而生。


一、交换函数(泛型编程)

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

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. 代码的可维护性比较低,一个出错可能所有的重载均出错

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

如果在C++中,也能够存在这样一个模具,通过给这个模具中填充不同材料(类型),来获得不同材料的铸件(即生成具体类型的代码),那将会节省许多头发。巧的是前人早已将树栽好,我们只需在此乘凉。
泛型编程:编写与类型无关的通用代码,是代码复用的一种手段。模板是泛型编程的基础。

二、函数模板

        2.1 函数模板概念

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

        2.2函数模板格式

template<typename T1, typename T2,......,typename Tn>
返回值类型 函数名(参数列表){}
template<typename T>
void Swap( T& left, T& right)
{T temp = left;left = right;right = temp;
}

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

        2.3使用方法

       下面的Swap使用的是否为同一个?

#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 = 1, b = 0;Swap(a, b);double c = 1.1, d = 2.2;Swap(c, d);int* p1 = &a, * p2 = &b;Swap(p1, p2);return 0;
}

不要被调试欺骗了,实际上它们调用的不是同一个函数,而是不同的三个函数。这是编译器自动生成的。类型不一样,开辟的大小也不一样,这是编译器造成的假象。

通过汇编观察:

调用的函数地址不相同,所以不是一个函数。

那么模板的原理是什么?

2.4 函数模板的原理

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

这个叫做函数模板的实例化。生成一个函数。

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

        实际过程没有变,只是编译器帮助我们去写。

2.4.1库中的swap

        在C语言阶段我们要交换函数swap需要自己写,而在C++阶段,由于swap使用的频繁,使用C++直接将swap纳入库中,库中的swap就是模板写的:

以后C++中调用库的std就可以了。

2.5 函数模板的实例化

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

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

template<class T>
T Add(const T& left, const T& right)
{return left + right;
}
int main()
{int a1 = 10, a2 = 20;double b1 = 10.2, b2 = 20.2;Add(a1, a2);Add(b1, b2);/*该语句不能通过编译,因为在编译期间,当编译器看到该实例化时,需要推演其实参类型通过实参a1将T推演为int,通过实参d1将T推演为double类型,但模板参数列表中只有一个T,编译器无法确定此处到底该将T确定为int 或者 double类型而报错注意:在模板中,编译器一般不会进行类型转换操作,因为一旦转化出问题,编译器就需要背黑锅Add(a1, b1);*/return 0;
}

像Add(a1,b1);这样编译器不知道该怎么推演。此处有种处理方式:1.用户自己来强制转化 2.使用显示实例化

Add(a1, (int)b1);

也可以像下面这样:
        下面这样就不会有问题,使用auto接收返回值的类型。

template<class T1,class T2>
auto Add(const T1& left, const T2& right)
{return left + right;
}
int main()
{int a1 = 10, a2 = 20;double b1 = 10.2, b2 = 20.2;Add(a1, a2);Add(b1, b2);Add(a1, b1);return 0;
}

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

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

int main(void)
{int a = 10;double b = 20.0;// 显式实例化Add<int>(a, b);return 0;
}

有一种情况必须进行显示实例化:

template<class T>
T* func(int a)
{//返回一个T的空间T* p = (T*)operator new(sizeof(T));return p;
}int main()
{//只能进行显示实例化int* ret = func<int>(1);return 0;
}

2.6 模板参数的匹配原则

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

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

结果:

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

// 专门处理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); // 模板函数可以生成更加匹配的版本,编译器根据实参生成更加匹配的Add函数 
}

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

匹配:

1.都有的情况,优先匹配普通函数+参数匹配(成品+口味对)。

2.没有普通函数,优先匹配参数匹配+函数模版(成品+口味对)。

3.只有一个,类型转换一下也能用,也可以匹配调用(口味不对,将就一下也行)。

三、类模板

        3.1 类模板的定义格式

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

        模板在C++中更多应用类模板。

如果不用模板,我们想要使用俩个不一样的栈stack存储俩个不同类型的数据时,使用typedef对类型进行更名,只能对一个类型使用。

typedef int T;
class Stack
{
public:Stack(size_t capacity = 4){_array = (T*)malloc(sizeof(T) * capacity);if (nullptr == _array){perror("malloc申请空间失败");return;}_capacity = capacity;_size = 0;}void Push(const T& data){//扩容_array[_size] = data;++_size;}
private:T* _array;size_t _capacity;size_t _size;
};int main()
{Stack st1;//intStack st2;//doublereturn 0;
}

C语言做不到,要做到只能写俩个类。所以使用模板节省了很多代码量。

// 类模版
template<class T>
class Stack
{
// 代码....
}

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

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

模板是写给编译器的。这俩个栈的类型不一样。是俩个不同的类实例化的对象

3.2类模板声明和定义分离

          模板的声明和定义分离最好不要分文件到.h 和 .cpp。因为会出很多链接错误

// 类模版
template<class T>
class Stack
{
public://。。。。void Push(const T& data);
private:T* _array;size_t _capacity;size_t _size;
};
//在同一个文件中
template<class T>
void Stack<T>::Push(const T& date)
{//扩容_array[_size];++_size;
}

全部放在类中,也不用担心内联问题,因为内联只是给编辑器的建议,要经过编辑器的允许才能是内联。


如果你又所收获可以留下你的关注和点赞,谢谢你的观看!!

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

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

相关文章

数据仓库——分层原理

目录 一、什么是数据仓库 二、数仓建模的意义&#xff0c;为什么要对数据仓库分层&#xff1f; 三、ETL 四、技术架构 五、数仓分层架构 数仓逻辑分层 1、数据引入层&#xff08;ODS&#xff0c;Operational Data Store&#xff0c;又称数据基础层&#xff09;&#xff…

解决 WooCommerce 的分析报表失效问题

今天明月的一个境外电商客户反应网站的 WooCommerce 分析报表已经十多天没有更新了&#xff0c;明明每天都有订单交易可分析报表里的数据依旧是十多天前的&#xff0c;好像更新完全停滞了似的。明月也及时的查看了后台的所有设置&#xff0c;确认没有任何问题&#xff0c;WooCo…

Android刮刮卡自定义控件

效果图 刮刮卡自定义控件 import android.content.Context; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Path; import android.graphics.PorterDuff; import …

类和对象03

六、继承 我们发现&#xff0c;定义这些类时&#xff0c;下级别的成员除了拥有上一级的共性&#xff0c;还有自己的特性。 这个时候我们就可以考虑利用继承的技术&#xff0c;减少重复代码 6.1 继承的基础语法 例如我们看到很多网站中, 都有公共的头部&#xff0c;公共的底…

乡村振兴的乡村人才引进与培养:引进和培养乡村人才,激发乡村发展活力,为乡村振兴提供人才保障

目录 一、引言 二、乡村人才引进与培养的重要性 &#xff08;一&#xff09;人才是乡村振兴的核心动力 &#xff08;二&#xff09;人才是乡村文化传承的载体 &#xff08;三&#xff09;人才是乡村社会治理的基石 三、乡村人才引进与培养的现状 &#xff08;一&#xf…

备战秋招c++ 【持续更新】

T1 牛牛的快递 原题链接&#xff1a;牛牛的快递_牛客题霸_牛客网 (nowcoder.com) 题目类型&#xff1a;模拟 审题&确定思路&#xff1a; 1、超过1kg和不足1kg有两种不同收费方案 ---- 起步价问题 2、超出部分不足1kg的按1kg计算 ----- 向上取整 3、向上取整的实现思路…

移动端应用订阅SDK接入攻略

本文档介绍了联想应用联运移动端订阅SDK接入操作指南&#xff0c;您可在了解文档内容后&#xff0c;自行接入应用联运移动端订阅SDK。 接入前准备 1请先与联想商务达成合作意向。 2.联系联想运营&#xff0c;提供应用和公司信息&#xff0c;并获取商户id、app id、key&#…

谷歌开发者账号身份验证不通过?该怎么办?

我们都清楚&#xff0c;随着谷歌上架行业的快速发展&#xff0c;谷歌政策也在不断更新变化&#xff0c;对开发者账号的审核标准也在不断提升。其中一项要求就是&#xff0c;开发者账号需要进行身份验证才能发布应用。 Your identity couldnt be verified&#xff01;“我们无法…

词法与语法分析器介绍

概述 词法和语法可以使用正则表达式和BNF范式表达&#xff0c;而最终描述文法含义的事状态转换图 Lex与YACC 词法分析器Lex 词法分析词Lex&#xff0c;是一种生成词法分析的工具&#xff0c;描述器是识别文本中词汇模式的程序&#xff0c;这些词汇模式是在特殊的句子结构中…

二叉树的实现(递归实现)

前言&#xff1a;本文讲解通过递归的方式实现二叉树的一些基本接口。 目录 通过左右子树的方式实现二叉树&#xff1a; 二叉树的遍历&#xff1a; 求二叉树结点的个数&#xff1a; 二叉树所有节点的个数&#xff1a; 二叉树叶子节点的个数&#xff1a; 求第k层节点的节点…

4,八种GPIO模式

资料来源:【STM32基础学习】八种GPIO模式总结-云社区-华为云 (huaweicloud.com) 【STM32基础学习】八种GPIO模式总结-云社区-华为云 (huaweicloud.com) 【STM32基础学习】八种GPIO模式总结-云社区-华为云 (huaweicloud.com) 仅作个人自学笔记&#xff0c;如有冒犯&#xf…

电子阅览室解决方案

一.方案概述 “电子阅览室”概念一经提出&#xff0c;就得到了广泛的关注&#xff0c;纷纷组织力量进行探讨、研究和开发&#xff0c;进行各种模型的试验。随着数字地球概念、技术、应用领域的发展&#xff0c;电子阅览室已成为数字地球家庭的成员&#xff0c;为信息高速公路提…

uniCloud云存储uni-cdn七牛云扩展存储-开发uniapp项目节约开发成本

为什么要使用uniCloud的扩展存储&#xff0c;那就是省钱&#xff0c;而且DCloud也一直在推uni-cdn&#xff0c;我在项目中也使用七牛云的扩展存储&#xff0c;确实是省钱&#xff0c;如果你的项目使用到大量的图片后者音视频&#xff0c;这些的算计可以帮你省不少钱。下面就通过…

【状态机动态规划】3129. 找出所有稳定的二进制数组 I

本文涉及知识点 动态规划汇总 LeetCode 3129. 找出所有稳定的二进制数组 I 给你 3 个正整数 zero &#xff0c;one 和 limit 。 一个 二进制数组 arr 如果满足以下条件&#xff0c;那么我们称它是 稳定的 &#xff1a; 0 在 arr 中出现次数 恰好 为 zero 。 1 在 arr 中出现…

dp背包问题

英雄联盟游戏中新出n个英雄&#xff0c;用长度为n的教组 costs 表示每个英雄的定价&#xff0c;其中 costs[i]表示第i个英雄的点券价格。假如你一共有coins点券可以用于消费&#xff0c;且想要买尽可能多的英雄并日选择英雄按costs[i]给出顺序获取。给你价格数组 costs 和金币量…

Golang | Leetcode Golang题解之第116题填充每个节点的下一个右侧节点指针

题目&#xff1a; 题解&#xff1a; func connect(root *Node) *Node {if root nil {return root}// 每次循环从该层的最左侧节点开始for leftmost : root; leftmost.Left ! nil; leftmost leftmost.Left {// 通过 Next 遍历这一层节点&#xff0c;为下一层的节点更新 Next …

vue3 uni-app 中小程序实现 底部tabbar 中间凸起部分 或者说自定义底部tabbar [保姆级别教程]

1、先来看一下效果 2、代码实现 我们还是在 pages.json 中正常配置我们底部的tabbar 但是需要 添加一个字段 "custom": true, //开启自定义tabBar 不填每次原来的tabbar在重新加载时都回闪现 3、 在 pages同一级 或者 里面创建一个 子组件 用来放我们的模版 4、 …

MPLS原理与配置

1.MPLS概述 &#xff08;1&#xff09;传统IP路由转发 &#xff08;2&#xff09;MPLS基本概念 ⦁ MPLS起源于IPv4&#xff08;Internet Protocol version 4&#xff09;&#xff0c;其核心技术可扩展到多种网络协议&#xff0c;包括IPv6&#xff08;Internet Protocol ver…

单片机的内存映射和重映射

内存映射 在单片机内&#xff0c;不管是RAM还是ROM还是寄存器&#xff0c;他们都是真实存在的物理存储器&#xff0c;为了方便操作&#xff0c;单片机会给每一个存储单元分配地址&#xff0c;这就叫做内存映射。 单片机的内存映射是指将外部设备或外部存储器映射到单片…

【软件设计师】——5.数据库系统

目录 5.1 基本概念 5.2 三级模式两级映射 5.3 设计过程和数据模型 5.4 关系代数 5.5 完整性约束 5.6 规范化和反规范化 5.7 控制功能 5.8 SQL语言 5.9 数据库安全 5.10 数据备份 5.11 数据库故障与恢复 5.12 数据仓库、数据挖掘和大数据 5.1 基本概念 相关术语 候选…