• 模板、函数模板、类模板


    一、模板  

      泛型(Generic Programming)即是指具有在多种数据类型上皆可操作的含意。泛型编程的代表作品 STL 是一种高效、泛型、可交互操作的软件组件
      泛型编程最初诞生于 C++中,目的是为了实现 C++的 STL(标准模板库)。其语言支持机制就是模板(Templates)。模板的精神其实很简单:参数化类型。换句话说, 把一个原本特定于某个类型的算法或类当中的类型信息抽掉,抽出来做成模板参数 T。

      所谓函数模板,实际上是建立一个通用函数,其函数类型和形参类型不具体指定,用一个虚拟的类型来代表。这个通用函数就称为函数模板。

    二、函数模板

    1.语法格式

      template 是语义是模板的意思,尖括号中先写关键字 typename 或是class,后面跟一个类型 T,此类即是虚拟的类型。至于为什么用 T,用的人多了,也就是 T 了。

    2.函数模板的实例

      调用过程是这样的,先将函数模板实在化为函数,然后再发生函数调用。

    #define _CRT_SECURE_NO_WARNINGS
    #include <iostream>
    
    using namespace std;
    
    template<class T>
    void MySwap(T& a, T& b)
    {
        T temp = a;
        a = b;
        b = temp;
    }
    
    int main(void)
    {
        int a = 10;
        int b = 20;
    
        //1.自动类型推导
        cout << "a:" << a << ",b:" << b << endl;//a:10,b:20
        MySwap(a, b);//编译器根据你传的值,进行类型自动推导
        cout << "a:" << a << ",b:" << b << endl;//a:20,b:10
    
        //2.显式的指定类型
        MySwap<int>(a, b);
        cout << "a:" << a << ",b:" << b << endl;//a:10, b:20
    
        double da = 10.01;
        double db = 20.02;
        cout << "da:" << da << ",db:" << db << endl;//da:10.01,db:20.02
        MySwap(da, db);//编译器根据你传的值,进行类型自动推导
        cout << "da:" << da << ",db:" << db << endl;//da:20.02,db:10.01
        MySwap<double>(da, db);
        cout << "da:" << da << ",db:" << db << endl;//da:10.01,db:20.02
    
        return 0;
    }

      函数模板,只适用于函数的参数个数相同而类型不同,且函数体相同的情况。如果个数不同,则不能用函数模板。

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

    • 函数模板不允许自动类型转化
    • 普通函数能够自动进行类型转化

    4.函数模板和普通函数在一起调用规则

    • 函数模板可以像普通函数那样被重载;
    • C++编译器优先考虑普通函数;
    • 如果函数模板可以产生一个更好的匹配,那么选择匹配;
    • 可以通过空模板实参列表的语法限定编译器只能通过模板匹配。

    5.编译器对模板机制剖析

    编译器编译原理

    总结: 

    (1)编译器并不是把函数模板处理成能够处理任意类的函数;

    (2)编译器从函数模板通过具体类型产生不同的函数;

    (3)编译器会对函数模板进行两次编译,在声明的地方对模板代码本身进行编译,在调用的地方对参数替换后的代码进行编译。

    函数模板案例——char和int类型数组排序

    #define _CRT_SECURE_NO_WARNINGS
    #include <iostream>
    using namespace std;
    
    //对char类型和int类型数组进行排序
    
    //打印函数
    template<class T>
    void PrintArray(T* arr, int len)
    {
        for (int i = 0;i < len;i++)
        {
            cout << arr[i] << " ";
        }
        cout << endl;
    }
    
    //排序
    template<class T>
    void MySort(T* arr, int len)
    {
        for (int i = 0;i < len;i++)
        {
            for (int j = i + 1;j < len;j++)
            {
                //从大到小排序
                if (arr[i] < arr[j])
                {
                    T temp = arr[i];
                    arr[i] = arr[j];
                    arr[j] = temp;
                }
            }
        }
    }
    
    int main(void)
    {
        int arr[] = { 2,6,1,8,9,2 };
        int len = sizeof(arr) / sizeof(int);
    
        cout << "排序前:";
        PrintArray(arr,len);//排序前:2 6 1 8 9 2
        MySort(arr, len);
        cout << "排序后:";
        PrintArray(arr, len);//排序后:9 8 6 2 2 1
    
        char chArr[] = { 'a','c','q','d','t', };
        len = sizeof(chArr) / sizeof(char);
    
        cout << "排序前:";
        PrintArray(chArr, len);//排序前:a c q d t
        MySort(chArr, len);
        cout << "排序后:";
        PrintArray(chArr, len);//排序后:t q d c a
    
        return 0;
    }

     三、类模板

    1.类模板定义

      类模板与函数模板的定义和使用类似,有时,有两个或多个类,其功能是相同的,仅仅是数据类型不同,所以将类中的类型进行泛化。

    类模板示例:

    #define _CRT_SECURE_NO_WARNINGS
    #include <iostream>
    
    using namespace std;
    
    template<class T>
    class Person
    {
    public:
        Person(T id, T age)
        {
            this->m_id = id;
            this->m_age = age;
        }
    
        void Show()
        {
            cout << "id:" << this->m_id << ",age:" << this->m_age << endl;
        }
    
    public:
        T m_id;
        T m_age;
    };
    
    void test()
    {
        //函数模板在调用的时候,可以自动类型推导
        //类模板必须显式指定类型
        Person<int> p(10, 20);
        p.Show();
    }
    
    int main(void)
    {
        test();//id:10,age:20
        return 0;
    }

    2.类模板派生普通类、类模板派生类模板

    #include <iostream>
    using namespace std;
    
    template<class T>
    class Person
    {
    public:
        Person(T age)
        {
            this->age = age;
        }
    private:
        T age;
    };
    
    //模板类派生普通类
    //为什么继承的类型需显式,而不是T?
    //原因:类去定义对象,这个对象需要编译分配内存,所以要在
    //public Person<int>这里显式的指定类型,可以知道给父类分配多少内存
    class SubPerson1 :public Person<int>
    {
    public:
        SubPerson1(int age, int id) :Person<int>(age)
        {
            this->id = id;
        }
    private:
        int id;
    };
    
    //模板类派生模板类
    template<class T>
    class SubPerson2 :public Person<T>
    {
    public:
        SubPerson2(T age, T id) :Person<T>(age)
        {
            this->id = id;
        }
    private:
        int id;
    };

     3、类模板实现

    (1)类模板类内实现

    (2)类模板类外实现(在一个.cpp中)

      模板类不要轻易使用友元函数

    (3)类模板类外实现(在.h和.cpp中)

      由于二次编译,模板类在.h在第一次编译之后,并没有最终确定类的具体实现,只是编译器的词法校验和分析。在第二次确定类的具体实现后,是在.hpp文件生成的最后的具体类,所以main函数需要引入.hpp文件。

      引入hpp文件一说也是曲线救国之计,所以实现模板方法建议在同一个文件.h中完成。

    (4)类模板中的static

    #define _CRT_SECURE_NO_WARNINGS
    #include <iostream>
    using namespace std;
    
    template<class T>
    class Person
    {
    public:
        static T a;
    };
    //类外初始化
    template<class T> T Person<T>::a = 0;
    
    int main(void)
    {
        Person<int> p1, p2, p3;
        Person<char> pp1, pp2, pp3;
    
        p1.a = 10;
        pp1.a = 'c';
    
        cout << p1.a << " " << p2.a << " " << p3.a << endl;//10 10 10
        cout << pp1.a << " " << pp2.a << " " << pp3.a << endl;//c c c
    //通过以上结果,说明p1,p2,p3是属于Person<int>家族的,他们共享Person<int>::a; //pp1,pp2,pp3是属于Person<char>家族的,他们共享Person<char>::a; return 0; }

    练习:

    设计一个数组模板类(MyArray),完成对int、char类型元素的管理。

    需要实现 构造函数 拷贝构造函数  [] 重载=操作符。

    #define _CRT_SECURE_NO_WARNINGS
    #include <iostream>
    using namespace std;
    
    template<class T>
    class MyArray
    {
    public:
        MyArray(int capacity)
        {
            this->mCapacity = capacity;
            this->mSize = 0;
            //申请内存
            this->pAddr = new T[this->mCapacity];
        }
        MyArray(const MyArray<T>& arr)
        {
            this->mCapacity = arr.mCapacity;
            this->mSize = arr.mSize;
    
            //申请内存空间
            this->pAddr = new T[this->mCapacity];
            //数据拷贝
            for (int i = 0; i < this->mSize;i++)
            {
                this->pAddr[i] = arr.pAddr[i];
            }
        }
    
        T& operator[](int index)
        {
            return this->pAddr[index];
        }
    
        MyArray<T> operator=(const MyArray<T>& arr)
        {
            if (this->pAddr != NULL)
            {
                delete[] this->pAddr;
            }
    
            this->mCapacity = arr.mCapacity;
            this->mSize = arr.mSize;
            //申请内存空间
            this->pAddr = new T[this->mCapacity];
            //数据拷贝
            for (int i = 0; i < this->mSize;i++)
            {
                this->pAddr[i] = arr.pAddr[i];
            }
    
            return *this;
        }
    
        void PushBack(T& data)
        {
            //判断容器中是否有位置
            if (this->mSize >= this->mCapacity)
            {
                return;
            }
    
            //调用拷贝构造 =操作符
            //1.对象元素必须能够被拷贝
            //2.容器都是值寓意,而非引用寓意 向容器中放入元素,都是放入的元素的拷贝
            //3.如果元素的成员有指针,注意深拷贝和浅拷贝问题
            this->pAddr[this->mSize] = data;
            this->mSize ++ ;
        }
    
        //T&& 对右值取引用
        void PushBack(T&& data)
        {
            //判断容器中是否有位置
            if (this->mSize >= this->mCapacity)
            {
                return;
            }
    
            this->pAddr[this->mSize] = data;
            this->mSize++;
        }
    
        ~MyArray()
        {
            if (this->pAddr != NULL)
            {
                delete[] this->pAddr;
            }
        }
    
    public:
        //一共可以容下多少元素
        int mCapacity;
        //当前数组有多少元素
        int mSize;
        //保存数据的首地址
        T* pAddr;
    };
    
    class Person 
    {
        
    
    };
    void test2()
    {
        Person p1, p2;
    
        MyArray<Person> arr(10);
        arr.PushBack(p1);
        arr.PushBack(p2);
    }
    
    void test1()
    {
        MyArray<int> marray(20);
        int a = 10, b = 20, c = 30, d = 40;
        marray.PushBack(a);
        marray.PushBack(b);
        marray.PushBack(c);
        marray.PushBack(d);
    
        //不能对右值取引用
        //左值 可以在多行使用
        //临时变量 只能当前行使用
        marray.PushBack(100);
        marray.PushBack(200);
        marray.PushBack(300);
    
        for (int i = 0;i < marray.mSize;i++)
        {
            cout << marray[i] << " ";
        }
        cout << endl;
    }
    int main(void)
    {
        test1();
        return 0;
    }
  • 相关阅读:
    synthetic-load-generator 一个不错的opentracing trace && metrics && logs 生成工具
    记一次php.ini配置不合理造成系统加载偏慢问题
    Data-Prepper opendistro 开源的基于es 的trace 分析工具
    使用babel-standalone 让浏览器支持es6特性
    tempo grafana 团队开源的分布式追踪框架
    grafana/agent grafana 团队开源的兼容prometheus 的agent
    k6 集成goja 的部分集成说明
    spf13/afero 通用文件系统试用
    goja 支持es6的一种方法
    salesforce 跨组织数据可见性的方案
  • 原文地址:https://www.cnblogs.com/yuehouse/p/9893087.html
Copyright © 2020-2023  润新知