首页 > 代码库 > C++ Primer 学习笔记_80_模板与泛型编程 --类模板成员

C++ Primer 学习笔记_80_模板与泛型编程 --类模板成员

模板与泛型编程

--类模板成员

引言:

这一节我们介绍怎样实现前面提到的Queue模板类。

标准库将queue实现为其他容器之上的适配器。为了强调在使用低级数据结构中设计的编程要点,我们将Queue实现为链表。实际上,在我们的实现中使用标准库可能是个更好的决定!!-_-



1Queue的实现策略

如图所示,我们实现两个类:

1QueueItem类表示Queue的链表中的节点,该类有两个数据成员itemnext

a. item保存Queue中元素的值,它的类型随Queue的每个实例而变化;

b. next是队列中指向下一QueueItem对象的指针。

2Queue类提供16.1.2节描述的接口函数,Queue类也有两个数据成员:headtail,这些成员是QueueItem指针。

像标准容器一样,Queue类将复制指定给它的值。



2QueueItem

template <typename Type> class QueueItem
{
    QueueItem(const Type &t):item(t),next(0) {}

    Type item;
    QueueItem *next;
};

每当实例化一个Queue类的时候,也将实例化QueueItem的相同版本。如:如果创建Queue<int>,则将实例化一个伙伴类QueueItem<int>

QueueItem类为私有类–他没有公共接口。我们定义这个类只是为了实现Queue,并不想用于一般目的。需要将Queue类设为QueueItem的友元,以便Queue的成员能够访问QueueItem的成员。

【注解】

在类模板的作用域内部,可以用它的非限定名字引用该类。



3Queue

template <typename Type> class Queue
{
public:
    Queue():head(0),tail(0) {}
    Queue(const Queue &Q):head(0),tail(0)
    {
        copy_elems(Q);
    }

    Queue &operator=(const Queue &);
    ~Queue()
    {
        destroy();
    }

    Type &front()
    {
        return head -> item;
    }
    const Type &front() const
    {
        return head -> item;
    }

    void push(const Type &);
    void pop();
    bool empty() const
    {
        return head == 0;
    }

private:
    QueueItem<Type> *head;
    QueueItem<Type> *tail;

    void destroy();
    void copy_elems(const Queue &);
};

 private实用函数destroycopy_elems将完成释放Queue中的元素以及从另一Queue复制元素到这个Queue的任务。复制控制成员用于管理数据成员headtail,headtail是指向 Queue中首尾元素的指针,这些成员是QueueItem<Type>类型的值。

Queue类实现了几个成员函数:

1)默认构造函数,将headtail指针置0,指明当前Queue为空。

2)复制构造函数,初始化headtail,并调用copy_item从它的初始器中复制元素。

3)几个front函数,返回头元素的值。这些函数不进行检查:



4、模板作用域中模板类型的引用

通常,当使用类模板的名字的时候,必须指定模板形参。但是有个例外:在类本身的作用域内部,可以使用类模板的非限定名。例如,在默认构造函数和复制构造函数的声明中,名字QueueQueue<Type>缩写表示。实质上,编译器推断,当我们引用类的名字时,引用的是同一版本。因此,复制构造函数定义其实等价于:

    //在类中这样写也不会出错
    Queue<Type>(const Queue<Type> &Q):head(0),tail(0)
    {
        copy_elems(Q);
    }

但是编译器会为类中使用的其他模板的模板形参进行这样的推断,因此,在声明伙伴类QueueItem的指针时,必须指定类型形参:

    QueueItem<Type> *head;
    QueueItem<Type> *tail;  //OK

这些声明指出,对于Queue类的给定实例化,headtail指向为同一模板形参实例化QueueItem类型的对象,,Queue<int>实例化的内部,head tail的类型是QueueItem<int>*。在 headtail成员的定义中省略模板形参将是错误的:

    QueueItem *head;	//Error
    QueueItem *tail;	//Error

//P547 习题16.31
template <typename elemType> class ListItem;
template <typename elemType> class List
{
public:
    List();
    List(const List &);
    List &operator=(const List &);
    ~List();

    void insert(ListItem<elemType> *ptr,elemType value);

    ListItem<elemType> *find(elemType value);

private:
    ListItem<elemType> *front;
    ListItem<elemType> *end;
};


一、类模板成员函数

类模板成员函数的定义具有如下形式:

1、必须以关键字template开头,后接类的模板形参表;

2、必须指出它是哪个类的成员;

3、类名必须包含其模板形参。

如:

template <class Type> ret-type Queue<Type>::member-name

1destroy函数

template <typename Type>
void Queue<Type>::destroy()
{
    while (!empty())
    {
        pop();
    }
}

2pop函数

template <typename Type>
void Queue<Type>::pop()
{
    QueueItem<Type> *p = head;
    head = head -> next;
    delete p;
}

  pop函数假设用户不会在空Queue上调用pop。该函数的唯一技巧是:记得保持指向该元素的一个单独指针,以便在重置head指针之后可以删除元素



3push函数

template <typename Type>
void Queue<Type>::push(const Type &val)
{
    QueueItem<Type> *pt = new QueueItem<Type>(val);

    if (empty())
    {
        head = tail = pt;
    }
    else
    {
        tail -> next = pt;
        tail = tail -> next;
    }
}

这个函数首先分配新的QueueItem对象,用传递的值初始化它。几点说明:

1QueueItem构造函数将实参复制到QueueItem对象的 item成员。像标准容器所做的一样,Queue类存储所给元素的副本。

2)如果item类类型,item的初始化使用item所具有任意类型复制构造函数

3QueueItem构造函数还将next指针初始化为0,以指出该元素没有指向其他QueueItem对象。在Queue的末尾添加元素,将next0正是我们所希望的。

创建和初始化新元素之后,必须将它链入Queue,如果Queue为空,则headtail都应该指向这个元素。如果Queue中已经有元素了,则使当前tail元素指向这个新元素。旧的tail不再是最后一个元素了,这也是通过使tail指向新构造的元素指明的



4copy_elem函数

该函数的目的就是提供给赋值操作符和复制构造函数使用:

template <typename Type>
void Queue<Type>::copy_elems(const Queue &orig)
{
    //循环进行直至获得orig中最后一个元素之后,pt为0
    for (QueueItem<Type> *pt = orig.head; pt ; pt = pt -> next)
    {
        push(pt -> item);
    }
}

【附:赋值操作符[P551习题16.32]

template <typename Type>
Queue<Type> &Queue<Type>::operator=(const Queue<Type> &rhs)
{
    destroy();
    copy_elems(rhs);

    return *this;
}


5、模板成员函数的实例化

类模板的成员函数本身也是函数模板。像任何其他函数模板一样,需要使用类模板的成员函数产生该成员的实例化。与其他函数模板不同的是,在实例化类模板成员函数的时候,编译器不执行模板实参推断,相反,类模板成员函数的模板形参由调用该函数的对象的类型确定。例如,当调用Queue<int>类型对象的push成员时,实例化push函数为:

	void Queue<int>::push(const int &val);

对象的模板实参能够确定成员函数模板实参,这一事实意味着,调用类模板成员函数比调用类似函数模板更灵活。用模板形参定义的函数函数形参的实参允许进行常规转换:

template <typename Type>
void f(const Type &);

int main()
{
    Queue<int> qi;
    short s = 42;
    int i = 42;
    qi.push(s);		//调用Queue<int>::push(const int&) 
    qi.push(i);		//调用Queue<int>::push(const int&) 

    f(s);				//调用f(const short&)
    f(i);				//调用f(const int&)
}

6、何时实例化类和成员

类模板的成员函数只有为程序所用才进行初始化。这样,用于实例化模板的类型只需满足实际使用的操作的要求

定义模板类型的对象时,该定义导致实例化类模板。定义对象也会实例化用于初始化该对象的任一构造函数,以及该构造函数调用的任意成员

	Queue<string> qs;   //实例化Queue<string> 类和 Queue<int>::Queue()
	qs.push("hello");   //实例化Queue<string>::push

第一个语句实例化Queue类及其默认构造函数,第二个语句实例化push成员函数。

template <typename Type>
void Queue<Type>::push(const Type &val)
{
    QueueItem<Type> *pt = new QueueItem<Type>(val);

    if (empty())
    {
        head = tail = pt;
    }
    else
    {
        tail -> next = pt;
        tail = tail -> next;
    }
}

将依次实例化伙伴类QueueItem<string>及其构造函数。

Queue类中的QueueItem成员是指针。类模板指针定义不会类进行初始化,只有用到这样的指针时才会对类进行实例化。因此,在创建Queue对象时不会实例化QueueItem类,相反在使用诸如frontpushpop这样的Queue成员时,才会实例化QueueItem类。