Основанный на узле класс стека (нуждаются в экспертной оценке),

Поскольку вся стандартная библиотека вводит функцию членства empty(), запрос, не команда, т.е. это означает, что "действительно ли Вы пусты?" не "выбросьте свое содержание".

clear() функция членства наследована от ios и используется для очистки состояния ошибки потока, например, если потоку файла установят состояние ошибки на eofbit (конец файла), то вызов clear() задержит состояние ошибки к goodbit (никакая ошибка).

Для очистки содержания stringstream, использование:

m.str("");

корректно, хотя с помощью:

m.str(std::string());

технически более эффективно, потому что Вы стараетесь не вызывать std::string конструктор, который берет const char*. Но любой компилятор в эти дни должен быть в состоянии генерировать тот же код в обоих случаях - таким образом, я просто пошел бы с тем, что более читаемо.

5
задан jkeys 6 July 2009 в 03:40
поделиться

7 ответов

Сначала , несколько общих комментариев, которые не вызывают проблем в этом случае, но некоторые могут сделать это в других ситуациях:

Вы должны генерировать исключения только, производные от класса std :: exception . C ++ позволяет использовать любой тип (например, строку в вашем случае), но это действительно плохая идея.

Члены класса должны быть инициализированы списками инициализаторов, как показано ниже: (в других случаях это может вызвать ошибки. Если вы не используете список инициализаторов, сначала создаются элементы по умолчанию, а затем оператор присваивания используется в теле конструктора для их перезаписи. Не все типы имеют оператор присваивания, или оператор присваивания может иметь нежелательные побочные эффекты, поэтому отказ от использования списков инициализаторов может быть проблематичным)

Node(int val, Node* ptrToLast) : value(val), prev(ptrToLast) {}
Stack() : size(0), top(NULL) {}

Именование ваших функций return * бессмысленно. Просто назовите их size () и topPtr () или, возможно, getSize () и getTopPtr ()

Second , вы не соблюдал правила. ;) или, возможно, getSize () и getTopPtr ()

Во-вторых , вы не следовали правилам. ;) или, возможно, getSize () и getTopPtr ()

Во-вторых , вы не следовали правилам. ;) Your stack class has two member variables, it was only allowed to have one. :)

Finally, things that break the stack:

This will crash, as you try to dereference a null pointer:

void test() {
  Stack s;
  s.peek(); // crashes
}

This will leak memory, as the allocated node is never deleted (the Stack destructor should do that):

void test() {
  Stack s;
  s.push(1);
}

The destructor should look something like this:

~Stack() {
  while (top != NULL){
    Node* next = top.returnPtr();
    delete top;
    top = next;
  }
}

This one should be fun too:

void test() {
  Stack s;
  s.push(1);
  Stack t(s);
  s.pop();
}

t.returnSize() will now return 1, but t.top points to the node in s that was just deleted. This should be fixed by defining a copy constructor and an assigment operator for the stack (and perhaps for the node class as well) The copy constructor would look like this:

Stack(const Stack& s);

and is called if you initialize one stack from another, like in the above. Оператор присваивания выглядит так:

Stack& operator= (const Stack& s);

и вызывается, если я назначаю один стек другому, после инициализации обоих:

Stack s;
Stack t;
t = s; // now both are already initialized, so the assigment operator is used, not the copy constructor

Роль этих функций состоит в том, чтобы гарантировать, что t станет копией с . Таким образом, каждый узел в s должен быть скопирован и назначен на t , чтобы они не указывали на одни и те же узлы. (Это замечательный пример вашего предыдущего вопроса о праве собственности, кстати. Узлы должны принадлежать ровно одному объекту Stack. Если он становится общим для нескольких, у вас есть проблема, и это лишь вопрос времени, прежде чем она превратится в сбой. )

И, наконец, если мы хотим немного поганить:

void test() {
  Stack s;
  s.push(1);
  s.push(2);
}

Что произойдет, если выделение памяти для второго узла не удастся (возможно, у нас закончилась память. Маловероятно, но это может случиться). Выдается исключение после увеличения размера. Размер s теперь будет 2, хотя top по-прежнему указывает на первый узел Если вы думаете, что это слишком маловероятная проблема, чтобы к ней относиться серьезно, представьте себе небольшое расширение вашего класса. Допустим, это был шаблон, чтобы он мог хранить другие типы, кроме int.

Это означает, что каждый раз, когда мы создаем узел, мы должны вызывать конструктор копирования типа значения. Это тоже может вызвать исключение. Мы не знаем, потому что не имеем ни малейшего понятия, какой тип пользователь может попытаться сохранить в стеке.

Понятие «безопасность исключений» важно, и его действительно очень сложно понять. В принципе, в каком состоянии находится ваш класс при возникновении исключения? Это все еще в действующем состоянии? (так должно быть всегда). Были ли потеряны какие-либо данные (в некоторых случаях это может быть неизбежно, в других этого можно избежать с осторожностью), и если данные были потеряны, были ли они удалены правильно? Деструкторы вызвали, память освободили? (очередной раз, так должно быть всегда)

И последнее, почему я был так уверен, что у вас будет хотя бы одна ошибка. Все ошибаются в вопросах безопасности исключений, включая меня большую часть времени. Написание правильной реализации такой простой вещи, как стек, в C ++ на удивление сложно. :)

Бонус:

В ответ на комментарии о конструкторах копирования, деструкторах и RAII, давайте просто сделаем все: Во-первых, позвольте мне сказать, что осталось, вероятно, одна или две ошибки, которых я не заметил. Во-вторых, вот код, который я тестировал, и все последующие его проходят. Не стесняйтесь запускать через него свой собственный код. (она должна работать как есть, за исключением того, что вам придется переименовать функцию getSize ): (переменная live - это та, которую я добавил для отладки. Я изменил свои реализации стека, поэтому что конструкторы увеличивают его, а деструкторы уменьшают его, просто чтобы убедиться, что количество построений и разрушений одинаково. Очевидно, это следует удалить из класса Stack, как только вы убедитесь, что он работает

Тестовый код

static int live; // debugging - keeps track of how many nodes have been allocated. Constructors add 1, destructors subtract. It should end in 0

#include "stack.h"
#include <iostream>
#include <cassert>

int main(){
    {
        // test stack creation + push
        Stack s;
        s.push(1);
        s.push(2);
        s.push(3);
        assert(s.getSize() == 3);

        Stack t;
        t.push(4);
        t.push(5);
        t.push(6);
        assert(t.getSize() == 3);

        // test assigment operator when both stacks contain data
        s = t;
        assert(s.getSize() == 3);
        assert(s.peek() == 6);
        assert(t.peek() == 6);

        Stack u(s);
        // test self assigment when stack contains data
        u = u;
        assert(u.getSize() == 3);
        assert(u.peek() == 6);


        Stack v;
        // test copy construction from stack with data
        Stack w(t);
        assert(w.getSize() == 3);
        assert(w.peek() == 6);
        assert(t.getSize() == 3);
        assert(t.peek() == 6);

        // test assignment operator when source is empty, destination contains data
        w = v;
        assert(w.getSize() == 0);
        assert(v.getSize() == 0);

        // test copy construction from empty stack
        Stack x(v);
        assert(x.getSize() == 0);
        assert(v.getSize() == 0);

        // test pop
        assert(t.pop() == 6);
        assert(t.pop() == 5);
        assert(t.pop() == 4);

        assert(s.pop() == 6);
        assert(s.pop() == 5);
        assert(s.pop() == 4);
    } // at this point, all allocated stacks go out of scope, so their destructors are called, so now is a good time to check for memory leaks:
    assert(live == 0);
}

Фиксированная реализация

Теперь сначала простое исправление. Конструктор копирования, оператор присваивания и деструктор были добавлены в класс Stack. Класс Node по-прежнему проблематичен, если используется изолированно, но пока он используется только через стек, мы можем обеспечить правильное копирование и удаление узлов. К сожалению, стеку теперь требуется доступ к Node.tail_ , чтобы копирование работало, поэтому я подружился с ним. Так что это работает, но не изящно.

#include <stdexcept> // for std::exception

class Stack;

class Node
{
    private: // changed naming to head/tail, which are commonly used in implementations of linked lists like this. The head is the current element, tail is a pointer to the remainder
        int head_;
        Node* tail_;

    public:
        friend class Stack; // this is necessary for the Stack copy constructor in order to modify the tail pointer after the node is created.
        // the elegant solution had been to define a copy constructor on the Node class as well, but we'll get to that

        int head() const { return head_; }
        Node* tail() const { return tail_; }

        Node(int val, Node* prev) : head_(val), tail_(prev) { ++live; } // use initializer list
        ~Node() { --live; }

        Node(const Node& other) : head_(other.head_), tail_(other.tail_){ ++live; }; // this could be omitted, but I use it to update 'live' for debugging purposes
};

class Stack
{
    private:
        Node* top;
//        int size; // we don't actually need the size at all, according to spec, so I removed it to keep things simple

        bool empty() const { return top == NULL;}

        void freeNodes() { // helper function to avoid duplicate code
            while (!empty()){
                pop();
            }
        }
    public:
        Stack() : top() {} // use initializer list
        ~Stack() { // destructor - the stack is being deleted, make sure to clean up all nodes
            freeNodes();
        }
        Stack(const Stack& other) : top() { // copy constuctor - we're being initialized as a copy of another stack, so make a copy of its contents
            if (other.empty()){
                return;
            }

            top = new Node(*other.top); // copy the first node, to get us started

            Node* otherNext = other.top->tail();            
            Node* current = top;

            while (otherNext != NULL){
                current->tail_ = new Node(*otherNext); // copy the current node
                current = current->tail(); // move to the next node
                otherNext = otherNext->tail();
            }
        }
        Stack& operator= (const Stack& other) {
            if (this == &other){ // If we assign this stack to itself (s = s), bail out early before we screw anything up
                return *this;
            }

            //now create the copy
            try {
                if (other.empty()){
                    freeNodes();
                    top = NULL;
                    return *this;
                }
                // naively, we'd first free our own stack's data before constructing the copy
                // but what happens then if an exception is thrown while creating the copy? We've lost all the current data, so we can't even roll back to a previous state
                // so instead, let's simply construct the copy elsewhere
                // this is almost straight copy/paste from the copy constructor. Should be factored out into a helper function to avoid duplicate code
                Node* newTop = new Node(*other.top); // copy the first node, to get us started

                Node* otherNext = other.top->tail();
                Node* current = newTop;

                while (otherNext != NULL){
                    current->tail_ = new Node(*otherNext); // copy the current node
                    current = current->tail(); // move to the next node
                    otherNext = otherNext->tail();
                }
                // once we're sure that we're able to create the copy of the other stack, we're ready to free the current one
                // this is a bit of duplicate code
                freeNodes();
                top = newTop;
                return *this;
            }
            catch (...){      // if an exception was thrown
                throw;        // and rethrow the exception so the application can deal with it
            }
        }

        // Node* returnTopPtr() { return top; } // not necessary. It's not a required part of the public interface, and class members can just access the top variable directly

        void push(int);
        int pop();
        int peek() const;

        int getSize() const{
            if (empty()){ return 0; }
            int i = 0;
            for (Node* cur = top; cur != NULL; cur = cur->tail_, ++i){}
            return i;
        }
};

void Stack::push(int value)
{ 
    Node* currentTop = top;
    top = new Node(value, currentTop); // this could throw an exception, but if it does, our stack will simply be left unchanged, so that's ok
}

int Stack::peek() const
{
    if (empty()){
        throw std::exception("Stack is empty");
    }
    return top->head();
}

int Stack::pop()
{    
    if (empty()){
        throw std::exception("Stack is empty");
    }

    Node* tail = top->tail();
    int result = top->head();
    delete top;
    top = tail;

    return result;
}

RAII v. 1

RAII - паршивое название для жизненно важной техники. Основная идея заключается в том, что каждое выделение ресурсов (включая, помимо прочего, выделение памяти с помощью new .) Должно быть заключено в класс, который при необходимости заботится о копировании или удалении ресурса. В нашем случае, вместо того, чтобы Stack отслеживать все узлы, мы могли бы немного упростить задачу, заставив класс Node выполнять большую часть этой работы. Теперь узел получил конструктор копирования, оператор присваивания и деструктор. Стек теперь просто должен отслеживать верхний узел ... почти. Это все еще немного сомнительно, потому что Stack.push выделяет новые узлы, но Узел теперь отвечает за большинство удалений. . Тем не менее, это позволяет нам избавиться от циклов, которые нам понадобились перед удалением или копированием списка узлов.

Стеку все еще требуется доступ к члену tail_ Node`, но на этот раз , Я сделал функцию доступа вместо того, чтобы сделать класс членом. В целом лучше, но я все еще недоволен.

#include <stdexcept>

class Node
{
private:
    int head_;
    Node* tail_;

public:
    int head() const { return head_; }
    Node* tail() const { return tail_; }
    Node*& tail() { return tail_; } // Another way to allow Stack to modify the tail. Needed for pop()


    Node(int val, Node* prev = NULL) : head_(val), tail_(prev) { ++live; }

    ~Node(){ --live; delete tail_; } // it is safe to call delete on a NULL pointer

    Node(const Node& other) : head_(other.head()), tail_(NULL) {
        ++live;
        if (other.tail() == NULL){
            return;
        }
        tail_ = new Node(*other.tail());
    }

    Node& operator= (const Node& other){
        if (this == &other){
            return *this;
        }
        head_ = other.head();
        if (other.tail() != NULL){
            return *this;
        }

        Node* oldTail = tail_;

        try {
            tail_ = new Node(*other.tail());
        }
        catch(...){
            tail_ = oldTail;
            throw;
        }
    }
};

class Stack
{
private:
    Node* top;

    bool empty() const { return top == NULL;}

public:
    Stack() : top() {} 
    ~Stack() {
        delete top;
    }

    Stack(const Stack& other) : top(){
        if (other.empty()){
            return;
        }

        top = new Node(*other.top);
    }

    Stack& operator= (const Stack& other) {
        if (this == &other){
            return *this;
        }

        Node* oldTop = top;

        try {
            top = NULL;
            if (other.top != NULL){
                top = new Node(*other.top);
            }
            delete oldTop;
            return *this;
        }
        catch (...){ 
            delete top;
            top = oldTop;
            throw;  
        }
    }

    void push(int);
    int pop();
    int peek() const;

    int getSize() const{
        if (empty()){ return 0; }
        int i = 0;
        for (Node* cur = top; cur != NULL; cur = cur->tail(), ++i){}
        return i;
    }
};

void Stack::push(int value)
{ 
    Node* currentTop = top;
    top = new Node(value, currentTop);
}

int Stack::peek() const
{
    if (empty()){
        throw std::exception("Stack is empty");
    }
    return top->head();
}

int Stack::pop()
{    
    if (empty()){
        throw std::exception("Stack is empty");
    }

    Node* tail = top->tail();
    int result = top->head();
    if (top != NULL){
        top->tail() = NULL; // detach top from the rest of the list
        delete top;
    }

    top = tail;

    return result;
}

RAII v. 2

Для решения упомянутых выше проблем я решил немного изменить свою стратегию. Узел теперь выполняет всю тяжелую работу, включая операции push / pop / peek. Стек - это просто тонкая обертка вокруг них. Оказалось, что это решило большинство проблем. Стек больше не должен возиться с частными членами узла , и у нас есть более четкие правила владения. Стек владеет верхним узлом, и каждый узел, не являющийся верхним, принадлежит его родительскому узлу - и на этот раз владелец и создает, копирует и уничтожает узел. Гораздо более согласованный.

Чтобы реализовать это, мне пришлось добавить функцию isLast в класс Node, потому что иначе, Stack. pop не мог узнать, пора ли удалить top . Я тоже не на 100% доволен этим решением (и если бы я не удалил член размера из стека, я мог бы использовать его для решения проблемы)

Но в целом это решение и чище, и проще, чем вышеуказанные попытки. (Это единственный язык, на отладку которого я потратил меньше часа;))

#include <stdexcept>

class Node {
public:
    Node(int value, Node* prev = 0) : head(value), tail(prev) { ++live;}
    ~Node() { 
        --live;
        delete tail;
    }

    Node(const Node& other) : head(other.head), tail(0) {
        ++live;
        if (other.tail != 0){
            tail = new Node(*other.tail);
        }
    }

    Node& operator= (const Node& other){
        if (this == &other){
            return *this;
        }

        Node* oldTail = tail;
        tail = new Node(*other.tail);
        delete oldTail;

        head = other.head;

        return *this;
    }

    void push(int val){
        tail = new Node(head, tail);
        head = val;
    }

    int peek(){
        return head;
    }
    void pop(){
        Node* oldTail = tail;
        head = tail->head;
        tail = tail->tail; // lol
        oldTail->tail = 0;
        delete oldTail;
    }

    bool isLast() { return tail == NULL; }

    int getSize() const{
        int i = 0;
        for (const Node* cur = this; cur != NULL; cur = cur->tail, ++i){}
        return i;
    }


private:
    Node* tail;
    int head;
};

class Stack {
public:
    Stack() : top(){}
    ~Stack() { delete top; }
    Stack(const Stack& other) : top() {
        if (other.empty()){
            return;
        }

        top = new Node(*other.top);
    }

    Stack& operator= (const Stack& other){
        if (this == &other){
            return *this;
        }

        Node* newTop = NULL;
        if (!other.empty()){
            newTop = new Node(*other.top);
        }
        delete top;
        top = newTop;

        return *this;
    }

    void push(int val){
        if (empty()) {
            top = new Node(val);
        }
        else {
            top->push(val); 
        }
    }

    int peek(){
        if (empty()){
            throw std::exception("Empty stack");
        }
        return top->peek();
    }
    int pop(){
        int result = peek();

        if (top->isLast()){
            delete top;
            top = NULL;
        }
        else {
            top->pop();
        }


        return result;
    }

    int getSize() const{
        if (empty()){ return 0; }
        return top->getSize(); 
    }

private:
    bool empty() const { return top == NULL; }
    Node* top;
};

Поскольку все это началось как попытка показать вам, почему C ++ не очень хороший язык для начинающих, я думаю, что могу с уверенностью сказать миссия выполнена!

:)

s единственный, на отладку которого я потратил меньше часа. ;))

#include <stdexcept>

class Node {
public:
    Node(int value, Node* prev = 0) : head(value), tail(prev) { ++live;}
    ~Node() { 
        --live;
        delete tail;
    }

    Node(const Node& other) : head(other.head), tail(0) {
        ++live;
        if (other.tail != 0){
            tail = new Node(*other.tail);
        }
    }

    Node& operator= (const Node& other){
        if (this == &other){
            return *this;
        }

        Node* oldTail = tail;
        tail = new Node(*other.tail);
        delete oldTail;

        head = other.head;

        return *this;
    }

    void push(int val){
        tail = new Node(head, tail);
        head = val;
    }

    int peek(){
        return head;
    }
    void pop(){
        Node* oldTail = tail;
        head = tail->head;
        tail = tail->tail; // lol
        oldTail->tail = 0;
        delete oldTail;
    }

    bool isLast() { return tail == NULL; }

    int getSize() const{
        int i = 0;
        for (const Node* cur = this; cur != NULL; cur = cur->tail, ++i){}
        return i;
    }


private:
    Node* tail;
    int head;
};

class Stack {
public:
    Stack() : top(){}
    ~Stack() { delete top; }
    Stack(const Stack& other) : top() {
        if (other.empty()){
            return;
        }

        top = new Node(*other.top);
    }

    Stack& operator= (const Stack& other){
        if (this == &other){
            return *this;
        }

        Node* newTop = NULL;
        if (!other.empty()){
            newTop = new Node(*other.top);
        }
        delete top;
        top = newTop;

        return *this;
    }

    void push(int val){
        if (empty()) {
            top = new Node(val);
        }
        else {
            top->push(val); 
        }
    }

    int peek(){
        if (empty()){
            throw std::exception("Empty stack");
        }
        return top->peek();
    }
    int pop(){
        int result = peek();

        if (top->isLast()){
            delete top;
            top = NULL;
        }
        else {
            top->pop();
        }


        return result;
    }

    int getSize() const{
        if (empty()){ return 0; }
        return top->getSize(); 
    }

private:
    bool empty() const { return top == NULL; }
    Node* top;
};

Поскольку все это началось как попытка показать вам, почему C ++ не очень хороший язык для начинающих, я думаю, что могу с уверенностью сказать миссия выполнена!

:)

s единственный, на отладку которого я потратил меньше часа. ;))

#include <stdexcept>

class Node {
public:
    Node(int value, Node* prev = 0) : head(value), tail(prev) { ++live;}
    ~Node() { 
        --live;
        delete tail;
    }

    Node(const Node& other) : head(other.head), tail(0) {
        ++live;
        if (other.tail != 0){
            tail = new Node(*other.tail);
        }
    }

    Node& operator= (const Node& other){
        if (this == &other){
            return *this;
        }

        Node* oldTail = tail;
        tail = new Node(*other.tail);
        delete oldTail;

        head = other.head;

        return *this;
    }

    void push(int val){
        tail = new Node(head, tail);
        head = val;
    }

    int peek(){
        return head;
    }
    void pop(){
        Node* oldTail = tail;
        head = tail->head;
        tail = tail->tail; // lol
        oldTail->tail = 0;
        delete oldTail;
    }

    bool isLast() { return tail == NULL; }

    int getSize() const{
        int i = 0;
        for (const Node* cur = this; cur != NULL; cur = cur->tail, ++i){}
        return i;
    }


private:
    Node* tail;
    int head;
};

class Stack {
public:
    Stack() : top(){}
    ~Stack() { delete top; }
    Stack(const Stack& other) : top() {
        if (other.empty()){
            return;
        }

        top = new Node(*other.top);
    }

    Stack& operator= (const Stack& other){
        if (this == &other){
            return *this;
        }

        Node* newTop = NULL;
        if (!other.empty()){
            newTop = new Node(*other.top);
        }
        delete top;
        top = newTop;

        return *this;
    }

    void push(int val){
        if (empty()) {
            top = new Node(val);
        }
        else {
            top->push(val); 
        }
    }

    int peek(){
        if (empty()){
            throw std::exception("Empty stack");
        }
        return top->peek();
    }
    int pop(){
        int result = peek();

        if (top->isLast()){
            delete top;
            top = NULL;
        }
        else {
            top->pop();
        }


        return result;
    }

    int getSize() const{
        if (empty()){ return 0; }
        return top->getSize(); 
    }

private:
    bool empty() const { return top == NULL; }
    Node* top;
};

Поскольку все это началось как попытка показать вам, почему C ++ не очень хороший язык для начинающих, я думаю, что могу с уверенностью сказать миссия выполнена!

:)

23
ответ дан 18 December 2019 в 05:27
поделиться

Хорошо, вот небольшой обзор. Имейте в виду, что кое-что будет моим личным мнением (точно так же, как и комментарий, который я написал.)

1 - всякий раз, когда вы обращаетесь к значению или методу, на который у вас есть указатель, сначала проверьте, действителен ли указатель! В противном случае это вызовет у вас ошибку. Например, если вы посмотрите перед тем, как вставить узел, вы вызовете NULL-> returnValue (). Это бесполезно.

2 - вам не нужен временный указатель, который вы используете в push, и вы должны проверить, можете ли вы успешно выделить память.

3 - вам нужен конструктор / деструктор копирования потому что ваш объект управляет динамически распределенными данными. Итак, что происходит, так это то, что по умолчанию c ++ копирует только ваши статические значения при копировании объекта и освобождает память только для статических переменных при разрушении объекта. Конструктор копирования и деструктор следят за тем, чтобы вы просматривали свою динамическую память и заботились о ней. (IE: для деструктора вы хотите удалить каждый узел.)

4 - returnTopPointer - ужасная идея - он дает людям доступ к вашим внутренним данным и позволяет им делать все, что они хотят.

Если вам нужна помощь с конструктор копирования и деструктор, просто дайте нам знать.

4
ответ дан 18 December 2019 в 05:27
поделиться

In Stack::push(), new can fail (out of memory), but you have already incremented size. It's unlikely to happen, but would lead to an inconsistent state.

You initialize top to NULL, so if you call peek() before pushing anything, you will crash. You should handle that. Similar bad things happen if you call pop() before calling push().

Consider using constructor initializer lists, e.g.:

   Node(int val, Node* ptrToLast) : value(val), prev(ptrToLast) {}
3
ответ дан 18 December 2019 в 05:27
поделиться

Есть много проблем со стилем, которые требуют обсуждения, но самая большая проблема заключается в том, что всякий раз, когда вы явно управляете динамической памятью в своем классе, вам, как минимум, нужен деструктор, определяемый пользователем, конструктор копирования и оператор присваивания, чтобы должным образом обрабатывать все проблемы с динамической памятью - иначе у вас будут утечки памяти и неопределенные удаления. Причина, по которой вам нужно явно определить эти функции, заключается в том, что вам нужна копия или назначение для создания копий структур, на которые указывают ваш указатель головы и последующих узлов, а не просто копирование адреса, на который они указывают (что является поведением по умолчанию для реализации, предоставленные компилятором) - и деструктор по умолчанию никогда не удаляет динамически выделенную память - вот почему вам необходимо определить деструктор, который это делает.

Вот разумная реализация, с которой вы можете поиграть, но, что более важно, противопоставьте ее подходу (включенному в конце), который использует вектор и не имеет никакого отношения к явному управлению памятью:

    
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(); }
};

3
ответ дан 18 December 2019 в 05:27
поделиться

Here's a minor suggestion - этот код:

Node* tempPtr = top;
top = new Node(value, tempPtr);

можно заменить на

top = new Node(value, top); 

, если только вы не хотите, чтобы дополнительный оператор присваивания сделал код более понятным. Если это так, вы можете сделать это:

Node* oldTopPtr = top;
top = new Node(value, oldTopPtr);  
2
ответ дан 18 December 2019 в 05:27
поделиться

Если вам нужен немного другой подход, вот как я (возможно) сделаю это. Основное отличие - это идиома копирования и обмена для оператора =, о которой, я думаю, никто не упоминал, поэтому вам может быть интересно посмотреть. Если jalf разрешено требовать конструктор копирования и operator =, даже если их нет в исходной спецификации, то мне разрешено требовать std :: swap; -)

Это передает тестовый код jalf. И для тех, кто предпочитает динамическую типизацию статической - первая версия, которая скомпилирована, прошла; -).

Я использовал только ограниченный RAII, потому что, как я упоминал в комментарии к ответу jalf, мне не нужен рекурсивный con / деструкторы. Есть несколько мест, которые являются «небезопасными» в том смысле, что определенные строки кода не должны быть простыми, и таковыми являются. Но конструктор копирования в 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);
    }
}
1
ответ дан 18 December 2019 в 05:27
поделиться

Выучив некоторые уроки из ответов, разработав стиль функций получения, сделав правильное копирование 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;    
}
0
ответ дан 18 December 2019 в 05:27
поделиться
Другие вопросы по тегам:

Похожие вопросы: