用C ++编写Stack类的各种实现

时间:2015-06-16 20:02:16

标签: c++

我最近参加了Java数据结构课程,我可以编写各种结构,并以各种方式在Java中轻松实现它们。我目前正在将这些知识转移到C ++世界,这有点不同。我目前为Stack接口编写了一个头文件(就像你在Java中编写一个用于Stack的接口),我想以各种方式实现它(链表,数组,向量等),这样我就可以掌握实现的想法了。任何语言的结构。我目前使用C ++的问题是理解在我的Stack和引用(E&)中使用指针的概念,并确保我可以编写Stack.h的各种源代码实现。这是我当前的堆栈标题......

/*
 * File: stack.h
 * -------------
 * Interface file of the Stack class. Follows a last-in/ first-out (LIFO) order. Includes contracts for the method bodies of Stack.
*/

#ifndef _stack_h
#define _stack_h

/*
 * declaration of Stack using template with type E for any data type or object using the Stack structure.
 *
 */

  template <typename E>
  class Stack {

    public: 

    //constructor blank
    Stack();

    //destructor
    ~Stack();

    //constructor with integer size
    Stack(int);

    //number of items in stack.
    int size() const;

    //is stack empty?
    bool empty() const;

    //top element of stack
    const E& top() const throw(StackEmpty);

    //push e onto the stack.
    void push(const E& e);

    //remove top element from stack
    void pop() throw(StackEmpty);

    //pop and return.
    E popReturn();

    //returns a string form of stack.
    std::string toString();
};

// Error exception
class StackEmpty : public RuntimeException {
    public:
       StackEmpty(const std::string& err) : RuntimeException(err) {}
};

#endif

抱歉格式化! :) 目前我正在研究这个堆栈的Array和Linked List实现。我知道头文件在它包含的文件之间创建了一个链接。我想确保当我创建一个用于测试的堆栈时,我可以使用我用这个头文件编写的两个实现。我也不确定我是否应该使用关键字virtual或不使用官方界面。我知道在java中声明Stack的实现时你会使用

Stack test = new ArrayStack();

C ++使用全局化头文件并使用此Stack的不同实现是否相同?此代码也是用C ++中的数据结构书来挖掘的,但遗憾的是作者并没有说这些接口是否是头文件,以及在哪里包含错误检查空栈的异常。我把它放在这个文件中。 C ++对我来说并不是一个很好的语言,但我知道如果我想构建更大的项目,如编译器,游戏,音频/视频播放器,操作系统,以及为新语言编写IDE,那么这对C和C来说很重要。如果可能的话,请给我任何有关我目前情况的见解我会非常感激。如果有人也可以用C ++解释指针和引用,我会非常接受这些知识。我相信E&amp;是一个参考,但这本书没有指定。谢谢! :)

P.S。这就是我认为对C ++使用标头的不同实现的方法....

#include "stack.h"

Stack test = new ArrayStack();
Stack test2 = new LinkedStack();

2 个答案:

答案 0 :(得分:2)

您的代码存在许多问题。

template <typename E>

除非你真的需要运行时多态(你几乎肯定不会在这种情况下),你想要传递你将用作模板参数而不是使用继承的底层存储:

template <typename E, typename Container = std::vector<E> >

这样,您可以避免(例如)虚拟函数调用实际上不需要它的基本操作。

//constructor blank
Stack();

您可能根本不需要声明此默认构造函数。 (请参阅下面有关您声明的其他构造函数的注释)。

//destructor
~Stack();

如果您打算使用Stack作为基类,就像您似乎建议的那样,那么您想要构建析构函数virtual

//constructor with integer size
Stack(int);

除非你真的想要使用固定大小的底层容器,否则没有必要在这里指定大小。如果你想要指定一个大小,我可能会提供一个默认值(这是使前面的默认构造函数声明无关的部分)所以你最终会得到这样的结果:Stack(int size = 20);

//top element of stack
const E& top() const throw(StackEmpty);

这很糟糕。您的throw(StackEmpty)等动态异常规范被证明是错误的。他们已经被弃用了一段时间,很快就会消失 1 。此外,如果您要使用继承,那么大多数这些接口函数可能应该是virtual(可能是纯虚拟的)。

//push e onto the stack.
void push(const E& e);

这没关系,但是如果你想充分利用C ++,你可能想要添加一个带右值引用的重载。

//remove top element from stack
void pop() throw(StackEmpty);

此动态异常规范与上述问题相同。

//pop and return.
E popReturn();

这有一个严重的设计缺陷 - 如果E的复制构造函数抛出异常,它将(不可避免地)破坏数据。有两种众所周知的方法可以避免这个问题。一种是消除它并要求用户编写如下代码:

Foo f = myStack.top();
myStack.pop();

这样,如果复制构造函数抛出,myStack.pop()永远不会执行。另一个众所周知的可能性是:

void pop(E &dest) { 
    dest = top();
    pop();
}

无论哪种方式,我们最终得到异常安全代码 - 它完成(并且值从堆栈顶部传输到目标),否则它完全失败(并且数据保留在堆栈中),所以没有效果。

//returns a string form of stack.
std::string toString();

在C ++中,这通常被命名为to_string

// Error exception
class StackEmpty : public RuntimeException {
    public:
       StackEmpty(const std::string& err) : RuntimeException(err) {}
};

标准已定义std::runtime_error。使用它可能会更好,直到/除非你对C ++有足够的了解,才能准确地知道你想要做些什么。

另请注意:如果您采用初始建议并使堆栈成为纯模板而不是尝试使用继承,那么许多其他注释都没有实际意义。

最后,C ++的stack类最终会更像这样:

template <class T, class C = std::vector<T> >
class Stack { 
    C data;
public:
    void push(T const &t) { 
        data.push_back(t);
    }

    T top() const {  return data.back(); }

    void pop(T &d) { 
        if (data.empty())
            throw std::runtime_error("Attempt to pop empty stack");
        d = data.top();
        data.pop_back();
    }

    bool empty() const { return data.empty(); }
};

您已表明要使用不同的基础容器对其进行测试。以下是使用三个标准容器的方法:

#include "stack.h"
#include <vector>
#include <list>
#include <deque>
...
// These two are equivalent:
Stack<int, std::vector<int>> vs;
Stack<int> vs1;

Stack<int, std::list<int>> ls;  
Stack<int, std::deque<int>> ds;

1.是的,Java几乎完整地复制了这个错误,然后设法通过添加紧耦合实际上使它变得更糟,这破坏了异常处理的一个最大优点。关于我所能说的就是:那伤害;不要再这样做了。 功能

答案 1 :(得分:1)

我不打算评论该堆栈的质量,可用性,正确性或“C ++ - ness”。对于任何(C ++)程序员来说,Java程序员编写那个堆栈类是很痛苦的,这在Java中可能很好,但是在C ++中有更好的方法。

我要做的是尝试回答关于接口的问题并使用它们,与包含文件有关。 (再次强调,堆栈类本身并不好。)

你可能想要做的是拥有一个“stack.h”,其内容类似于:

template <typename E>
class Stack
{
public:
    // These are *usually* useless in an abstract interface 
    // Stack();
    // ~Stack();
    // Stack(int);

    // Every concrete class inheriting from this class must implement all these:
    virtual int size() const = 0;
    virtual bool empty() const = 0;
    virtual const E& top() const throw(StackEmpty) = 0;
    virtual void push(const E& e) = 0;
    virtual void pop() throw(StackEmpty) = 0;
    virtual E popReturn() = 0;
};

现在,你可能有一个像这样的“vector_stack.h”:

#include "stack.h"

template <typename E>
class VectorStack
    : public Stack<E>
{
public:
    VectorStack() {...}
    virtual ~VectorStack() {...}
    Virtual VectorStack(int) {...}

    virtual int size() const override {...}
    virtual bool empty() const override {...}
    virtual const E& top() const throw(StackEmpty) override {...}
    virtual void push(const E& e) override {...}
    virtual void pop() throw(StackEmpty) override {...}
    virtual E popReturn() override {...}
};

你要对你想要做的任何其他实现做同样的事情。

现在,在要使用所有这些的代码文件中,执行以下操作:

#include "VectorStack.h"
#include "ListStack.h"
#include "WhateverStack.h"

...
// I apologize to all C++ programmers, for not using unique_ptr<>!
Stack<int> * s1 = new VectorStack<int>;
Stack<int> * s2 = new ListStack<int>;
Stack<int> * s3 = new WhateverStack<int>;
...
...
// Don't forget to delete if you use raw pointers; you're
// not in Kansas anymore!
delete s3;
delete s2;
delete s1;

但这不是非常“C ++”,更不用说灵活或高效了。特别是在这种情况下,对于像堆栈这样的容器。但是如果你坚持使用那个界面(一般意义上说),这可能就是你必须要做的事情。

哦,不要忘记#include您使用的标头,例如<string>。请注意,如果您需要这些异常规范(不要这样做!),您的异常类的声明必须在之前声明您的类。

(顺便说一下,我很确定它是popReturn方法抛出,而不是pop。)