【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.hqwc.cn/news/283974.html

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

相关文章

Excel只读模式带有密码,怎么办?

打开Excel文件之后发现是只读模式&#xff0c;并且excel只读模式是带有密码的&#xff0c;该如何取消带有密码的excel只读文件呢&#xff1f; 带有密码的只读模式&#xff0c;是设置了excel文件的修改权限&#xff0c;取消修改权限&#xff0c;我们需要先输入密码&#xff0c;…

NET Core使用SkiaSharp生成二维码

在.NET 6之前我们一直是使用QRCoder来生成二维码&#xff08;QRCoder是一个非常强大的生成二维码的组件&#xff0c;用到了System.Drawing.Common 包&#xff09;&#xff0c;然后从.NET 6开始&#xff0c;当为非 Windows 操作系统编译引用代码时&#xff0c;平台分析器会发出编…

计算机图形学头歌合集(题集附解)

目录 CG1-v1.0-点和直线的绘制 第1关&#xff1a;OpenGL点的绘制 第2关&#xff1a;OpenGL简单图形绘制 第3关&#xff1a;OpenGL直线绘制 第4关&#xff1a;0<1直线绘制-dda算法<> 第5关&#xff1a;0<1直线绘制-中点算法<> 第6关&#xff1a;一般直线绘…

网络攻击1——网络安全基本概念与终端安全介绍(僵尸网路、勒索病毒、木马植入、0day漏洞)

目录 网络安全的基本术语 黑客攻击路径 终端安全 僵尸网络 勒索病毒 挖矿病毒 宏病毒 木马的植入 0day漏洞 流氓/间谍软件 网络安全的基本术语 网络安全的定义&#xff08;CIA原则&#xff09; 数据的保密性Confidentiality&#xff08;对称/非对称秘钥&#xff09; …

「Leetcode」滑动窗口—长度最小的子数组

&#x1f4bb;文章目录 &#x1f4c4;题目✏️题目解析 & 思路&#x1f4d3;总结 &#x1f4c4;题目 209. 长度最小的子数组 给定一个含有 n 个正整数的数组和一个正整数 target 。 找出该数组中满足其总和大于等于 target 的长度最小的 连续子数组 [numsl, numsl1, …,…

低代码平台+阿里云存储:让业务开发更简单,数据存储更安全

本文由葡萄城技术团队发布。转载请注明出处&#xff1a;葡萄城官网&#xff0c;葡萄城为开发者提供专业的开发工具、解决方案和服务&#xff0c;赋能开发者。 前言 随着云计算技术的不断发展&#xff0c;越来越多的企业开始将业务数据存储到云端。阿里云作为国内领先的云计算服…

低噪声 256 细分微步进电机驱动MS35776

产品简述 MS35776 是一款高精度、低噪声的两相步进电机驱动芯 片。芯片集成了快速模式与静音模式来满足高速与低速下的不 同应用。芯片内置功率 MOSFET &#xff0c;长时间工作平均电流可以达 到 1.4A &#xff0c;峰值电流 2A 。芯片集成了欠压保护、过流保护、短 地…

机器学习——自领域适应作业

任务 游戏里面的话有很多跟现实不一样的情况。 想办法让中间的特征更加的接近&#xff0c;让feat A适应feat B&#xff0c;产生相对正常的输出。 在有标签数据和没有数据的上面进行训练&#xff0c;并能预测绘画图像。 数据集 训练5000张总数&#xff0c;每类有500张测试100…

时序预测 | Python实现LSTM-Attention电力需求预测

时序预测 | Python实现LSTM-Attention电力需求预测 目录 时序预测 | Python实现LSTM-Attention电力需求预测预测效果基本描述程序设计参考资料预测效果 基本描述 该数据集因其每小时的用电量数据以及 TSO 对消耗和定价的相应预测而值得注意,从而可以将预期预测与当前最先进的行…

劲松中西医结合医院hpv诊疗中心回应:hpv对夫妻关系的影响

HPV&#xff0c;即人类乳头瘤病毒&#xff0c;是一种常见的性传播疾病。它的传播途径主要是通过性接触&#xff0c;因此&#xff0c;夫妻之间很可能会受到它的影响。 首先&#xff0c;HPV对夫妻关系的直接影响是它可能导致生殖器疣。这是一种常见的性病&#xff0c;主要症状是…

苹果手机中的备忘录app加密方法 这样做保护隐私

隐私&#xff0c;是我们内心世界的一部分&#xff0c;它涉及我们的个人信息、感受、想法和决定。没有隐私&#xff0c;我们就失去了这种掌控感&#xff0c;我们的生活就可能变得透明&#xff0c;好像一个没有窗帘的窗户&#xff0c;任何人都可以随时窥视。 作为一名现代人&…

智能优化算法应用:基于入侵杂草算法3D无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于入侵杂草算法3D无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于入侵杂草算法3D无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.入侵杂草算法4.实验参数设定5.算法结果6.…