• C++模板


    fcf6292a67ec49bfb426abf42aa06b77.jpeg

    目录

    ​一、认识模板

    1.什么是模板

    2.模板的分类

    二、函数模板

    1.泛型和函数模板

    2.函数模板的格式

    三、类模板

    四、实例化

    1.隐式实例化

    2.显式实例化

    3.隐式类型转换

    4.模板参数的匹配原则


    一、认识模板

    1.什么是模板

    模板(Template)指C++程序设计设计语言中采用类型作为参数的程序设计,支持通用程序设计。C++ 的标准库提供许多有用的函数大多结合了模板的观念,如STL以及IO Stream。模板是C++支持参数化多态的工具,使用模板可以使用户为类或者函数声明一种一般模式,使得类中的某些数据成员或者成员函数的参数、返回值取得任意类型。

    2.模板的分类

    模板分为函数模板和类模板函数模板针对参数类型不同的函数;类模板针对数据成员和成员函数类型不同的类。

    模板支持与了类型无关的代码的编写。

    二、函数模板

    1.泛型和函数模板

    编写与类型无关的通用代码,是代码复用的一种手段。

    比如说,我们定义一个交换intl类型元素值的函数。

    1. void swap(int& a, int& b)
    2. {
    3. int temp = 0;
    4. temp = a;
    5. a = b;
    6. b = temp;
    7. }

    这个函数可以交换int类型的变量值,但是只能操作int类型。如果我们需要交换double类型的变量,按照C++的逻辑就需要再次定义一个新的重载函数。

    1. void swap(double& a, double& b)
    2. {
    3. double temp = 0;
    4. temp = a;
    5. a = b;
    6. b = temp;
    7. }

    实现所有类型变量的交换需要写太多逻辑相同的函数,过于冗余。那么,如果我们不指定这个参数的类型,用一个泛型去替代这些类型,让编译器去推导这个参数的具体类型,那么我们就只需要一个函数定义了。

    这样的泛型函数就叫做模板,内部变量的类型叫做泛型。

    2.函数模板的格式

    1. template<typename T1, typename T2,......,typename Tn>//typename也可以换成class
    2. 返回值类型 函数名 ( 参数列表 )
    3. {}
    4. template<typename T1>//用template关键字定义泛型
    5. void swap(T1& a, T1& b)//内部变量也用泛型
    6. {
    7. T1 temp = 0;
    8. temp = a;
    9. a = b;
    10. b = temp;
    11. }

    可以定义多个泛型T1、T2、T3等等,一个泛型只能推导出一个具体类型。T1可以推导为int类型,但是不能又推导为int,又推导为其他类型。

    三、类模板

    我们定义一个没有完全写好的栈

    1. #define TYPE int
    2. class Stack
    3. {
    4. public:
    5. Stack()
    6. :_a((TYPE*)malloc(sizeof(TYPE)* 4))
    7. ,_size(0)
    8. , _volume(0)
    9. {}
    10. void push(TYPE data)
    11. {
    12. _a[_size++] = data;
    13. }
    14. private:
    15. TYPE* _a;
    16. int _size;
    17. int _volume;
    18. };

    虽然通过改变TYPE对应的类型可以改变存储数据的类型,但是如果我们想同时使用一个储存int类型元素的栈和一个储存double类型元素的栈,那么还需要定义另一个栈的类,依旧冗余。所以类似于函数,类也可以使用泛型。这种使用泛型的类也叫做类模板,与函数模板相似。

    1. template<typename T1>
    2. class Stack
    3. {
    4. public:
    5. Stack()
    6. :_a((T1*)malloc(sizeof(T1)* 4))
    7. ,_size(0)
    8. , _volume(0)
    9. {}
    10. void push(T1 data)
    11. {
    12. _a[_size++] = data;
    13. }
    14. private:
    15. T1* _a;
    16. int _size;
    17. int _volume;
    18. };

    四、实例化

    1.隐式实例化

    函数模板是一个蓝图,它本身并不是函数,是编译器用使用方式产生特定具体类型函数和类的声明,相当于建筑的图纸,只有使用到这个函数和类时才会定义。所以其实模板就是将本来应该我们做的重复的事情交给了编译器。

    简单说就是本来我们应该多去写的Swap函数和Stack类的重复工作去给编译器做了。

    这是我们之前的函数模板

    1. template<typename T1>//用template关键字定义泛型
    2. void swap(T1& a, T1& b)//内部变量也用泛型
    3. {
    4. T1 temp = 0;
    5. temp = a;
    6. a = b;
    7. b = temp;
    8. }

    对于不同类型的变量进行swap,当编译器遇到符合模板但没有定义的函数,编译器就会自己生成一个对应的重载函数,如果这个函数之前定义过那就使用之前定义过的函数。

    1. template<typename T1>
    2. void swap(T1& a, T1& b)
    3. {
    4. T1 temp = 0;
    5. temp = a;
    6. a = b;
    7. b = temp;
    8. }
    9. int main()
    10. {
    11. int a = 1;
    12. int b = 2;
    13. swap(a,b);
    14. //交换两个int变量a和b,编译器会根据模板定义一个int类型的交换函数,如下:
    15. //void swap(int& a, int& b)
    16. double c = 1.0;
    17. double d = 2.0;
    18. swap(c,d);
    19. //交换两个double变量c和d,编译器会根据模板再次定义一个double类型的交换函数,如下:
    20. //void swap(double& a, double& b)
    21. int e = 1;
    22. int f = 2;
    23. swap(e,f);
    24. //再次交换两个int变量e和f,编译器会继续使用之前定义的int类型交换函数
    25. //void swap(int& a, int& b)
    26. return 0;
    27. }

    最后的结果还是定义了很多不同类型的重载函数,与我们自己定义许多不同类型的函数的结果是一样的。但是这时不再需要程序员去一个一个类型定义函数,大大简化了程序的代码量。

    模板对于类也是一样的效果,也会自己定义许多不同类型的类去使用,但是类只支持显式实例化。

    2.显式实例化

    我们不光可以让计算机自己推断类型,而且也可以自己指定生成哪一种类型参数的函数和类。

    1. template<typename T1>//定义泛型T1,在这里也表示下面的函数中会用到T1
    2. void Swap(T1& a, T1& b)
    3. {
    4. T1 temp = 0;
    5. temp = a;
    6. a = b;
    7. b = temp;
    8. }
    9. template<typename T1, typename T2>//定义泛型T2,T1在前面已经定义了表示会用到T1和T2
    10. void print(T1& a, T2& b)//注意,这个T1经过推导只能对应一个类型,不能在上面的函数中是int下面的是double
    11. {
    12. cout << a << ' ' << b << endl;
    13. }
    14. int main()
    15. {
    16. int a = 1;
    17. int b = 2;
    18. double c = 1.0;
    19. Swap<int>(a, b);//在函数后面加<类型>就可以指定定义和使用对应类型的函数
    20. print<int, double>(a, c);//<>内部多个类型用,隔开对应上面的template
    21. cout << a << ' ' << b << endl;
    22. return 0;
    23. }
    24. //输出:
    25. //2 1
    26. //2 1

    之前说过,类只支持显式实例化,不能通过编译器推断。类模板也是一个声明或者说是一个蓝图,只有使用了这个类型的函数或者类才会被定义。

    1. template<typename T>
    2. class Stack
    3. {
    4. public:
    5. Stack()
    6. :_a((T*)malloc(sizeof(T)* 4))
    7. , _size(0)
    8. , _volume(0)
    9. {}
    10. void push(T data)
    11. {
    12. _a[_size++] = data;
    13. }
    14. private:
    15. T* _a;
    16. int _size;
    17. int _volume;
    18. };
    19. int main()
    20. {
    21. Stack<int> s1;//定义一个储存int类型的类,类型为Stack
    22. Stack<double> s2;//定义一个储存double类型的类,类型为Stack
    23. s1.push(1);//int类型栈可以插入数据
    24. s2.push(1.0);//double类型栈也可以插入数据
    25. return 0;
    26. }

    3.隐式类型转换

    我们可以清楚地看到T会推导出两个变量类型int和double,此时T还是会推导为int,但是b参数会隐式转化为int,我们知道这个转换的临时参数是有常性的,所以需要用const修饰。

    1. template<typename T>
    2. T Add(const T& left, const T& right)
    3. {
    4. return left + right;
    5. }
    6. int main()
    7. {
    8. int a = 10;
    9. double b = 20.2;
    10. cout << Add<int>(a, b) << endl;//调用int类型Add函数,b生成一个临时int常量传递给函数
    11. return 0;
    12. }

    在不能完成隐式类型转换时,程序就会报错。

    4.模板参数的匹配原则

    • 一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这个非模板函数
    • 对于非模板函数和同名函数模板,如果其他条件都相同,在调动时会优先调用非模板函数而不会从该模 板产生出一个实例。如果模板可以产生一个具有更好匹配的函数, 那么将选择模板
    • 模板函数不允许自动类型转换,但普通函数可以进行自动类型转换

  • 相关阅读:
    VGW在 Windows 平台上局域网就绪的旁路由器程序
    第三十一章 使用 CSP 进行基于标签的开发 - 转义和引用HTTP输出
    代码随想录算法训练营Day53|动态规划12
    如何在网页中实现动画效果
    Vue根据屏幕分辨率计算div可以显示的数量,dom渲染在v-if之后造成的复杂处理
    [附源码]java毕业设计“佳倍清家政”服务管理系统
    .NET周刊【8月第4期 2023-08-27】
    891. 子序列宽度之和 : 逐步分析如何求解对展开式的最终贡献
    Java项目:基于JavaWeb的教务管理系统的设计与实现
    通过空间占用和执行计划了解SQL Server的行存储索引
  • 原文地址:https://blog.csdn.net/qq_65285898/article/details/127560510