• C++——模板


    1.泛型模板

    编写与类型无关的同用代码,是代码复用的一种手段,模板是泛型编程的基础

    2.函数模板

    2.1概念

    函数模板代表了一个函数家族,该函数模板与类型无关,在使用时被参数化,根据实参类型产生函数的特定类型版本

    2.2函数模板格式

    template<typename T1,typename T2,……,typename Tn>
    
    返回值类型 函数名(参数列表){}
    
    • 1
    • 2
    • 3
    template<typename T>
    void Swap( T& left, T& right)
    {
        T temp = left;
        left = right;
        right = temp;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    注意:typename是用来定义模板参数关键字,也可以用class

    2.3函数模板的原理

    函数模板的原理是根据参数化的类型,编译器在实例化阶段生成针对具体类型的函数代码。
    当我们调用一个函数模板时,编译器会根据提供的实际参数类型,自动推导出模板参数的具体类型。然后,编译器会实例化函数模板,即根据具体类型生成针对该类型的函数代码。

    函数模板的编译器会根据具体的实际类型进行类型检查,以确保代码的类型安全性。如果参数类型不符合模板定义的约束条件,编译器会报错。
    请添加图片描述

    2.4函数模板的实例化

    用不同类型的参数使用函数模板时,称为函数模板的实例化

    函数参数实例化分为:隐式实例化和显示实例化

    1.隐式实例化:让编译器根据实参推演模板参数的实际类型

    #include
    using namespace std;
    template<class T>
    T Add(const T& left, const T& right)
    {
    	return left + right;
    }
    int main()
    {
    	int a1 = 10, a2 = 20;
    	double d1 = 10.1, d2 = 20.1;
    	cout << Add(a1, a2) << endl;
    	cout << Add(d1, d2) << endl;
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    2.显示实例化:在函数名后的<>中指定模板参数的实际类型

    #include
    using namespace std;
    template<class T>
    T Add(const T& left, const T& right)
    {
    	return left + right;
    }
    int main()
    {
    	int a = 10;
    	double b = 20.0;
    	cout<<Add<int>(a, b)<<endl;
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    如果类型不匹配,编译器会尝试进行隐式类型转换,如果无法转换成功编译将会报错

    2.5模板参数的匹配原则

    1.一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为则这个非模板函数

    #include
    using namespace std;
    // 专门处理int的加法函数
    int Add(int left, int right)
    {
    	return left + right;
    }
    // 通用加法函数
    template<class T>
    T Add(T left, T right)
    {
    	return left + right;
    }
    void Test()
    {
    	int sum1=Add(1, 2); // 与非模板函数匹配,编译器不需要特化
    	int sum2=Add<int>(1, 2); // 调用编译器特化的Add版本
    	cout << sum1 << endl;
    	cout << sum2 << endl;
    }
    int main()
    {
    	Test();
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    2.对于非模板函数和同名函数模板,如果其他条件都相同,在调动时会优先调用非模板函数而不会从该模板产生出一个新的实例。如果模板可以产生一个具有更好匹配的函数,那么将选择模板

    #include
    using namespace std;
    // 专门处理int的加法函数
    int Add(int left, int right)
    {
    	return left + right;
    }
    // 通用加法函数
    template<class T1, class T2>
    T1 Add(T1 left, T2 right)
    {
    	return left + right;
    }
    void Test()
    {
    	cout << Add(1, 2) << endl;// 与非函数模板类型完全匹配,不需要函数模板实例化
    	cout << Add(1, 2.1) << endl; // 模板函数可以生成更加匹配的版本,编译器根据实参生成更加匹配的Add函数
    }
    int main()
    {
    	Test();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    3.模板函数不允许类型转换,但普通函数可以进行自动类型转换。

    3.类模板

    3.1类模板的定义格式

    template<class T1, class T2, ..., class Tn>
    class 类模板名
    {
    	// 类内成员定义
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    // 类模板定义
    template <typename T>
    class MyArray {
    private:
        T* arr;
        int size;
    public:
        MyArray(int s) {
            size = s;
            arr = new T[size];
        }
    
        ~MyArray() {
            delete[] arr;
        }
    
        T& operator[](int index) {
            return arr[index];
        }
    };
    
    // 使用类模板
    int main() {
        MyArray<int> intArray(5);
        for (int i = 0; i < 5; i++) {
            intArray[i] = i;
        }
    
        MyArray<double> doubleArray(3);
        for (int i = 0; i < 3; i++) {
            doubleArray[i] = 0.5 * i;
        }
    
        return 0;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    3.2类模板的实例化

    类模板实例化需要在类模板名字后跟<>,然后将实例化的类型放在<>中即可,类模板名字不是真正的类,而实例化的结果才是真正的类

    //vector类名,vector才是类型
    vector<int> s1;
    vector<double> s2;
    
    • 1
    • 2
    • 3

    4.非类型模板参数

    模板形参分类类型形参与非类型形参

    类型形参:出现在模板参数列表中,跟在class或者typename之类的参数类型名称

    非类型形参:就是用一个常量作为类(函数)模板的一个参数,在类(函数)模板中可将该参数当成常量来使用

    #include
    using namespace std;
    namespace bite
    {
        // 定义一个模板类型的静态数组
        template<class T, size_t N = 10>
        class array
        {
        public:
            T& operator[](size_t index) 
            { return _array[index]; }
            const T& operator[](size_t index) const
            { return _array[index]; }
            size_t size() const
            { return _size; }
            bool empty() const
            { return 0 == _size; }
        private:
            T _array[N];
            size_t _size=N;
        };
    }
    
    int main() {
        bite::array<int, 10> arr; // 创建一个大小为 5 的整数数组
    
        // 对数组赋值
        for (int i = 0; i < arr.size(); i++) {
            arr[i] = i + 1;
        }
    
        // 输出数组元素
        for (int i = 0; i < arr.size(); i++) {
            cout << arr[i] << " ";
        }
    
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    【注意】

    1. 浮点数、类对象以及字符串是不允许作为非类型模板参数的
    2. 非类型的模板参数必须在编译期就能确认结果

    5.模板的特化

    5.1概念

    使用模板可以实现一些与类型无关的代码,但对于一些特殊类型的可能会得到一些错误的结果,需要进行特殊处理。模板的特化分为函数模板特化与类模板特化

    5.2函数模板的特化

    函数模板的特化步骤:

    1. 必须要先有一个基础的函数模板
    2. 关键字template后面接一对空白的尖括号<>
    3. 函数名后跟一对尖括号,尖括号中指定需要特化的类型
    4. 函数形参表:必须要和模板函数的基础参数类型完全相同,如果不同编译器可能会报一些奇怪的错误
    // 函数模板 -- 参数匹配
    template<class T>
    bool Less(T left, T right)
    {
    	return left < right;
    }
    // 对Less函数模板进行特化
    template<>
    bool Less<Date*>(Date* left, Date* right)
    {
        return *left < *right;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    5.3类模板的特化

    5.3.1全特化

    全特化即是将模板参数列表中所有的参数都确定化

    template<class T1, class T2>
    class Data
    {
    public:
        Data() {cout<<"Data" <<endl;}
        private:
        T1 _d1;
        T2 _d2;
    };
    template<>
    class Data<int, char>
    {
    public:
    	Data() {cout<<"Data" <<endl;}
    private:
    	int _d1;
    	char _d2
    };
    void TestVector()
    {
    	Data<int, int> d1;
    	Data<int, char> d2;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    5.3.2偏特化

    任何针对模板参数进一步进行条件限制设计的特化版本

    template<class T1, class T2>
    class Data
    {
    public:
    	Data() {cout<<"Data" <<endl;}
    private:
    	T1 _d1;
    	T2 _d2;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    部分特化

    将模板参数类表中的一部分参数特化

    // 将第二个参数特化为int
    template <class T1>
    class Data<T1, int>
    {
    public:
    	Data() {cout<<"Data" <<endl;}
    private:
    	T1 _d1;
    	int _d2;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    参数更进一步的限制

    偏特化并不仅仅是指特化部分参数,而是针对模板参数更进一步的条件限制所设计出来的一个特化版本

    //两个参数偏特化为指针类型
    template <typename T1, typename T2>
    class Data <T1*, T2*>
    {
    public:
    	Data() {cout<<"Data" <<endl;}
    private:
    	T1 _d1;
    	T2 _d2;
    };
    //两个参数偏特化为引用类型
    template <typename T1, typename T2>
    class Data <T1&, T2&>
    {
    public:
    	Data(const T1& d1, const T2& d2)
    	: _d1(d1)
    	, _d2(d2)
    	{
    		cout<<"Data" <<endl;
    	}
    private:
    	const T1 & _d1;
    	const T2 & _d2;
    };
    void test2 ()
    {
    	Data<double , int> d1; 		// 调用特化的int版本
    	Data<int , double> d2; 		// 调用基础的模板
    	Data<int *, int*> d3; 		// 调用特化的指针版本
    	Data<int&, int&> d4(1, 2);  // 调用特化的引用版本
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    6.模板分离编译

    6.1什么是分离编译

    一个程序由若干个源文件共同实现,而每个源文件单独编译生成目标文件,最后将所有目标文件链接起来形成单一的可执行文件的过程称为分离编译模式

    6.2模板的分离编译的正确写法

    将声明和定义放到一个文件 “xxx.cpp” 里面或者“xxx.h”其实也是可以的。

    //test.h
    #pragma once
    #include
    #include
    using namespace std;
    template<class T>
    T Add(const T& left, const T& right)
    {
    	return left + right;
    }
    
    //test.cpp
    #include"test.h"
    int main()
    {
    	cout << Add(1, 2) << endl;
    	cout << Add(1.0, 2.0) << endl;
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    7.模板的优点和缺点

    7.1优点

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

    7.2缺点

    1. 模板会导致代码膨胀问题,也会导致编译时间变长
    2. 出现模板编译错误时,错误信息非常凌乱,不容易定位错误
  • 相关阅读:
    pinia安装使用
    数据结构简述,时间、空间复杂度,学习网站推荐
    Dapr Workflow构建块的.NET Demo
    【N皇后问题】递归+回溯算法求解N皇后问题
    基于YOLOv5的口罩佩戴检测方法
    Norms and Inner Products
    mongodb 5.0.14 副本集 安装 单机版 记录
    基于遗传算法的红绿灯间隔时间优化(代码完整,数据齐全)
    LeetCode209长度最小子数组
    gridControlExport.ExportToXls(fileName) dev gridcontrol导出excel列宽问题
  • 原文地址:https://blog.csdn.net/weixin_51799303/article/details/133620400