在C++中编写堆栈类的各种实现
Writing various implementations of Stack class in C++
我最近上了一门Java数据结构课程,我可以编写各种结构,并在Java中以各种方式轻松地实现它们。我目前正在将这些知识转移到C++世界,这有点不同。我目前为 Stack 接口编写了一个头文件(就像你在 Java for 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
对不起,格式! :)目前,我正在研究此堆栈的数组和链表实现。我知道头文件在它所包含的文件之间创建了一个链接。我想确保当我创建一个用于测试的堆栈时,我可以使用我用这个标头编写的两个实现。我也不确定我是否应该使用关键字 virtual 来制作官方界面。我知道在java中,当您声明堆栈的实现时,您将使用
Stack test = new ArrayStack();
对于C++来说,使用全球化头文件并使用此堆栈的不同实现是否相同?此外,这段代码是从C++的数据结构书中挖出来的,但遗憾的是,作者没有说是否将这些接口作为头文件,以及在哪里包含空堆栈的错误检查异常。我只是把它放在这个文件中。C++对我来说不是一个很好的语言,但我知道,如果我想构建更大的项目,如编译器、游戏、音频/视频播放器、操作系统,并为一门新语言编写 IDE,那么这个和 C 很重要掌握。如果可能的话,请给我任何关于我目前情况的见解,我将不胜感激。如果有人也能用C++解释指针和参考,我会非常接受这些知识。我相信E&是一个参考,但这本书没有具体说明。谢谢!:)
附言这就是我认为适用于使用标头的不同实现C++的方法......
#include "stack.h"
Stack test = new ArrayStack();
Stack test2 = new LinkedStack();
您的代码存在许多问题。
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 几乎完整地复制了这个错误,然后通过添加紧密耦合实际上使它变得更糟,这破坏了异常处理的最大优势之一。我只能说:这很痛;不要再这样做了。
我不打算评论该堆栈的质量、可用性、正确性或"C++性"。对于任何(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
。
- 如何在 c++ 中实现堆栈数组?
- 使用链表实现堆栈时出错
- 在给定程序中降低矢量数组实现堆栈的时间复杂度有哪些不同的可能方法?
- C++ 使用数组实现堆栈
- 关于在C 中实现堆栈的问题
- 使用链接列表在C 中实现堆栈
- c++ 中 if 语句中的多个条件(通过链表实现堆栈)
- C 内存泄漏错误在实现堆栈类时
- 尝试实现堆栈时C++未定义的引用
- 链表与动态数组用于使用向量类实现堆栈
- 在C++中实现堆栈类
- 可视化 在C++中实现堆栈
- 使用链接列表实现堆栈,调试断言失败
- 我怎样才能实现堆栈的向量
- 在没有动态内存分配的情况下实现堆栈
- 如何使用 std::vector 实现堆栈
- 在c++中使用链表实现堆栈
- 在哪里实现堆栈类(在非递归二进制搜索函数中使用)
- 使用双链表实现堆栈的错误
- 在c++中使用链表实现堆栈,复制构造函数