我最近参加了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();
答案 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
。)