C++之模板函数、类模板

爱被打了一巴掌 2022-09-27 06:24 414阅读 0赞

在编程的时候,一般是一个函数对应一种功能但有时候我们需要实现的是同一类的功能。

Center

这时候就引入了函数模板,所谓函数模板,实际上是建立一个通用函数,其函数返回值类型和参数类型不具体指定,用一个虚拟类型来代替,这个通用用函数就是函数模板

例一:

  1. #include<iostream>
  2. #include<stdlib.h>
  3. using namespace std;
  4. template<typename T>//对模板类型进行声明,其中T为类型参数,可以给任意的名字
  5. T Add(T a, T b)//定义一个通用函数,用T作为类型
  6. {
  7. return a + b;
  8. }
  9. int main()
  10. {
  11. cout << Add(10, 20) << endl;
  12. system("pause");
  13. return 0;
  14. }

Center 1

我们可以看到即使我们不给定类型编译器也会去自动识别它的类型然后调用类型相对应的函数。

在模板定义中,模板参数不能为空

定义模板可以采用template 也可以采用template但是我们不能用struct来替代typename

在定义函数时候用T来定义变量a和b 显然a和b的类型也是未知的,我们只有通过实参才能确定模板形参和返回值的类型

模板函数也可以定义成内联函数

模板函数也支持重载

但是inline 这个关键字必须加在模板类型声明之后,返回值之前。

即 inline C Add(C a, C b)

模板参数:

类型参数:参数类型是模糊的需要通过实参对其进行实例化如上述例一。

非类型参数:参数的类型是确定的

在调用非类型参数的函数时需要采用引用的方式或者显式调用。

  1. template<typename T,int N>
  2. void Print(T (&array)[N])
  3. {
  4. for (int index = 0; index < N; index++)
  5. {
  6. cout << array[index] << " ";
  7. }
  8. }
  9. int main()
  10. {
  11. int array[10] = {0};
  12. Print(array);//采用传引用的方式
  13. system("pause");
  14. return 0;
  15. }

类型形参转换:

在对模板进行传参的时候不会发生隐式类型转换

只接受两种转换:一种为const类型的转换

  1. 一种为数组或函数到指针的转换
  2. template<typename T>
  3. void FunTest(const T a)//const类型的转换
  4. {
  5. int b = 10;
  6. a = &b;
  7. cout << typeid(a).name() << endl;
  8. }
  9. int main()
  10. {
  11. int a = 10;
  12. int *p=&a;
  13. FunTest(p);
  14. system("pause");
  15. return 0;
  16. }

Center 2

  1. template<typename T1,typename T2>
  2. void FunTest(T1 a,T2 b)//数组或函数到指针的转换
  3. {
  4. cout << typeid(a).name() << endl;
  5. cout << typeid(b).name() << endl;
  6. }
  7. void FunTest1()
  8. {
  9. int a = 10;
  10. }
  11. int main()
  12. {
  13. int array[10] = { 0 };
  14. FunTest(array,FunTest1);
  15. system("pause");
  16. return 0;
  17. }

Center 3

模板函数支持重载

  1. template<typename T>
  2. T Max(T left, T right)
  3. {
  4. return left > right ? left : right;
  5. }
  6. int Max(int left, int right)
  7. {
  8. return left > right ? left : right;
  9. }
  10. template<typename T>
  11. T Max(T left, T middle, T right)
  12. {
  13. return Max(Max(left, middle), right);
  14. }
  15. int main()
  16. {
  17. int a = 10;
  18. int b = 20;
  19. int c = 15;
  20. cout<<Max(a, b);
  21. system("pause");
  22. return 0;
  23. }

非模板函数可以和一个同名的模板函数同时存在

如果如上述例子在调用的时候条件相同,那么在调用的的时候会优先调用非模板函数而并非编译器生成的函数

如果条件相同要调用模板函数必须显式调用。即Max(a, b);

显式指定一个空的模板实参列表,该语法告诉编译器只有模板才能来匹配这个调用,而且所有的模板参数都应该根据实参演绎出来,即Max<>(a,b),那么它必须去调用模板函数,并且参数类型是由实参推演而来。

模板函数不支持隐士类型转换而普通函数可以

例如如果将上述例子中的非模板函数屏蔽掉之后以这样的方式调用:Max(1,’2’);就会出错

Center 4

而普通函数却支持隐式类型转换,会自动将’2’这个字符转换成int类型

模板函数的特化:

有时候模板并不是对所有类型的实例化都合适,比如在比较两个字符串的时候,上述这个模板就不太合适,例如

  1. char *str = "dfghjk";
  2. char *dst = "artyui";
  3. cout << Max(str, dst);

Center 5

出现这种情况是因为你传参传进去的是两个指针变量的地址传进去,比较的是两个指针变量地址的大小而并非所指向地址里面的内容。

Center 6

在这种情况下上述模板已经不能满足我们的要求,此时我们就需要特化模板函数以实现我们的需求

模板函数特化形式如下:

1、关键字template后面接一对空的尖括号<>

2、函数名后接模板名和一对尖括号,尖括号中指定这个特化定义的模板形参

3、函数形参表

4、函数体

特化模板函数需要注意以下几点:1、模板函数必须已经存在

  1. 2、和已存在的模板函数的类型必须保持一致

例如:

  1. template<typename T>
  2. int Max(const T left, const T right)
  3. {
  4. return left > right ? left : right;
  5. }
  6. template<>
  7. int Max<char*>(char *const left, char *const right)
  8. {
  9. return strcmp(left, right);
  10. }

Center 7

  1. ![Center 8][]

如果参数和已有模板参数类型不一致就会出错。

  1. template<typename T>
  2. T Max(const T left, const T right)//要注意如果传指针const其实修饰的是char* const p
  3. {
  4. return left > right ? left : right;
  5. }
  6. template<>
  7. char *Max<char*>(char *const left, char *const right)
  8. {
  9. if (strcmp(left, right) == 1)
  10. {
  11. return left;
  12. }
  13. else
  14. {
  15. return right;
  16. }
  17. }
  18. int main()
  19. {
  20. int a = 10;
  21. int b = 20;
  22. int c = 15;
  23. char *const str = "dfghjk";
  24. char *const dst = "artyui";
  25. cout << Max(str, dst);
  26. system("pause");
  27. return 0;
  28. }

注意在调用的时候保持类型一致。

类模板

实现类模板的格式:

  1. template<typename T>
  2. class Vector
  3. {
  4. };

template

class +类名{};

但是在使用类模板的时候需要注意:

1、模板函数不允许有缺省参数而类模板确是支持的

2、用类模板定义对象的时候必须在类模板名之后在尖括号内指定实际的类型名

例如

  1. template<typename T>
  2. class Vector
  3. {...
  4. };
  5. int main()
  6. {
  7. Vector<int>d;
  8. return 0;
  9. }

因为Vector 是类模板名,而不是一个具体的类,类模板中的T并不是一个实际的类型,无法用它定义类型,必须用实际的类型名去取代T

3、在类模板外定义成员函数的时候与一般定义类的成员函数的方法不同

  1. template<typename T>
  2. void Vector<T>::PushBack(const T& value)
  3. {
  4. CheckCapacity();
  5. _start[Size()] = value;
  6. ++_size;
  7. _end = _start + Size();
  8. _endOfStorage = _start + Capacity();
  9. }

从上述例子可以看出在类模板外定义成员函数

1、必须先声明模板参数列表

2、函数返回值类型+类模板名<不确定的类型参数T>::成员函数(函数参数列表){…}

4、类模板不支持分离式编译

因为编译器在编译某一个cpp文件的时候并不知道另一个cpp文件,也不会去查找,遇见外部符号的时候会寄希望于链接器,但是模板只有需要的时候才会被实例化出来

如果在另一个cpp文件里面并没有将其实例化,那么在当前cpp文件下只能看到声明,根本找不到模板被实例化之后的代码,因此就会出错。

Center 9

在链接过程中会报错

C++标准明确表示,当一个模板不被用到的时侯它就不该被实例化出来

模板小结:关于模板我们需要注意

优点: 模板复用了代码,节省资源,更快的迭代开发,C++的标准模板库(STL)因此而产生。 增强了代码的灵活性。

缺点: 模板让代码变得凌乱复杂,不易维护,编译代码时间变长。 出现模板编译错误时,错误信息非常凌乱,不易定位错误。

发表评论

表情:
评论列表 (有 0 条评论,414人围观)

还没有评论,来说两句吧...

相关阅读