• 浅谈C++|模板篇


    一.模板模板概念

    模板就是建立通用的模具,大大提高复用性

     模板的特点:

    1.模板不可以直接使用,它只是一个框架

    2.模板的通用并不是万能的

    ·C++另一种编程思想称为泛型编程,主要利用的技术就是模板。

    ·C++提供两种模板机制:函数模板和类模板

     二.函数模板

    函数模板作用:
    建立一个通用函数,其函数返回值类型和形参类型可以不具体制定,用一个虚拟的类型来代表。 

    2.1语法

    1. template<typename T>
    2. 函数声明或定义

    自动推导类型代码实例:

    1. #include
    2. using namespace std;
    3. template<typename T>
    4. void my_swap(T& a, T& b) {
    5. T temp = a;
    6. a = b;
    7. b = temp;
    8. }
    9. int main() {
    10. int a = 10,b = 20;
    11. char a2 = 'a', b2 = 'b';
    12. string a3 = "abc", b3 = "efg";
    13. my_swap(a, b);
    14. my_swap(a2, b2);
    15. my_swap(a3, b3);
    16. cout << "a=" << a << " b=" << b << endl;
    17. cout << "a2=" << a2 << " b2=" << b2 << endl;
    18. cout << "a3=" << a3 << " b3=" << b3 << endl;
    19. return 0;
    20. }

     显示指定类型代码:

    1. #include
    2. using namespace std;
    3. template<typename T>
    4. void my_swap(T& a, T& b) {
    5. T temp = a;
    6. a = b;
    7. b = temp;
    8. }
    9. int main() {
    10. int a = 10,b = 20;
    11. char a2 = 'a', b2 = 'b';
    12. string a3 = "abc", b3 = "efg";
    13. my_swap<int>(a, b);
    14. my_swap<char>(a2, b2);
    15. my_swap(a3, b3);
    16. cout << "a=" << a << " b=" << b << endl;
    17. cout << "a2=" << a2 << " b2=" << b2 << endl;
    18. cout << "a3=" << a3 << " b3=" << b3 << endl;
    19. return 0;
    20. }

     2.2注意事项

    注意事项:
    1.自动类型推导,必须推导出一致的数据类型T,才可以使用

    2.模板必须要确定出T的数据类型,才可以使用

     代码实例:

    1. #include
    2. using namespace std;
    3. template<typename T>
    4. void my_swap(T a, T B) {
    5. T temp = a;
    6. a = b;
    7. b = a;
    8. }
    9. template<typename T>
    10. void fun() {
    11. cout << "函数模板调用" << endl;
    12. }
    13. int mian() {
    14. //fun();//报错,因为不能推理出T的类型
    15. int a = 10, b = 20;
    16. fun<int>();
    17. //必须这样写,int换为其它类型也可
    18. //my_swap(10, 'p');
    19. //报错,类型不一致
    20. my_swap(20, 90);
    21. my_swap<int>(20, 90);
    22. my_swap(a, b);
    23. return 0;
    24. }

    2.3普通函数和模板函数的区别

    普通函数与函数模板区别:

    ·普通函数调用时可以发生自动类型转换(隐式类型转换)

    ·函数模板调用时,如果利用自动类型推导,不会发生隐式类型转换

    ·如果利用显示指定类型的方式,可以发生隐式类型转换

    1. #include
    2. using namespace std;
    3. void fun(int one, int two) {
    4. cout << "one=" << one << " two=" << two << endl;
    5. }
    6. template<typename T>
    7. void fun1(T one, T two) {
    8. cout << "one=" << one << " two=" << two << endl;
    9. }
    10. void ce() {
    11. int a = 10, b = 20;
    12. char c = 'a';
    13. fun(a, b);
    14. fun(a, c);
    15. fun1(a, b);
    16. //fun1(a, c);
    17. //报错,因为自动推理类型不会发生隐式转换
    18. fun1<int>(a, c);
    19. }
    20. int main() {
    21. ce();
    22. return 0;
    23. }

     总结:建议使用显示指定类型的方式,调用函数模板,因为可以自己确定通用类型T

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

    调用规则如下:
    1.如果函数模板和普通函数都可以实现,优先调用普通函数

    2.可以通过空模板参数列表来强制调用函数模板
    3.函数模板也可以发生重载
    4.如果函数模板可以产生更好的匹配,优先调用函数模板

    代码实例: 

    1. #include
    2. using namespace std;
    3. void fun(int a,int b) {
    4. cout << a << ' ' << b << ' ' << endl;
    5. cout << "普通函数调用" << endl;
    6. }
    7. template<class T>
    8. void fun(T a, T b) {
    9. cout << a << ' ' << b << ' ' << endl;
    10. cout << "模板函数调用" << endl;
    11. }
    12. template<class T>
    13. void fun(T a, T b,T c) {
    14. cout << a << ' ' << b << ' ' <
    15. cout << "模板函数调用" << endl;
    16. }
    17. int main() {
    18. fun(10,20); //默认调用普通函数
    19. fun<>(10,90); //空列表强制调用模板函数
    20. fun<>(10,90,90); //模板函数可以发生重载
    21. return 0;
    22. }

     2.5模板的局限性

     局限性:

    模板的通用性并不是万能的

     代码实例:

    1. #include
    2. using namespace std;
    3. class person {
    4. public:
    5. person(string name, int temp) {
    6. this->name = name;
    7. this->temp = temp;
    8. }
    9. int temp;
    10. string name;
    11. };
    12. template <class T>
    13. bool eq(T a, T b) {
    14. if (a == b) {
    15. return true;
    16. }
    17. else {
    18. return false;
    19. }
    20. }
    21. int main() {
    22. person p1("tom", 20);
    23. person p2("tom", 20);
    24. //报错,因为自定义的类型,重载‘=’运算符
    25. if (eq(p1, p2)) {
    26. cout << "=" << endl;
    27. }
    28. else {
    29. cout << "!=" << endl;
    30. }
    31. return 0;
    32. }

    此时模板函数不报错,但运行时会报错。因为类的‘==’没有重载。

     代码实例:

    1. #include
    2. using namespace std;
    3. class person {
    4. public:
    5. person(string name, int temp) {
    6. this->name = name;
    7. this->temp = temp;
    8. }
    9. int temp;
    10. string name;
    11. };
    12. template <class T>
    13. bool eq(T a, T b) {
    14. if (a == b) {
    15. return true;
    16. }
    17. else {
    18. return false;
    19. }
    20. }
    21. //实例化模板函数
    22. template<> bool eq(person a, person b) {
    23. if (a.name == b.name && a.temp == b.temp) {
    24. return true;
    25. }
    26. else {
    27. return false;
    28. }
    29. }
    30. int main() {
    31. person p1("tom", 20);
    32. person p2("tom", 20);
    33. //报错,因为自定义的类型,重载‘=’运算符
    34. if (eq(p1, p2)) {
    35. cout << "=" << endl;
    36. }
    37. else {
    38. cout << "!=" << endl;
    39. }
    40. return 0;
    41. }

    此时可以运行,函数模板实例化,相当于模板的补充。

    三.类模板

    类模板作用:
    ·建立一个通用类,类中的成员数据类型可以不具体制定,用一个虚拟的类型来代表。

    类模板不能使用自动推导类型,只能用显示指定的方法。

    3.1语法 

    1. template<typename T1, typename T2>
    2. class person {
    3. public:
    4. person(T1 name, T2 age) {
    5. this->name = name;
    6. this->age = age;
    7. }
    8. T1 name;
    9. T2 age;
    10. };

    代码实例:

    1. #include
    2. using namespace std;
    3. template<typename T1, typename T2>
    4. class person {
    5. public:
    6. person(T1 name, T2 age) {
    7. this->name = name;
    8. this->age = age;
    9. }
    10. T1 name;
    11. T2 age;
    12. };
    13. int main() {
    14. personint> P("TOM", 90);
    15. cout << P.name << ' ' << P.age << endl;
    16. return 0;
    17. }

     3.2类模板和函数模板区别

    类模板与函数模板区别主要有两点:

    1.类模板没有自动类型推导的使用方式

    2.类模板在模板参数列表中可以有默认参数

    代码实例: 

    1. #include
    2. using namespace std;
    3. //类模板可以添加默认参数,但是要优先右边全有默认参数yuanze
    4. template<class T1=string, typename T2=int>
    5. class person {
    6. public:
    7. person(T1 name, T2 age) {
    8. this->name = name;
    9. this->age = age;
    10. }
    11. T1 name;
    12. T2 age;
    13. };
    14. int main() {
    15. //person P("TOM", 90); //报错因为。类模板没有自动推导型
    16. personint> P("TOM", 90);
    17. //有默认参数,可以省略类型,但是<>不能省略
    18. person<> P1("SOM", 900);
    19. cout << P.name << ' ' << P.age << endl;
    20. cout << P1.name << ' ' << P.age << endl;
    21. return 0;
    22. }

    注意:< >不可以省略,设置默认参数时,优先右边。

     3.3类模板中成员函数创建时机

    类模板中成员函数和普通类中成员函数创建时机是有区别的:
    1.普通类中的成员函数—开始就可以创建
    2.类模板中的成员函数在调用时才创建

    代码实例:

    1. #include
    2. using namespace std;
    3. class person1 {
    4. public:
    5. void fun1() {
    6. cout << "person1的fun函数调用" << endl;
    7. }
    8. };
    9. class person2 {
    10. public:
    11. void fun2() {
    12. cout << "person2的fun函数调用" << endl;
    13. }
    14. };
    15. template<typename T>
    16. class p {
    17. public:
    18. p(T p) {
    19. this->fun = p;
    20. }
    21. T fun;
    22. void fun1() {
    23. //定义时不报错,只有实例化后,调用此函数才会检测是否报错。
    24. fun.fun1();
    25. }
    26. void fun2() {
    27. //定义时不报错,只有实例化后,调用此函数才会检测是否报错。
    28. fun.fun2();
    29. }
    30. };
    31. int main() {
    32. person1 p1;
    33. p p(p1);
    34. //此时会正常运行,不会报错,因为没有调用p.fun2();
    35. p.fun1();
    36. //p.fun2(); 运行报错
    37. return 0;
    38. }

    3.4类模板做函数参数

    —共有三种传入方式:
    1.指定传入的类型------------直接显示对象的数据类型

    ⒉参数模板化------------------将对象中的参数变为模板进行传递
    3.整个类模板化----------------将这个对象类型模板化进行传递

    3.4.1指定传入

    代码实例:

    1. #include
    2. using namespace std;
    3. //默认参数
    4. template<class T1=string,class T2=int>
    5. class person {
    6. public:
    7. T1 name;
    8. T2 id;
    9. person(T1 name, T2 id) {
    10. this->name = name;
    11. this->id = id;
    12. }
    13. void fun() {
    14. cout << name << ' ' << id << endl;
    15. }
    16. };
    17. //此函数不是模板函数,所以其参数要具体的参数
    18. void fun(personint>& p) {
    19. p.fun();
    20. }
    21. int main() {
    22. personint> p("孙悟空", 78);
    23. fun(p);
    24. return 0;
    25. }

     3.4.2参数模板化

    代码实例:

    1. #include
    2. using namespace std;
    3. //默认参数
    4. template<class T1 = string, class T2 = int>
    5. class person {
    6. public:
    7. T1 name;
    8. T2 id;
    9. person(T1 name, T2 id) {
    10. this->name = name;
    11. this->id = id;
    12. }
    13. void fun() {
    14. cout << name << ' ' << id << endl;
    15. }
    16. };
    17. //实际上是创建模板函数
    18. template<class T1,class T2>
    19. void fun(person& p) {
    20. p.fun();
    21. }
    22. int main() {
    23. personint> p("猪八戒", 19);
    24. fun(p);
    25. return 0;
    26. }

    3.4.3类模板化

    代码实例:

    1. #include
    2. #include
    3. using namespace std;
    4. //默认参数
    5. template<class T1 = string, class T2 = int>
    6. class person {
    7. public:
    8. T1 name;
    9. T2 id;
    10. person(T1 name, T2 id) {
    11. this->name = name;
    12. this->id = id;
    13. }
    14. void fun() {
    15. cout << name << ' ' << id << endl;
    16. }
    17. };
    18. //将整个类作为一个类型,创建模板函数
    19. template<class T1>
    20. void fun(T1& p) {
    21. p.fun();
    22. }
    23. int main() {
    24. personint> p("沙僧", 19);
    25. fun(p);
    26. return 0;
    27. }

    总结:
    通过类模板创建的对象,可以有三种方式向函数中进行传参

    使用比较广泛是第一种:指定传入的类型

     3.5类模板与继承

    当类模板碰到继承时,需要注意一下几点:
    1.当子类继承的父类是一个类模板时,子类在声明的时候,要指定出父类中T的类型,如果不指定,编译器无法给子类分配内存。
    2.如果想灵活指定出父类中T的类型,子类也需变为类模板。

     1.代码实例:

    1. #include
    2. using namespace std;
    3. template<typename T>
    4. class father {
    5. public:
    6. T name;
    7. };
    8. //class father :public son
    9. //如果想灵活指定出父类中T的类型,子类也需变为类模板。
    10. class son :public father {
    11. public:
    12. son(string name) {
    13. this->name = name;
    14. }
    15. void fun() {
    16. cout << this->name << endl;
    17. }
    18. };
    19. int main() {
    20. son s("Tom");
    21. s.fun();
    22. return 0;
    23. }

     2.代码实例:

    1. #include
    2. using namespace std;
    3. template<typename T>
    4. class father {
    5. public:
    6. T name;
    7. };
    8. //class father :public son
    9. //父类是模板类时,子类继承时要指定模板列表
    10. template<class T1,class T2>
    11. class son :public father {
    12. public:
    13. T1 id;
    14. son(T1 id,T2 name) {
    15. this->name = name;
    16. this->id = id;
    17. }
    18. void fun() {
    19. cout << this->name << endl;
    20. cout << this->id << endl;
    21. }
    22. };
    23. int main() {
    24. son<int,string> s(100,"Tom");
    25. s.fun();
    26. return 0;
    27. }

     3.6类模板成员函数类外实现

    代码实例:

    1. #include
    2. using namespace std;
    3. template<class T1,class T2>
    4. class person {
    5. public:
    6. T1 name;
    7. T2 id;
    8. person(T1 name, T2 id);
    9. void fun();
    10. };
    11. template<class T1, class T2>
    12. person::person(T1 name, T2 id) {
    13. this->name = name;
    14. this->id = id;
    15. }
    16. template<class T1, class T2>
    17. void person::fun() {
    18. cout << name << endl;
    19. cout << id << endl;
    20. }
    21. int main() {
    22. personint> p("tom", 100);
    23. p.fun();
    24. return 0;
    25. }

    总结:类模板中成员函数类外实现时,需要加上模板参数列表

    3.7类模板分文件编写

    问题:

    ·类模板中成员函数创建时机是在调用阶段,导致分文件编写时链接不到

    解决

    ·解决方式1:直接包含.cpp源文件
    ·解决方式2∶将声明和实现写到同一个文件中,并更改后缀名为.hpp,hpp是约定的名称,并不是强制

     代码:

    person.hpp

    1. #include
    2. using namespace std;
    3. template<class T1 = string, class T2 = int>
    4. class person {
    5. public:
    6. T1 name;
    7. T2 age;
    8. person(T1 name, T2 age);
    9. void person_show();
    10. };
    11. template<class T1, class T2>
    12. person::person(T1 name, T2 age) {
    13. this->name = name;
    14. this->age = age;
    15. }
    16. template<class T1, class T2>
    17. void person::person_show() {
    18. cout << this->name << endl << this->age << endl;
    19. }

    person.cpp

    1. #include "person.hpp";
    2. int main() {
    3. person<> p("猪八戒", 100);
    4. p.person_show();
    5. return 0;
    6. }

    3.8类模板和友元

    全局函数类内实现-直接在类内声明友元即可 
    全局函数类外实现·需要提前让编译器知道全局函数的存在 

    类内实现 

     代码:

    1. #include
    2. using namespace std;
    3. template<class T1=string,class T2=int>
    4. class person {
    5. //定义友元函数,属于全局函数
    6. friend void person_show(person p) {
    7. cout << p.name << endl << p.age << endl;
    8. }
    9. public:
    10. person(T1 name, T2 age);
    11. private:
    12. T1 name;
    13. T2 age;
    14. };
    15. template<class T1, class T2>
    16. person::person(T1 name, T2 age) {
    17. this->name = name;
    18. this->age = age;
    19. }
    20. int main() {
    21. person<> p("小明", 100);
    22. person_show(p);
    23. return 0;
    24. }

     

     类外实现

    代码:

    1. #include
    2. using namespace std;
    3. //先声明有person类,防止报错
    4. template<class T1, class T2>
    5. class person;
    6. //写在person定义之前,防止找不到报错
    7. template<class T1, class T2>
    8. void person_show(person &p) {
    9. cout << p.name << endl << p.age << endl;
    10. }
    11. template<class T1,class T2>
    12. class person {
    13. //friend void person_show(person& p);
    14. //错误写法,因为person_show是模板函数,此时T1是模板没的T1
    15. friend void person_show<>(person& p);
    16. //正确写法,加入模板列表声明是模板
    17. public:
    18. person(T1 name, T2 age);
    19. private:
    20. T1 name;
    21. T2 age;
    22. };
    23. template<class T1, class T2>
    24. person::person(T1 name, T2 age) {
    25. this->name = name;
    26. this->age = age;
    27. }
    28. int main() {
    29. personint> p("猪八戒", 100);
    30. person_show(p);
    31. return 0;
    32. }

    总结:建议全局函数做类内实现,用法简单,而且编译器可以直接识别 

    类模板案例

    案例描述: 实现一个通用的数组类,要求如下
    。可以对内置数据类型以及自定义数据类型的数据进行存储
    。将数组中的数据存储到堆区
    。构造函数中可以传入数组的容量
    。提供对应的拷贝构造函数以及operator=防止浅拷贝问题。

    。提供尾插法和尾删法对数组中的数据进行增加和删除。

    。可以通过下标的方式访问数组中的元素。

    。可以获取数组中当前元素个数和数组的容量

    代码 

    myArray.hpp

    1. #pragma
    2. #include
    3. using namespace std;
    4. template<class T>
    5. class myArray {
    6. public:
    7. myArray(int capacity);
    8. myArray(myArray& p);
    9. ~myArray();
    10. //重载=,防止浅拷贝
    11. myArray& operator=(const myArray& p) {
    12. if (this->Array != NULL) {
    13. delete[] this->Array;
    14. this->Array = NULL;
    15. }
    16. this->capacity = p.capacity;
    17. this->size = p.size;
    18. this->Array = p.Array;
    19. for (int i = 0; i < this->size; ++i) {
    20. this->Array[i] = p.Array[i];
    21. }
    22. }
    23. T& operator[](int index) {
    24. return this->Array[index];
    25. }
    26. void push_back(const T& val);
    27. void pop_back();
    28. int getsize() {
    29. return this->size;
    30. }
    31. int getcapacity() {
    32. return this->capacity;
    33. }
    34. private:
    35. T* Array;
    36. int size;
    37. int capacity;
    38. };
    39. //拷贝构造函数,注意要用深拷贝
    40. template<class T>
    41. myArray::myArray(myArray& p) {
    42. this->size = p.size;
    43. this->capacity = p.capacity;
    44. this->Array = new T[this->capacity];
    45. for (int i = 0; i < this->size; ++i) {
    46. this->Array[i] = p.Array[i];
    47. }
    48. }
    49. //有参构造函数
    50. template<class T>
    51. myArray::myArray(int capacity) {
    52. this->capacity = capacity;
    53. this->Array = new T[this->capacity];
    54. this->size = 0;
    55. }
    56. //析构函数
    57. template<class T>
    58. myArray::~myArray() {
    59. if (this->Array != NULL) {
    60. delete[] this->Array;
    61. this->Array = NULL;
    62. }
    63. }
    64. template<class T>
    65. void myArray :: push_back(const T& val) {
    66. if (this->size == this->capacity) {
    67. return;
    68. }
    69. this->Array[this->size++] = val;
    70. }
    71. template<class T>
    72. void myArray ::pop_back() {
    73. if (this->size == 0) {
    74. return;
    75. }
    76. --this->size;
    77. }

    main.cpp

    1. #include "myArray.hpp"
    2. class person {
    3. public:
    4. string name;
    5. int age;
    6. person() {}
    7. person(string name, int age) {
    8. this->name = name;
    9. this->age = age;
    10. }
    11. };
    12. void myprintf(myArray& p) {
    13. for (int i = 0; i < p.getsize(); ++i) {
    14. cout << p[i].name << " " << p[i].age << endl;
    15. }
    16. }
    17. void fun() {
    18. person p1("小明", 100);
    19. person p2("小壮", 130);
    20. person p3("小聪", 103);
    21. person p4("小红", 1560);
    22. person p5("小懒", 190);
    23. //调用默认构造函数
    24. myArray p(10);
    25. p.push_back(p1);
    26. p.push_back(p2);
    27. p.push_back(p3);
    28. p.push_back(p4);
    29. p.push_back(p5);
    30. cout << p.getsize() << endl;
    31. cout << p.getcapacity() << endl;
    32. myprintf(p);
    33. p.pop_back();
    34. cout << "弹出最后一个" << endl;
    35. myprintf(p);
    36. cout << "判断拷贝构造函数" << endl;
    37. myArray p6(p);
    38. myprintf(p6);
    39. cout << "判断operator=" << endl;
    40. myArray p7(0);
    41. p7 = p;
    42. myprintf(p7);
    43. }
    44. int main() {
    45. fun();
    46. return 0;
    47. }

  • 相关阅读:
    目标检测——ADAS实战
    CSS之margin塌陷
    【目标检测】【AnchorFree】【P-PicoDet】 移动端的目标检测器
    服务器cpu一直处于100%解决思路
    语义熵:QoE-Aware Resource Allocation for Semantic Communication Networks
    Java高级——内存分配机制
    Linux日志超详细使用操作
    数据结构——排序
    OSPFv2 和 OSPFv3 的区别?
    伯俊ERP与金蝶云星空对接集成表头表体组合查询打通应收单新增
  • 原文地址:https://blog.csdn.net/m0_73731708/article/details/132892775