泛型机制——模板----总结笔记

  • 泛型程序设计:
    在面向对象的程序设计中,允许将类中成员的类型设为一个可变的参数,使多个类变成一个类,这种程序设计机制称为泛型程序设计。泛型程序设计可以以独立于任何特定类型的方式编写代码,使用泛型程序时,必须提供具体的所操作的类型或值。
  • 函数模板:
    一组重载函数仅仅是参数的类型不一样,程序的逻辑完全一样,代码大致相同,类型不同,可以写成一个函数,称为函数模板,减少代码量。是实现类型的参数化(泛型化),即把函数中某些形式参数的类型设计成可变的参数,称为模板参数。编译器根据函数实际参数的类型确定模板参数的值,生成不同的模板参数。
    定义形式:
    template<class T>
    T max(T a,T b){
      return a > b ? a : b ;
    }
    注意:通常每个模板的形式参数都要在函数的形式参数表中至少出现一次,这样编译器才能通过函数调用确定模板参数的值。如果有些模板参数在函数的形式参数表中没有出现,编译器就无法推断出模板实际参数的类型,可以通过显式指定模板实参来解决。
    template<class T1, class T2, class T3>
    T3 calc(T1 x, T2 y){
      return x+y;
    }
    //调用时
    calc<int, char, char>(5, 'a');   //模板实例化
    calc<int, char, int>(5, 'a');            
  • 类模板:
    类中不确定的类型设计成一个模板参数,用模板参数代替类中的某些成员的类型。
    定义形式:

    template<模板形式参数表>
    class 类名(...);
    类模板可以定义数据成员和成员函数,构造函数和析构函数,也可以重载运算符。
    成员函数的参数类型或返回值类型也可以是模板的形式参数,成员函数的某些局部变量的类型也可以是模板的形式参数。
  1. 类模板成员函数定义形式:
    必须以template开头,后接模板形式参数表。
    必须用作用域限定符 :: 说明其是哪个类的成员函数。
    类名必须包含其模板形式参数。
    // 可指定下标范围的、支持任意类型的、安全的动态数组的定义
    template <class T>                         // 模板参数T是数组元素的类型
    class Array
    {
        int low;
        int high;
        T *storage;
    public:
        //根据low和high为数组分配空间。分配成功,返回值为true,否则返回值为false
        Array(int lh = 0, int rh = 0):low(lh),high(rh)
        {  storage = new T [high - low + 1];  }
        //复制构造函数
        Array(const Array &arr);
        //赋值运算符重载函数
        Array &operator=(const Array & a);
        //下标运算符重载函数
        T & operator[](int index);
        //回收数组空间
        ~Array() { if (storage) delete [] storage; }
    };
    
    // 类模板Array的成员函数的实现
    
    // 复制构造函数
    template <class T>
    Array<T>::Array(const Array<T> &arr)     //函数名为Array
    {
        low =arr.low;
        high = arr.high;
        storage = new T [high - low + 1];
        for (int i = 0; i < high -low + 1;++i) 
            storage[i] = arr.storage[i];
    }
    
    // 赋值运算符重载函数
    template<class T>
    Array<T> &Array<T>::operator=(const Array<T> &other )  //函数名为operator=
    {
        if (this == &other) return *this;                    //防止自己复制自己
        delete [] storage;                                   //归还空间
        low = other.low;
        high = other.high;
        storage = new T[high - low + 1];                      //根据新的数组大小重新申请空间
        for (int i=0; i <= high - low; ++i)                  //复制数组元素
        storage[i] = other.storage[i];
        return *this;
    }
    
    // 下标运算符重载函数
    template<class T>
    T &Array<T>::operator[](int index)    //函数名为operator[]
    {
        if(index < low || index > high) 
          {  cout<< "下标越界"; exit(-1); }
        return storage[index - low];
    }
  2. 类模板实例化:
    函数模板的实例化通常由编译器自动完成,编译器根据函数调用时的实际参数类型推断出模板参数的值,将模板参数的值代入函数模板生成一个真正可执行的模板函数。
    对于类模板,编译器无法根据对象定义的过程确定模板参数的类型,因而需要用户明确指出模板形式参数的值,定义格式:
    类模板名<模板实际参数表> 对象表;
    Array<int> array1(20,30);
    
    编译器首先将模板的实际参数值代入类模板,生成一个可真正使用的类,然后定义这个类的对象。
  3. 非类型参数和参数的默认值:
    类型参数:模板参数都是用来表示一个尚未明确的类型,这些参数是类型参数。
    非类型参数:模板的形式参数不一定都是类型,可以是整数型或实数型。
    非类型模板实例化,非类型参数将用一个常量表达式作为实际参数。
    // 将数组的下标范围作为模板的非类型参数
    template <class T, int low, int high>
    class Array{
        T storage[high - low + 1];
    public:    
        T & operator[](int index) ;                       //下标运算符重载函数
    };
    
    template <class T, int low, int high>
    T & Array<T, low, high>::operator[](int index)
    {
       if (index < low || index > high)
          {   cout << "下标越界"; exit(-1); }
    
        return storage[index - low];
    }
    
    Array<int, 10, 20>array;
    模板参数和普通的函数参数一样,也可以指定默认值,可在类模板定义时指定默认值。
    template<class T = int> 
    class Array{...}; 
    这样就可以不指定模板的实参
    Array<> array
  4. 类模板友元:(两种友元)

    普通友元:声明某个普通的类或全局函数为所定义的类模板的友元。
    定义形式:
    template<class type>
    class A{
      friend class B;
      friend void f();
      ..;
    };
    意味类B和函数f是类模板A所有的实例的友元,B的所有的成员函数和全局函数f可以访问类模板A的所有实例的私有成员。

    模板的特定实例的友元:声明某个类模板或函数模板的特定实例是所定义类模板的友元。
    将类模板B和函数模板f对应于模板参数为int时的那个实例,作为类模板A的所有实例的友元(如下)
    template<class T>class B;   //类模板B的声明,B有一个模板参数
    template<class T>void f(const T &);   //函数模板f的声明,f有一个模板参数
    template<class type>
    class A{
      friend class B <int>;
      friend void f(const int &);
      ...
    };
    将使用某一模板实参的类模板B和函数模板f的实例是使用同一模板参数的类模板A的特定实例的友元(如下)
    template<class T>class B;
    template<class T>void f(const T &);
    template<class type>
    class A{
      friend class B <type>;
      friend void f(const type &);
      ...
    };
    当声明类模板B和函数模板f为类模板A的友元时,编译器必须知道有这样一个类模板和函数模板的存在,并且知道类模板B和函数模板f的原型,因此,必须在友元声明之前先声明B和f的存在。
    // 类模板的友元实例:重载输出运算符
    template<class type>
    ostream &operator<<(ostream &os, const Array<type> &obj)
    {
        os << endl;
        for (int i=0; i < obj.high - obj.low + 1; ++i)  
            os << obj.storage[i] << '\t';
        return os;
    }
    
    // 类模板的友元实例:重载输出运算符
    template <class T> class Array;	                              //类模板Array的声明
    template<class T> ostream &operator<<(ostream &os, const Array<T>&obj); //输出重载声明
    //声明了函数模板operator<<的一个实参为T的实例是类模板Array的实参为T的实例的友元。
    
    template <class T>
    class Array {
        friend ostream &operator<<(ostream &, const Array<T> &);
    
    private:
        int low;  
        int high;
        T *storage;
    
    public:
        //根据low和high为数组分配空间。分配成功,返回值为true,否则返回值为false
        Array(int lh = 0, int rh = 0):low(lh),high(rh)
            { storage = new T [high - low + 1]; }
    
       //复制构造函数
        Array(const Array &arr);
        
        //赋值运算符重载函数
        Array &operator=(const Array & a);
    
        //下标运算符重载函数
        T & operator[](int index) {return storage[index - low];}
    
        //回收数组空间
        ~Array() {delete [] storage; }  
    };
    
    对于Array的任何一个实例:
    Array <int> array(10, 20);
    可以用
    cout << array;
    输出
  • 类模板应用:
  1. 单链表类及结点类:
    定义两个独立的类,由于链表操作经常需要访问结点的数据成员,因此,将同一模板参数的链表类设置为结点类的友元。
    // 单链表模板类中的结点类和链表类的定义
    template <class elemType> class linkList;
    template <class T> ostream &operator<<(ostream &, const linkList<T> &);
    template <class elemType> class Node ;
    
    template <class elemType>
    class Node {
        friend class linkList<elemType>;
        friend ostream &operator<<( ostream &, const linkList<elemType> &);
    private:
        elemType  data;
      	  Node <elemType> *next;
    public:
        Node(const elemType &x, Node <elemType> *N = NULL) { data = x; next = N;}
        Node( ):next(NULL) {}
        ~Node() {}
    };
    
    template <class elemType>
    class linkList {
        friend ostream &operator<<( ostream &, const linkList<elemType> &);
    protected:
        Node <elemType> *head;
        void makeEmpty();                         // 清空链表
    public:
        linkList() { head  = new Node<elemType>;  }
        ~linkList() {makeEmpty(); delete head;}
    
        void create(const elemType &flag);
    };
    
    // 单链表类的成员函数及友元函数的定义
    template <class elemType>
    void linkList<elemType>::makeEmpty()
    {
        Node <elemType> *p = head->next, *q;
        head->next=NULL;
        while (p != NULL) { q=p->next; delete p;  p=q;}
    }
    
    template <class elemType>
    void linkList<elemType>::create(const elemType &flag)
    {
        elemType tmp;
        Node <elemType> *p, *q = head;
     
        cout << "请输入链表数据," << flag << "表示结束" << endl;
    
        while (true) {
            cin >> tmp;
            if (tmp == flag) break;
            p = new Node<elemType>(tmp);
            q->next = p;
            q = p;
        }
    }
    
    template <class T>
    ostream &operator<<(ostream &os, const linkList<T> &obj)
    {
        Node <T> *q = obj.head->next;	 
    
        os << endl;
        while (q != NULL){ os << q->data;  q = q->next;  }
    
        return os;
    }
    
    实现对任何类型的单链表的操作
    linkList <int> intList;   //实例化定义一个整型的单链表
    intList.create(0);    //输入链表中的元素值,直到输入0为止。
    cout << intList;    //输出链表的所有元素
  2. 栈的类模板:
    类模板可以作为继承关系的基类,类模板的继承和普通的继承方法类似,只是在涉及基类时,都必须带上模板参数。
    栈是特殊的线性表,插入和删除只能在表的一端进行,允许插入和删除的一端称为栈顶,另一端为栈底,常用操作为进栈(push)和出栈(pop)。
    栈在单链表的基础上增加两个操作push和pop,push在单链表的表头插入一个元素,pop是删除单链表的表头元素。
    // 在链表类的基础上派生一个栈类
    template <class elemType>
    class Stack:public linkList<elemType> {
    public:
        void push(const elemType &data)
        {
             Node <elemType> *p = new Node<elemType>(data); 
             p->next = head->next; 
             head->next = p;
        }
        bool pop(elemType &data)                  //栈为空时返回false,出栈的值在data中
        {
             Node <elemType> *p = head->next;
    
             if ( p == NULL) return false;
             head->next = p->next; 
             data = p->data; 
             delete p;
    
             return true;
        }
    };
    
    为使Stack能访问Node的数据成员,必须将Stack设为Node的友元。
  • 编程规范:
    继承和组合提供一种重用对象代码的方法
    模板提供了重用源代码的方法,模板通过将类型作为参数,将处理不同类型数据的一组函数或类综合成一个函数或一个类,即函数模板和类模板,减轻代码量。
    类模板的形式参数可以是普通类型,称为非类型参数,非类型参数的实际参数必须是编译时的常量。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值