【C++初阶】六、模板初阶(函数模板+类模板)

news/2024/4/28 4:20:45/文章来源:https://blog.csdn.net/m0_58124165/article/details/127710923

文章目录

  • 泛型编程
  • 函数模板
    • 函数模板的概念
    • 函数模板的格式
    • 函数模板的原理
    • 函数模板的实例化
    • 模板参数的匹配原则
  • 类模板
    • 类模板的定义格式
    • 类模板的实例化

泛型编程

引入 - 通用的交换函数

如果让你编写一个函数,用于两个数的交换。在C语言中,我们会用如下方法:

// 交换两个整型
void Swapi(int* p1, int* p2)
{int temp = *p1;*p1 = *p2;*p2 = tmp;
}
// 交换两个双精度浮点型
void Swapd(double* p1, double* p2)
{double temp = *p1;*p1 = *p2;*p2 = tmp;
}

因为C语言不支持函数重载,所以用于交换不同类型变量的函数的函数名是不能相同的,并且传参形式必须是址传递,不能是值传递。
而在学习了C++的函数重载和引用后,我们又会用如下方法实现两个数的交换:

// 交换两个整型
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;
}

C++的函数重载使得用于交换不同类型变量的函数可以拥有相同的函数名,并且传参使用引用传参,使得代码看起来不那么晦涩难懂。

但是,这种代码仍然存在它的不足之处:

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

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

就像小时候玩橡皮泥的一些动物等模子一样,我们放入不同颜色的材料,就能得到形状相同但颜色不同的动物模型了。
在这里插入图片描述

如果在C++中,也能够存在这样一个模具,通过给这个模具填充不同颜色的材料(类型),从而得到形状相同但颜色不同的月饼(生成具体类型的代码),那将会大大减少代码的冗余。巧的是前人早已将树栽好,我们只需在此乘凉。

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

在这里插入图片描述

函数模板

函数模板的概念

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

函数模板的格式

template<typename T1, typename T2,......,typename Tn>
返回值类型 函数名(参数列表)
{//函数体
}

例如:

template<typename T> 
void Swap(T& left, T& right)
{T temp = left;left = right;right = temp;
}

注意:

  1. template 是定义模板的关键字,后面跟的是尖括号 < >
  2. typename是用来定义模板参数关键字,也可以使用class(切记:不能使用struct代替class)
  3. T1, T2, …, Tn 表示的是函数名(就像函数的参数名一样),可以理解为模板的名字,名字你可以自己取。

有了函数模板,就可以解决我们上述交换的问题了。
在这里插入图片描述
我们可以发现,这些不同的类型,我们都完成了交换。

函数模板的原理

函数模板是一个蓝图,它本身并不是函数。是编译器产生特定具体类型函数的模具。所以其实模板就是将本来应该我们做的重复的事情交给了编译器。
在这里插入图片描述
在编译器编译阶段,对于模板函数的使用,编译器需要根据传入的实参类型来推演生成对应类型的函数以供调用。比如:当用double类型使用函数模板时,编译器通过对实参类型的推演,将T确定为double类型,然后产生一份专门处理double类型的代码,对于字符类型也是如此

函数模板的实例化

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

上面只体现了对同一类型变量的交换,若我们想对不同类型变量进行交换时,使用上面的函数模板就会报错,完成不了我们的需求。那我们该怎么办呢?

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

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.2;// 自动推演实例化cout << Add(a1, a2) << endl;cout << Add(d1, d2) << endl;return 0;
}

这还是对同一类型的变量进行相加,那如果是不同类型相加呢?为什么不同类型的变量相加不能使用上面的模板呢?

Add(a1, d1);//什么不行呢?

注意: 在模板中,编译器一般不会进行类型转换操作,因为一旦转化出问题,编译器就需要背黑锅。

因为在编译期间,当编译器看到该实例化时,需要推演其实参类型通过实参a1将T推演为int,通过实参d1将T推演为double类型,但模板参数列表中只有一个T,编译器无法确定此处到底该将T确定为int 或者 double类型而报错。

此时,我们有三种处理方式,第一种就是我们在传参时将b强制转换为int类型,第二种就是使用下面说到的显示实例化,第三种就是使用多个参数模板

不同类型形参传参时的处理:

  1. 传参时强转(对应形参需要const修饰)
template<class T>
T Add(const T& left, const T& right)//const接收常性实参
{return left + right;
}int main()
{int a1 = 10, a2 = 20;double d1 = 10.1, d2 = 20.2;//对于只有一个参数模板,只能在传参时,自己去强转成一样的类型cout << Add((double)a1, d2) << endl;//强转,临时变量传参,具有常性cout << Add(a1, (int)d2) << endl;//强转,临时变量传参,具有常性return 0;
}

使用强制类型转换在推演的时候将形参转换成同一类型。

  1. 显式实例化(传参时隐式类型转换,对应形参需要const修饰)
template<class T>
T Add(const T& left, const T& right)//需要使用const接收
{return left + right;
}int main()
{int a1 = 10, a2 = 20;double d1 = 10.1, d2 = 20.2;// 显示实例化//指定模板参数的实际类型为doublecout << Add<double>(a1, d2) << endl;//显式实例化,a1发生隐式类型转换//指定模板参数的实际类型为intcout << Add<int>(a1, d2) << endl;//显式实例化,d2发生隐式类型转换return 0;
}
  1. 使用多个模板
template<class T1, class T2>//两个参数模板,可以传不同类型的参数  //可以写typename也可以写class
T1 Add(const T1& left, const T2& right)//返回值类型只能选择其中一种
{return left + right;
}int main()
{int a1 = 10, a2 = 20;double d1 = 10.1, d2 = 20.2;cout << Add(a1, d2) << endl;cout << Add(d1, a2) << endl;return 0;
}

模板参数的匹配原则

  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版本
}
  1. 对于非模板函数和同名的函数模板,如果其他条件都相同,在调用时会优先调用非模板函数,而不会从该模板产生出一个实例。如果模板可以产生一个具有更好匹配的函数,那么选择模板
//专门用于int类型加法的非模板函数
int Add(const int& x, const int& y)
{return x + y;
}
//通用类型加法的函数模板
template<typename T1, typename T2>
T1 Add(const T1& x, const T2& y)
{return x + y;
}
int main()
{Add(10, 20); //与非模板函数完全匹配,不需要函数模板实例化Add(2.2, 2); //函数模板可以生成更加匹配的版本,编译器会根据实参生成更加匹配的Add函数return 0;
}
  1. 模板函数不允许自动类型转换,但普通函数可以进行自动类型转换
template<typename T>
T Add(const T& x, const T& y)
{return x + y;
}
int main()
{Add(2, 2.2); //模板函数不允许自动类型转换,不能通过编译//因为只有一个参数模板,只能在传参时,自己去强转成一样的类型return 0;
}

类模板

类模板的定义格式

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

例如:

template<typename T>
class Scores
{
public:void Print(){cout << "数学:" << _Math << endl;cout << "语文:" << _Chinese << endl;cout << "英语:" << _English << endl;}
private:T _Math;T _Chinese;T _English;
};

注意: 类模板中的成员函数若是放在类外定义时,需要加模板参数列表。

template<class T>
class Scores
{
public:void Print();
private:T _Math;T _Chinese;T _English;
};
template<class T>
void Scores<T>::Print()
{cout << "数学:" << _Math << endl;cout << "语文:" << _Chinese << endl;cout << "英语:" << _English << endl;
}

类模板的实例化

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

这句话的理解:

//Score不是真正的类,Score<int>和Score<double>才是真正的类
Score<int> s1;//需要显示实例化
Score<double> s2;

我们还需注意的是,虽然是由同一个类模板实例化出来的,但实例化出来的两个类不是同一个类,因为他们的类型大小都不相同。
例如:我们以前定义的栈类

template<typename T>
class Stack
{
public:Stack(int capacity = 4){cout << "Stack(int capacity = )" <<capacity<<endl;_a = (T*)malloc(sizeof(T)*capacity);if (_a == nullptr){perror("malloc fail");exit(-1);}_top = 0;_capacity = capacity;}~Stack(){cout << "~Stack()" << endl;free(_a);_a = nullptr;_top = _capacity = 0;}void Push(const T& x){// ....// 扩容_a[_top++] = x;}private:T* _a;int _top;int _capacity;
};int main()
{// 类模板一般没有推演时机,函数模板实参传递形参,推演模板参数// 类模板只能显示实例化// 他们是同一个类模板实例化出来的// 但是模板参数不同,他们就是不同类型Stack<double> st1; // doublest1.Push(1.1);Stack<int> st2; // intst2.Push(1);return 0;
}

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

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

相关文章

我让Chat GPT准备了几份SAP 顾问英文面试自我介绍的模板,大家感受一下

有个朋友说有个面试要用英文来做自我介绍&#xff0c;我灵机一动&#xff0c;不如让Chat GPT准备了几份SAP 顾问英文面试自我介绍的模板&#xff0c;大家感受一下。我看下来感觉写的还是中规中矩&#xff0c;可以一用&#xff0c;。 模板1 Sure, I can help you with that! Her…

【Java学习笔记】39.Java 多线程编程

Java 多线程编程 Java 给多线程编程提供了内置的支持。 一条线程指的是进程中一个单一顺序的控制流&#xff0c;一个进程中可以并发多个线程&#xff0c;每条线程并行执行不同的任务。 多线程是多任务的一种特别的形式&#xff0c;但多线程使用了更小的资源开销。 这里定义和…

navigator 拓宽前端视野

前言&#x1f490; 写本文的起因是最近做了一个共享屏幕在线演示ppt的需求&#xff0c;发现了navigator的新大陆。原来web端开启屏幕共享是如此的简单&#xff0c;在接触之前还以为是多么高大上的功能,需求评审时内心还有些慌张。 人总是对自己不了解的东西心生恐惧&#x1f6…

VMware虚拟机卸载详细教程

安装过VMware虚拟机的小伙伴&#xff0c;90%可能都会遇到这样的问题&#xff1a;安装容易&#xff0c;卸载难。而且卸载不干净&#xff0c;就会导致后续安装和使用出现各种Bug。今天就给大家详细说说如何彻底干净的从本机卸载VMware。 1. 卸载之前&#xff0c;需要先关闭VMware…

【ChatGPT】Notion AI 从注册到体验:如何免费使用

欢迎关注【youcans的GPT学习笔记】原创作品&#xff0c;火热更新中 【ChatGPT】Notion AI 从注册到体验1. Notion AI 介绍1.1 Notion AI 简介1.2 Notion AI 的核心能力1.3 Notion AI 与 ChatGPT 的比较2. Notion AI 国内用户注册2.1 PC 端用户注册2.2 移动端用户注册3. Notion …

如何用C语言实现渣男通讯录

注意&#xff1a;纯属玩笑&#xff0c;博大家一乐&#xff0c;切勿当真&#x1f4d6;首先我们要知道一个渣男通讯录有哪些信息要包含哪些功能1.你的通讯录要装多少个女朋友你得规定吧&#xff1b;2.每个女朋友的姓名&#xff0c;年龄&#xff0c;电话&#xff0c;爱好这些要有吧…

springboot项目中外卖用户下单业务功能之需求分析+数据模型+功能开发(详细步骤)

一、需求分析 移动端用户将菜品或者套餐加入到购物车后&#xff0c;可以点击购物车种的 去结算 按钮&#xff0c;页面跳转到订单确认页面&#xff0c;点击 去支付 按钮则完成下单操作。 二、数据模型 用户下单业务对应的数据表为orders表和order_detail表&#xff1a; order…

ESP32-CAM 环境搭建(离线)

云盘&#xff1a;Arduino IDE链接&#xff1a;https://pan.baidu.com/s/1yk_tKH8eK8okp5z9qv1TLg 提取码&#xff1a;pz2h内含&#xff1a;IDE arduino-ide_nightly-20230322_Windows_64bit.zipSDK esp32_package_2.0.5_arduinocn.exe步骤1&#xff0c;IDE为绿色版&#xff0c;…

57、C语言程序设计谭浩强第五章习题

1、输入两个正整数m和n,求其最大公约数和最小公倍数 辗转相除法&#xff1a; #include <stdio.h>int main(){int m,n,temp;scanf("%d %d",&m,&n);if(m<n){tempm;mn;ntemp;}int dn*m;int r;while(rm%n){mn;nr;}printf("最大公因数为:%d&#…

ONES 入选北大光华 MBA 整合实践项目,推动校企合作

近日&#xff0c;ONES 旗下开源问答社区软件 Answer 入选北京大学光华管理学院 MBA 整合实践项目&#xff0c;并受邀出席项目启动会。同时入选的还有国电投清洁能源基金、京东零售、瑞尔集团、美国丹纳赫集团、大众汽车&#xff08;中国&#xff09;和贝壳找房六家国内外知名企…

队列----数据结构

队列&#x1f506;队列的概念&#x1f506;队列的结构&#x1f506;队列的实现&#x1f506;设计循环队列&#x1f506;循环队列的结构&#x1f506;循环队列的实现&#x1f506;结语&#x1f506;队列的概念 队列&#xff1a;只允许在一端进行插入数据操作&#xff0c;在另一端…

5G、工业4.0、车载以太网……TSN时间敏感网络能给我们带来哪些市场机会?

TSN是一项面向未来的新兴技术&#xff0c;它定义了以太网数据传输的时间敏感机制&#xff0c;为标准以太网增加了确定性和可靠性&#xff0c;以确保数据实时、确定和可靠地传输。这些扩展使得以太网能够应用于更加广泛的行业中&#xff0c;进而带来了更多新的市场机会。 1. 5G…

电力行业等保定级评级依据是什么?分为几个等级?

最近看到不少电力行业小伙伴在问&#xff0c;电力行业等保定级评级依据是什么&#xff1f;分为几个等级&#xff1f;今天我们小编就来给大家简单回答一下&#xff0c;仅供参考哦&#xff01; 电力行业等保定级评级依据是什么&#xff1f;分为几个等级&#xff1f; 【回答】&a…

【2023.3.18 美团校招】

文章目录1. 小美剪彩带2. 最多修改两个字符&#xff0c;生成字典序最小的回文串1. 小美剪彩带 题意&#xff1a;找出区间内不超过k种数字子数组的最大长度 使用双指针的方式&#xff0c;用哈希表来统计每个数出现次数。在双指针移动的过程中&#xff0c;动态的维护区间内不同数…

bean的作用域和生命周期和后置处理器以及作用域对生命周期的影响~

scope属性可以指定bean的作用范围&#xff1a; 在spring中可以通过配置bean标签的scope属性来指定bean的作用域范围&#xff0c;各取值含义参照表如下&#xff1a; 编写spring_test.xml文件&#xff1a; <?xml version"1.0" encoding"UTF-8"?> &…

Spring Quartz项目实现Job的动态控制

一、简单介绍 Quartz可以对job进行简单控制&#xff0c;但不支持分布式&#xff0c;也没有管理界面和任务分片&#xff0c;但使用起来比较简易。 二、创建简单Demo 我们开始简单使用一下&#xff0c;首先创建Demo&#xff0c;在pom文件中加入以下依赖。 <parent><g…

golang项目实战2023/03/21

初始化项目首先建立一个文件夹在文件夹下面建一个main.go文件执行初始化命令行go mod init page // page 为自己的项目名称 go mod tidy 安装框架 go get gorm.io/gorm // gorm go get gorm.io/driver/mysql // 这里是用的sql数据库 go get -u github.com/gin-gonic/gin // 这…

Numpy 广播域

NumPy广播(Broadcast)&#xff0c;广播(Broadcast)是 numpy 对不同形状(shape)的数组进行数值计算的方式&#xff0c; 对数组的算术运算通常在相应的元素上进行。不同形状指的是&#xff0c;大小不同当然不是指的维度&#xff0c;但是要求数组各维度的长度相同例如&#xff1a;…

ICG-MAL,吲哚菁绿-马来酰亚胺,CAS:2143933-81-5,科研级别试剂

ICG-MAL,吲哚菁绿-马来酰亚胺 中文名称&#xff1a;吲哚菁绿-马来酰亚胺 英文名称&#xff1a;ICG-MAL 性状&#xff1a;粉末或固体 CAS&#xff1a;2143933-81-5 分子式&#xff1a;C51H56N4O6S 分子量&#xff1a;853.09 溶剂&#xff1a;溶于二氯甲烷等常规性有机溶…

【洛谷刷题】蓝桥杯专题突破-深度优先搜索-dfs(7)

目录 写在前面&#xff1a; 题目&#xff1a;P1596 [USACO10OCT]Lake Counting S - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 题目描述&#xff1a; 输入格式&#xff1a; 输出格式&#xff1a; 输入样例&#xff1a; 输出样例&#xff1a; 解题思路&#xff1a; …