• 【Example】C++ 模板概念讲解及编译避坑


    C++ 不同于 Java,它没有标准的 Object 类型。也就意味着 C++ 并不存在完整的泛型编程概念。

    为什么不存在完整的泛型编程概念,放到最后一个例子讲,先讲 “部分的” 泛型编程概念的实现方式:模板

    什么是模板?

    引用 Microsoft Docs:

    模板是 c + + 中的泛型编程的基础。 作为强类型语言,c + + 要求所有变量都具有特定类型,由程序员显式声明或由编译器推断。

    但是,许多数据结构和算法的外观都是相同的,无论它们的操作类型是什么。

    利用模板,您可以定义类或函数的操作,并允许用户指定这些操作应使用的具体类型。

    总结:模板是 C++ 当中支持参数类型与返回值动态化的工具,使开发人员可以动态自定义函数、类中参数与返回值类型。

    模板又分为两种:函数模板 与 类模板。

    ====================================

    1,函数模板

    先从最简单的定义讲起:

    template<class T>
    T AddNum(T a, T b) {
        return a + b;
    }
    
    int main()
    {
        int a = 1;
        int b = 2;
        int c = AddNum(a, b);
        std::cout << "Add Num: " << c << std::endl;
        
        return EXIT_SUCCESS; 
    }

    可以看到,以上函数实现了最简单的任意同类型变量相加的一个功能。

    定义模板的关键字就是 template,语法:

    template<class T>
    or
    template<typename T>

    template<> 对函数声明或定义进行修饰,其中 T 可以是任意名字(例如Object)。

    进行在模板函数调用时,编译器会根据变量类型推断函数参数类型。

    那么,函数模板是否可以支持多种类型呢?可以!

    template<class I, class F>
    F GetProduct(I a, F b) {
        return a * b;
    }
    
    int main()
    {
        int a_i = 5;
        float b_f = 8.5;
        float c_f = GetProduct(a_i, b_f);
        std::cout << "Product: " << c_f << std::endl;
       
        return EXIT_SUCCESS; 
    }

    以上函数最简单实现了求两个不同类型变量的乘积。当你的参数类型需要两种或以上的时候,就是在 template<> 当中增加声明,再对函数或类进行修饰:

    template<class I, class F, class D>

    那么,除了基本的数据类型,模板是否支持结构体(struct)或其他类型呢?可以!但是,进行运算操作的时候,你要确保你的 struct 或 class 重载的相应的运算符!

    typedef struct IntCell {
        int a;
        int b;
        int c;
    
        struct IntCell(int i, int j, int k) : a(i), b(j), c(k) {};
    
    }IntCell;
    typedef
    struct DoubleCell { double a; double b; double c; struct DoubleCell(double i, double j, double k) : a(i), b(j), c(k) {}; }DoubleCell;
    // ------------------------
    
    template <class structT, class structY>
    inline bool CompareStructMemSize(structT a, structY b) {
        return sizeof(a) > sizeof(b);
    };
    
    // ------------------------
    int main()
    {
        IntCell cell_a(1, 2, 3);
        DoubleCell cell_b(1.1, 2.2, 3.3);
        bool flag = CompareStructMemSize(cell_a, cell_b);
        std::cout << "bool: " << flag << std::endl;
    
        return EXIT_SUCCESS;
    }

    可以看到,以上代码当中我定义了两个不同的结构体,只是单纯的去比较这两个结构体的大小。

    ====================================

    2,类模板

    函数模板很好理解,那么类模板是什么呢?

    可以在类模板的内部或外部定义成员函数。 如果在类模板的外部定义成员函数,则会像定义函数模板一样定义它们。 --Microsoft Docs

    以下演示了一个最简单的使用模板的类:

    template<class Object>
    class VectorMod {
    public:
    
        VectorMod() {
            this->_vec.reserve(10);
        };
    
        ~VectorMod()
        {
            this->Clear();
        };
    
    
        std::vector<Object>& GetVec() {
            return this->_vec;
        };
    
        void AddData(Object in) {
            this->_vec.push_back(in);
        };
    
        int GetSize() {
            return this->_vec.size();
        };
    
        void Clear() {
            this->_vec.clear();
            std::vector<Object>().swap(this->_vec);
        }
    
    private:
        std::vector<Object> _vec;
    
    };

    这个类仅仅是简单将 std::vector 包装了一下而已。

    于是我们可以总结出语法:

    1,使用 template<> 对类声明和类定义进行修饰。

    2,类内部需要使用模板类型时,直接使用相应的模板形参名。

    template<class Object>
    class VectorMod {
        std::vector<Object> _vec;
    };

    请注意,就像任何模板类成员函数一样,类的构造函数成员函数的定义包含模板参数列表两次。

    成员函数可以是函数模板,并指定附加参数。 --Microsoft Docs

    PS: 模板可以在模板类当中被定义并使用,这种情况下成为 “成员模板”,但是逻辑会过于复杂,实际开发不建议使用,了解成员模板

    当模板类需要被使用的时候,如何进行声明并创建呢?

    VectorMod<int> v_mod;
    shared_ptr<VectorMod<int>> int_pool = make_shared<VectorMod<int>>();

    以上演示了局部变量及智能指针的创建。然后,像平常那样调用即可:

    VectorMod<int> v_mod;
    v_mod.AddData(971);
    v_mod.AddData(981);
    v_mod.AddData(991);
    
    for (auto i : v_mod.GetVec())
    {
       std::cout << i << std::endl;
    }

    好了,上面是最基本的类模板。

    然后:类模板当中非类型形参

    这是一个什么东西呢?

    1,它是一个常量。

    2,它的类型只能是 int 、指针、引用这三种内置类型。

    3,调用它的只能是一个常量表达式

    它的使用场景?

    1,你封装了一个可以容纳固定大小的容器。

    2,可以预初始化固定资源。

    样例:

    #include<vector>
    using std::vector;
    
    template<class Object, int PREMEM>
    class DataPool
    {
    public:
        DataPool();
    
        ~DataPool();
    
        vector<Object>& GetVec();
    
        void AddData(Object in);
    
        int GetSize();
    
        void Clear();
    
    private:
        vector<Object> _vec;
    
    };
    // ------------------------
    template<class Object, int PREMEM>
    DataPool<Object, PREMEM>::DataPool()
    {
        this->_vec.reserve(PREMEM);
        return;
    }
    
    template<class Object, int PREMEM>
    DataPool<Object, PREMEM>::~DataPool()
    {
        this->Clear();
        return;
    }
    
    template<class Object, int PREMEM>
    vector<Object>& DataPool<Object, PREMEM>::GetVec()
    {
        return this->_vec;
    }
    
    template<class Object, int PREMEM>
    void DataPool<Object, PREMEM>::AddData(Object in)
    {
        this->_vec.push_back(in);
        return;
    }
    
    template<class Object, int PREMEM>
    int DataPool<Object, PREMEM>::GetSize()
    {
        return this->_vec;
    }
    
    template<class Object, int PREMEM>
    void DataPool<Object, PREMEM>::Clear()
    {
        this->_vec.clear();
        std::vector<Object>().swap(this->_vec);
        return;
    }

    以上代码,同样是将 std::vector 简单无意义包装了一下,但是,却使用了非类型形参进行了内存预分配操作以提高性能。

    所以模板非类型形参的语法是:

    template<class Object, int PREMEM>
    or
    template<class Object, type*>
    or
    template<class Object, type&>

    即不使用 class 或者 typename,直接使用 INT or PTR or REF。

    那么该如何使用呢?

    #include "DataPool.hpp"
    
    int main()
    {
        shared_ptr<DataPool<string, 10>> str_pool = make_shared<DataPool<string, 10>>();
        str_pool->AddData("Hello Byte!");
        str_pool->AddData("Hello Blu!");
        str_pool->AddData("Hello Frog!");
    
        for (auto s : str_pool->GetVec())
        {
            std::cout << s << std::endl;
        }
    
        return EXIT_SUCCESS;
    }

    可以看到,使用它的语法就是:

    DataPool<string, 10>
    or
    DataPool<string, ptr>
    or
    DataPool<string, &str>

    ====================================

    3,模板与完整泛型编程的区别(编译避坑)

    C++ 的模板类在没有被使用之前,编译器完全不知道它会占用多少空间!而 C++ 每一个变量及对象占用的空间在编译的时候就要被确定!所以 C++ 当中没有绝对的泛型编程概念。

    所以,模板类必须是声明与实现同源(不一定是文件不分离),最合适的写法也就是 hpp 文件。

    简单化使用.h头文件和.cpp文件分类声明时,几乎确定会报链接错误。

    解决方法可以简单粗暴的将 cpp 文件 include 到 h 文件当中,但这并非标准做法,MSC编译器也已经不支持,所以最合适的做法还是使用 hpp 文件。

  • 相关阅读:
    vim 插件之NERD tree
    vim 插件之supertab
    离开页面时发送请求
    修改属性item1(1变化)
    node+express 中安装nodemon实时更新server.js
    vue封装element中table组件
    mysql常用语句
    mysql新建表
    node+express POST请求
    node+express 发送get请求
  • 原文地址:https://www.cnblogs.com/airchip/p/15941405.html
Copyright © 2020-2023  润新知