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

【C++】平衡树优化实战:如何手搓一棵查找更快的 AVL 树?

【C++】平衡树优化实战:如何手搓一棵查找更快的 AVL 树?

🎬 个人主页:MSTcheng · ZEEKLOG 🌱 代码仓库 :MSTcheng · Gitee 🔥 精选专栏: 《C语言》 《数据结构》 《C++由浅入深》 💬座右铭:路虽远行则将至,事虽难做则必成! 前言:前两篇文章我们已经向大家介绍了map和set这两个容器,他们的底层都是平衡二叉搜索树,而今天我们就来介绍一种平衡二叉搜索树——AVL树。 文章目录 * 一、AVL树的认识 * 1.1AVL树的概念 * 二、AVL树的实现 * 2.1AVL树的基本框架 * 2.2AVL树的插入 * 2.3AVL树的中序遍历 * 2.4AVL树其他功能实现 * 三、总结 一、AVL树的认识 1.1AVL树的概念 AVL树是由G. M. Adelson-Velsky和E. M. Landis两个前苏联的科学家所发明的,它的具体定义如下: * AVL树是最先发明的自平衡⼆叉查找树,AVL是⼀颗空树,

By Ne0inhk
【直接可用源码免费送】计算机毕业设计精选项目:511+外卖点餐系统设计与实现 Java/PHP/Python/C#小程序、单片机、成品+文档源码支持定制

【直接可用源码免费送】计算机毕业设计精选项目:511+外卖点餐系统设计与实现 Java/PHP/Python/C#小程序、单片机、成品+文档源码支持定制

外卖点餐系统设计与实现 摘  要 随着信息技术的高速发展和网络的广泛应用,外卖点餐服务逐渐成为现代人生活中不可或缺的一部分。为了提高点餐效率和用户体验,本研究引入信息化技术设计并实现一套高效简便且功能齐全的外卖点餐系统。该系统采用Spring Boot框架结合Java语言进行后端开发,利用HTML、CSS和JavaScript等技术构建前端界面,以MySQL数据库作为数据存储核心,并运用RESTful API实现前后端分离的架构。系统主要面向顾客用户和管理员等不同角色,实现了包括注册登录、数据分析、系统用户、系统管理、通知公告管理、资源管理、商城管理等核心功能,基本满足整个外卖点餐、支付、配送及售后等业务流程,有效提升管理效率和服务质量。最终经过功能验证和性能测试,确保系统在实际应用中的可行性和高效性。本系统的实施不仅能够有效整合并管理用户、菜品与订单信息,能够有效提升服务管理效率和用户点餐的便捷性,还提供了实时的数据分析和反馈机制,为决策运营提升提供了支持。同时可以为今后类似外卖点餐系统的开发提供了宝贵参考,具有良好的实践意义和推广价值。 关键词:外卖点餐系统;Spr

By Ne0inhk
C++ 类与对象:封装特性的实现与实战应用

C++ 类与对象:封装特性的实现与实战应用

C++ 类与对象:封装特性的实现与实战应用 💡 学习目标:掌握类与对象的核心概念,理解封装的本质与价值,能够独立设计并实现具有封装特性的 C++ 类。 💡 学习重点:类的定义与对象实例化、访问权限控制、构造函数与析构函数的使用、封装的实战场景应用。 一、类与对象的核心概念 ✅ 结论:类是 C++ 面向对象编程的核心载体,是对一类事物属性和行为的抽象描述;对象是类的具体实例,是内存中实际存在的实体。 1.1 类的组成 一个完整的 C++ 类通常包含两部分: * 成员变量:描述类的属性,如人的姓名、年龄,圆的半径等。 * 成员函数:描述类的行为,如人的吃饭、跑步,圆的面积计算等。 1.2 类的定义格式 #include<iostream>#include<string>

By Ne0inhk
【C++】深入浅出“图”——最短路径算法

【C++】深入浅出“图”——最短路径算法

文章目录 * 一、Dijkstra算法 * 二、Bellman_Ford算法 * 三、Floyd_Warshall算法 一、Dijkstra算法 最短路径问题是指,从在带权的有向图中从某一顶点出发,找到通往另一顶点的最短路径,“最短”指的是沿路径各边的权值总和最小。 Dijkstra算法是单源最短路径的经典贪心算法,只能用于没有负权的图。它从起点出发,每次选当前距离最小且未确定最短路径的节点,用它去松弛(更新)所有邻接点的最短路径估计值,标记该节点为 “已确定”,重复此过程直到所有节点处理完毕,最终得到起点到图中所有节点的最短路径。 // src是选定的起点,dist记录起点到各点的最短路径,pPath记录到每个点的最短路径的前驱顶点下标voidDijkstra(const V& src, vector<W>& dist, vector<int>& pPath){ size_t srci =GetVertexIndex(

By Ne0inhk