C++基础语法:类模板(容器)定义的内容

前言

       "打牢基础,万事不愁" .C++的基础语法的学习

引入

        类模板是泛型编程的重要组成部分,通常用来实现数据结构,做成容器类.从几个现成容器探寻类模板定义的内容.引用了参考书<C++ Prime Plus> 6th Edition(以下称"本书")的几个例子:数组栈,指针栈,链队列

类模板回顾

        类模板把数据类型---class类型或基本类型作为参数传入,设类型为T,则可以把T*(指针),T**(双重指针), T a[](数组)作为类模板属性.(自用)关于程序的一些概念4:C++泛型初探-CSDN博客

         类模板的类名和普通class类相同,构造函数形式和普通class类也相同.在类模板对象使用时,要类名后加上"<具体类型名>",在定义类模板方法时,要在类名后加上"<形参类型名>"

  数组栈 

        本书P569,复制书中stacktp.h代码如下:

// stacktp.h -- a stack template
#ifndef STACKTP_H_
#define STACKTP_H_
template <class Type>
class Stack
{
private:
    enum {MAX = 10};    // constant specific to class
    Type items[MAX];    // holds stack items
    int top;            // index for top stack item
public:
    Stack();
    bool isempty();
    bool isfull();
    bool push(const Type & item); // add item to stack
    bool pop(Type & item);        // pop top into item
};

template <class Type>
Stack<Type>::Stack()
{
    top = 0;
}

template <class Type>
bool Stack<Type>::isempty()
{
    return top == 0;
}

template <class Type>
bool Stack<Type>::isfull()
{
    return top == MAX;
}

template <class Type>
bool Stack<Type>::push(const Type & item)
{
    if (top < MAX)
    {
        items[top++] = item;
        return true;
    }
    else
        return false;
}

template <class Type>
bool Stack<Type>::pop(Type & item)
{
    if (top > 0)
    {
        item = items[--top];
        return true;
    }
    else
        return false; 
}

#endif

 数据部分:

private:
    enum {MAX = 10};    // constant specific to class
    Type items[MAX];    // holds stack items
    int top;            // index for top stack item

 其中:items[MAX]是物理容器,必须存在的.MAX是数组长度;top表示当前数组元素个数,同时也是指针,可以获得最后的那个元素items[--top].top值比数组索引值多1.添加第一个元素后,数组里有元素items[0],此时top等于1,依次类推.

初始化:建立数组,把当前元素个数top设为0.

算法:入栈和出栈

指针栈

本书P574,复制代码stcktp1.h,如下: 

// stcktp1.h -- modified Stack template
#ifndef STCKTP1_H_
#define STCKTP1_H_

template <class Type>
class Stack
{
private:
    enum {SIZE = 10};    // default size
    int stacksize;
    Type * items;       // holds stack items
    int top;            // index for top stack item
public:
    explicit Stack(int ss = SIZE);
    Stack(const Stack & st);
    ~Stack() { delete [] items; }
    bool isempty() { return top == 0; }
    bool isfull() { return top == stacksize; }
    bool push(const Type & item);   // add item to stack
    bool pop(Type & item);          // pop top into item
    Stack & operator=(const Stack & st);
};

template <class Type>
Stack<Type>::Stack(int ss) : stacksize(ss), top(0)
{
    items = new Type [stacksize];
}

template <class Type>
Stack<Type>::Stack(const Stack & st)
{
    stacksize = st.stacksize;
    top = st.top;
    items = new Type [stacksize];
    for (int i = 0; i < top; i++)
        items[i] = st.items[i];
}

template <class Type>
bool Stack<Type>::push(const Type & item)
{
    if (top < stacksize)
    {
        items[top++] = item;
        return true;
    }
    else
        return false;
}

template <class Type>
bool Stack<Type>::pop(Type & item)
{
    if (top > 0)
    {
        item = items[--top];
        return true;
    }
    else
        return false;
}

template <class Type>
Stack<Type> & Stack<Type>::operator=(const Stack<Type> & st)
{
    if (this == &st)
        return *this;
    delete [] items;
    stacksize = st.stacksize;
    top = st.top;
    items = new Type [stacksize];
    for (int i = 0; i < top; i++)
        items[i] = st.items[i];
    return *this; 
}

#endif

数据部分:

private:
    enum {SIZE = 10};    // default size
    int stacksize;
    Type * items;       // holds stack items
    int top;            // index for top stack item

其中:物理容器是指针*items,其本质上还是数组,数组长度由stacksize描述.top和上面数组栈含义相同.

初始化: new函数动态生成一个数组,长度为stacksize,默认为SIZE.

算法:入栈和出栈,加上了复制构造函数,和赋值构造函数.析构函数删除指针指向数据

        ---如果不复制类模板对象,可以不定义复制构造函数和赋值构造函数

指针栈分析

----指针栈的优点

        1>可以在生成栈的时候决定栈的长度,比数组栈灵活一点.(当然只是看起来灵活,实际上数组栈也很容易做到,一是在泛型传入表达式参数,二是定义构造函数时传入长度).

        2>析构函数把栈内数据全删除,节省空间.

        前提:指针栈用的数据类型是内置数据类型或者类的集合,而非指针类型.即Type类型的要求,如果是stack<int>,stack<double>,stack<string>,则可以达到效果.但如果是stack<int *>则不行.原理很简单,new产生基本类型或类类型的数组,delete则删除数组中数据(语法规定);new产生指针类型数组时,delete删除数组中所有指针,而这些指针指向的数据还在.

        3>数组栈能做内置数据类型和类的集合,不能做指针的集合.指针栈可以,本书给了例证.

----代码方面

        Type可以是指针类型

        1>双重指针的一种写法: 

items = new Type [stacksize]; //Type是指针类型,items双重指针

        2>指针传入指针的引用 

        含义和"变量传入变量的引用"相同,传入的变量可被修改,传入指针也可被修改

        在push()函数中,函数内部的item是指针,被赋给指针数组中的某个值  

template <class Type>
bool Stack<Type>::push(const Type & item)    
{
    if (top < stacksize)
    {
        items[top++] = item;                //items双重指针
        return true;
    }
    else
        return false;
}

         pop()函数,item被修改

template <class Type>
bool Stack<Type>::pop(Type & item)
{
    if (top > 0)
    {
        item = items[--top];
        return true;
    }
    else
        return false;
}

 本质上new和引用的用法没有改变:

new函数生成"某个类型"的数组,返回指向该数组的指针,不管"某个类型"是基本类型还是指针类型; 引用做形参时传入"某个类型"的变量或引用,不管是否指针类型

整理概念,一句话概括:new运算符支持指针类型,引用支持指针类型

 链队列

         本书P613,章节号15.2.2模板中的嵌套,程序清单15.5queuetp.h,代码如下

// queuetp.h -- queue template with a nested class
#ifndef QUEUETP_H_
#define QUEUETP_H_

template <class Item>
class QueueTP
{
private:
    enum {Q_SIZE = 10};
    // Node is a nested class definition
    class Node
    {
    public:
        Item item;
        Node * next;
        Node(const Item & i):item(i), next(0){ }
    };
    Node * front;       // pointer to front of Queue
    Node * rear;        // pointer to rear of Queue
    int items;          // current number of items in Queue
    const int qsize;    // maximum number of items in Queue
    QueueTP(const QueueTP & q) : qsize(0) {}
    QueueTP & operator=(const QueueTP & q) { return *this; }
public:
    QueueTP(int qs = Q_SIZE);
    ~QueueTP();
    bool isempty() const
    {
        return items == 0;
    }
    bool isfull() const
    {
        return items == qsize;
    }
    int queuecount() const
    {
        return items;
    }
    bool enqueue(const Item &item); // add item to end
    bool dequeue(Item &item);       // remove item from front
};

// QueueTP methods
template <class Item>
QueueTP<Item>::QueueTP(int qs) : qsize(qs)
{
    front = rear = 0;
    items = 0;
}

template <class Item>
QueueTP<Item>::~QueueTP()
{
    Node * temp;
    while (front != 0)      // while queue is not yet empty
    {
        temp = front;       // save address of front item
        front = front->next;// reset pointer to next item
        delete temp;        // delete former front
    }
}

// Add item to queue
template <class Item>
bool QueueTP<Item>::enqueue(const Item & item)
{
    if (isfull())
        return false;
    Node * add = new Node(item);    // create node
// on failure, new throws std::bad_alloc exception
    items++;
    if (front == 0)         // if queue is empty,
        front = add;        // place item at front
    else
        rear->next = add;   // else place at rear
    rear = add;             // have rear point to new node
    return true;
}

// Place front item into item variable and remove from queue
template <class Item>
bool QueueTP<Item>::dequeue(Item & item)
{
    if (front == 0)
        return false;
    item = front->item;     // set item to first item in queue
    items--;
    Node * temp = front;    // save location of first item
    front = front->next;    // reset front to next item
    delete temp;            // delete former first item
    if (items == 0)
        rear = 0;
    return true; 
}

#endif

 数据部分:

private:
    enum {Q_SIZE = 10};
    // Node is a nested class definition
    class Node
    {
    public:
        Item item;
        Node * next;
        Node(const Item & i):item(i), next(0){ }
    };
    Node * front;       // pointer to front of Queue
    Node * rear;        // pointer to rear of Queue
    int items;          // current number of items in Queue
    const int qsize;    // maximum number of items in Queue
    QueueTP(const QueueTP & q) : qsize(0) {}
    QueueTP & operator=(const QueueTP & q) { return *this; }

        1>表示结点的内部类Node.链表为底层的数据结构,统一写法,把要加入数据集合的数据做成结点.为什么内部类?因为只有外部类QueueTP与之有联系.

        2>Node* front //前结点,相当于"物理容器",可以用他遍历整个队列.

        3>Node* rear //尾结点,标识链队列里最后一个结点.

        4>items  //队列当前元素个数

        5>qsize //队列允许最大元素个数,默认为Q_SIZE,由默认参数传入

   初始化:

        前结点,尾结点指空,当前元素个数置0;设置最大元素个数.

   算法:

        构造函数,析构函数,判断满,判断空,入队,出队.

链队列分析 

        1>入队采用"尾插法",在队列尾部插入结点(这也是Node* rear意义所在).

        和链表相比较,多了一个指向尾结点的指针. 没有采用"头结点",不存在一个头部数据为空的结点,在第一个元素插入时,前结点指向了他.所以插入结点时区分front等于0和不等于0的情况编码.

        头结点的插入不需要用if区分编码.那么可以用头插法吗?队列的数据结构要求"先进先出"(FIFO),头插法的形式方便实现"后进先出",适合做链栈.理论上可以做,但是要麻烦一些.可以试试看.

===============================内容分隔线:入队细节==========================

如果自己写,估计会写成下面这样:

    Node * add = new Node(item);    // create node
    items++;
    if (front == 0)         // 队列为空时
        front = add;        // 前结点指向新结点
        rear=add;           // 尾结点指向新结点
    else
        add->next=0;        //插入时先说明后面一个结点是空
        rear->next = add;   //前面结点是rear
        rear = add;         //尾结点指向新结点
    return true;

        好像和 示例有点不一样.仔细看是一样的.rear=add;被提取到外面.add->next=0;在生成add结点时,调用Node的构造方法已经说明了,无需多写.所以示例代码写得更加优雅.

        在代码正确的基础上可以追求一种编码的美感

===============================内容分隔线:入队细节==========================

        2>析构函数:从前结点开始挨个删除队列内元素.

        3>出队操作.删除前结点,当前元素个数减1

        此外item=front->item;这一句乍一看没用上,其实是把item的值传给了引用形参.

类模板(容器类)小结

        学习的根本目的之一是抽取一些固定的解决问题模式,在三个类模板的示例中,得到一些共通点:

        1>容器类属性基本上围绕着"物理容器"而来,这个数组(链表)的当前元素个数,限制最多个数,当前指针等.(少一个固然不行,多两个似乎无所谓).也有和算法相关的属性,比如链队列里的rear.

        2>对于链表做物理层的容器,必然有个结点的内部类,有个指向头结点的指针做物理容器.

        3>容器类的构造方法,建立起没有数据元素的数据集合.

        栈和队列是基本的数据结构,从中了解容器类的基本信息.

  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

jllws1

你的鼓励是我创作的动力,谢谢

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值