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

GitHub Copilot安装使用

GitHub Copilot安装使用

GitHub Copilot 怎么安装使用 一、 安装前准备 1. 拥有一个 GitHub 账号:如果没有,请先在 GitHub 官网 注册。 2. 订阅 GitHub Copilot: * 访问订阅页面:登录 GitHub 后,访问 GitHub Copilot 官网。 * 选择订阅计划: * 个人版:适合独立开发者,提供 30 天免费试用,之后每月 $10 或每年 $100。 * 商业版 (Copilot for Business):适用于企业或团队,每位用户每月 $19。 * 教育优惠:学生、教师和热门开源项目维护者可免费使用,需通过身份验证。 * 完成支付:根据所选计划完成支付流程(个人版需绑定信用卡或

By Ne0inhk
VS Code使用 GitHub Copilot 高效重构代码:10 大实战技巧 + 自定义指令封装指南

VS Code使用 GitHub Copilot 高效重构代码:10 大实战技巧 + 自定义指令封装指南

🔍 为什么重构比写新代码更重要? 根据《重构:改善既有代码的设计》作者 Martin Fowler 的定义: “重构是在不改变软件可观察行为的前提下,调整其内部结构,以提升可读性、可维护性与扩展性。” 但人工重构成本高、易出错—— ✅ Copilot 的出现让重构从“高风险操作”变为“日常习惯”。 本文将教你: 1. 掌握 10 种高频重构场景的 Copilot 实战技巧; 2. 将它们封装为 自定义 / 指令(如 /extract, /inline),像调用函数一样调用重构动作; 3. 避开常见陷阱,确保重构零回归。 🛠️ 一、Copilot 重构基础:三步走工作流 所有重构操作,建议遵循以下标准化流程: 步骤操作快捷键(VS Code)目的1. 理解选中代码 → /explain

By Ne0inhk

从 0 到 1:解决 VsCode 远程连服务器后 Github Copilot 无法使用问题

从 0 到 1:解决 VS Code 远程连服务器后 GitHub Copilot 无法使用问题 当您使用 VS Code 的远程功能(如 SSH 或容器)连接到服务器时,GitHub Copilot 可能无法正常工作,这通常是由于远程环境中的网络、扩展安装或身份验证问题导致的。我将一步步引导您解决这个问题,确保过程清晰可靠。请按照顺序操作,并测试每个步骤。 步骤 1: 确认本地 Copilot 正常工作 在开始远程连接前,先确保 Copilot 在您的本地 VS Code 中工作正常。 * 打开本地 VS Code。 * 创建一个新文件(如 test.py),输入一些代码(如 def

By Ne0inhk
Copilot vs Claude Code终极对决哪个会更好用呢?

Copilot vs Claude Code终极对决哪个会更好用呢?

📊 核心差异:一句话概括 * GitHub Copilot:你的智能代码补全器 * Claude Code:你的全栈AI开发伙伴 🎯 一、产品定位对比 GitHub Copilot:专注代码补全 <TEXT> 定位:AI结对编程助手 核心理念:让你写代码更快 核心功能:基于上下文的代码建议和补全 收费模式:个人$10/月,企业$19/用户/月 Claude Code:全栈开发加速器 <TEXT> 定位:AI驱动的开发平台 核心理念:提升整个开发流程效率 核心功能:代码生成+架构设计+调试+部署 收费模式:按token计费,灵活弹性 ⚡ 二、核心技术对比

By Ne0inhk