【C++初阶】八、初识模板(泛型编程、函数模板、类模板)

=========================================================================

相关代码gitee自取

C语言学习日记: 加油努力 (gitee.com)

 =========================================================================

接上期

【C++初阶】七、内存管理
(C/C++内存分布、C++内存管理方式、operator new / delete 函数、定位new表达式)
-CSDN博客

 =========================================================================

                     

目录

             

一 . 泛型编程


二 . 函数模板

函数模板的概念

函数模板的格式

函数模板的原理

函数模板的实例化

隐式实例化:

显式实例化:

模板参数的匹配原则


三 . 类模板

类模板的定义格式

类模板的实例化

图示 --  以栈类为例:


本篇博客相关代码

Test.cpp文件 -- C++文件

         

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

             

一 . 泛型编程

                 

  • 我们以前写的函数一般都是针对某种类型实现两值交换Swap函数

    如果交换的两值int类型那就要将Swap函数参数设置为int类型

    如果交换的两值double类型那就要将Swap函数参数设置为double类型……
    通过函数重载实现
                      

  • 对于函数虽然函数重载可以实现函数参数多类型的问题
    但也有一些不好的地方
    1、重载的函数仅仅是类型不同而已具体实现实现逻辑都是很类似
    当接收的函数参数类型不同就需要用户自己增加对应的重载函数
    2、代码可维护性比较其中一个重载函数出错可能所有的重载函数都会出错
                           

  • 那能不能实现一个通用的Swap函数实现泛型编程
    泛型编程 -- 编写与类型无关的通用代码代码复用的一种手段
    C++中为解决这个问题有了模板的概念模板泛型编程的基础
                      
  • 有了模板,相当于告诉编译器一个模子
    编译器能够根据不同的类型利用该模子生成对应类型的代码

    模板分为函数模板类模板
图示:

         

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

             

二 . 函数模板

函数模板的概念

                   

函数模板代表了一个函数家族该函数模板与类型无关

在使用时被参数化根据实参类型产生函数的特定类型版本

                     

                     


                    

函数模板的格式

              

  • 注意
    typename是用来定义模板参数的关键字上面的T1T2Tn就是模板参数),
    除了可以使用typename来定义还可以使用class来定义

    //函数模板格式:
    template<typename T1, typename T2, ……, typename Tn>
    函数返回值类型 函数名(参数列表)
    {// 函数体
    }
图示:

                     

                     


                    

函数模板的原理

            

  • 函数模板是一个蓝图它本身并不是函数
    编译器使用后能产生特定具体类型函数摸具
    所以模板就是将本来应该由我们完成的重复的事情交给了编译器完成
                   
  • 编译器编译阶段对于函数模板的使用
    编译器需要根据传入的实参类型推演生成对应类型的函数以供调用
    比如
    当使用double类型调用函数模板编译器通过对实参类型推演
    模板参数T确定为double类型然后产生一份专门处理double类型的代码
图示:

                     

                     


                    

函数模板的实例化

                   

不同类型的参数调用模板称为函数模板的实例化
模板参数实例化分为隐式示例化显式实例化
               

                   

隐式实例化:

                       

  • 编译器根据实参推演模板参数的实际类型
    上面的图示中的模板参数实例化都是隐式实例化
图示:

                   

  • 隐式实例化
    如果只设置了一个模板参数实参中却有多种类型这时将不能通过编译
    此时有两种处理方式
    1、用户自己来强制转化2、使用显式实例化
图示:

                     

                       

---------------------------------------------------------------------------------------------

                  

显式实例化:

               

  • 不通过模板参数推演识别出实参的类型而是自己显式设置模板参数的类型
    函数名后的<>指定模板参数的实际类型即可
                   
  • 显式实例化如果类型不匹配编译器会尝试进行隐式类型转换
    如果无法转换成功编译器将会报错
                       
  • 显式实例化真正用法
    设置了一个模板函数参数中并没有设置模板参数
    函数体中却使用了模板参数类型或者返回值模板参数类型
    这种情况就需要显式实例化确定实参类型
图示:

                     

                     


                    

模板参数的匹配原则

                     

  • 一个非模板函数可以和一个同名的函数模板同时存在
    而且该函数模板还可以被实例化为这个非模板函数
图示:

                

  • 对于非模板函数同名函数模板如果其它条件都相同
    在调动时会优先调用非模板函数不会从该模板产生出一个示例
    如果模板可以产生一个具有更好匹配的函数那么将选择模板
图示:

                   

  • 模板函数不允许自动类型转换普通函数可以进行自动类型转换

         

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

             

三 . 类模板

类模板的定义格式

                 

  • 类模板定义函数模板定义类似定义时将函数的位置换成类即可
    模板参数类型类中定义成员类型时进行使用

    //类模板定义格式:
    template<class T1, class T2, ……, class Tn>
    class 类模板名
    {    // 类内成员定义    
    }

                     


                    

类模板的实例化

                

  • 类模板实例化函数模板实例化不同类模板实例化需要在类模板名字后跟<>
    然后将实例化的类型放在<>即可
                        
  • 类模板名字不是真正的类名显式实例化后的结果才是真正的类名
              
  • 同一个类模板显式实例化出的不同类这些类的类型不一样
    栈类模板为例
    Stack<int> st1 Stack<double> st2
    st1 的类型是 Stack<int> ,是用于存储int类型数据
    st2 的类型是 Stack<double> ,是用于存储double类型数据
    st1 st2 类型不一样
图示 --  以栈类为例:

            

  • 注意
    类模板成员函数的声明和实现分离不能分离到两个文件中
    分离时通常都写在一个.h文件
    而且分离后的成员函数实现部分需要设置对应的函数模板
    分离后不指定成员函数的类域而是指定其类模板类型
图示:

         

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

             

本篇博客相关代码

Test.cpp文件 -- C++文件:

#define _CRT_SECURE_NO_WARNINGS 1//包含IO流:
#include <iostream>;
//完全展开std命名空间:
using namespace std;//Swap函数 -- 交换两个int类型数据:
void Swap(int& left, int& right)
{int temp = left;left = right;right = temp;
}//Swap函数 -- 交换两个double类型数据:
void Swap(double& left, double& right)
{double temp = left;left = right;right = temp;
}//Swap函数 -- 交换两个char类型数据:
void Swap(char& left, char& right)
{char temp = left;left = right;right = temp;
}/*
* 这里实现了三个Swap函数,
* 分别交换了三种不同的类型,
* 但实现的逻辑都是相同的,就只有交换类型不同,
* 所以就造成了某种程度的“冗余”
* 
* 上面的函数都需要针对具体的类型,
* 那能不能让一个代码能够针对广泛的类型呢,
* C++中就有了泛型编程:
*///函数模板:
template<typename T>
//tyename 也可以写成 class
//template<class T> 
//Swap函数 -- 交换两类型数据(泛型编程):
void Swap(T& left, T& right)
{char temp = left;left = right;right = temp;
}
/*
* 使用函数模板即可实现泛型编程,
* 让函数能够针对广泛的类型,
* 而不只能针对一种类型,
* 
* 通过关键字template即可定义一个模板,
* Swap函数的参数设置为模板参数
*///主函数:
int main()
{int a = 0; //int类型变量int b = 1; //int类型变量double c = 1.1; //double类型变量double d = 2.2; //double类型变量//调用设置了模板参数的Swap函数:Swap(a, b); //int类型 -- 模板参数TSwap(c, d); //double类型 -- 模板参数T/** 这里调用的两个Swap函数实际不是同一个,* 两个Swap函数的函数地址不同,* 不同类型调用的Swap函数不同是由模板参数导致的* *			模板的原理:* 模板参数接受参数如果是int类型,* 需要调用到int类型的函数,* T 模板参数就会推演成 int类型,(模板参数推演)* 然后就会实例化出具体的函数:* T 是int类型的对应函数。(模板实例化)* * 如果接收的是double类型数据,* T 模板参数就会推演成 double类型,(模板参数推演)* 然后就会示例化出具体的函数:* T 是double类型的对应函数。(模板实例化)*/return 0;
}//如果一个函数需要接收不同类型的参数:
template<class T1, class T2>
/*
* 如果需要接收不同类型的参数,
* 直接在模板中设置多个模板参数即可,
*(模板参数名可以随便取,但一般会取为T -- type)
* 
* 模板参数 和 函数参数 类似,
* 但是 函数参数 定义的是 形参对象,
* 而 模板参数 定义的则是 类型
*/
void func(const T1& t1, const T2& t2)
//模板参数T1接收一种类型,T2接收另一种类型
{cout << t1 << endl;cout << t2 << endl;/** 设置了模板参数的函数,* 如果要进行输入或输出,* 就必须使用 cin/cout 进行 输入/输出 了,* 因为设置了模板参数,* 不知道实际传进来的数据是什么数据,* 因为使用 scanf/printf 必须要指定数据类型,* 所以这里使用 scanf/printf 来 输入/输出*/
}//通用(泛型)加法函数:
template<class T>
T Add(T left, T right)
//接收 T 模板参数类型
{return left + right;//返回值也是 T 模板类型
}template<class T>
T* f()
{//开辟T类型的动态空间:T* p = new T[10];//没设置模板参数T,却使用了T//返回T类型指针:return p;/** 该函数没有设置模板参数T,* (设置的参数不是模板参数)* 但返回值却返回模板指针类型(T*),* * 没设置模板参数就无法进行类型推演*/
}//主函数:
int main()
{/**			推演实例化:* 函数参数传递,推演出模板参数的类型,* 再生成(实例化)对应的函数*///隐式实例化://T1推演为int,T2推演为int:func(1, 2);//T1推演为double,T2推演为double:func(1.1, 2.2);//T1推演为double,T2推演为int:func(1.1, 2);//调用通用(泛型)加法函数:cout << Add(1, 2.2) << endl;/** 该语句不能通过编译,因为在编译期间,* 当编译器看到该实例化时,需要推演其实参类型* 通过实参a1将T推演为int,通过实参d1将T推演为double类型,* 但模板参数列表中只有一个T,* 编译器无法确定此处到底该将T确定为int 或者 double类型而报错* * 注意:在模板中,编译器一般不会进行类型转换操作,* 因为一旦转化出问题,编译器就需要背黑锅* * 此时有两种处理方式:1. 用户自己来强制转化 2. 使用显式实例化*/	cout << Add(1, (int)2.2) << endl;//显式实例化:// 2.2 隐式转换为int类型:cout << Add<int>(1, 2.2) << endl; // 1 隐式转换为double类型:cout << Add<double>(1, 2.2) << endl;/** 直接显式实例化,指定将参数实例化为某种类型,* 而不通过模板参数的类型推演*///函数没有设置模板参数:double* p = f<double>();/** 函数参数没设置模板参数,* 但却使用了模板参数,* 编译器没法进行类型推演,* 所以此时就需要显式实例化来确定类型* (显式实例化的真正用法)*/return 0;
}//使用栈解决多类型问题:
typedef int STDataType;
/*
* 想让栈存储int类型数据,
* 就在这里设置类型为int,
* 想让栈存储double类型数据,
* 就在这里设置类型为double,
* ……
*/类模板:
//template<class T>
//
类模板 -- 栈类:
//class Stack
//{
//public: //公有成员函数:
//
//	//构造函数:
//	Stack(int capacity = 4)
//	{
//		//调用了构造函数则打印:
//		cout << "Stack(int capacity = 4)" << endl;
//
//		//使用new开辟栈容量大小的空间:
//		
//		// typedef 设置多类型:
//		//_a = new STDataType[capacity];
//
//		// 类模板 设置多类型:
//		_a = new T[capacity]; //使用模板T类型
//
//		_top = 0; //栈顶值默认为0
//		_capacity = capacity; //设置栈容量
//	}
//
//	//析构函数:
//	~Stack()
//	{
//		//调用了析构函数则打印:
//		cout << "~Stack()" << endl;
//
//		//使用delete释放new开辟的空间:
//		delete[] _a;
//
//		_a = nullptr; //置为空指针
//		_top = 0; //栈顶值置为0
//		_capacity = 0; //栈容量置为0
//	}
//
//private: //私有成员变量:
//
//	T* _a; //栈指针 -- 使用模板T类型
//	int _top; //栈顶值
//	int _capacity; //栈容量
//
//};//类模板:
template<class T>//类模板 -- 栈类:
class Stack
{
public: //公有成员函数://构造函数 -- 类模板成员函数声明和定义分离:Stack(int capacity = 4);//析构函数:~Stack(){//调用了析构函数则打印:cout << "~Stack()" << endl;//使用delete释放new开辟的空间:delete[] _a;_a = nullptr; //置为空指针_top = 0; //栈顶值置为0_capacity = 0; //栈容量置为0}private: //私有成员变量:T* _a; //栈指针 -- 使用模板T类型int _top; //栈顶值int _capacity; //栈容量 };//类模板成员函数的声明和实现分离:
template<class T>
Stack<T>::Stack(int capacity)
/*
* 实现时指定的不是Stack成员函数的类名(类域),
* 而是Stack成员函数的类型,
* (不是Stack::,而是Stack<T>)
* 需要把模板参数写出来
* 
*				注:
* 类模板不允许声明和定义分离到两个文件,
* 分离时都写在一个.h文件中
*/
{//调用了构造函数则打印:cout << "Stack(int capacity = 4)" << endl;//使用new开辟栈容量大小的空间:// typedef 设置多类型://_a = new STDataType[capacity];// 类模板 设置多类型:_a = new T[capacity]; //使用模板T类型_top = 0; //栈顶值默认为0_capacity = capacity; //设置栈容量
}//主函数:
int main()
{/** typedef 可以解决多类型问题,* 那 typedef 可以代替 模板 吗?* * 答案是不能,typedef设置一个类的类型后,* 该类的类型就只能是typedef设置的那一种了,* * 如果用类模板的话设置一个类的话,* 该类的一个对象就可以是int类型,* 而该类的另一个对象还可以是double类型*///显式实例化://让这个栈类型对象存储int类型:Stack<int> st1; //int//让这个栈类型对象存储double类型:Stack<double> st2; //double/** 函数模板可以显式实例化,也可以让它自己推演,* 函数模板大多数情况让它自己推演出类型,* 不进行显式实例化* * 类模板能让一个类的对象是不同类型的,* (如:都是栈类对象,但一个栈对象存储int类型数据,* 另一个栈对象存储double类型数据)* 只需要在创建对象时显式实例化需要的类型即可。* 这时我们实现的数据结构,* 就跟具体的存储类型是无关的,想要哪种类型,* 在创建对象时就显式实例化哪种类型*/return 0;
}// 专门处理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版本:Add<int>(1, 2); //函数模板Add被实例化为非模板函数Add
}//专门处理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(int, int):Add(1, 2);/** 与非函数模板类型完全匹配,* 不需要函数模板实例化*///Add(int, double):Add(1, 2.0);/** 模板函数可以生成更加匹配的版本,* 编译器根据实参生成更加匹配的Add函数*/
}

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

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

相关文章

使用ffmpeg将图片合成为mp4

首先在在图片文件夹输入cmd 这里确保已经安装ffmpeg并配置好环境变量。 然后这是我的文件夹目录&#xff1a; 将21张图片合成为mp4视频 这里使用如下命令&#xff1a; ffmpeg -framerate 1 -start_number 0 -i %d.png -c:v libx264 -pix_fmt yuv420p output.mp4 -framerat…

设计模式——中介者模式

引言 中介者模式是一种行为设计模式&#xff0c; 能让你减少对象之间混乱无序的依赖关系。 该模式会限制对象之间的直接交互&#xff0c; 迫使它们通过一个中介者对象进行合作。 问题 假如你有一个创建和修改客户资料的对话框&#xff0c; 它由各种控件组成&#xff0c; 例如…

[渗透测试学习] Analytics - HackTheBox

文章目录 信息搜集漏洞利用内核提权 信息搜集 nmap扫描一下端口 nmap -sV -sC -p- -v --min-rate 1000 10.10.11.233发现两个端口&#xff0c;22端口为ssh服务&#xff0c;80端口有http服务 尝试访问80端口&#xff0c;发现重定向到http://analytical.htb/并且无法访问 编辑/…

Java+Swing: 数据回显和修改功能的实现 整理14

1. 数据回显 其实数据回显就是为修改功能的实现做准备的 1.1 在MainView类中&#xff0c;创建一个方法获取选中行的id // 获取选中的行的idpublic int[] getSelectedRowIds() {int[] selectedRows mainViewTable.getSelectedRows();int[] ids new int[selectedRows.length];…

第六届江苏人工智能大会成功举办,赛氪网荣获“优秀合作伙伴”奖项

2023年12月15日&#xff0c;第六届江苏人工智能大会在南京成功举办。本次大会汇集了众多人工智能领域的专家、学者和企业代表&#xff0c;共同探讨人工智能技术的最新发展和应用。江苏人工智能大会由江苏省人工智能学会&#xff08;简称&#xff1a;JSAI&#xff09;创办于2018…

【每日一题】反转二叉树的奇数层

文章目录 Tag题目来源题目解读解题思路方法一&#xff1a;广度优先搜索方法二&#xff1a;深度优先搜索 写在最后 Tag 【深度优先搜索】【广度优先搜索】【二叉树】【2023-12-15】 题目来源 2415. 反转二叉树的奇数层 题目解读 反转二叉树奇数层的节点。 解题思路 对于二叉…

【LeetCode刷题笔记(8-1)】【Python】【接雨水】【动态规划】【困难】

文章目录 引言接雨水题目描述提示 解决方案1&#xff1a;【动态规划】结束语 接雨水 引言 编写通过所有测试案例的代码并不简单&#xff0c;通常需要深思熟虑和理性分析。虽然这些代码能够通过所有的测试案例&#xff0c;但如果不了解代码背后的思考过程&#xff0c;那么这些代…

C++指针

本文章对C指针的使用做一个全面的阐述与解释 1.1指针的定义使用 指针&#xff1a; 通过指针间接访问内存 指针就是地址 看下面代码&#xff1a; #include<iostream> using namespace std; int main(){//1、定义指针int * p;int a 10;//2、使用指针p &a;cout<…

STM32-02-STM32基础知识

文章目录 STM32基础知识1. STM32F103系统架构2. STM32寻址范围3. 存储器映射4. 寄存器映射 STM32基础知识 1. STM32F103系统架构 STM32F103 STM32F103是ST公司基于ARM授权Cortex M3内核而设计的一款芯片&#xff0c;而Cortex M内核使用的是ARM v7-M架构&#xff0c;是为了替代…

根据星历文件实现卫星的动态运行模拟matlab仿真

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 5.算法完整程序工程 1.算法运行效果图预览 2.算法运行软件版本 MATLAB2022a 3.部分核心程序 .................................................................................... % …

测试用例的修改更新

测试用例的修改更新是指测试过程中由于用户需求的改变&#xff0c;或者测试过程中发现有新的需求产生&#xff0c;使得测试用例需要进行修改。修改更新测试用例不仅是一种测试技术&#xff0c;更是一种质量保证的方法。但修改和更新测试用例的技术要点在于&#xff1a; 1、执行…

Note3---初阶二叉树~~

目录​​​​​​​ 前言&#x1f344; 1.树概念及结构☎️ 1.1 树的概念&#x1f384; 1.2 树的相关概念&#x1f99c; 1.2.1 部分概念的加深理解&#x1f43e; 1.2.2 树与非树&#x1fab4; 1.3 树的表示&#x1f38b; 1.4 树在实际中的运用&#xff08;表示文件系统…

RT-Smart elf 动态加载技术 : 开启 ldso 动态加载

前言 RT-Smart,类似于Linux,可以动态的加载运行 应用程序 elf 文件 应用程序 elf 文件,有的是静态链接编译的,有的是动态链接编译的,动态链接编译的,还需要加载 动态共享库 (.so) 本篇讲解一下 RT-Smart 上 elf 动态加载功能的实现,顺便讲一下动态加载的原理 环境搭建 R…

RTrPPG

研究背景 心率 (HR) 和脉搏率变异性 (PRV) 是允许分析心脏行为的两个生理参数。心率监测可以通过接触式和非接触式的两种方法进行。通常用于测量 HR 和 PRV 的两种接触式技术是心电图 (ECG) 和光电容积脉搏波 (PPG)。 ECG 测量由心脏活动引起的电场。另一方面&#xff0c;PPG …

02什么是CPU上下文切换

上⼀节&#xff0c; 讲了要怎么理解平均负载&#xff08; Load Average&#xff09; &#xff0c; 并⽤三个案例展示了不同场景下平均负载升⾼的分析⽅法。 这其中&#xff0c; 多个进程竞争 CPU 就是⼀个经常被我们忽视的问题。 1、CPU上下文切换的概念 我想你⼀定很好奇&am…

2023/12/18 work

1. 机械臂 #include <stdio.h> #include <string.h> #include <stdlib.h> #include <myhead.h>#define CLI_PORT 6667 //本机的端口号 #define CLI_IP "192.168.125.79" //本机的IP#define SER_PORT 8888 //服务器的端口号 #define…

智能家居和智能家居控制设备有什么区别?

智能家居和智能家居控制设备在功能和用途伤的区别&#xff1a; 智能家居是一种整体的概念&#xff0c;它涵盖了整个家庭环境的智能化&#xff0c;包括智能家电、智能照明、智能安防等设备的互联互通和协同工作。智能家居的目标是通过中央控制器或智能音箱等设备&#xff0c;实现…

瑞安籍侨领池万进荣任意大利瑞安同乡总会第五届会长

浙江温州瑞安是全国重点侨乡&#xff0c;拥有海外侨胞16万人、侨领2100多名、归侨侨眷近12万人&#xff0c;在世界100多个国家和地区建有70多个侨团组织。 意大利瑞安同乡总会第五届理事会会长团全体成员 青年部全体成员 各兄弟会长及代表 12月10日&#xff0c;意大利瑞安同乡…

LabVIEW在燃气轮机发电机组励磁控制系统测试中的应用

LabVIEW在燃气轮机发电机组励磁控制系统测试中的应用 燃气轮机发电机组作为一种高效可靠的常备应急电源&#xff0c;在保障发电品质稳定性和可靠性方面发挥着关键作用。其中&#xff0c;励磁控制系统是保证供电质量的重要环节&#xff0c;对发电机组的稳定运行至关重要。为了有…

档案数字化管理可以提供什么服务?

档案数字化管理提供了便捷、高效和安全的档案管理服务&#xff0c;帮助组织更好地管理和利用自己的档案资源。 具体来说&#xff0c;专久智能档案数字化管理可以提供以下服务&#xff1a; 1. 档案扫描和数字化&#xff1a;将纸质档案通过扫描仪转换为数字格式&#xff0c;包括文…