zoukankan      html  css  js  c++  java
  • 模板的简单介绍与使用

    模板的简单介绍与使用

    模板的简单介绍与使用

    2013-11-07 18:12 by 烟,灭在风里, 242 阅读, 0 评论, 收藏编辑

    什么是模板?


    模板(template)指c++中的函数模板与类模板,大体对应于C#和Java众的泛型的概念。目前,模板已经成为C++的泛型编程中不可缺少的一部分。

    模板定义以关键字template开始,后接模板形参表,模板形参表是用尖括号括住的一个或者多个模板形参的列表,形参之间以逗号分隔。 模板形参可以是表示类型的类型形参,也可以是表示常量表达式的非类型形参。非类型形参跟在类型说明符之后声明。类型形参跟在关键字class或typename之后定义(至于class与typename的区别实际并不大,c++的早期版本中只有class,没有typename。在绝大多数场景下两者是通用的,只有少数特殊情况下必须使用typename。总之,使用typename是万无一失的。两者的区别可以参考这篇文章)。

    模板是C++程序员绝佳的武器, 特别是结合了多重继承(multiple inheritance)与运算符重载(operator overloading)之后。C++ 的标准库提供许多有用的函数大多结合了模板的观念,如STL以及IO Stream

     函数模板

      所谓函数模板,实际上是建立一个通用函数,其函数类型和形参类型不具体指定,用一个虚拟的类型来代表。这个通用函数就称为函数模板。凡是函数体相同的函数都可以用这个模板来代替,不必定义多个函数,只需在模板中定义一次即可。在调用函数时系统会根据实参的类型来取代模板中的虚拟类型,从而实现了不同函数的功能。 

      网上大多数介绍都是从比较两个数大小入手的,本文章介绍依然如此,假设有一个需要要比较两个数的大小,但是这两个数的类型是不确定的,可能是int、float、double类型的。

      当然有一种方式就是可以用函数的重载来实现,但用重载的方式造成的问题是:有多少类型的可能性,就要写多少个重载函数。假设当前需求里可能要求只有float和double两种类型,但有一天增加了对int类型的允许,则要在代码中增加对int类型参数的重载函数。

      这个时候,函数模板就排上用场了。只需要定义一个带有泛型参数的函数,就可以实现多种类型参数的比较,直接看下面的代码吧:

      

    复制代码
     1 class MyTemplate
     2 {
     3 public:
     4     MyTemplate(void);
     5     ~MyTemplate(void);
     6 
     7     //以关键字template开头 后面接<typename T>或<class T> 返回值类型和参数类型都是T
     8     template <typename T> T Max(T a,T b) 
     9     {
    10         return a>=b?a:b;
    11     };
    12 };
    复制代码

    这里的T会在程序编译的时候特化为代码调用处传入的实际参数,例如

    如果比较两个int类型的大小:

        MyTemplate mytemplate;
        int x = 10;
        int y = 100;
        int val = mytemplate.Max(x,y);    

    程序里的T在编译时就用int替换,替换后的程序应该是下面这个样子:

      template <typename int> int Max(int a,int b) 
      {
          return a>=b?a:b;
      };

    float和double类型的是同样的道理,这里就不重复了。

    类模板

     当我们有更加复杂的需求的时候,例如要实现一个队列,这个队列中可能不止有int类型的数据,还有可能有string类型、double类型、或者更复杂的自定义类型。例如下面这个队列中存储了多种数据类型的对象,这个时候就需要定义一个类模板了。

    类模板实现的简单队列


     View Code

    #pragma once

    template <typename T> class FZQueue;
    template <typename T> class queueItem
    {
    private:
    friend class FZQueue<T>; //因为queueItem只有FZQueue要直接调用 所以这里要声明为FZQueue的友元

    queueItem(void){};

    queueItem(const T &item1,const queueItem *p):item(item1),next(p){};

    queueItem(const T &t):item(t),next(0){}; //显式定义复制构造函数 将item置为t next设置为空指针

    ~queueItem(void){};//析构函数

    T item;

    queueItem *next;
    };

    template <typename T>
    class FZQueue
    {
    public:
    FZQueue(void):head(0),tail(0){};
    ~FZQueue(void)
    {
    destroy();
    };

    /*显式定义复制构造函数 可以不显式的声明 对此功能无影响*/
    FZQueue(const FZQueue &t):head(0),tail(0) // 将t中的每个元素的值拷贝到新声明的对象中 必须对指针head和tail初始化 否则调用时会报内存访问异常
    {
    copy_elements(t);
    };

    FZQueue& operator=(const FZQueue&); //显式赋值操作符重载 可以不显式的声明 对此功能无影响

    T& front() //获取队列头
    {
    return head->item;
    };


    void push(const T &item) //添加元素到队尾
    {
    queueItem<T> *p_item = new queueItem<T>(item);
    if(empty())
    {
    head=tail=p_item;
    }
    else
    {
    tail->next = p_item;
    tail=p_item;
    }
    };

    void pop() //删除队列头元素
    {
    queueItem<T> *p_curHead = head;
    head=head->next;
    delete p_curHead;
    };

    bool empty() //判断队列是否为空
    {
    return head==0;
    };

    private:
    queueItem<T> *head; //队列头元素的指针

    queueItem<T> *tail; //队尾元素的指针

    void destroy()
    {
    while (!empty())
    {
    pop();
    }
    };

    void copy_elements(const FZQueue &t)
    {
    queueItem<T> *p = t.head;
    //int i = 0;
    while (p) /*i<5*/
    {
    push(p->item);
    p = p->next;
    //i++;
    }
    };

    };

     调用代码如下:

     View Code

    if(valIndexs.empty())
    {
    for (int zi = 10;zi!=15;zi++)
    {
    valIndexs.push(zi);
    }
    }
    FZQueue<int> clone_valZindexs(valIndexs);

    cout<<"valIndexs:"<<valIndexs.front()<<"______clone_valZindexs:"<<clone_valZindexs.front()<<endl;

    cout<<"valIndexs:"<<valIndexs.front()<<"______clone_valZindexs:"<<clone_valZindexs.front()<<endl;

     以上就是用类模板实现简单队列的完整代码。

    问题与总结


    1.把类中的构造函数重载(FZQueue(const T &t);)和操作符重载(FZQueue& operator=(const FZQueue&);)去掉后都是一样正常执行,不知道这个构造函数重载和操作符重载在什么情况下使用。

    总结:参考《C++ Primer》第四版第13章 复制控制 里介绍的复制构造函数一节,对复制构造函数的描述是这样的:

    复制代码
    复制构造函数是一种特殊构造函数,具有单个形参,该形参(常用const修饰)是对该类类型的引用。当定义一个新对象并用一个同类型的对象对它进行初始化时,将显式使用复制构造函数。当将该类型的对象传递给函数或从函数返回该类型的对象时,将隐式使用复制构造函数。可用于:
    
    1.根据另一个同类型的对象显示或隐式初始化一个对象
    2.复制一个对象,将它的作为实参传递给一个函数
    3.从函数返回时复制一个对象
    4.初始化顺序容器中的元素
    5.根据元素初始化式列表初始化数组元素
    复制代码

    并且:如果程序中没有显示定义并实现复制构造函数,编译器会自动生成。赋值操作符重载与析构函数都是如此。

        不能将自定义的类声明为指针形式,例如FZQueue<int> *clone_zindexs,如果这样做,之后将这个指针当参数调用复制构造函数时,复制构造函数不起作用,因为这里只是声明了一个指针而已。

     

  • 相关阅读:
    HYSBZ 3813 奇数国
    HYSBZ 4419 发微博
    HYSBZ 1079 着色方案
    HYSBZ 3506 排序机械臂
    HYSBZ 3224 Tyvj 1728 普通平衡树
    Unity 3D,地形属性
    nginx 的naginx 种包含include关键字
    Redis 出现NOAUTH Authentication required解决方案
    mysql 8.0出现 Public Key Retrieval is not allowed
    修改jar包里的源码时候需要注意的问题
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/3413368.html
Copyright © 2011-2022 走看看