C++ 面向对象(类和对象)—— 函数模板_模板类对象 c++
🎁个人主页:工藤新一¹
🔍系列专栏:C++面向对象(类和对象篇)
🌟心中的天空之城,终会照亮我前方的路
🎉欢迎大家点赞👍评论📝收藏⭐文章
文章目录
模板
-
本阶段主要针对于==C++范式编程和STL技术==进行详细讲解,探讨 C++ 更深层的应用
template — 类模板
template — 函数模板
这两种模板的形式,在作用上是无差别的,唯一用法可能只是在于对函数模板和类模板的区分
一、模板简介
- 类型安全:模板提供了类型安全,因为编译器会在编译时检查类型错误。
- 代码重用:通过使用模板,你可以编写一次代码,然后对多种类型重用它,从而减少代码重复,提高复用性。
- 性能:模板在编译时实例化,这意味着没有运行时开销,性能通常比使用虚函数或动态类型识别(如
typeid
)更好。
模板就是建立通用的摸具,大大提高复用性
- 模板的特点:
- 模板不可以直接使用,它只是一个框架
- 模板的通用并不是万能的
二、函数模板
- C++的另一种编程思想叫做泛型编程,主要利用的技术就是模板
- C++提供两种模板机制:函数模板 和 类模板
2.1函数模板的基本语法
-
函数模板的作用:
建立一个通用函数,其函数返回值类型和形参类型可以不具体制定,用一个虚拟类型来代表
C++void func(int a); | | 返回值类型 形参类型 T T 使用虚拟类型T来代表
语法:template +
后紧跟 + 函数声明或定义
紧跟的这一部分(函数声明或定义)就叫做函数模板
template ---> 声明创建模板
typename ---> 表示其后面的符号是一种数据类型,也可以使用class代替
T ---> 通用的数据类型,名称可替换
模板存在的意义:数据类型的参数化
- 当我们打算实现两数交换,但我们并不明确两数的数据类型,我们可以写出
无数种方式
C++ 内置数据类型 void SwapInt(int& x, int& y){ int temp = x; x = y;y = temp;}void SwapDouble(double& x, double& y){double temp = x;x = y;y = temp;}自定义数据类型void SwapPerson(Person& x, Person& y);...
- 但是,我们发现,除返回值类型与参数的数据类型不同外,其余代码逻辑都是相同的
- 于是函数模板就实现了
2.1.1函数模板的调用方式
C++ //声明一个模板,告诉编译器T是一个通用的数据类型,及告诉编译器模板T后紧跟着的那段代码不要报错! template<typename T> void My_Swap(T& x, T& y){ T temp = x; x = y; y = x;}--->模板的使用方式:int main() { //1.自动类型推导 My_Swap(参数1, 参数2); //2.显示指定类型(推荐 - 隐式类型转换) - 明确告诉编译器你想要的数据类型 My_Swap<数据类型>(参数1, 参数2);---><int>(a, b); }
2.2函数模板的注意事项
注意事项:
- 自动类型推导,必须推导出一致的数据类型
T
- 模板必须要确定
T
的数据类型
1.自动类型推导,必须推导出一致的数据类型T
C++ template<typename T> void My_Swap(T& x, T& y){...}int main(){ int a = 1, b = 2; char c = \'a\'; My_Swap(a, b);--->true My_Swap(a, c);--->false return 0;}
2.模板必须要确定T
的数据类型
C++ template<typename T>void func()--->函数体 func() 是函数模板,但没有指定模板参数 T 的具体类型{cout<< \"func() 的调用\" << endl;}int main(){func();--->也没有在调用 func 时提供类型参数 //如果想调用func(),那么可以给模板显示指定一个数据类型 func<int>();--->这样,就可以调用到func()return 0;}
2.4函数模板案例
案例描述:
-
利用函数模板封装一个排序的函数,可以对不同的数据类型进行排序
-
排序规则从大到小,排序算法为选择排序
-
分别用char数组和int数组进行排序
1.逐一解决模板,首先我们需要先建立一个排序模板
#includeusing namespace std;template<typename T>void My_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]);}}void test01(){char chArr[] = \"udoijqwadcknzx\"; My_Sort(chArr, sizeof(chArr) / sizeof(char)); }int main(){test01();return 0;}
2.在我们搭建排序模板的过程中,我们发现我们还需要一个支持两数交换的模板
C++template<typename T>void My_Swap(T& x, T& y){T temp = x;x = y;y = temp;}
3.当我们所有的算法步骤解决后,我们就可以再次利用模板的方式来输出结果了
C++//输出模板template<typename T>void My_Print(T arr[], int len){for (int i = 0; i < len; i++) cout << arr[i] << \" \";}
4.总代码:
#includeusing namespace std;//交换模板template<typename T>void My_Swap(T& x, T& y){T temp = x;x = y;y = temp;}//排序模板 - 从大到小template<typename T>void My_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<typename T>void My_Print(T arr[], int len){for (int i = 0; i < len; i++) cout << arr[i] << \" \";}void test01() {char charArr[] = \"dasjhdaxaodnqb\";My_Sort(charArr, sizeof(charArr) / sizeof(char));My_Print(charArr, sizeof(charArr) / sizeof(char));}void test02(){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));}int main(){test01();cout << endl;test02();return 0;}
2.5普通函数和函数模板的区别
普通函数与 函数模板 的区别(是否会发生隐式类型转换):
- 普通函数调用时可以发生自动类型转换(隐式类型转换)
- 函数模板调用时,如果利用自动类型推导,不会发生隐式转换
- 如果利用显示指定类型的方式,可以发生隐式类型转换
2.5.1隐式类型转换
- 在编程语言中,隐式类型转换 是指在程序运行过程中,由编译器自动进行的类型转换,而不需要程序员显式地指定。例如,在C++中,如果将一个整数赋值给一个浮点数变量,编译器会自动将整数转换为浮点数。这种转换是隐式的,因为它是在程序员不知情的情况下由编译器完成的。
隐式类型转换的常见情况:
-
数值类型之间的转换
在**C++**中,当将一个较小范围的整数类型(如
char
或short
)赋值给一个较大范围的整数类型(如int
)时,会发生隐式类型转换。例如:
C++ char c = \'a\';int i = c; // char类型隐式转换为int类型输出结果97
C++ int Add(int x, int y) return x + y;void test() { int a = 10; char c = \'a\'; //发生隐式类型转换 cout << Add(a, c) << endl;--->109 }
- 函数模板 - 无法推导出一致的数据类型
C++template<typename T>void My_Add(T& x, T& y){return x + y;}int main(){int a = 10;char c = \'c\';//1.自动类型推导cout << My_Add(a, c);--->false//2.显示指定类型cout << My_Add<int>(a, c);--->true,会发生隐式类型转换return 0;}
- 总结:建议使用显示类型的方式,调用函数模板,因为可以自己确定通用类型
2.6普通函数和函数模板的调用规则
- 调用规则如下:
1.如果函数模板和普通函数都可以实现,优先调用普通函数
2.可以通过空模板参数列表来强制调用函数模板
3.函数模板也可以发生重载
4.如果函数模板可以发生更好的匹配,优先调用函数模板
C++ void My_Print(int a, int b)--->哪怕只有函数声明 void My_Print(int a, int b);{ 也无法直接调用不加空模板参数列表修饰的函数模板cout << \"调用普通函数\" << endl;}template<typename T>void My_Print(T& a, T& b){cout << \"调用函数模板\" << endl;}int main(){int a = 10, b = 20;My_Print(a, b);return 0;}
- 空模板参数列表:强制调用函数模板
C++ My_Print<>(a, b);
- 函数模板也可以发生函数重载
C++template<typename T>void My_Print(T& a, T& b){cout << \"调用函数模板\" << endl;}template<typename T>void My_Print(T& a, T& b, T& c){cout << \"调用重载的函数模板\" << endl;}
- 函数模板可以产生更好的匹配时,编译器会优先调用函数模板
C++ void My_Print(int a, int b){cout << \"调用普通函数\" << endl;}template<typename T>void My_Print(T& a, T& b){cout << \"调用函数模板\" << endl;}int main(){char c1 = \'a\', c2 = \'b\';My_Print(c1, c2);return 0;}
- 因为编译器认为,调用普通函数时还需要进行隐式类型转换,那就过于麻烦了,所以就不去调用普通函数了。
总结:当我们使用函数模板时,最好减少使用普通函数,否则容易出现二义性
2.7模板的局限性
局限性:
- 模板不是万能的
例如:
C++template<typename T> void f(T& a, T& b){ a = b;}
如上述代码中,提供一个赋值操作,如果 a 和 b 是一个数组,就无法实现了
再例如:
C++ template<typename T> void f(T& a, T& b){ if(a > b){ ... }}
如上述代码中,如果 T 的数据类型传入的是像 Pereson 这样的自定义数据类型,也无法正常运行
因此,C++为了解决这种问题,提供了模板的重载,可以为这些特定的数据类型提供具体化的模板
C++class Person{public:Person(string Name, int Age){this->Name = Name;this->Age = Age;}public:string Name;int Age;};template<class T>bool My_Compare(T& x, T& y){return x == y;}int main(){Person p1(\"啦啦啦\", 20);Person p2(\"呦呦呦\", 3);cout << (My_Compare(p1, p2) ? \"p1 == p2\" : \"p1 != p2\") << endl;return 0;}
2.7.1运算符重载
- 解决方法一:运算符重载
C++class Person { public: bool operator== (const Person& other) cosnt{ return Name == other.Name && Age == other.Age; } };
2.7.2具体化模板
- 利用 Person具体化的模板实现代码,且这种基于具体化实现的模板会优先被调用
C++template<typename T>bool My_Compare(T& p1, T& p2)--->函数模板声明{return p1 == p2;}template<> bool My_Compare(Person& p1, Person& p2){return p1.Name == p2.Name && p1.Age == p2.Age;}
- 相比于运算符重载,模板具体化的优点:可以省去对每一个运算符都进行重载的过程,大大降低了代码量
总结:
-
利用具体化的模板,可以解决自定义类型的通用化
-
学习模板并不是为了写模板,而是为了在 STL 中能够运用系统提供的模板
🌟 各位看官好,我是工藤新一¹呀~
🌈 愿各位心中所想,终有所致!
🌟下一篇章类模板 ~~🌟