十分钟且一次性带你学懂泛型编程思想(模板化思想)

引言

  在编程的世界里,泛型编程思想(模板化思想)是一种极具魅力的编程范式。它允许我们编写出具有高度通用性和可重用性的代码,极大地提高了开发效率和代码质量。无论你是初学者还是有一定经验的开发者,掌握泛型编程思想都至关重要。在本博客中,小杨将带你深入浅出地了解泛型编程思想,让你一次性学懂这一重要概念。

1.模板的概念

  什么是模板呢?模板就是建立的一种通用的模具,模式,做法,来提高做事做产品的效率,提高复用性。编程中的模板可以提升代码可以提升代码的复用性。它允许在编程时使用抽象类型而非具体的类型。这种范式使得算法和数据处理方法可以独立于它们操作的数据类型,从而提高代码的复用性和灵活性。泛型编程的核心思想是编写尽可能通用的代码,这些代码可以在多种数据类型上工作,而不需要对每种数据类型都写一套单独的代码。
  我们学习模板,主要是学习STL(标准模板库)的使用,因为STL大量使用了模板技术来实现。

2.函数模板

2.1函数模板的语法

   函数模板的作用:建立一个通用的函数,它的返回值类型和参数类型不具体指定,而是用一个虚拟的类型(泛型)来表示。

   语法:在函数头的上一行添加声明:template<typename T>或者template<class T>
   使用:自动类型推导、显式指定泛型类型
代码示例:

#include <iostream>using namespace std;//	写函数,完成两个值的交换//	1.交换两个int
void mySwap(int& a, int& b)
{int temp = a;a = b;b = temp;
}
//	2.交换两个double,需要继续定义函数,使用同名函数来重载
void mySwap(double& a, double& b)
{double temp = a;a = b;b = temp;
}
// 接下来使用模板技术,实现任意类型的值交换
template<typename T>
void mySwap(T& a, T& b)
{T temp = a;a = b;b = temp;
}
//	使用模板技术可用下边一个函数模板代替上述重载函数两个函数。
//	下面测试函数mySwap()
void test01()
{//	交换两个intint n1 = 10;int n2 = 20;mySwap(n1, n2);cout << "n1=" << n1 << ",n2=" << n2 << endl;//	交换两个doubledouble d1 = 2.45;double d2 = 24.2;mySwap(d1, d2);cout << "d1=" << d1 << ",d2=" << d2 << endl;//	交换两个charchar c1 = 'a';char c2 = 'b';mySwap(c1,c2);cout << "c1=" << c1 << ",c2=" << c2 << endl;//	以上使用的是自动类型推导的方式,来确定T的类型//	接下来使用显式手动的方式,指定泛型T的类型int n3 = 5;int n4 = 8;mySwap<int>(n3, n4);cout << "n3=" << n3 << ",n4=" << n4 << endl;//	mySwap<int>(c1,c2);//	报错了,因为显式指定了泛型为int,那就要传入int参数,不能传入char
}

2.2函数模板的注意事项

  • 自动类型推导时,必须推导出一致的类型T
  • 模板必须确定出T的类型才可以使用。如果不能自动推导,就需要手动指定。
  • 代码示例:
template<class T>
void func(){ cout << "func被调用" << endl; }
void test02()
{//	1)自动类型推导时,必须推导出一致的类型Tint n = 10;char c = 'a';//	mySwap(n, c);	//	报错,因为n推导T为int,而c推导T为char,不一致//	2)模板必须确定出T的类型才可以使用。如果不能自动推导,就需要手动指定。//	func();//	报错,因为T的类型无法确定func<int>();	func<double>();//	可以通过手动指定T的类型来使用
}
//	课堂练习
//	1.写一个函数模板,实现两个值的大小比较,返回较大的那个值
template<class T>
T getLarger(T a, T b) { return a > b ? a : b; } // 三元运算符
void test03()
{int n1 = 10;int n2 = 20;double d1 = 2.54;double d2 = 5.43;cout << "n1和n2的比较结果:" << getLarger(n1, n2) << endl;cout << "d1和d2的比较结果:" << getLarger(d1, d2) << endl;
}
//	2.写一个函数模板,可以返回一个数组中的最大值
template<class T>	
T getMax(T arr[], int len)	
//	注意:数组传参不是传递整个数组,而是数组的首元素地址,这里T arr[]相当于T* arr,既然传的是地址,那就会丢失长度信息,所以必须增加第二个参数传长度信息
{//	遍历数组,找出最大值,返回T temp = arr[0];for (int i = 1; i < len; i++){if (arr[i]>temp){temp = arr[i];}}return temp;
}
void test04()
{//	整型数组int int_arr[] = { 4,33,64,2,35,41,34,17 };cout << "int_arr数组中的最大值:" << getMax(int_arr, 8) << endl;//	char型数组char char_arr[] = { 'a','b','c','d' };cout << "char_arr数组中的最大值:" << getMax(char_arr, 4) << endl;
}

2.3普通函数和函数模板的区别

  • 普通函数调用时可以发生自动类型转换(隐式类型转换)
  • 函数模板调用时,如果利用自动类型推导,不会发生隐式类型转换。如果显式指定泛型类型,也可以发生隐式类型转换。
  • 建议:使用显式指定泛型的方式来调用函数模板。
  • 代码示例:
//	普通函数
int add(int a, int b) { return a + b; }
//	函数模板
template<class T>
T add_tpl(T a, T b) { return a + b; }
void test05()
{//	1)普通函数调用时可以发生自动类型转换(隐式类型转换)int a = 10;char c = 'a';cout << add(a, c) << endl;//	这里发生了隐式类型转换,将char型转成了int,然后进行运算,而'a'字符的ASCII码是97,所以转成了97,结果是10+97=107// 2)函数模板调用时,如果利用自动类型推导,不会发生隐式类型转换。如果显式指定泛型类型,也可以发生隐式类型转换//	cout << add_tpl(a, c) << endl;//	报错,不会发生隐式类型转换,所以推导出不一致的类型,报错cout << add_tpl<int>(a, c) << endl;	//	显式指定泛型的类型后,发生了隐式类型转换
}

2.4普通函数和函数模板的调用区别

  • 如果普通函数和函数模板都可以实现,优先调用普通函数(具体的高于通用的)
  • 可以通过空模板参数列表的语法来强制调用模板。<>
  • 函数模板也可以发生重载(多个同名的函数模板,根据参数来重载)
  • 建议:既然写了函数模板,就没必要再写普通函数了。
  • 代码示例:
void myPrint(int a, int b) 
{ cout << "a=" << a << ",b=" << b << ",调用普通函数" << endl; 
}
template<class T>
void myPrint(T a,T b) 
{ cout << "a=" << a << ",b=" << b << ",调用函数模板,两个参数" << endl; 
}
template<class T>
void myPrint(T a, T b,T c)
{cout << "a=" << a << ",b=" << b<<",c="<<c << ",调用函数模板,三个参数" << endl;
}
void test06()
{//	1)如果普通函数和函数模板都可以实现,优先调用普通函数(具体的高于通用的)int a = 10;int b = 20;myPrint(a, b);//	优先调用的是普通函数//	2)可以通过空模板参数列表的语法来强制调用模板。<>myPrint<>(a, b);//	强制调用模板//	3)函数模板也可以发生重载(多个同名的函数模板,根据参数来重载)int c = 30;myPrint(a, b, c);//	这里发生重载,自动调用匹配的函数char c1 = 'a';char c2 = 'b';myPrint(c1, c2);//	这里调用两个参数的模板,因为它才能匹配,其他的不匹配
}

2.5函数模板的局限性

   模板的通用性不是万能的,在遇到自定义类型的数据时,就无法处理了。

  • 代码示例:
class Student
{
public:string m_Name;int m_Age;Student(string name, int age){m_Name = name;m_Age = age;}
};
//比较两个值是否相等
template<class T>
bool myCompare(T a, T b)
{if (a==b){return true;}else{return false;}
}
//写个重载函数,专门用于对象的比较
bool myCompare(Student& s1, Student& s2)
{if (s1.m_Name == s2.m_Name and s1.m_Age==s2.m_Age){return true;}else{return false;}
}
void test07()
{int n1 = 10;int n2 = 20;cout << "n1和n2比较结果:" << myCompare(n1, n2) << endl;Student s1("Tom", 10);Student s2("Lucy", 12);Student s3("Lucy", 12);cout << "s1和s2比较结果" << myCompare(s1, s2) << endl;cout << "s2和s3比较结果" << myCompare(s2, s3) << endl;
}

3. 类模板

  类模板的作用:建立一个通用的类,类中的数据成员不具体指定类型,也用泛型来代替。此时我们可能需要多个泛型。

3.1类模板的语法

  在类前面加一行声明即可template<typename T,...>或者template<class T,...>
  注意:类模板和函数模板的最大区别是:类模板不能自动推导泛型,必须显示指定泛型的类型才可以使用。
   代码示例:

#include <iostream>
#include <string>
using namespace std;//	3.1类模板的语法
template<class NameType,class AgeType=int>
class Person
{NameType m_Name;AgeType m_Age;
public:Person(NameType name, AgeType age){m_Name = name;m_Age = age;}void show() { cout << "name:" << m_Name << ",age:" << m_Age << endl; }
};
void test01()
{Person<string, int> p1("tom", 10);//	类模板使用时必须指定泛型类型,不能自动推导Person<string, string> p2("john","八岁");Person<string> p3("lucy", 12);//	AgeType使用默认值p1.show();p2.show();p3.show();
}

3.2类模板结合函数模板来使用

  当类模板实例化出来的对象作为参数传递的时候,常见的有以下几种方式:
  1)指定传入的泛型类型(使用较多的,因为便于理解,灵活度不高)
   2)对象参数模板化(进一步使用函数模板将对象的泛型模板化,提高灵活度)
  3)整个对象模板化(对象用泛型代替,灵活度最高)
代码示例:

//	1)指定传入的泛型类型(使用较多的,因为便于理解,灵活度不高)
void show_type(Person<string, int>& p) { p.show(); }
//	2)对象参数模板化(进一步使用函数模板将对象的泛型模板化,提高灵活度)
template<class T1,class T2>
void show_tpl_para(Person<T1, T2>& p)
{p.show();cout << "T1的类型是:" << typeid(T1).name() << endl;cout << "T2的类型是:" << typeid(T2).name() << endl;
}
//	3)整个对象模板化(对象用泛型代替,灵活度最高)
template<class T>
void show_tpl_class(T& p)
{p.show();cout << "T的类型是:" << typeid(T).name() << endl;
}
//	再写一个学生类,来测试整个对象模板化
class Student
{
public:string m_Name;int m_Age;Student(string name, int age){m_Name = name;m_Age = age;}void show() { cout << "name:" << m_Name << ",age:" << m_Age << endl; }
};
void test02()
{Person<string, int> p1("tom", 10);//	类模板使用时必须指定泛型类型,不能自动推导Person<string, string> p2("john", "八岁");Person<string> p3("lucy", 12);//AgeType使用默认值//	接下来将上面几个对象作为函数参数使用//	1)指定传入的泛型类型(使用较多的,因为便于理解,灵活度不高)show_type(p1);//	必须传入Person<string,int>类型的对象才可以//	show_type(p2);//类型不匹配,则报错//	2)对象参数模板化(进一步使用函数模板将对象的泛型模板化,提高灵活度)show_tpl_para(p1);//	自动类型推导show_tpl_para<string, string>(p2);//	手动指定泛型类型//	3)整个对象模板化(对象用泛型代替,灵活度最高)show_tpl_class(p1);show_tpl_class(p2);Student s1("jerry", 9);show_tpl_class(s1);//	即使使用其他类型的对象,也没问题
}

3.3类模板遇到继承

  • 如果父类是类模板,子类既可以是类模板,也可以是普通类。
  • 父类子类都是类模板,子类和父类的泛型可以不同,各自指定。
  • 父类子类都是类模板,让它们使用同一个泛型。
  • 代码示例:
//定义父类水果类Fruit,是个类模板
template<class T>
class Fruit
{
public:Fruit();//	只在这声明,去类外实现
};
//	在函数外实现的时候,也需要加模板声明
template<class T>
Fruit<T>::Fruit() { cout << "Fruit类在构造,泛型类型是:" << typeid(T).name() << endl; }//	1)如果父类是类模板,子类既可以是类模板,也可以是普通类。
//	子类,是个普通类,继承父类的时候指定父类的泛型类型
class Apple :public Fruit<int>
{
public:Apple(){ cout << "Apple类在构造,它是普通类,同时父类被指定成int" << endl; }
};
//	2)父类子类都是类模板,子类和父类的泛型可以不同,各自指定。
template<class T>
class Banana :public Fruit<int>
{
public:Banana() { cout << "Banana类在构造,它是类模板,他的泛型是:" << typeid(T).name() << ",他的父类也是类模板,父类的泛型是int" << endl; }
};
//	3)父类子类都是类模板,让它们使用同一个泛型。
template<class T>
class Orange :public Fruit<T>
{
public:Orange() { cout << "Orange类在构造,它是类模板,它的父类也是类模板,并且它们共用一个泛型:" << typeid(T).name() << endl; }
};
void test03()
{Apple a;//	Apple是普通类,它的父类是类模板,父类的泛型是intBanana<double> b;//	Banana和父类Fruit都是类模板,各有各的泛型类型Orange<long> o;//	Orange和父类Fruit都是类模板,并且共用同一泛型类型
}

结语

  通过小杨的介绍,相信你已经对泛型编程思想(模板化思想)有了更深入的了解。泛型编程不仅能够提高代码的通用性和可重用性,还能够降低代码的复杂性,使得我们的程序更加健壮和易于维护。虽然泛型编程的概念相对抽象,但只要我们通过实践和思考,逐渐掌握其精髓,就能在编程的道路上更进一步。

  希望本章能为你打开泛型编程思想的大门,激发小伙伴们深入学习的兴趣。编程之路漫长而充满挑战,但只要我们勇于探索和不断学习,定能在这条道路上越走越远。接下来,就让我们一起努力,将泛型编程思想融入到实际开发中,提升我们的编程技能吧。小伙伴们要加油呀!!!!!

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

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

相关文章

GNU/Linux - 什么是loopback设备

在计算机科学中&#xff0c;特别是在类Unix操作系统中&#xff0c;环回设备&#xff08;loopback device&#xff09;是一种虚拟设备&#xff0c;它将一个文件映射为块设备。这使得可以像访问物理磁盘或分区一样访问一个文件。环回设备通常用于挂载磁盘镜像、操作文件系统镜像以…

【单链表】05 有一个带头结点的单链表L,设计一个算法使其元素递增有序。

&#x1f57a;作者&#xff1a; 主页 我的专栏C语言从0到1探秘C数据结构从0到1探秘Linux算法题上机准备 &#x1f618;欢迎 ❤️关注 &#x1f44d;点赞 &#x1f64c;收藏 ✍️留言 题目 有一个带头结点的单链表L,设计一个算法使其元素递增有序。 算法思路 解决办法有很多&…

代码随想录训练营第三十三天 509斐波那契数列 70爬楼梯 746使用最小花费爬楼梯

第一题&#xff1a; 原题链接&#xff1a;509. 斐波那契数 - 力扣&#xff08;LeetCode&#xff09; 本题很简单&#xff0c; 递推公式&#xff1a;dp[i] dp[i - 1] dp[i - 2]; 初始化dp[0] 0; dp[1] 1; 顺序遍历即可 代码如下&#xff1a; class Solution { public…

商品分页,商品模糊查询

一、商品分页 引入分页 定义分页主件的参数 在请求url上拼接参数 定义改变当前页码后触发的事件&#xff0c;把当前页码的值给到分页表单&#xff0c;重新查询 二、商品查询&#xff08;以商品的名称查询name为例&#xff09; 引入elementplus的from表单组件 定义一个FormData…

实现在列表框内及列表框间实现数据拖动:在工作表界面窗体的加载

《VBA高级应用30例》&#xff08;版权10178985&#xff09;&#xff0c;是我推出的第十套教程&#xff0c;教程是专门针对高级学员在学习VBA过程中提高路途上的案例展开&#xff0c;这套教程案例与理论结合&#xff0c;紧贴“实战”&#xff0c;并做“战术总结”&#xff0c;以…

three-platformize 微信小程序 uniapp 使用截图功能

最近需要将3d场景进行截图&#xff0c;但是网上的各种各样&#xff0c;看的我一团乱麻&#xff0c;因此在解决完后就将这些简单的分享一下&#xff1b; 原理&#xff1a;将3维场景的那个canvas中的像素提取出来&#xff0c;找一个空的canvas二维画布放上去&#xff0c;然后用二…

jitsi 使用JWT验证用户身份

前言 Jitsi Meet是一个很棒的会议系统,但是默认他运行所有人创建会议,这样在某种程度上,我们会觉得他不安全,下面我们就来介绍下使用JWT来验证用户身份 方案 卸载旧的lua依赖性sudo apt-get purge lua5.1 liblua5.1-0 liblua5.1-dev luarocks添加ubuntu的依赖源,有则不需…

运维锅总详解设计模式

本首先简介23种设计模式&#xff0c;然后用Go语言实现这23种设计模式进行举例分析。希望对您理解这些设计模式有所帮助&#xff01; 一、设计模式简介 设计模式是软件设计中用于解决常见设计问题的一套最佳实践。它们不是代码片段&#xff0c;而是解决特定问题的通用方案。设…

Xilinx 的aarch32 aarch64 armr5 编译器区别

对于 Xilinx 设备&#xff0c;特别是使用 Vivado 或 Vitis IDEs 开发的那些&#xff0c;针对不同 ARM 架构如 aarch32、aarch64 和 ARM R5&#xff0c;使用的编译器也会有所不同。以下是这些架构在 Xilinx 开发环境中常用的编译器&#xff1a; 1. AArch32 (ARM 32位) 对于 Xi…

给元素的margin-top/top设置百分比,是基于什么进行计算?

1、普通元素 margin-top&#xff1a;基于父元素的宽度计算 2、relative元素 margin-top&#xff1a;基于自身的宽度计算top&#xff1a;基于自身的宽度计算 3、absolute元素 margin-top&#xff1a;基于最近一级的非static元素的宽度计算top&#xff1a;基于最近一级的非s…

Apache AGE 安装部署

AGE概述 概述 我们可以通过源码安装、拉取docker镜像运行、直接使用公有云三种方式中的任意一种来使用Apache AGE 获取 AGE 发布版本 可以在 https://github.com/apache/age/releases 找到发布版本和发布说明。 源代码 源代码可以在 https://github.com/apache/age 找到…

PowerShell install 一键部署mysql 9.0.0

mysql 前言 MySQL 是一个基于 SQL(Structured Query Language)的数据库系统,SQL 是一种用于访问和管理数据库的标准语言。MySQL 以其高性能、稳定性和易用性而闻名,它被广泛应用于各种场景,包括: Web 应用程序:许多动态网站和内容管理系统(如 WordPress)使用 MySQL 存…

vite项目使用qiankun构建hash路由微前端

文章目录 前言一、主应用使用react18 react-router-dom61、项目安装2、主应用中注册微应用3、主应用中设置路由和挂载子应用的组件 二、创建react18 react-router-dom6子应用1、项目安装2、修改子应用 vite.config.ts3、修改子应用 main.tsx,区分qiankun环境和独立部署环境4、…

mybatis-plus中的方法的作用

在MyBatis-Plus中&#xff0c;这些方法是用于构建查询条件或排序条件的工具&#xff0c;它们大多属于com.baomidou.mybatisplus.core.conditions.query.QueryWrapper、UpdateWrapper或类似的包装器类中&#xff0c;用于构建复杂的SQL查询或更新语句。这些方法通过链式调用的方式…

【割点 C++BFS】2556. 二进制矩阵中翻转最多一次使路径不连通

本文涉及知识点 割点 图论知识汇总 CBFS算法 LeetCode2556. 二进制矩阵中翻转最多一次使路径不连通 给你一个下标从 0 开始的 m x n 二进制 矩阵 grid 。你可以从一个格子 (row, col) 移动到格子 (row 1, col) 或者 (row, col 1) &#xff0c;前提是前往的格子值为 1 。如…

日志无界:Eureka中服务的分布式日志记录策略

日志无界&#xff1a;Eureka中服务的分布式日志记录策略 引言 在微服务架构中&#xff0c;服务的分布式日志记录对于监控、故障排查和安全审计至关重要。Eureka作为Netflix开源的服务发现框架&#xff0c;虽然本身不直接提供日志记录功能&#xff0c;但可以与其他日志记录工具…

AE常用工具

目录 图形工具&#xff08;快捷键Q&#xff09; 选取工具&#xff08;快捷键V&#xff09; 抓手工具&#xff08;快捷键H或空格&#xff09; 放缩工具&#xff08;快捷键Z或滚动滑轮&#xff09; 图形工具&#xff08;快捷键Q&#xff09; 按住alt并点击&#xff0c;可切换…

单目深度估计部署 rk3588

搞了一小段时间的单目深度估计&#xff0c;目标是在板端部署用起来&#xff0c;但由于基于开源数据或开源模型&#xff0c;将模型估计的相对深度转换成绝对深度误差非常大&#xff08;或许是转换方法有问题&#xff09;&#xff0c;另一方面如何具体的在项目中用起来还没好的想…

huggingface登不进去?

HF-Mirror点这个镜像网站

二重积分 - 包括计算方法和可视化

二重积分 - 包括计算方法和可视化 flyfish 计算在矩形区域 R [ 0 , 1 ] [ 0 , 2 ] R [0, 1] \times [0, 2] R[0,1][0,2] 下&#xff0c;函数 z 8 x 6 y z 8x 6y z8x6y 的二重积分。这相当于计算曲面 z 8 x 6 y z 8x 6y z8x6y 与 xy 平面之间的体积。 二重积分…