• C++——模板


    一、模板的概念

    模板就是建立通用的模具,大大提高复用性 。是c++泛型编程思想和STL技术的主要实现。

    二、函数模板

    (一)概念:

    函数模板是一种通用函数,用到关键字template和typename(或class),我们可以用类型参数代替具体的数据类型,从而使函数可以处理不同的数据,大大提高了代码复用性。例如:

    1. template <typename T>
    2. void myCompare(T &a, T &b) {
    3. if (a == b) {
    4. cout << "a == b" << endl;
    5. }
    6. else if (a > b) {
    7. cout << "a > b" << endl;
    8. }
    9. else {
    10. cout << "a < b" << endl;
    11. }
    12. }
    13. void test() {
    14. int a = 5, b = 10;
    15. myCompare(a, b);
    16. double c = 0.9, d = 0.5;
    17. myCompare(c, d);
    18. }

    代码中模板的定义从template开始,声明了参数列表,void myCompare(T &a, T &b){……}就是函数定义,T代替了参数类型。该函数模板可以用来比较两个int、double、long等内置类型的大小。和函数重载相比,函数模板更简洁,减少了代码量。

    (二)用法

    函数模板用法有两种:1、自动类型推导;2、显示指定类型

    1. template<typename T>
    2. T myAdd(T a, T b) {
    3. return a + b;
    4. }
    5. void test() {
    6. int a = 5, b = 10;
    7. double c = 3.14, d = 6.28;
    8. cout << myAdd(a, b) << endl; //自动类型推导
    9. cout << myAdd<double>(c, d) << endl; //显示指定类型
    10. }

    函数模板的参数列表可以指定默认参数类型,和函数默认参数类型一样,需要从后向前指定。 

    (三)注意事项

    函数模板的使用中,传入的参数,需要与函数定义中参数列表一致。例如:

    1. template <typename T,typename K=int>
    2. void myFunc(T &a, T &b,K &c) {
    3. cout << "重载模板调用" << endl;
    4. }
    5. void test() {
    6. int a = 5;
    7. int b = 10;
    8. char c;
    9. myFunc(a, b, c); //正确
    10. //myFunc(a, c, b); //错误
    11. }

    (四)函数模板实例

    实现一个排序函数模板,能够满足整形、浮点型、字符型等多种内置类型数据数组的排序

    1. template<typename T>
    2. void mySort(T arr[], int len) {
    3. for (int i = 0; i < len; i++) {
    4. int maxID = i;
    5. for (int j = i + 1; j < len; j++) {
    6. if (arr[maxID] < arr[j]) {
    7. maxID = j;
    8. }
    9. }
    10. if (maxID != i) {
    11. T temp = arr[maxID];
    12. arr[maxID] = arr[i];
    13. arr[i] = temp;
    14. }
    15. }
    16. }
    17. template<typename T>
    18. void printArr(T* arr, int len) {
    19. for (int i = 0; i < len; i++) {
    20. cout << setw(2) << setiosflags(ios::left) << arr[i];
    21. }
    22. cout << endl;
    23. }
    24. void test01() {
    25. int intArr[] = { 1,3,5,6,4,7,2,9,8 };
    26. int len = sizeof(intArr) / sizeof(int);
    27. mySort(intArr, len);
    28. printArr(intArr, len);
    29. cout << "…………………………………………" << endl;
    30. char charArr[] = "asdfghjklqwertyuiopzxcvbnm";
    31. len = sizeof(charArr) / sizeof(char);
    32. mySort(charArr, len);
    33. printArr(charArr, len);
    34. }

    (五)普通函数和函数模板对比

    1、普通函数和函数模板的区别

    普通函数支持隐式类型转换;函数模板采用自动类型推导时,不支持参数的隐式类型转换;函数模板使用显示指定类型的方式,可以发生隐式类型转换

    1. template <typename T>
    2. T myAdd(T a, T b) {
    3. return a + b;
    4. }
    5. void test() {
    6. int a = 5, b = 10;
    7. char c = 'a';
    8. cout << myAdd(a, b) << endl;
    9. //cout << myAdd(a, c) << endl;//错误,采用自动类型推导,不能发生隐式类型转换
    10. cout << myAdd<int>(a, c) << endl;//正确,采用显示指定类型,能够发生隐式类型转换
    11. }

    2、普通函数和函数模板的调用规则

    (1)当普通函数和函数模板同时存在时,优先调用普通函数;

    (2)可以通过空模板参数列表来强制调用函数模板;

    (3)函数模板可发生重载;

    (4)如果函数模板可以产生更好的匹配,优先调用函数模板。

    1. template <typename T>
    2. T myAdd(T a, T b) {
    3. cout << "函数模板调用" << endl;
    4. return a + b;
    5. }
    6. int myAdd(int a, int b) {
    7. cout << "普通函数调用" << endl;
    8. return a + b;
    9. }
    10. template <typename T>
    11. T myAdd(T a, T b) {
    12. cout << "函数模板调用" << endl;
    13. return a + b;
    14. }
    15. template <typename T>
    16. T myAdd(T a, T b, T c) {
    17. cout << "函数模板的重载调用" << endl;
    18. return a + b + c;
    19. }
    20. void test() {
    21. int a = 5, b = 10;
    22. cout << myAdd(a, b) << endl; //优先调用普通函数
    23. cout << myAdd<>(a, b) << endl; //通过空模板参数列表强制调用函数模板
    24. cout << myAdd(a, b, 10) << endl;//函数模板的重载
    25. char c = 'c', d = 'd';
    26. cout << (int)myAdd(c, d) << endl; //函数模板比普通函数更加适配,调用函数模板
    27. }

    输出结果:普通函数调用

                      函数模板调用

                      函数模板的重载调用

                      函数模板调用

    例1:当普通函数和函数模板同时存在,且函数原型完全相同,优先调用普通函数。

    例2:通过空模板参数列表,强制调用函数模板。

    例3:函数模板发生了重载,通过对传入参数的数量控制调用模板

    例4:例子中普通函数调用会发生隐式类型转换,函数模板更加适配,则调用函数模板。

    TIP:为了避免普通函数和普板函数出现二义性,一般提供了函数模板就不再提供普通函数

    三、模板的局限性

    函数模板并不是万能的,当参数为数组类型,或者自定义类型(类)时,无法进行部分运算。例如:

    1. template <typename T>
    2. void myfunc(T &a, T &b) {
    3. a = b;
    4. }

    c++提供了函数模板的重载,为特殊类型参数提供具体化模板。语法如下:

    1. template <typename T>
    2. void myfunc(T &a, T &b) {
    3. a = b;
    4. }
    5. class Person {
    6. public:
    7. string m_name;
    8. int m_age;
    9. };
    10. template<> void myfunc(Person &a, Person &b) {
    11. a.m_name = b.m_name;
    12. a.m_age = b.m_age;
    13. }

    特殊类型参数的函数模板具体化后,传入该类型参数会直接调用具体化类型参数的函数模板,不再调用通用函数模板。

    四、类模板

    类模板和函数模板相似,在template后面紧跟类定义

    1. template <class NameType,class AgeType>
    2. class Person {
    3. public:
    4. Person(){}
    5. Person(NameType name, AgeType age) {
    6. m_name = name;
    7. m_age = age;
    8. }
    9. NameType m_name;
    10. AgeType m_age;
    11. };

    (一)类模板和函数模板的比较:

    1、类模板没有自动类型推导的使用方式,只有显式指定类型实例化对象。

    2、类模板和函数模板的参数列表中均可以指定默认类型。

    1. class Person {
    2. public:
    3. Person(){}
    4. Person(NameType name=string, AgeType age=int) {
    5. m_name = name;
    6. m_age = age;
    7. }
    8. NameType m_name;
    9. AgeType m_age;
    10. };
    11. //Person p("孙悟空", 999); //错误,缺少类模板Person的参数列表
    12. Person int>p("猪八戒", 888);//正确,类模板只能使用显示指定类型实例化对象

    (二)类模板中成员函数创建时机

    1、普通类中成员函数在声明时即可创建

    2、类模板中成员函数在调用时创建,当模板中T类型不确定时,func1()函数和func2()函数无法创建,只有当实例化Myclass  p,确定了T的类型为Person1后调用p.func1()时才会创建。

    1. class Person1 {
    2. public:
    3. void show1() {
    4. cout << "Person1 show" << endl;
    5. }
    6. };
    7. class Person2 {
    8. public:
    9. void show2() {
    10. cout << "Person2 show" << endl;
    11. }
    12. };
    13. template<class T>
    14. class MyClass {
    15. public:
    16. T obj;
    17. void func1() {
    18. obj.show1();
    19. }
    20. void func2() {
    21. obj.show2();
    22. }
    23. };
    24. void test() {
    25. MyClass p;
    26. p.func1();
    27. //p.func2();
    28. }
    29. int main(int argc, char const *argv[]) {
    30. test();
    31. return 0;
    32. }

    (三)类模板作为参数传入函数的三种方式

    1、指定参数类型传参(常用)

    2、利用函数模板,声明类模板中参数

    3、利用函数模板,声明函数参数后自动推导

    1. template<class T,class K>
    2. class Person {
    3. public:
    4. Person(T name, K age) {
    5. this->m_name = name;
    6. this->m_age = age;
    7. }
    8. T m_name;
    9. K m_age;
    10. void show() {
    11. cout << "姓名:" << this->m_name << endl;
    12. cout << "年龄:" << this->m_age << endl;
    13. }
    14. };
    15. //第一种传参方式:指定参数类型传参
    16. void printPerson1(Personint> &p) {
    17. p.show();
    18. cout << typeid(p).name() << endl;
    19. }
    20. //第二种传参方式:利用函数模板,声明类中参数类型
    21. template<class T,class K>
    22. void printPerson2(Person &p) {
    23. p.show();
    24. cout << typeid(p).name() << endl;
    25. }
    26. //第三种传参方式:利用函数模板,声明函数参数类型后自动推导
    27. template<class T>
    28. void printPerson3(T &p) {
    29. p.show();
    30. cout << typeid(p).name() << endl;
    31. }
    32. void test() {
    33. Personint> p("唐僧", 30);
    34. printPerson1(p);
    35. printPerson2(p);
    36. printPerson3(p);
    37. }

    (四)类模板与继承

    当子类继承的父类为模板时,声明子类时需要指定父类中的参数类型;

    或者子类也作为模板去继承父类模板,用子类模板中的参数类型来指定父类模板参数类型。

    1. template<class T>
    2. class Base {
    3. public:
    4. T m;
    5. };
    6. //子类继承类模板,需指定父类参数类型
    7. class Son1 :public Base<char> {
    8. };
    9. //子类继承类模板也可声明为类模板
    10. //此时相当于指定了父类模板中参数类型为T
    11. template<class T,class K>
    12. class Son2 :public Base {
    13. public:
    14. K obj;
    15. };
    16. void test01() {
    17. Base<char> b; //类模板实例化对象时,需指定参数类型
    18. Son1 s1; //子类声明时已指定父类参数类型为字符型
    19. Son2<int,char> s2; //子类为模板时,先指定了父类参数类型为T,也就是实例化时指定的int
    20. }

    (五)类模板成员函数的类外实现和分文件编写

    1、类模板中成员函数的类外实现,每次要重申模板和模板的参数列表

    1. template<class TypeName,class TypeAge>
    2. class Person {
    3. public:
    4. TypeName m_name;
    5. TypeAge m_age;
    6. Person(TypeName name,TypeAge age);
    7. void show();
    8. };
    9. template<class TypeName,class TypeAge>
    10. Person::Person(TypeName name, TypeAge age) {
    11. this->m_name = name;
    12. this->m_age = age;
    13. }
    14. template<class TypeName,class TypeAge>
    15. void Person::show() {
    16. cout << this->m_name << endl;
    17. cout << this->m_age << endl;
    18. }

    2、分文件编写:因类模板函数是在调用阶段创建,并不是在编译阶段创建,则编译器进行到调用语句时发生错误,无法识别类模板函数。解决办法:在编译阶段将函数体提供给编译器,代替函数原型。可以在编译阶段将函数体所在的cpp文件包含进去,也可以将函数体写进头文件,命名为.hpp文件(约定俗成,不强制),一般用第二种处理方法。

    Person.hpp

    1. #pragma once
    2. template<class TypeName,class TypeAge>
    3. class Person
    4. {
    5. public:
    6. TypeName m_name;
    7. TypeAge m_age;
    8. Person(TypeName name, TypeAge age);
    9. void show();
    10. };
    11. template<class TypeName, class TypeAge>
    12. Person::Person(TypeName name, TypeAge age) {
    13. this->m_name = name;
    14. this->m_age = age;
    15. }
    16. template<class TypeName, class TypeAge>
    17. void Person::show() {
    18. cout << this->m_name << endl;
    19. cout << this->m_age << endl;
    20. }

    main.cpp

    1. #include
    2. using namespace std;
    3. #include
    4. #include"Person.hpp"
    5. void test() {
    6. Personint> p("张三", 20);
    7. p.show();
    8. }
    9. int main(int argc, char const *argv[]) {
    10. test();
    11. return 0;
    12. }

    (六)类模板——友元

    类模板的全局友元函数可以分为内部实现和外部实现两种方式

    1、内部实现:直接在类内部实现函数体,函数前加上friend友元关键字即可。

    2、外部实现:因传入参数不确定类型,则外部全局函数也需要用模板对应类模板

    (1)声明类模板

    (2)声明全局函数模板(模板参数列表是类模板参数列表)

    (3)定义类模板

    (4)类内部将全局函数模板声明为友元函数模板

    (5)实现函数模板

    1. #pragma once
    2. #include
    3. using namespace std;
    4. #include
    5. //声明类模板,目的是声明全局函数模板
    6. template<class TypeName,class TypeAge>
    7. class Person;
    8. //声明全局函数模板
    9. template<class TypeName,class TypeAge>
    10. void printPerson(Person &p);
    11. //定义类模板
    12. template<class TypeName,class TypeAge>
    13. class Person {
    14. private:
    15. TypeName m_Name;
    16. TypeAge m_Age;
    17. public:
    18. Person(TypeName name,TypeAge age);
    19. //外部实现全局友元函数模板
    20. friend void printPerson<>(Person &p);
    21. //内部实现全局友元函数
    22. friend void printPerson1(Person &p) {
    23. cout << p.m_Name << endl;
    24. cout << p.m_Age << endl;
    25. }
    26. };
    27. //构造函数的外部实现
    28. template<class TypeName,class TypeAge>
    29. Person::Person(TypeName name, TypeAge age) {
    30. this->m_Name = name;
    31. this->m_Age = age;
    32. }
    33. //全局友元函数模板的外部实现
    34. template<class TypeName,class TypeAge>
    35. void printPerson(Person &p) {
    36. cout << p.m_Name << endl;
    37. cout << p.m_Age << endl;
    38. }

    五、模板实例

    用模板实现数组,能够实现对内置数据类型和自定义数据类型的储存和基本操作。通过独立写代码的过程对类的三种权限复习一下;对运算符的重载复习一下;对new的用法理解加深了一些。

    MyArray.hpp

    1. #pragma once
    2. #include
    3. using namespace std;
    4. //定义数组类模板
    5. template<class T>
    6. class MyArray {
    7. private:
    8. int m_size = 0; //元素数量
    9. int m_capacity ; //数组容量
    10. T *m_array = NULL; //数组指针
    11. public:
    12. MyArray(int capacity);
    13. MyArray(const MyArray& array);
    14. void add(const T &value); //添加元素
    15. void del(const T &value); //删除元素
    16. int search(const T &value); //搜索元素
    17. void print(); //打印数组
    18. T& operator[](int index); //[]重载
    19. MyArray& operator=(const MyArray& array);//=重载
    20. bool isfull(); //判定数组是否已满
    21. ~MyArray();
    22. };
    23. //普通构造函数
    24. template<class T>
    25. MyArray::MyArray(int capacity) {
    26. this->m_size = 0;
    27. this->m_capacity = capacity;
    28. this->m_array = new T[capacity];
    29. }
    30. //拷贝构造函数,利用=重载避免浅拷贝
    31. template<class T>
    32. MyArray::MyArray(const MyArray& array) {
    33. *this = array;
    34. }
    35. //=号重载,返回对象本身实现连续复制
    36. template<class T>
    37. MyArray& MyArray::operator=(const MyArray& array) {
    38. this->m_capacity = array.m_capacity;
    39. this->m_size = array.m_size;
    40. if (this->m_array) {
    41. delete this->m_array;
    42. }
    43. this->m_array = new T[m_capacity];
    44. for (int i = 0; i < this->m_size; i++) {
    45. this->m_array[i] = array.m_array[i];
    46. }
    47. return *this;
    48. }
    49. //[]重载,取当前index下标元素
    50. template<class T>
    51. T& MyArray::operator[](int index) {
    52. return m_array[index];
    53. }
    54. //判定数组是否已满
    55. template<class T>
    56. bool MyArray::isfull() {
    57. return this->m_size == this->m_capacity;
    58. }
    59. //添加元素
    60. template<class T>
    61. void MyArray::add(const T &value) {
    62. if (!this->isfull()) {
    63. this->m_array[this->m_size++] = value;
    64. }
    65. }
    66. //删除元素
    67. template<class T>
    68. void MyArray::del(const T &value) {
    69. int index = search(value);
    70. while (index != -1) {
    71. for (int i = index; i < m_size - 1; i++) {
    72. m_array[i] = m_array[i + 1];
    73. }
    74. m_size--;
    75. index = search(value);
    76. }
    77. }
    78. //查找元素
    79. template<class T>
    80. int MyArray::search(const T &value) {
    81. int index = -1;
    82. for (int i = 0; i < m_size; i++) {
    83. if (this->m_array[i] == value) {
    84. index = i;
    85. break;
    86. }
    87. }
    88. return index;
    89. }
    90. //打印元素
    91. template<class T>
    92. void MyArray::print() {
    93. for (int i = 0; i < m_size; i++) {
    94. cout << m_array[i] ;
    95. if (i != m_size - 1) {
    96. cout << " ";
    97. }else{
    98. cout << endl;
    99. }
    100. }
    101. cout << endl;
    102. }
    103. template<class T>
    104. MyArray::~MyArray() {
    105. if (m_array) {
    106. delete m_array;
    107. m_array = NULL;
    108. }
    109. }

    main.cpp

    1. #include
    2. using namespace std;
    3. #include
    4. #include"myArray.hpp"
    5. void test() {
    6. MyArray<int> *pA_int = new MyArray<int>(10);
    7. pA_int->add(3);
    8. pA_int->add(5);
    9. pA_int->add(7);
    10. pA_int->add(8);
    11. pA_int->add(8);
    12. cout << (*pA_int)[1] << endl;
    13. pA_int->print();
    14. MyArray<int> pA_int2(*pA_int);
    15. pA_int2.print();
    16. pA_int2.del(8);
    17. pA_int2.print();
    18. }
    19. int main(int argc, char const *argv[]) {
    20. test();
    21. return 0;
    22. }

  • 相关阅读:
    JMeter —— 接口自动化测试(数据驱动)
    web3之链上情报平台Arkham
    ethernet eth0: Could not attach to PHY
    【错误记录】PySpark 运行报错 ( Did not find winutils.exe | HADOOP_HOME and hadoop.home.dir are unset )
    再获5G RedCap能力认证!宏电5G RedCap工业智能网关通过中国联通5G物联网OPENLAB开放实验室测试验证
    成都精灵云C++ 二面(hr面,30min)
    Windows11下载安装vscode并编写第一个页面
    【实战】学习 Electron:构建跨平台桌面应用
    施工企业数字化转型如何避免IT技术与企业管理的“两张皮”
    (附源码)spring boot考试系统 毕业设计 191015
  • 原文地址:https://blog.csdn.net/qq_42106610/article/details/134169337