C++ 面向对象(类和对象)—— 函数模板
🎁个人主页:工藤新一¹
🔍系列专栏:C++面向对象(类和对象篇)
🌟心中的天空之城,终会照亮我前方的路
🎉欢迎大家点赞👍评论📝收藏⭐文章
文章目录
模板
- 本阶段主要针对于==C++范式编程和STL技术==进行详细讲解,探讨 C++ 更深层的应用
template<class T> — 类模板
template<typename T> — 函数模板
这两种模板的形式,在作用上是无差别的,唯一用法可能只是在于对函数模板和类模板的区分
一、模板简介
- 类型安全:模板提供了类型安全,因为编译器会在编译时检查类型错误。
- 代码重用:通过使用模板,你可以编写一次代码,然后对多种类型重用它,从而减少代码重复,提高复用性。
- 性能:模板在编译时实例化,这意味着没有运行时开销,性能通常比使用虚函数或动态类型识别(如
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++**中,当将一个较小范围的整数类型(如
char或short)赋值给一个较大范围的整数类型(如int)时,会发生隐式类型转换。例如:
C++char c ='a';int i = c;// char类型隐式转换为int类型 输出结果97C++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中能够运用系统提供的模板
🌟各位看官好,我是工藤新一¹呀~
🌈愿各位心中所想,终有所致!
🌟下一篇章类模板 ~~🌟