• C++模板 —— 万字带你了解C++模板(蓝桥杯算法比赛必备知识STL基础)


    目录

     

    模板的概念

    函数模板 

             函数模板的作用:

    函数模板的语法:

    解释:

    示例:

    总结:

    函数模板注意事项

    注意事项:

    示例:

    总结:

     函数模板案例

    案例描述:

    示例:

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

    总结:

    普通函数与函数模板的调用规则

    调用规则如下:

    示例:

    模板的局限性(函数模板的第二种实现)

    类模板

    类模板基础语法 

    语法:

    解释:

    示例:

    类模板与函数模板的区别

    问题引出(一)

    问题引出(二)

    类模板中成员函数调用时机

    添加测试代码:

    总结:

    类模板对象做函数参数

    类模板与继承

    示例:

    类模板成员函数类外实现

    类模板与友元


    模板的概念

    模板就是我们建立的通用的模具,用来提高代码的复用性。

    生活中最经典的就是证件照模板了

     上面模板的特点:

    • 模板不可以直接使用,它只是一个框架(不可能拿一个照片模板交给领导或者老师吧,得有你自己的“信息”)

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


    函数模板 

    •  C++的另一种编程思想——泛型编程,利用的主要就是模板
    • C++提供两种模板机制:函数模板类模板

    函数模板的作用:

            建立一个通用函数,其函数返回值类型和形参类型不具制定,用一个虚拟类型代替。

    函数模板的语法:

    1. //template告诉编译器,要开始写模板了
    2. //typename或者class都可以
    3. //T是虚拟类型
    4. template<typename T>
    5. 函数声明或定义

    解释:

    template --- 声明创建模板

    typename --- 表面其后面的符号是一种数据类型,可以用class代替

    T --- 通用的数据类型,名称可以替换,通常为大写字母

    示例:

    1. //交换整型函数
    2. void swapInt(int &a,int &b){
    3. int temp = 0;
    4. a = b;
    5. b = temp;
    6. }
    7. //交换浮点型函数
    8. void swapDouble(double &a,double &b){
    9. double temp = a;
    10. a = b;
    11. b = temp;
    12. }
    13. //利用函数模板提供通用的交换函数
    14. template
    15. void mySwap(T &A,T &b)
    16. {
    17. T temp = a;
    18. a = b;
    19. b = temp;
    20. }
    21. void test01()
    22. {
    23. int a = 10;
    24. int b = 20;
    25. //swapInt(a, b);
    26. //利用模板实现交换
    27. //1、自动类型推导
    28. mySwap(a, b);
    29. //2、显示指定类型
    30. mySwap<int>(a, b);
    31. cout << "a = " << a << endl;
    32. cout << "b = " << b << endl;
    33. }
    34. int main() {
    35. test01();
    36. system("pause");
    37. return 0;
    38. }

    总结:

    • 函数模板利用关键字 template

    • 使用函数模板有两种方式:自动类型推导、显示指定类型

    • 模板的目的是为了提高复用性,将类型参数化


    函数模板注意事项

    注意事项:

    • 自动类型推导,必须推导出一致的数据类型T,才可以使用

    • 模板必须要确定出T的数据类型,才可以使用(函数中没有用到T的话,就只能自己写了)

    示例:

    1. //利用模板提供通用的交换函数
    2. template<class T> //typename可以替换成class
    3. void mySwap(T& a, T& b)
    4. {
    5. T temp = a;
    6. a = b;
    7. b = temp;
    8. }
    9. // 1、自动类型推导,必须推导出一致的数据类型T,才可以使用
    10. void test01()
    11. {
    12. int a = 10;
    13. int b = 20;
    14. char c = 'c';
    15. mySwap(a, b); // 正确,可以推导出一致的T
    16. //mySwap(a, c); // 错误,推导不出一致的T类型
    17. }
    18. // 2、模板必须要确定出T的数据类型,才可以使用
    19. template<class T>
    20. void func()
    21. {
    22. cout << "func 调用" << endl;
    23. }
    24. void test02()
    25. {
    26. //因为函数体中没有用到T,所以自动类型推导也不知道怎么推导了
    27. //func(); //错误,模板不能独立使用,必须确定出T的类型
    28. func<int>(); //利用显示指定类型的方式,给T一个类型,才可以使用该模板
    29. }
    30. int main() {
    31. test01();
    32. test02();
    33. system("pause");
    34. return 0;
    35. }

    总结:

    • 使用模板时必须确定出通用数据类型T,并且能够推导出一致的类型


     函数模板案例

    案例描述:

    • 利用函数模板封装一个排序的函数,可以对不同数据类型数组进行排序

    • 排序规则从大到小,排序算法为选择排序

    • 分别利用char数组int数组进行测试

    示例:

    1. //交换函数模板
    2. template<class T>
    3. void mySwap(T& a, T& b)
    4. {
    5. T temp = a;
    6. a = b;
    7. b = temp;
    8. }
    9. //排序算法
    10. template<class T>
    11. void mySort(T arr[] , int len) {
    12. for (int i = 0; i < len ; i++) {
    13. int max = i; //先随便找一个设为最大值
    14. for (int j = i + 1; j < len; j++)
    15. {
    16. if (arr[max] < arr[j])
    17. //更新下标
    18. max = j;
    19. }
    20. if (max != i)
    21. {
    22. //交换max和i下标的元素
    23. mySwap(arr[max],arr[i]);
    24. }
    25. }
    26. }
    27. //提供打印数组模板
    28. template<class T>
    29. void printArray(T arr[], int len)
    30. {
    31. for (int i = 0; i < len; i++)
    32. {
    33. cout << arr[i] << " ";
    34. }
    35. cout << endl;
    36. }
    37. void test01()
    38. {
    39. //测试char数组
    40. char charArr[] = "bcfac";
    41. int num = sizeof(charArr) / sizeof(char);
    42. mySort(charArr, num);
    43. printArray(charArr , num);
    44. }
    45. void test02()
    46. {
    47. //测试int数组
    48. int intArr[] = { 7, 5, 8, 1, 3, 9, 2, 4, 6 };
    49. int num = sizeof(intArr) / sizeof(int);
    50. mySort(intArr, num);
    51. printArray(intArr, num);
    52. }
    53. int main() {
    54. test01();
    55. test02();
    56. system("pause");
    57. return 0;
    58. }

    如果上面这些程序你都实现了,那么你一定会对模板深有体会,模板大大提高了代码复用,需要熟练掌握


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

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

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

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

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

    1. //普通函数
    2. int myAdd01(int a, int b)
    3. {
    4. return a + b;
    5. }
    6. //函数模板
    7. template<class T>
    8. T myAdd02(T a, T b)
    9. {
    10. return a + b;
    11. }
    12. //使用函数模板时,如果用自动类型推导,不会发生自动类型转换,即隐式类型转换
    13. void test01()
    14. {
    15. int a = 10;
    16. int b = 20;
    17. char c = 'c';
    18. cout << myAdd01(a, c) << endl; //正确,将char类型的'c'隐式转换为int类型 'c' 对应 ASCII码 99
    19. //myAdd02(a, c); // 报错,使用自动类型推导时,不会发生隐式类型转换
    20. myAdd02<int>(a, c); //正确,如果用显示指定类型,可以发生隐式类型转换
    21. }
    22. int main() {
    23. test01();
    24. system("pause");
    25. return 0;
    26. }

    总结:

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


    普通函数与函数模板的调用规则

    调用规则如下:

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

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

    3. 函数模板也可以发生重载

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

    简单来说就是,函数模板里面,同名普通函数(只有声明也成立,只是会报错🤣)优先调用,同时函数模板也是可以重载的。

    示例:

    1. //普通函数与函数模板调用规则
    2. void myPrint(int a, int b)
    3. {
    4. cout << "调用的普通函数" << endl;
    5. }
    6. template<typename T>
    7. void myPrint(T a, T b)
    8. {
    9. cout << "调用的模板" << endl;
    10. //函数模板重载
    11. template<typename T>
    12. void myPrint(T a, T b, T c)
    13. {
    14. cout << "调用重载的模板" << endl;
    15. }
    16. void test01()
    17. {
    18. //1、如果函数模板和普通函数都可以实现,优先调用普通函数
    19. // 注意 如果告诉编译器 普通函数是有的,但只是声明没有实现,或者不在当前文件内实现,就会报错找不到
    20. int a = 10;
    21. int b = 20;
    22. myPrint(a, b); //调用普通函数
    23. //2、可以通过空模板参数列表来强制调用函数模板
    24. myPrint<>(a, b); //调用函数模板
    25. //3、函数模板也可以发生重载
    26. int c = 30;
    27. myPrint(a, b, c); //调用重载的函数模板
    28. //4、 如果函数模板可以产生更好的匹配,优先调用函数模板
    29. char c1 = 'a';
    30. char c2 = 'b';
    31. myPrint(c1, c2); //调用函数模板
    32. }
    33. int main() {
    34. test01();
    35. system("pause");
    36. return 0;
    37. }

    其实我们提供函数模板了,就尽量不要再写普通函数了,不然会出现二义性的。


    模板的局限性(函数模板的第二种实现)

    函数模板并不是万能的,有些特定数据类型,需要用具体化方式做特殊实现。

    只有C++内置的数据类型可以直接使用前面学到的函数模板,自定义数据类型往往不能实现。

    1. #include
    2. using namespace std;
    3. #include
    4. class Person
    5. {
    6. public:
    7. Person(string name, int age)
    8. {
    9. this->m_Name = name;
    10. this->m_Age = age;
    11. }
    12. string m_Name;
    13. int m_Age;
    14. };
    15. //普通函数模板
    16. template<class T>
    17. bool myCompare(T& a, T& b)
    18. {
    19. if (a == b)
    20. {
    21. return true;
    22. }
    23. else
    24. {
    25. return false;
    26. }
    27. }
    28. //具体化,显示具体化的原型和定意思以template<>开头,并通过名称来指出类型
    29. //具体化优先于常规模板
    30. template<> bool myCompare(Person &p1, Person &p2)
    31. {
    32. if ( p1.m_Name == p2.m_Name && p1.m_Age == p2.m_Age)
    33. {
    34. return true;
    35. }
    36. else
    37. {
    38. return false;
    39. }
    40. }
    41. void test01()
    42. {
    43. int a = 10;
    44. int b = 20;
    45. //内置数据类型可以直接使用通用的函数模板
    46. bool ret = myCompare(a, b);
    47. if (ret)
    48. {
    49. cout << "a == b " << endl;
    50. }
    51. else
    52. {
    53. cout << "a != b " << endl;
    54. }
    55. }
    56. void test02()
    57. {
    58. Person p1("Tom", 10);
    59. Person p2("Tom", 10);
    60. //自定义数据类型,不会调用普通的函数模板
    61. //可以创建具体化的Person数据类型的模板,用于特殊处理这个类型
    62. bool ret = myCompare(p1, p2);
    63. if (ret)
    64. {
    65. cout << "p1 == p2 " << endl;
    66. }
    67. else
    68. {
    69. cout << "p1 != p2 " << endl;
    70. }
    71. }
    72. int main() {
    73. test01();
    74. test02();
    75. system("pause");
    76. return 0;
    77. }

    如上,利用具体化的模板,可以解决自定义类型的通用化。


    类模板

    类模板和函数模板的区别在于模板声明下面加的是类还是函数。

    类模板基础语法 

    语法:

    template

    解释:

    template --- 声明创建模板

    typename --- 表面其后面的符号是一种数据类型,可以用class代替

    T --- 通用的数据类型,名称可以替换,通常为大写字母

    示例:

    1. //类模板
    2. // 流程:
    3. //class后面紧跟着的就是通用数据类型(如果成员中需要两个,就用逗号分隔写两个)
    4. //然后test01()中,在传入的时候,用模板参数列表给这里的两个..Type传值
    5. //后面的两个数据"猴哥",500;是给有参构造里面的name和age传值
    6. //最后调用showPerson输出
    7. template<class NameType,class AgeType>
    8. class Person
    9. {
    10. public:
    11. //写构造函数赋初值
    12. Person(NameType name, AgeType age)
    13. {
    14. this->m_Age = age;
    15. this->m_Name = name;
    16. }
    17. void showPerson()
    18. {
    19. cout << "name:" << this->m_Name << "age:" << this->m_Age << endl;
    20. }
    21. //两个类型不一样,需要两个模板数据类型
    22. NameType m_Name;
    23. AgeType m_Age;
    24. };
    25. void test01()
    26. { //将类型参数化
    27. Personint> p("猴哥",500);//后两个数据是实参,传给有参构造
    28. p.showPerson();
    29. }
    30. int main()
    31. {
    32. test01();
    33. system("pause");
    34. return 0;
    35. }

    类模板与函数模板的区别

    问题引出(一)

    现在我们对上节示例稍微进行一下改动,把我们的显示类型声明给去掉。报错

    所以类模板是没有自动类型推导的。

    问题引出(二)

    看这里,我没有声明传入int型,但是也能实现,你猜是为什么?

     

     原来,我在前面加了点“小料”。

    我在声明模板的时候直接把AgeTyoe = int了,后面传进去的时候就不需要说是int型了。

    所以这就是类模板在模板参数列表中可以有默认参数


    类模板中成员函数调用时机

    1. //类模板中成员函数创建时机
    2. //类模板中成员函数在调用时才去创建
    3. class Person1 {
    4. public:
    5. void showPerson1() {
    6. cout << "showPerson1函数调用" << endl;
    7. }
    8. };
    9. class Person2 {
    10. public:
    11. void showPerson2() {
    12. cout << "showPerson2函数调用" << endl;
    13. }
    14. };
    15. //类模板
    16. template<class T>
    17. class MyClass
    18. {
    19. public:
    20. T obj;
    21. //类模板中的成员函数
    22. void func1() {
    23. obj.showPerson1();
    24. }
    25. void func2() {
    26. obj.showPerson2();
    27. }
    28. };
    29. int main()
    30. {
    31. system("pause");
    32. return 0;
    33. }

    这里没有调用类模板里面的成员函数,运行发现成功了。

    这两个成员函数fuc1,func2只要不调用就不会创建,因为编译器不知道类模板里面的obj是什么类型的。

    添加测试代码:

    1. void test01() {
    2. MyClassm;
    3. m.func1();
    4. m.func2();
    5. }

    调用测试运行结果:

    我们调用成员函数func1,func2,就可以确定obj就是Person1的数据类型,Person1没有showPerson2成员函数,所以会报错。

    总结:

            类模板中的成员函数并不是一开始就创建的,在调用时才去创建


    类模板对象做函数参数

    一共有三种传入方式:

    1. 指定传入的类型 --- 直接显示对象的数据类型

    2. 参数模板化 --- 将对象中的参数变为模板进行传递

    3. 整个类模板化 --- 将这个对象类型 模板化进行传递

    1. #include
    2. //类模板
    3. template<class NameType, class AgeType = int>
    4. class Person
    5. {
    6. public:
    7. Person(NameType name, AgeType age)
    8. {
    9. this->mName = name;
    10. this->mAge = age;
    11. }
    12. void showPerson()
    13. {
    14. cout << "name: " << this->mName << " age: " << this->mAge << endl;
    15. }
    16. public:
    17. NameType mName;
    18. AgeType mAge;
    19. };

    1. //1、指定传入的类型
    2. void printPerson1(Personint> &p)
    3. {
    4. p.showPerson();
    5. }
    6. void test01()
    7. {
    8. Person int >p("孙悟空", 100);
    9. printPerson1(p);
    10. }
    1. //2、参数模板化
    2. template <class T1, class T2>
    3. void printPerson2(Person&p)
    4. {
    5. p.showPerson();
    6. cout << "T1的类型为: " << typeid(T1).name() << endl;
    7. cout << "T2的类型为: " << typeid(T2).name() << endl;
    8. }
    9. void test02()
    10. {
    11. Person int >p("猪八戒", 90);
    12. printPerson2(p);
    13. }
    1. //3、整个类模板化
    2. template<class T>
    3. void printPerson3(T & p)
    4. {
    5. cout << "T的类型为: " << typeid(T).name() << endl;
    6. p.showPerson();
    7. }
    8. void test03()
    9. {
    10. Person int >p("唐僧", 30);
    11. printPerson3(p);
    12. }

    类模板与继承

    当类模板碰到继承时,需要注意一下几点:

    • 当子类继承的父类是一个类模板时,子类在声明的时候,要指定出父类中T的类型

    • 如果不指定,编译器无法给子类分配内存

    • 如果想灵活指定出父类中T的类型,子类也需变为类模板

    示例:

    1. template<class T>
    2. class Base
    3. {
    4. T m;
    5. };
    6. //class Son:public Base //错误,c++编译需要给子类分配内存,必须知道父类中T的类型才可以向下继承
    7. class Son :public Base<int> //必须指定一个类型
    8. {
    9. };
    10. void test01()
    11. {
    12. Son c;
    13. }
    14. //类模板继承类模板 ,可以用T2指定父类中的T类型
    15. template<class T1, class T2>//想要灵活的指定父类中的T类型,子类也需要变成类模板
    16. class Son2 :public Base
    17. {
    18. public:
    19. Son2()
    20. {
    21. cout << typeid(T1).name() << endl;
    22. cout << typeid(T2).name() << endl;
    23. }
    24. };
    25. void test02()
    26. {
    27. Son2<int, char> child1;
    28. }
    29. int main() {
    30. test01();
    31. test02();
    32. system("pause");
    33. return 0;
    34. }

    类模板成员函数类外实现

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

    1. #include
    2. template<class T1, class T2>
    3. class Person {
    4. public:
    5. //成员函数类内声明
    6. Person(T1 name, T2 age);
    7. void showPerson();
    8. public:
    9. T1 m_Name;
    10. T2 m_Age;
    11. };
    12. //构造函数 类外实现
    13. template<class T1, class T2>
    14. Person::Person(T1 name, T2 age) {
    15. this->m_Name = name;
    16. this->m_Age = age;
    17. }
    18. //成员函数 类外实现
    19. template<class T1, class T2>
    20. void Person::showPerson() {
    21. cout << "姓名: " << this->m_Name << " 年龄:" << this->m_Age << endl;
    22. }
    23. void test01()
    24. {
    25. Personint> p("Tom", 20);
    26. p.showPerson();
    27. }
    28. int main() {
    29. test01();
    30. system("pause");
    31. return 0;
    32. }

    类模板与友元

    全局函数类内实现 - 直接在类内声明友元即可

    全局函数类外实现 - 需要提前让编译器知道全局函数的存在

    1. #include
    2. //2、全局函数配合友元 类外实现 - 先做函数模板声明,下方在做函数模板定义,在做友元
    3. template<class T1, class T2> class Person;
    4. //如果声明了函数模板,可以将实现写到后面,否则需要将实现体写到类的前面让编译器提前看到
    5. //template void printPerson2(Person & p);
    6. template<class T1, class T2>
    7. void printPerson2(Person & p)
    8. {
    9. cout << "类外实现 ---- 姓名: " << p.m_Name << " 年龄:" << p.m_Age << endl;
    10. }
    11. template<class T1, class T2>
    12. class Person
    13. {
    14. //1、全局函数配合友元 类内实现
    15. friend void printPerson(Person & p)
    16. {
    17. cout << "姓名: " << p.m_Name << " 年龄:" << p.m_Age << endl;
    18. }
    19. //全局函数配合友元 类外实现
    20. friend void printPerson2<>(Person & p);
    21. public:
    22. Person(T1 name, T2 age)
    23. {
    24. this->m_Name = name;
    25. this->m_Age = age;
    26. }
    27. private:
    28. T1 m_Name;
    29. T2 m_Age;
    30. };
    31. //1、全局函数在类内实现
    32. void test01()
    33. {
    34. Person int >p("Tom", 20);
    35. printPerson(p);
    36. }
    37. //2、全局函数在类外实现
    38. void test02()
    39. {
    40. Person int >p("Jerry", 30);
    41. printPerson2(p);
    42. }
    43. int main() {
    44. //test01();
    45. test02();
    46. system("pause");
    47. return 0;
    48. }

  • 相关阅读:
    Kotlin 使用@BindingAdapter编译出错
    mac配置环境变量总结
    CockroachDB架构-分布式层
    【leetcode】最近最少使用缓存
    4、QtCharts 做心电图
    向量数据库是如何检索的?基于 Feder 的 HNSW 可视化实现
    SQL基础理论篇(六):多表的连接方式
    Springboot踩坑-request body重复读问题
    索引特性之存列值优化sum avg
    Java:Java和C有什么区别?
  • 原文地址:https://blog.csdn.net/m0_62853489/article/details/127668750