C++ 面向对象(类和对象)—— 函数模板

C++ 面向对象(类和对象)—— 函数模板

在这里插入图片描述

🎁个人主页:工藤新一¹

🔍系列专栏:C++面向对象(类和对象篇)

​ 🌟心中的天空之城,终会照亮我前方的路

🎉欢迎大家点赞👍评论📝收藏⭐文章

文章目录

模板

  • 本阶段主要针对于==C++范式编程STL技术==进行详细讲解,探讨 C++ 更深层的应用template<class T> — 类模板
    template<typename T> — 函数模板

这两种模板的形式,在作用上是无差别的,唯一用法可能只是在于对函数模板和类模板的区分


一、模板简介

  1. 类型安全:模板提供了类型安全,因为编译器会在编译时检查类型错误。
  2. 代码重用:通过使用模板,你可以编写一次代码,然后对多种类型重用它,从而减少代码重复,提高复用性。
  3. 性能:模板在编译时实例化,这意味着没有运行时开销,性能通常比使用虚函数或动态类型识别(如 typeid)更好。

模板就是建立通用的摸具,大大提高复用性

在这里插入图片描述
  • 模板的特点:
    • 模板不可以直接使用,它只是一个框架
    • 模板的通用并不是万能的

二、函数模板

  • C++的另一种编程思想叫做泛型编程,主要利用的技术就是模板
  • C++提供两种模板机制:函数模板 类模板

2.1函数模板的基本语法

  • 函数模板的作用:​ 建立一个通用函数,其函数返回值类型和形参类型可以不具体制定,用一个虚拟类型来代表
C++voidfunc(int a);|| 返回值类型 形参类型 T T 使用虚拟类型T来代表 

语法:template <typename T> +后紧跟+ 函数声明或定义
紧跟的这一部分(函数声明或定义)就叫做函数模板

  • template ---> 声明创建模板
  • typename ---> 表示其后面的符号是一种数据类型,也可以使用class代替
  • T ---> 通用的数据类型,名称可替换

模板存在的意义:数据类型的参数化


  • 当我们打算实现两数交换,但我们并不明确两数的数据类型,我们可以写出无数种方式
C++ 内置数据类型 voidSwapInt(int& x,int& y){int temp = x; x = y; y = temp;}voidSwapDouble(double& x,double& y){double temp = x; x = y; y = temp;} 自定义数据类型 voidSwapPerson(Person& x, Person& y);...
  • 但是,我们发现,除返回值类型与参数的数据类型不同外,其余代码逻辑都是相同的
  • 于是函数模板就实现了

2.1.1函数模板的调用方式
C++//声明一个模板,告诉编译器T是一个通用的数据类型,及告诉编译器模板T后紧跟着的那段代码不要报错!template<typenameT>voidMy_Swap(T& x, T& y){ T temp = x; x = y; y = x;}--->模板的使用方式: intmain(){//1.自动类型推导My_Swap(参数1, 参数2);//2.显示指定类型(推荐 - 隐式类型转换) - 明确告诉编译器你想要的数据类型 My_Swap<数据类型>(参数1, 参数2);---><int>(a, b);}

2.2函数模板的注意事项

注意事项:

  • 自动类型推导,必须推导出一致的数据类型T
  • 模板必须要确定T的数据类型

1.自动类型推导,必须推导出一致的数据类型T

C++template<typenameT>voidMy_Swap(T& x, T& y){...}intmain(){int a =1, b =2;char c ='a';My_Swap(a, b);--->trueMy_Swap(a, c);--->falsereturn0;}
在这里插入图片描述

2.模板必须要确定T的数据类型

C++template<typenameT>voidfunc()--->函数体 func() 是函数模板,但没有指定模板参数 T 的具体类型 { cout<<"func() 的调用"<< endl;}intmain(){func();--->也没有在调用 func 时提供类型参数 //如果想调用func(),那么可以给模板显示指定一个数据类型 func<int>();--->这样,就可以调用到func()return0;}
在这里插入图片描述

2.4函数模板案例

案例描述:

  • 利用函数模板封装一个排序的函数,可以对不同的数据类型进行排序
  • 排序规则从大到小,排序算法为选择排序
  • 分别用char数组和int数组进行排序

1.逐一解决模板,首先我们需要先建立一个排序模板

#include<iostream>usingnamespace std;template<typenameT>voidMy_Sort(T arr[],int len){for(int i =0; i < len; i++){int pos = i;//默认当前i位置下标所对应的元素为最大元素for(int j = i +1; j < len; j++){if(arr[pos]< arr[j]) pos = j;}//当发现arr[i]不是最大值时,进行交换if(pos != i)My_Swap(arr[pos], arr[i]);}}voidtest01(){char chArr[]="udoijqwadcknzx";My_Sort(chArr,sizeof(chArr)/sizeof(char));}intmain(){test01();return0;}

2.在我们搭建排序模板的过程中,我们发现我们还需要一个支持两数交换的模板

C++template<typenameT>voidMy_Swap(T& x, T& y){ T temp = x; x = y; y = temp;}

3.当我们所有的算法步骤解决后,我们就可以再次利用模板的方式来输出结果了

C++//输出模板template<typenameT>voidMy_Print(T arr[],int len){for(int i =0; i < len; i++) cout << arr[i]<<" ";}

4.总代码:

#include<iostream>usingnamespace std;//交换模板template<typenameT>voidMy_Swap(T& x, T& y){ T temp = x; x = y; y = temp;}//排序模板 - 从大到小template<typenameT>voidMy_Sort(T arr[],int len){for(int i =0; i < len; i++){int pos = i;//默认当前i位置下标所对应的元素为最大元素for(int j = i +1; j < len; j++){if(arr[pos]< arr[j]) pos = j;}//当发现arr[i]不是最大值时,进行交换if(pos != i)My_Swap(arr[pos], arr[i]);}}//输出模板template<typenameT>voidMy_Print(T arr[],int len){for(int i =0; i < len; i++) cout << arr[i]<<" ";}voidtest01(){char charArr[]="dasjhdaxaodnqb";My_Sort(charArr,sizeof(charArr)/sizeof(char));My_Print(charArr,sizeof(charArr)/sizeof(char));}voidtest02(){int intArr[]={4,8,4,3,1,3,1,3,13,3,21,56,46};My_Sort(intArr,sizeof(intArr)/sizeof(int));My_Print(intArr,sizeof(intArr)/sizeof(int));}intmain(){test01(); cout << endl;test02();return0;}

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

普通函数与函数模板的区别(是否会发生隐式类型转换):

  • 普通函数调用时可以发生自动类型转换(隐式类型转换)
  • 函数模板调用时,如果利用自动类型推导,不会发生隐式转换
  • 如果利用显示指定类型的方式,可以发生隐式类型转换

2.5.1隐式类型转换

  • 在编程语言中,隐式类型转换是指在程序运行过程中,由编译器自动进行的类型转换,而不需要程序员显式地指定。例如,在C++中,如果将一个整数赋值给一个浮点数变量,编译器会自动将整数转换为浮点数。这种转换是隐式的,因为它是在程序员不知情的情况下由编译器完成的。

隐式类型转换的常见情况:

  • 数值类型之间的转换在**C++**中,当将一个较小范围的整数类型(如charshort)赋值给一个较大范围的整数类型(如int)时,会发生隐式类型转换。例如:
C++char c ='a';int i = c;// char类型隐式转换为int类型 输出结果97
C++intAdd(int x,int y)return x + y;voidtest(){int a =10;char c ='a';//发生隐式类型转换 cout <<Add(a, c)<< endl;--->109}

  • 函数模板- 无法推导出一致的数据类型
C++template<typenameT>voidMy_Add(T& x, T& y){return x + y;}intmain(){int a =10;char c ='c';//1.自动类型推导 cout <<My_Add(a, c);--->false//2.显示指定类型 cout << My_Add<int>(a, c);--->true,会发生隐式类型转换 return0;}
在这里插入图片描述
  • 总结:建议使用显示类型的方式,调用函数模板,因为可以自己确定通用类型

2.6普通函数和函数模板的调用规则

  • 调用规则如下:

​ 1.如果函数模板和普通函数都可以实现,优先调用普通函数

​ 2.可以通过空模板参数列表来强制调用函数模板

​ 3.函数模板也可以发生重载

​ 4.如果函数模板可以发生更好的匹配,优先调用函数模板

C++voidMy_Print(int a,int b)--->哪怕只有函数声明 voidMy_Print(int a,int b);{ 也无法直接调用不加空模板参数列表修饰的函数模板 cout <<"调用普通函数"<< endl;}template<typenameT>voidMy_Print(T& a, T& b){ cout <<"调用函数模板"<< endl;}intmain(){int a =10, b =20;My_Print(a, b);return0;}
在这里插入图片描述

  • 空模板参数列表:强制调用函数模板
C++ My_Print<>(a, b);
在这里插入图片描述

  • 函数模板也可以发生函数重载
C++template<typenameT>voidMy_Print(T& a, T& b){ cout <<"调用函数模板"<< endl;}template<typenameT>voidMy_Print(T& a, T& b, T& c){ cout <<"调用重载的函数模板"<< endl;}

  • 函数模板可以产生更好的匹配时,编译器会优先调用函数模板
C++voidMy_Print(int a,int b){ cout <<"调用普通函数"<< endl;}template<typenameT>voidMy_Print(T& a, T& b){ cout <<"调用函数模板"<< endl;}intmain(){char c1 ='a', c2 ='b';My_Print(c1, c2);return0;}
在这里插入图片描述
在这里插入图片描述
  • 因为编译器认为,调用普通函数时还需要进行隐式类型转换,那就过于麻烦了,所以就不去调用普通函数了。

总结:当我们使用函数模板时,最好减少使用普通函数,否则容易出现二义性


2.7模板的局限性

局限性:

  • 模板不是万能的

例如:

C++template<typenameT>voidf(T& a, T& b){ a = b;}

如上述代码中,提供一个赋值操作,如果 a 和 b 是一个数组,就无法实现了

再例如:

C++template<typenameT>voidf(T& a, T& b){if(a > b){...}}

如上述代码中,如果 T 的数据类型传入的是像 Pereson 这样的自定义数据类型,也无法正常运行

因此,C++为了解决这种问题,提供了模板的重载,可以为这些特定的数据类型提供具体化的模板

C++classPerson{public:Person(string Name,int Age){this->Name = Name;this->Age = Age;}public: string Name;int Age;};template<classT>boolMy_Compare(T& x, T& y){return x == y;}intmain(){ Person p1("啦啦啦",20); Person p2("呦呦呦",3); cout <<(My_Compare(p1, p2)?"p1 == p2":"p1 != p2")<< endl;return0;}
在这里插入图片描述

2.7.1运算符重载
  • 解决方法一:运算符重载
C++classPerson{public:booloperator==(const Person& other) cosnt{return Name == other.Name && Age == other.Age;}};

2.7.2具体化模板
  • 利用 Person具体化的模板实现代码,且这种基于具体化实现的模板会优先被调用
C++template<typenameT>boolMy_Compare(T& p1, T& p2)--->函数模板声明 {return p1 == p2;}template<>boolMy_Compare(Person& p1, Person& p2){return p1.Name == p2.Name && p1.Age == p2.Age;}
在这里插入图片描述
  • 相比于运算符重载模板具体化的优点:可以省去对每一个运算符都进行重载的过程,大大降低了代码量

总结:

  • 利用具体化的模板,可以解决自定义类型的通用化
  • 学习模板并不是为了写模板,而是为了在STL中能够运用系统提供的模板

🌟各位看官好,我是工藤新一¹呀~

🌈愿各位心中所想,终有所致!

🌟下一篇章类模板 ~~🌟

Read more

Rust异步编程的错误处理艺术

Rust异步编程的错误处理艺术

Rust异步编程的错误处理艺术 一、异步错误的本质与分类 1.1 异步错误与同步错误的区别 💡在Rust同步编程中,错误通常是通过Result<T, E>类型返回的,Err变体包含了错误信息,程序会阻塞线程直到操作完成。而在异步编程中,操作的结果是一个Future<Output = Result<T, E>>,程序会暂停任务直到操作完成,Err变体可能是IO错误、超时错误、取消错误等异步场景特有的错误。 同步错误示例: usestd::fs::File;usestd::io::Read;// 同步读取文件,阻塞线程fnread_file_sync()->Result<String,std::io::Error>{letmut

By Ne0inhk
【MySQL数据库基础】(二)MySQL 数据库基础从入门到上手,一篇带你吃透核心知识点!

【MySQL数据库基础】(二)MySQL 数据库基础从入门到上手,一篇带你吃透核心知识点!

目录 前言 一、为什么需要数据库?文件存储的痛点全解析 二、主流数据库大盘点,MySQL 的适用场景是什么? 2.1 主流数据库特性对比 2.2 MySQL 的核心优势 三、MySQL 基础操作,从安装到数据 CRUD 手把手教 3.1 MySQL 的多平台安装方式 3.2 连接 MySQL 服务器,核心指令解析 指令参数详解 简化连接方式 连接成功的反馈 3.3 MySQL 服务器管理(Windows 平台) 3.4 服务器、数据库、表的层级关系 3.5 MySQL 核心

By Ne0inhk

ESP8266 Web配网+MQTT+STM32串口上云+免AT指令

本文详细讲解 ESP8266/ESP12F Web 配网、MQTT 通信、STM32/Arduino 串口透传一体化实现方案WiFi强制入户,连接自动打开网页配置,核心亮点是单片机免 ESP8266 AT 指令,串口直接上云,通过串口向 ESP8266 发送数据即可自动上传至 MQTT 服务器,固件开源可直接用于学习调试。 固件下载: 通过网盘分享的文件:mqtt_usart_wifi.ino.bin 链接: https://pan.baidu.com/s/1mZt5diatyYvnSZ-N1eF75w?pwd=e8we 提取码: e8we 免AT指令全网首发!数据直接上传MQTT、秒下发指令,无需复杂配置!下载固件即可使用 一、项目背景与开发初衷         在物联网设备开发过程中,配网和远程通信是两个核心痛点:传统的

By Ne0inhk

4.2_WEB——前端语言三剑客(HTML、JS、CSS)

前言:本文只对三种前端常用的编辑于言 HTML、JS、CSS 的语法做最基础的描述。 一.HTML(Hyper Text Markup Language) 1.概念 * HTML的全称为超文本标记语言,是一种标记语言。它包括一系列标签,通过这些标签可以将网络上的文档格式统一,使分散的Internet资源连接为一个逻辑整体。 * 超文本是一种组织信息的方式,它通过超级链接方法将文本中的文字、图表与其他信息媒体相关联。这些相互关联的信息媒体可能在同一文本中,也可能是其他文件,或是地理位置相距遥远的某台计算机上的文件。 * HTML是一种基础技术,常与CSS、JavaScript一起被众多网站用于设计网页、网页应用程序以及移动应用程序的用户界面 。网页浏览器可以读取HTML文件,并将其渲染成可视化网页。HTML描述了一个网站的结构语义随着线索的呈现,使之成为一种标记语言而非编程语言。 2.特点 超文本标记语言文档制作不是很复杂,但功能强大,支持不同数据格式的文件镶入,这也是万维网(WWW)盛行的原因之一,其主要特点如下: 1. 简易性: 2. 超文本标记语言版本升级采

By Ne0inhk