zoukankan      html  css  js  c++  java
  • 模板template用法

    模板(Template)指C++程序设计设计语言中采用类型作为参数的程序设计,支持通用程序设计。C++ 的标准库提供许多有用的函数大多结合了模板的观念,如STL以及IO Stream。  参考:https://www.jianshu.com/p/31d7e18372e2

    函数模板定义一族函数。

    //template1.cpp #include <iostream>

    template<typename T> void swap(T &a, T &b) {

    T tmp{a}; a = b;

    b = tmp;

    }

    int main(int argc, char* argv[])

    {

    int a = 2; int b = 3;

    swap(a, b); // 使用函数模板

    std::cout << "a=" << a << ", b=" << b << std::endl;

    double c = 1.1; double d = 2.2; swap(c, d);std::cout << "c=" << c << ", d=" << d << std::endl;return 0;

    }

    函数模板的格式:

    template<parameter-list> function-declaration

    函数模板在形式上分为两部分:模板、函数。在函数前面加上 template<...>就成为函数模板,因此对函数的各种修饰(inline、constexpr 等)需要加在 function-declaration 上,而不是 template 前。如

    template

    inline T min(const T &, const T &);

    parameter-list 是由英文逗号(,)分隔的列表,每项可以是下列之一:

     
     

    上面 swap 函数模板,使用了类型形参。函数模板就像是一种契约,任何满足该契约的类型都可以做为模板实参。而契约就是函数实现中,模板实参需要支持的各种操作。上面

    swap 中 T 需要满足的契约为:支持拷贝构造和赋值。

    C++ 的函数模板本质上函数的重载,泛型只是简化了程序员的工作,让这些重载通过编译器来完成。我们可以用 c++flit 来观察这一现象。

     
     

    通过 objdump 可以把 template1.out 中代码段的与 swap 相关的符号提出来,有两个, 分别是_Z4swapIdEvRT_S1_和_Z4swapIiEvRT_S1_

    再用 f++filt 将 Name Mangling 后的名字翻转过来,就对应了两个函数原型:

    void swap(double&, double&)

    void swap(int&, int&)

    1.2 函数模板不是函数

    刚才我们提到函数模板用来定义一族函数,而不是一个函数。C++是一种强类型的语

    言,在不知道 T 的具体类型前,无法确定 swap 需要占用的栈大小(参数栈,局部变量), 同时也不知道函数体中 T 的各种操作如何实现,无法生成具体的函数。只有当用具体

    类型去替换 T 时,才会生成具体函数,该过程叫做函数模板的实例化。当在 main 函数中调用 swap(a,b)时,编译器推断出此时 T 为 int,然后编译器会生成 int 版的 swap 函数供调用。所以相较普通函数,函数模板多了生成具体函数这一步。如果我们只是编

    写了函数模板,但不在任何地方使用它(也不显式实例化),则编译器不会为该函数模板生成任何代码。函数模板实例化分为隐式实例化和显式实例化。

     
     

    1.3 隐式实例化 implicit instantiation

    仍以 swap 为例,我们在main 中调用 swap(a,b)时,就发生了隐式实例化。当函数模板被调用,且在之前没有显式实例化时,即发生函数模板的隐式实例化。如果模板实参能从调用的语境中推导,则不需要提供。效率较低。

    //template2.cpp #include

    template void print(const T &r) {

    std::cout << r << std::endl;

    }

    int main() {

    // 隐式实例化print(int) print(1);

    // 实例化 print(char) print<>('c');

    // 仍然是隐式实例化,我们希望编译器生成print(double) print(1);

    return 0;

    }

    1.4 显式实例化 explicit instantiation

    隐式实例化可能影响效率,所以需要提高效率的显式实例化,显式实例化在编译期间就会生成实例(增加了编译时间)。在函数模板定义后,我们可以通过显式实例化的方式告诉编译器生成指定实参的函数。显式实例化声明会阻止隐式实例化。如果我们在显式实例化时,只指定部分模板实参,则指定顺序必须自左至右依次指定,不能越过前参模板形参,直接指定后面的。

     
     
     
     
     
     

    1.5. 模板函数的隐式/显示化与特化辨析

    如果你真的想要实例化(而不是特殊化或某物)的功能,请执行以下操作:

    template void func(T param) {} // definition

    template void func(int param); // explicit instantiation.

    template <> void func(int param) {} // specialization

    总结一下,C++只有模板显式实例化(explicit instantiation),隐式实例化(implicit instantiation),特化(specialization,也译作具体化,偏特化)。首先考虑如下模板函数代码:

    template

    void swap(T &a, T &b){

    ...

    }

    1.隐式实例化

    我们知道,模板函数不是真正的函数定义,他只是如其名提供一个模板,模板只有在运行时才会生成相应的实例,隐式实例化就是这种情况:

    int main(){

    ....

    swap(a,b);

    ....

    }

    它会在运行到这里的时候才生成相应的实例,很显然的影响效率这里顺便提一下 swap<int>(a,b);中的<int>是可选的,因为编译器可以根据函数参数类型自动进行判断,也就是说如果编译器不不能自动判断的时候这个就是必要的;

    2.显式实例化

    前面已经提到隐式实例化可能影响效率,所以需要提高效率的显式实例化,显式实例化在编译期间就会生成实例,方法如下:

    template void swap(int &a,int &b);

    这样就不会影响运行时的效率,但编译时间随之增加。

    3.特化

    这个 swap 可以处理一些基本类型如 long int double,但是如果想处理用户自定义的类型就不行了,特化就是为了解决这个问题而出现的:

    template <> void swap(job a,job b){...}

    其中 job 是用户定义的类型.

    2. 函数模板的使用

    2.1 使用非类型形参

    //template3.cpp

    #include

    // N 必须是编译时的常量表达式

    template

    void printArray(const T (&a)[N]) {

    std::cout << "[";

    const char *sep = "";

    for (int i = 0; i < N; i++, (sep = ", ")) {

    std::cout << sep << a[i];

    }

    std::cout << "]" << std::endl;

    }

    int main() {

    // T: int, N: 3

    int a[]={1, 2, 3};

    printArray(a);

    float b[] = {1.1, 2.2, 3.3};

    printArray(b);

    return 0;

    }

     
     

    2.2返回值为 auto

    有些时候我们会碰到这样一种情况,函数的返回值类型取决于函数参数某种运算后的类型。对于这种情况可以采用 auto 关键字作为返回值占位符。 decltype 操作符用于查询表达式的数据类型,也是 C++11 标准引入的新的运算符,其目的是解决泛型编程中有些类型由模板参数决定,而难以表示的问题。为何要将返回值后置呢?

    // 这样是编译不过去的,因为 decltype(a*b)中,a 和 b 还未声明,编译器不知道 a 和 b 是什么。

    template

    decltype(a*b) multi(T a, T b) {

    return a*+ b;

    }

    //编译时会产生如下错误:error: use of undeclared identifier 'a'

    //template4.cpp

    #include

    using namespace std;

    template

    auto multi(T1 a, T2 b) -> decltype(a * b) {

    return a * b;

    }

    int main(int argc, char* argv[])

    {

    cout << multi(2, 3) << endl;

    cout << multi(2.2, 3.0) << endl;

    cout << multi(2.2, 4) << endl;

    cout << multi(3, 4.4) << endl;

    return 0;

    }

     
     

    2.3 类成员函数模板

    函数模板可以做为类的成员函数。

    需要注意的是:函数模板不能用作虚函数。这是因为 C++编译器在解析类的时候就要确定虚函数表(vtable)的大小,如果允许一个虚函数是函数模板,那么就需要在解析这个类之前扫描所有的代码,找出这个模板成员函数的调用或显式实例化操作,然后才能确定虚函数表的大小,而显然这是不可行的。

    //template5.cpp

    #include <iostream>

    class object {

    public:

    template<typename T>

    void print(const char *name, const T &v){

    std::cout << name << ": " << v << std::endl;

    }

    };

    int main() {

    object o;

    o.print("name", "Crystal");

    o.print("age", 18);

    return 0;

    }

     
     

    2.4 函数模板重载

    函数模板之间、普通函数和模板函数之间可以重载。编译器会根据调用时提供的函数参数,调用能够处理这一类型的最佳匹配版本。在匹配度上,一般按照如下顺序考虑:

     
     

    可以通过空模板实参列表来限定编译器只匹配函数模板,比如 main 函数中的最后一条语句。

    //template6.cpp

    #include

    #include

    template

    const T &max(const T &a, const T &b)

    { std::cout << "max(&, &) = ";

    return a > b ? a : b;

    }

    // 函数模板重载

    template

    const T *max(T *a, T *b) {

    std::cout << "max(*, *) = ";

    return *a > *b ? a : b;

    }

    // 函数模板重载

    template

    const T &max(const T &a, const T &b, const T &c)

    { std::cout << "max(&, &, &) = ";

    const T &t = (a > b ? a : b);

    return t > c ? t : c;

    }

    // 普通函数

    const char *max(const char *a, const char *b)

    { std::cout << "max(const char *, const char *) = ";

    return strcmp(a, b) > 0 ? a : b;

    }

    int main() {

    int a = 1, b = 2;

    std::cout << max(a, b) << std::endl;

    std::cout << *max(&a, &b) << std::endl;

    std::cout << max(a, b, 3) << std::endl;

    std::cout << max("en", "ch") << std::endl;

    // 可以通过空模板实参列表来限定编译器只匹配函数模板

    std::cout << max<>("en", "ch") << std::endl;

    std::cout << max(100, 200) << std::endl;

    return 0;

    }

     
     

    2.5 函数模板特化 specialization

    当函数模板需要对某些类型进行特别处理,这称为函数模板的特化。当我们定义一个特化版本时,函数参数类型必须与一个先前声明的模板中对应的类型匹配。函数模板特化的本质是实例化一个模板,而非重载它。因此,特化不影响编译器函数匹配。

    //template7.cpp

    #include

    #include

    using namespace std;

    template

    int compare(const T1 &a, const T2 b) {

    return a - b;

    }

    // 对 const char *进行特化

    template<>

    int compare(const char * const &a, const char * const &b) {

    return strcmp(a, b);

    }

    int main(int argc, char* argv[])

    {

    cout << compare(100, 200) << endl;

    cout << compare("abc", "xyz") << endl;

    return 0;

    }

     
     

    上面的例子中针对 const char *的特化,我们其实可以通过函数重载达到相同效果。因此对于函数模板特化,目前公认的观点是没什么用,并且最好别用。Why Not Specialize Function Templates?

    但函数模板特化和重载在重载决议时有些细微的差别。这些差别中比较有用的一个是阻止某些隐式转换。如当你只有 void foo(int)时,以浮点类型调用会发生隐式转换,这可以通过特化来阻止:

    template <class T>void foo(T);

    template <> void foo(int) {}

    foo(3.0); // link error,阻止 float 隐式转换为 int

    虽然模板配重载也可以达到同样的效果,但特化版的意图更加明确。

    函数模板及其特化版本应该声明在同一个头文件中。所有同名模板的声明应该放在前面,然后是这些模板的特化版本。

    程序运行结果和使用函数模板特化相同。但是,使用普通函数重载和使用模板特化还是有不同之处,主要表现在如下两个方面:

    (1)如果使用普通重载函数,那么不管是否发生实际的函数调用,都会在目标文件中生成该函数的二进制代码。而如果使用模板的特化版本,除非发生函数调用,否则不会在目标文件中包含特化模板函数的二进制代码。这符合函数模板的“惰性实例化”准则。

    (2)如果使用普通重载函数,那么在分离编译模式下,应该在各个源文件中包含重载函数的申明,否则在某些源文件中就会使用模板函数,而不是重载函数。

    2.6 变参函数模板(模板参数包)

    这是 C++11 引入的新特性,用来表示任意数量的模板形参。其语法样式如下:

    template<typename ...Args> // Args: 模板参数包

    void foo(Args ... args);// args: 函数参数包

    在模板形参 Args 的左边出现三个英文点号"...",表示 Args 是零个或多个类型的列表,是一个模板参数包(template parameter pack)。正如其名称一样,编译器会将 Args 所表示的类型列表打成一个包,将其当做一个特殊类型处理。相应的函数参数列表中也有一个函数参数包。与普通模板函数一样,编译器从函数的实参推断模板参数类型,与此同时还会推断包中参数的数量。

    // sizeof...() 是 C++11 引入的参数包的操作函数,用来取参数的数量

    template

    int length(Args ... args) {

    return sizeof...(Args);

    }

    // 以下语句将在屏幕打印出:2

    std::cout << length(1, "hello") << std::endl;

    变参函数模板主要用来处理既不知道要处理的实参的数目也不知道它们的类型时的场景。既然我们对实参数量以及类型都一无所知,那么我们怎么使用它呢?最常用的方法是递归。

    //template8.cpp

    #include

    using namespace std;

    // sizeof...() 是 C++11 引入的参数包的操作函数,用来取参数的数量

    template

    int length(Args ... args) {

    return sizeof...(Args);

    }

    int main(int argc, char* argv[])

    {

    // 以下语句将在屏幕打印出:2

    cout << length(1, "hello") << endl; // 以下语句将在屏幕打印出:3

    cout << length(1, "hello", 2) << endl; // 以下语句将在屏幕打印出:4

    cout << length(1, "hello", 2, 3) << endl; // 以下语句将在屏幕打印出:5

    cout << length(1, "hello", 2, 3, 4) << endl;

    return 0;

    }

    #define _CRT_SECURE_NO_WARNINGS
    
    #include <iostream>
    #include <string>
    #include <vector>
    #include <map>
    
    // 在C++11之前,类模板和函数模板只能含有固定数量的模板参数。C++11增强了模板功能,允许模板定义中包含0到任意个模板参数,这就是可变参数模板。
    
    // 可变参数模板类 继承方式展开参数包
    // 可变参数模板类的展开一般需要定义2 ~ 3个类,包含类声明和特化的模板类
    template<typename... A> class BMW{};  // 变长模板的声明
    
    template<typename Head, typename... Tail>  // 递归的偏特化定义
    class BMW<Head, Tail...> : public BMW<Tail...>
    {//当实例化对象时,则会引起基类的递归构造
    public:
        BMW()
        {
            printf("type: %s
    ", typeid(Head).name());
        }
    
        Head head;
    };
    
    template<> class BMW<>{};  // 边界条件
    
    // 模板递归和特化方式展开参数包
    template <long... nums> struct Multiply;// 变长模板的声明
    
    template <long first, long... last>
    struct Multiply<first, last...> // 变长模板类
    {
        static const long val = first * Multiply<last...>::val;
    };
    
    template<>
    struct Multiply<> // 边界条件
    {
        static const long val = 1;
    };
    
    
    void mytest()
    {
        BMW<int, char, float> car;
        /*
        运行结果:
            type: f
            type: c
            type: i
        */
    
        std::cout << Multiply<2, 3, 4, 5>::val << std::endl; // 120
    
    
        return;
    }
    
    
    int main()
    {
        mytest();
    
        system("pause");
        return 0;
    }

    类模板  https://blog.csdn.net/qq_35637562/article/details/55194097

    考虑我们写一个简单的栈的类,这个栈可以支持int类型,long类型,string类型等等,不利用类模板,我们就要写三个以上的stack类,其中代码基本一样,通过类模板,我们可以定义一个简单的栈模板,再根据需要实例化为int栈,long栈,string栈。

    statck.h
    template <class T> class Stack {
        public:
            Stack();
            ~Stack();
            void push(T t);
            T pop();
            bool isEmpty();
        private:
            T *m_pT;        
            int m_maxSize;
            int m_size;
    };
    
    #include "stack.cpp"
    //stack.cpp
    template <class  T>  Stack<T>::Stack(){
       m_maxSize = 100;      
       m_size = 0;
       m_pT = new T[m_maxSize];
    }
    template <class T>  Stack<T>::~Stack() {
       delete [] m_pT ;
    }
            
    template <class T> void Stack<T>::push(T t) {
        m_size++;
        m_pT[m_size - 1] = t;
        
    }
    template <class T> T Stack<T>::pop() {
        T t = m_pT[m_size - 1];
        m_size--;
        return t;
    }
    template <class T> bool Stack<T>::isEmpty() {
        return m_size == 0;
    }

    模板参数
    模板可以有类型参数,也可以有常规的类型参数int,也可以有默认模板参数,例如

    template<class T, T def_val> class Stack{...}

    上述类模板的栈有一个限制,就是最多只能支持100个元素,我们可以使用模板参数配置这个栈的最大元素数,如果不配置,就设置默认最大值为100

  • 相关阅读:
    超强web页面上绘图...
    jQuery plugins ...
    [转]asp.net文件下载方法...
    SQL SERVER 在做字符串比较时会自动去掉首尾空格?
    原来DataTable的Distinct竟如此简单!
    哎呀!可能有弹出式窗口拦截器生成Gmail无法打开该网页。如果您使用弹出式窗口拦截器,请将其关闭以便打开窗口。
    jquery的val() 的疑惑 ...
    [转].NET程序中打包安装程序中的卸载程序的制作
    小程序跳转页面选择数据
    dokcer kibana
  • 原文地址:https://www.cnblogs.com/klb561/p/14192169.html
Copyright © 2011-2022 走看看