我最近根据指令编写了一个基于节点的堆栈类(代码之前的注释中的规范,取自论坛帖子)。我被告知要在这里发布它以供SO社区的一位更友好的成员审阅,所以在这里。为简单起见:我将定义与实现相结合。我知道何时使用头文件=)
主要是,我想知道我对删除的使用是否合理。在使用析构函数时,我仍然不确定自己;规范使它听起来像我应该删除节点的唯一时间应该是在流行期间,其他任何东西是不安全的。我也不明白在这里使用复制构造函数/赋值构造函数。
无论如何,关于代码的任何错误或评论都会很棒。
/*stack class
Background: the specs for this are, verbatim:
"Write a node-based stack class smile.gif
The stack is one of the most fundamental data structures used in computer science.
A stack has three basic operations:
push(value) - puts a value on the top of the stack
pop() - removes and returns the value that's on the top of the stack
peek() - return (but does not remove) the value off the top of the stack
Before creating the stack, you first have to create a Node class, which is a
very basic class with just two member variables: the value of the node, and a
pointer to the previous node in the stack.
Your stack should have only one member variable: the top node of the stack.
When you push, you add a node with the new value, with it's previous pointer
pointing towards the current stack top item. When you pop, you delete the top
node and then set the top of the stack to whatever that node's previous node
pointer was.
push, pop, and peek must all run in constant time.
You should write it so that it can only push (and pop/peek) ints."
*/
#include <string>
#include <iostream>
class Node
{
private:
int value;
Node* prev;
public:
int returnValue() { return value; }
Node* returnPtr() { return prev; }
/* constructors and destructors */
Node(int val, Node* ptrToLast)
{
value = val;
prev = ptrToLast;
}
};
class Stack
{
private:
Node* top;
int size;
public:
Stack() { size = 0; top = NULL; }
//added this after told the need for a destructor; not sure if it works
~Stack() {
while (top != NULL)
{
Node* tempPtr = top.returnPtr();
delete top;
top = tempPtr;
}
}
Node* returnTopPtr() { return top; }
void push(int);
int pop();
int peek();
//bonus; figured it might be worth knowing how many
//nodes are in a given stack
int returnSize();
};
int Stack::returnSize()
{
return size;
}
void Stack::push(int value)
{
++size;
Node* tempPtr = top;
top = new Node(value, tempPtr);
}
int Stack::peek()
{
return top->returnValue();
}
int Stack::pop()
{
const std::string throwStr = "You are trying to access/delete a node that doesn't exist. Seriously. ";
if (size == 0)
{
throw(throwStr);
}
--size;
Node* tempPtr = top->returnPtr();
int tempVal = top->returnValue();
delete top;
top = tempPtr;
return tempVal;
}
答案 0 :(得分:23)
答案 1 :(得分:4)
好的 - 所以这是一个快速回顾。请记住,有些事情将是我个人的意见(就像我写的评论一样。)
1 - 每当访问指针所在的值或方法时,首先检查指针是否有效!否则会导致您出现故障。例如,如果在推入节点之前查看,则调用NULL-&gt; returnValue()。这不好。
2 - 您不需要在推送和放大器中使用的临时指针。你应该检查一下你是否能够成功分配内存。
3 - 您需要一个复制构造函数/析构函数,因为您的对象管理动态分配的数据。所以会发生的是,默认情况下,c ++只在复制对象时复制静态值。只有在破坏对象时才会为静态变量设置内存。复制构造函数&amp;析构函数确保您通过动态记忆和搞定此事。 (IE:对于要删除每个节点的析构函数。)
4 - returnTopPointer是一个可怕的想法 - 它让人们可以访问您的内部数据&amp;让他们做他们想做的事。
如果您需要有关复制构造函数的帮助&amp;析构函数让我们知道。
答案 2 :(得分:3)
在Stack :: push()中,new可能会失败(内存不足),但您已经增加了大小。它不太可能发生,但会导致状态不一致。
您将top初始化为NULL,因此如果在推送任何内容之前调用peek(),则会崩溃。你应该处理。如果在调用push()之前调用pop(),也会发生类似的坏事。
考虑使用构造函数初始化列表,例如:
Node(int val, Node* ptrToLast) : value(val), prev(ptrToLast) {}
答案 3 :(得分:3)
有许多样式问题需要讨论 - 但最大的问题是,无论何时在类中明确管理动态内存,至少需要用户定义的析构函数,复制构造函数和赋值操作符来处理所有动态内存问题适当 - 否则你将有内存泄漏和未定义的删除。您需要显式定义这些函数的原因是因为您需要一个副本或赋值来复制头指针和后续节点所指向的结构,而不仅仅是复制它们指向的地址(这是默认行为)编译器提供的实现) - 默认的析构函数不会删除动态分配的内存 - 这就是你需要定义一个析构函数的原因。
这是一个可以使用的合理实现 - 但更重要的是,将它与使用向量的方法(包括在最后)进行对比,而不必处理显式内存管理:
class Stack { // a nested implementation class that the client needs to know nothing about struct Node { Node* prev; int value; Node(Node* prev, int value) : prev(prev), value(value) { } Node() : prev(0), value(0) { } ~Node() { delete prev; } // clean up after yourself // copy recursively until you hit a null pointer Node(const Node& o) : value(o.value), prev( prev ? new Node(*prev) : 0 ) { } }; Node* head_; int size_; public: Stack() : head_(0), size_(0) { } ~Stack() { delete head_; } // copy recursively until null Stack(const Stack& o) : head_(o.head_ ? new Node(*o.head_) : 0) { } // use copy constructor to do assignment Stack& operator=(const Stack& o) { Stack copy(o); Node* cur = head_; head_ = copy.head_; size_ = copy.size_; copy.head_ = cur; // copy's destructor will delete return *this; } void push(int value) { head_ = new Node(head_,value); ++size_; } int peek() const { if (!head_) throw "Attempting to peek off an empty stack!"; return head_->value; } int pop() { if (!head_) throw "Attempting to pop off an empty stack!"; int ret = head_->value; Node* cur = head_; // hold on to it so we can delete it head_ = head_->prev; // adjust my pointer cur->prev = 0; // if this is not set to 0, all nodes will be deleted delete cur; --size_; return ret; } int size() const { return size_; } }; // -- an easier way to write a stack of ints ;) struct VecStack { std::vector<int> vec; void push(int x) { vec.push_back(x); } int peek() const { if(vec.empty()) throw "Is Empty"; return *--vec.end(); // you may prefer vec[vec.size() - 1]; } int pop() { if (vec.empty()) throw "Is Empty"; int ret = *--vec.end(); vec.pop_back(); return ret; } int size() const { return vec.size(); } };
答案 4 :(得分:2)
这是一个小建议 - 这段代码:
Node* tempPtr = top;
top = new Node(value, tempPtr);
可以替换为
top = new Node(value, top);
除非您想要额外的赋值语句以使代码更清晰。如果是这种情况,你可以这样做:
Node* oldTopPtr = top;
top = new Node(value, oldTopPtr);
答案 5 :(得分:1)
如果你想要一个稍微不同的方法,这就是我(可能)这样做的方式。主要区别在于operator =的复制和交换习惯用语,我认为没有其他人提到过,所以你可能有兴趣看看。如果允许jalf要求复制构造函数和operator =,即使它们不在原始规范中,那么我也可以要求std :: swap; - )
这传递了jalf的测试代码。对于任何喜欢动态到静态类型的人 - 第一个编译,传递的版本; - )。
我只使用了有限的RAII,因为正如我在评论jalf的答案中提到的那样,我不想要递归的con / destructors。在某些意义上,某些代码行必须不是,而且有些地方是“不安全的”。但SafeNode上的复制构造函数是异常安全的,无需try-catch,因此实际可能抛出的部分将被覆盖。
#include <stdexcept>
#include <algorithm>
class Stack {
private:
struct Node {
Node *prev;
int value;
Node(int v, Node *p = 0): value(v), prev(p) { ++live; }
~Node() { --live; }
};
public:
Stack() : top(0), size(0) { }
Stack &operator=(const Stack &rhs) {
if (this != &rhs) {
Stack s(rhs);
swap(s);
}
return *this;
}
public:
void push(int value) {
top.node = new Node(value, top.node);
++size;
}
int pop() {
// get node and value at the top of the stack
Node *thisnode = top.get();
int retval = thisnode->value;
// remove top node from the stack and delete it
top.node = thisnode->prev;
--size;
delete thisnode;
return retval;
}
int peek() const {
return top.get()->value;
}
size_t getSize() {
return size;
}
void swap(Stack &rhs) {
top.swap(rhs.top);
std::swap(size, rhs.size);
}
private:
struct SafeNode {
Node *node;
SafeNode(Node *n) : node(n) {}
SafeNode(const SafeNode &rhs_) : node(0) {
const Node *rhs = rhs_.node;
if (rhs == 0) return;
SafeNode top(new Node(rhs->value));
Node *thisnode = top.node;
while(rhs = rhs->prev) {
thisnode->prev = new Node(rhs->value);
thisnode = thisnode->prev;
}
swap(top);
}
~SafeNode() {
while (node != 0) {
Node *nextnode = node->prev;
delete node;
node = nextnode;
}
}
void swap(SafeNode &rhs) { std::swap(node, rhs.node); }
Node *get() const {
if (node == 0) throw std::logic_error("Empty stack");
return node;
}
private: SafeNode &operator=(const SafeNode &);
};
private:
SafeNode top;
size_t size;
};
namespace std {
template <>
void swap<Stack>(Stack &lhs, Stack &rhs) {
lhs.swap(rhs);
}
}
答案 6 :(得分:0)
在从答案中学到一些教训,开发一种getter函数的风格,制作一个正确的复制ctor和dtor之后,我认为这个最新的代码比我的第一次尝试更好。
这是一些不那么糟糕,更好的内存管理代码:
/*stack class
Background: the specs for this are, verbatim:
"Write a node-based stack class smile.gif
The stack is one of the most fundamental data structures used in computer science.
A stack has three basic operations:
push(value) - puts a value on the top of the stack
pop() - removes and returns the value that's on the top of the stack
peek() - return (but does not remove) the value off the top of the stack
Before creating the stack, you first have to create a Node class, which is a
very basic class with just two member variables: the value of the node, and a
pointer to the previous node in the stack.
Your stack should have only one member variable: the top node of the stack.
ADDENDUM: also a size variable is allowed.
When you push, you add a node with the new value, with it's previous pointer
pointing towards the current stack top item. When you pop, you delete the top
node and then set the top of the stack to whatever that node's previous node
pointer was.
push, pop, and peek must all run in constant time.
You should write it so that it can only push (and pop/peek) ints."
*/
#include <string>
#include <iostream>
class Stack
{
private:
struct Node
{
public:
/* constructors and destructors */
Node(int value, Node* prev) : value_(value), prev_(prev) { }
Node(Node const& other) { value_ = other.value_; prev_ = other.prev_; }
//there is no ~Node, because the Stack does all the manual management
/* private data members */
private:
/* the value of the node */
int value_;
/* a pointer to the previous node on the stack */
Node* prev_;
/* getter functions */
public:
int value() { return value_; }
Node* prev() { return prev_; }
};
public:
/* constructors and destructors */
Stack() : size_(0), top_(0) { }
~Stack();
private:
/* pointer to the very top node; important to LIFO phil */
Node* top_;
/* size of the stack (main value is whether stack is empty */
int size_;
public:
//not for public use
void setTop(Node *top) { top_ = top; }
void setSize(int size) { size_ = size; }
Node* top() { return top_; }
int size() { return size_; }
public:
/* insertion, deletion, and traversal functions */
void push(int);
int pop();
int peek();
};
Stack::~Stack()
{
while (top() != NULL)
{
Node* tempPtr = top()->prev();
delete top_;
setTop(tempPtr);
}
}
void Stack::push(int value)
{
setSize(size() + 1);
Node *newTop = new Node(value, top());
setTop(newTop);
}
int Stack::peek()
{
return top()->value();
}
int Stack::pop()
{
if (size() == 0)
{
throw; //up
}
setSize(size() - 1);
Node* tempPtr = top()->prev();
int tempVal = top()->value();
delete top();
setTop(tempPtr);
return tempVal;
}
答案 7 :(得分:0)
push(value)
- 将值放在堆栈顶部pop()
- 删除并返回堆栈顶部的值peek()
- 返回(但不删除)堆栈顶部的值