5


3

私は最近、命令ごとにノードベースのスタッククラスを書きました(フォーラム投稿から取られたコードの前のコメントの仕様)。 私はSOコミュニティの友好的なメンバーの一人によるレビューのためにここにそれを投稿するように言われた、それでここにある。 簡単にするために、定義を実装に置きます。 私はいつヘッダファイルを使うべきか理解しています=)

主に、自分のdeleteの使い方が正しいかどうか知りたいのです。 デストラクタを使用することになると私はまだ自分自身がわからない。スペックはそれが私がノードを削除するべきである唯一の時間がポップの間にあるべきであるように聞こえさせました、そして、他に何かは危険です。 私はまた、コピーコンストラクタ/代入コンストラクタの使い方を理解していません。

とにかく、コードに関するバグやコメントは素晴らしいでしょう。

/ *スタッククラス

背景:この仕様は逐語的です。

「ノードベースのスタッククラスを書くsmile.gif

スタックは、コンピュータサイエンスで使用される最も基本的なデータ構造の1つです。

スタックには3つの基本操作があります。

push(value) - スタックの一番上に値を置くpop() - スタックの一番上にある値を削除して返すpeek() - スタックの一番上から値を返す

スタックを作成する前に、まずNodeクラスを作成する必要があります。これは、ノードの値とスタック内の前のノードへのポインタという2つのメンバー変数のみを持つ非常に基本的なクラスです。

あなたのスタックはただ一つのメンバ変数を持つべきです:スタックのトップノード。 押すと、新しい値を持つノードを追加します。その前のポインタは現在のスタックトップ項目を指しています。 ポップしたら、一番上のノードを削除してから、スタックの一番上をそのノードの前のノードポインタに設定します。

push、pop、およびpeekはすべて一定の時間内に実行する必要があります。

あなたはそれがただ(そしてポップ/ピーク)イントをプッシュできるようにそれを書くべきです。 "* /

#include #include

クラスNode {private:int value;ノード*前

public:int returnValue(){戻り値; } Node * returnPtr(){return prev;} }

/ *コンストラクタとデストラクタ* /

Node(int val、Node * ptrToLast){値= val; prev = ptrToLast; ;}};

クラスStack {private:Node * top; intサイズ。

public:Stack(){size = 0; top = NULL。 //デストラクタが必要であると言われた後にこれを追加しました。それが動作するかどうかわからない〜Stack(){while(top!= NULL){Node * tempPtr = top.returnPtr();}トップを削除top = tempPtr; }}

Node * returnTopPtr(){return top; }

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

//ボーナス;特定のスタックに//ノードがいくつあるかを知っておく価値があるかもしれません。int returnSize(); ;

int Stack :: returnSize(){戻りサイズ; }

void Stack :: push(int value){
    ++size;
ノード* tempPtr = top; top = new Node(value、tempPtr); }

int Stack :: peek(){return top-> returnValue();} }

int Stack :: pop(){const std :: string throwStr = "存在しないノードにアクセスまたは削除しようとしています。 真剣に。 ";

if(size == 0){throw(throwStr); }

 - サイズ;

ノード* tempPtr = top-> returnPtr(); int tempVal = top-> returnValue();トップを削除top = tempPtr;

tempValを返します。 }

8 Answer


23


最初、この場合問題を起こさない一般的なコメントがいくつかありますが、他の状況ではそうなることもあります。

クラス `+ std

exception +`から派生した例外のみをスローする必要があります。 Cはあなたが(あなたのケースでは、文字列のように)任意の型をスローすることを可能にしますが、それは本当に悪い考えです。

以下に示すように、クラスメンバはイニシャライザリストで初期化する必要があります。(これは他の場合にエラーを引き起こす可能性があります。 イニシャライザリストを使用しない場合、メンバは最初にデフォルトで構築され、次に代入演算子がコンストラクタの本体で使用されて上書きされます。 すべての型が代入演算子を持っているわけではなく、あるいは代入演算子が望ましくない副作用を持つ可能性があるので、初期化子リストを使わないことは問題になる可能性があります。

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

関数に「+ return * 」という名前を付けると、意味がありません。 それらを単に「 size()」と「 topPtr()」、または「 getSize()」と「 getTopPtr()+」と呼ぶだけです

  • 2番目*、あなたは規則に従わなかった。 あなたのスタッククラスは二つのメンバ変数を持っています、それは一つしか持つことが許されていませんでした。 :)

最後に、スタックを壊すもの:

nullポインタを間接参照しようとするとクラッシュします。

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

割り当てられたノードは削除されないので、これはメモリリークを起こします(Stackデストラクタはそれをするべきです):

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

デストラクタは次のようになります。

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

これも楽しいはずです。

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

`+ t.returnSize()`は1を返しますが、 ` t.top `は削除されたばかりの ` s +`のノードを指します。 これは、コピーコンストラクタとスタック用(そしておそらくノードクラス用)の代入演算子を定義することによって修正されるべきです。

Stack(const Stack& s);

上記のように、あるスタックを別のスタックから初期化する場合に呼び出されます。 代入演算子は次のようになります。

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 `の_copy_になることを保証することです。 したがって、「 s 」の各ノードをコピーし、「 t +」に割り当てて、同じノードを指すのを避ける必要があります。 (これは以前の所有権についてのあなたの質問の素晴らしい例です、ところで。 ノードは1つのStackオブジェクトに属する必要があります。 それが複数の間で共有されるようになるならば、あなたは問題を抱えています、そしてそれはそれがクラッシュに変わる前にちょうど時間の問題です)

そして最後に、もう少し厄介なことをしたいのなら:

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

2番目のノードのメモリ割り当てが失敗した場合はどうなりますか(おそらくメモリ不足になりました)。 ありそうもないが、それは起こり得る。 サイズを増やすと、例外がスローされます。 `+ top +`がまだ最初のノードを指している場合でも、sのサイズは2になります。これがあまりに深刻な問題とは思えない場合は、クラスの小さな拡張を想像してください。 int以外の型を格納できるように、テンプレートだとしましょう。

つまり、ノードを作成するたびに、値型のコピーコンストラクタを呼び出す必要があります。 それも例外をスローする可能性があります。 ユーザーがスタックに格納しようとしている可能性のあるタイプがわからないため、わかりません。

「例外安全性」という概念は重要であり、正しく理解するのは本当に困難です。 基本的に、例外がスローされた場合、あなたのクラスはどの状態になりますか? まだ有効な状態ですか? (それは常にあるはずです)。 データを紛失した場合(避けられない場合もあれば、慎重に回避できる場合もあります)、そしてデータを紛失した場合は正しく削除されましたか。 デストラクタと呼ばれる、メモリ解放? (繰り返しますが、これは常に当てはまるはずです)

最後のポイントは、少なくとも1つのバグがあると確信していた理由です。 ほとんどの場合、私を含め、誰もが例外安全を誤って取得します。 スタックと同じくらい単純なsometihngの正しい実装をCで書くのは驚くほど困難です。 :)

ボーナス:

コピーコンストラクタ、デストラクタ、そしてRAIIについてのコメントに答えて、全部をやってみましょう。 第二に、これが私がテストしたコードです、そして以下のすべてがそれを渡します。 それを通してあなた自身のコードを自由に実行してください。 ( `+ getSize `関数の名前を変更する必要があることを除いて、そのまま動作するはずです):( ` live +`変数はデバッグ用に追加したものです。 コンストラクターとデストラクトの数が等しいことを確認するためだけに、Stack実装を変更して、コンストラクターがそれを増分し、デストラクタがそれをデクリメントするようにしました。 これが機能することを確認したら、明らかにこれは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
#include

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クラスを単独で使用すると問題が残りますが、Stackを通してのみ使用される限り、ノードが確実にコピーおよび削除されるようにすることができます。 残念なことに、 `+ Stack `は、コピーを機能させるために ` Node.tail_ +`にアクセスする必要があるため、友達になりました。 それでうまくいきますが、エレガントではありません。

#include  // 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

http://en.wikipedia.org/wiki/RAII [RAII]は重要なテクニックのお粗末な名前です。 基本的な考え方は、すべてのリソース割り当て(これに限定されないが、 `+ new `によるメモリ割り当てを含む)は、必要に応じてリソースをコピーまたは削除するクラスでラップする必要があるということです。 私たちの場合、 ` Stack `にすべてのノードを追跡させるのではなく、 ` Node `クラス自体にそのほとんどの機能を実行させることで、物事を少し単純化できます。 現在、 ` Node `にはコピーコンストラクタ、代入演算子、およびデストラクタも指定されています。 スタックは ` top `ノードを追跡するだけです... ほとんど。 ` Stack.push `が新しいノードを割り当てるため、まだ少し不確かですが、ほとんどの削除の責任は ` Node +`にあります。 。ただし、ノードリストを削除またはコピーする前に必要だったループを削除することはできます。

+ Stackはまだ+ tail_` + member of + `Node`にアクセスする必要がありますが、今回はクラスをメンバーにする代わりにアクセサー関数を作成しました。 全体的に、より良い、しかし私はまだそれに満足していない。

#include

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

上記の問題を解決するために、戦略を少し変更することにしました。 `+ Node `は、プッシュ/ポップ/ピークの操作を含む、すべての面倒な作業を行います。 ` Stack `は、これらの単純なラッパーです。 これは問題のほとんどを解決するために判明しました。 ` Stack `は ` Node +`のプライベートメンバーをいじる必要がなくなり、所有権に関する明確なルールがいくつかあります。 スタックはトップノードを所有し、トップ以外のすべてのノードはその親によって所有されます - そして今度は、所有者_both_がそのノードを作成、コピー、破棄します。 もっと一貫性があります。

これを実装するには、Nodeクラスに `+ isLast `関数を追加する必要がありました。それ以外の場合、 ` Stack.pop `には ` top +`を削除するタイミングかどうかを知る方法がありませんでした。 私はこの解決策に100%満足しているわけでもありません(そして、サイズメンバをスタックから削除していなければ、それを使って問題を解決することもできたでしょう)

しかし全体的に見て、これは上記の試みよりもきれいで単純です。 (たった1つの理由で、1時間以内のデバッグに費やしたのはこれだけです。 ;))

#include

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が初心者向けの言語ではないのかを説明する試みとして始まったので、ミッションが完了したと言っても問題ないと思います

:)


4


さて - ここで簡単にレビューしましょう。 私の個人的な意見が私の個人的な意見になることを覚えておいてください(私が書いたコメントのように)。

1 - あなたがポインタを持っている値やメソッドにアクセスするときはいつでも、ポインタが最初に有効であるかどうかチェックしてください! それ以外の場合は、これによってセグメンテーション違反が発生します。 たとえば、ノードを押し込む前に覗いた場合は、NULL→ returnValue()を呼び出します。 これはダメです。

2 - プッシュで使用している一時ポインタは不要です

3 - あなたのオブジェクトは動的に割り当てられたデータを管理するので、あなたはコピーコンストラクタ/デストラクタが必要です。 つまり、デフォルトでは、cはオブジェクトをコピーするときに静的な値だけをコピーします。 コピーコンストラクタ (IE:デストラクタの場合は各ノードを削除します)

4 - returnTopPointerは恐ろしい考えです - それは人々があなたの内部データにアクセスすることを可能にします

コピーコンストラクタについて手助けが必要な場合


3


Stack

push()では、newは失敗する可能性があります(メモリ不足)が、サイズはすでに増加しています。 起こりそうもないが、矛盾した状態につながるだろう。

topをNULLに初期化するので、何かをプッシュする前にpeek()を呼び出すとクラッシュします。 あなたはそれを扱うべきです。 push()を呼び出す前にpop()を呼び出すと、同様の悪いことが起こります。

コンストラクタ初期化子リストの使用を検討してください。

Node(int val、Node * ptrToLast):値(val)、前(ptrToLast){}


3


議論を正当化する多くのスタイルの問題があります - しかし最大の問題はあなたがあなたのクラスの中で明示的に動的メモリを管理する時はいつでも最低限ユーザ定義デストラクタ、コピーコンストラクタそしてすべての動的メモリ問題を適切に扱う代入演算子が必要ですそれ以外の場合は、メモリリークと未定義の削除があります。 これらの関数を明示的に定義する必要があるのは、ヘッドポインタとそれに続くノードが指す構造体のコピーを作成するためにコピーまたは代入が必要であり、それらが指すアドレスだけをコピーするのではないコンパイラによって提供される実装) - そしてデフォルトのデストラクタは動的に割り当てられたメモリを削除することは決してありません - そのためにデストラクタを定義する必要があります。

ここにあなたが遊ぶことができる合理的な実装があります - しかしより重要なことに、それはベクトルを使用し、明示的なメモリ管理をまったく扱う必要がない(最後に含まれる)アプローチと対比します。

class Stack {//クライアントがstruct Nodeについて何も知る必要がないネストされた実装クラス。 int値Node(Node * prev、int value):prev(prev)、value(value){} Node():prev(0)、value(0){}〜Node(){delete prev; //自分の後を片付ける

// nullポインタに到達するまで再帰的にコピーする 新しいノード(*前):0){}};

ノード* head_; int size_;

パブリック:

Stack():head_(0)、size_(0){}〜Stack(){head_を削除します。 }

// null Stack(const Stack)まで再帰的にコピーする 新しいノード(* o.head_):0){}

//コピーコンストラクタを使って代入を行う

head_ = copy.head_; size_ = copy.size_;

copy.head_ = cur; // copyのデストラクタはreturn * thisを削除します}

void push(int value){head_ = new Node(head_、value);
    ++size_;
} int peek()const {if(!head_) "空のスタックを覗き見しようとしています。";ヘッド_-> valueを返します。 } int pop(){if(!head_) "空のスタックからポップしようとしています。"; int ret = head _-> value;

ノード* cur = head_; //それを保持して、削除できるようにします。head_ = head _-> prev; //ポインタを調整します。cur-> prev = 0; //これが0に設定されていない場合、すべてのノードが削除されます

curを削除します。 - サイズ_; retを返す。 } int size()const {return size_;}; ;}};


// -- an easier way to write a stack of ints ;)
struct VecStack {std :: vector vec;

void push(int x){vec.push_back(x); int peek()const {if(vec.empty()) "Is Empty"をスローします。戻り値*  -  vec.end(); // vec [vec.size() -  1]をお勧めします。 int pop(){if(vec.empty())は「空です」をスローします。 int ret = *  -  vec.end(); vec.pop_back(); retを返す。 int size()const {return vec.size();}; ;}};


2


これはちょっとした提案です - このコード:

ノード* tempPtr = top; top = new Node(value、tempPtr);

に置き換えることができます

top = new Node(value、top);

コードをより明確にするために追加の代入文が必要でない限り。 もしそうなら、あなたはこれを行うことができます:

ノード* oldTopPtr = top; top = new Node(value、oldTopPtr);


1


ちょっと違うアプローチをしたいのなら、ここで私は(おそらく)それをやります。 主な違いは、operator =のコピーアンドスワップイディオムです。他の誰かが言っているとは思わないので、興味があるかもしれません。 jalfがコピーコンストラクタとoperator =を要求することを許可されている場合、それらが元の仕様に含まれていなくても、私はstd

swapを要求することを許可されます;-)

これはjalfのテストコードに合格します。 そして、動的型から静的型への移行を好む人のために - コンパイルした最初のバージョン; --)を渡しました。

私はjalfの答えに対するコメントで述べたように、再帰的なcon / destructorsは望まないので、私は限られたRAIIのみを使った。 特定のコード行を非公開にする必要があるという意味で「安全でない」場所がいくつかあります。 しかし、SafeNodeのコピーコンストラクタはtry-catchすることなく例外セーフであるため、実際にスローされる可能性がある部分については説明します。

#include #include

クラスStack {private:struct Node {Node * prev;} int値Node(int v、Node * p = 0):値(v)、前(p){ライブNode(){--live;} ;}};

public:Stack():top(0)、size(0){}スタック

public:void push(int value){top.node = new Node(value、top.node);}
        ++size;
}

int pop(){//スタックの一番上にあるノードと値を取得します。Node * thisnode = top.get(); int retval = thisnode-> value;

//一番上のノードをスタックから削除して削除するtop.node = thisnode-> prev; - サイズ;このノードを削除してください。

retvalを返します。 }

int peek()const {return top.get() - > value;}; }

size_t getSize(){戻りサイズ; }

void swap(スタック

private:struct SafeNode {Node * node;}; SafeNode(Node * n):ノード(n){} SafeNode(const SafeNode)

非公開:SafeNodeトップ。 size_tサイズ。

;

名前空間std {template <> void swap(Stack


0


答えからいくつかの教訓を学び、ゲッター関数のスタイルを開発し、適切なコピーコントロールとdtorを作成した後、私はこの最新のコードが私の最初の試みよりずっと良いと思います。

ここでは、それほど難しくなく、より優れたメモリ管理コードを紹介します。

/ *スタッククラス

背景:この仕様は逐語的です。

「ノードベースのスタッククラスを書くsmile.gif

スタックは、コンピュータサイエンスで使用される最も基本的なデータ構造の1つです。

スタックには3つの基本操作があります。

push(value) - スタックの一番上に値を置くpop() - スタックの一番上にある値を削除して返すpeek() - スタックの一番上から値を返す

スタックを作成する前に、まずNodeクラスを作成する必要があります。これは、ノードの値とスタック内の前のノードへのポインタという2つのメンバー変数のみを持つ非常に基本的なクラスです。

あなたのスタックはただ一つのメンバ変数を持つべきです:スタックのトップノード。

補遺:サイズ変数も許されます。

押すと、新しい値を持つノードを追加します。その前のポインタは現在のスタックトップ項目を指しています。 ポップしたら、一番上のノードを削除してから、スタックの一番上をそのノードの前のノードポインタに設定します。

push、pop、およびpeekはすべて一定の時間内に実行する必要があります。

あなたはそれがただ(そしてポップ/ピーク)イントをプッシュできるようにそれを書くべきです。 "* /

#include #include

class Stack {private:struct Node {public:/ *コンストラクタとデストラクタ* / Node(int value、Node * prev):value_(value)、prev_(prev){} Node(Node const

/ *プライベートデータメンバ* / private:/ *ノードの値* / int value_; / *スタック上の前のノードへのポインタ* / Node * prev_;

/ * getter関数* / public:int value(){戻り値_;} } Node * prev(){return prev_;} ;}};

public:/ *コンストラクタとデストラクタ* / Stack():size_(0)、top_(0){}〜Stack();

private:/ *一番上のノードへのポインタ。 LIFOにとって重要* / Node * top_; / *スタックのサイズ(主な値はスタックが空かどうかです* / int size_;

public://公衆用ではありません。void setTop(Node * top){top_ = top;} void setSize(int size){size_ = size;} } Node * top(){return top_;} } int size(){return size_;} }

public:/ *挿入、削除、トラバース関数* / void push(int); int pop(); int peek(); ;

Stack ::〜Stack(){while(top()!= NULL){Node * tempPtr = top() - > prev(); 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);

ノード* tempPtr = top() - > prev(); int tempVal = top() - > value(); top()を削除します。 setTop(tempPtr);

tempValを返します。 }


0


  • + push(value)+-スタックの一番上に値を置きます

  • + pop()+-の先頭にある値を削除して返します スタック

  • + peek()+-を返します(ただし、削除はしません)。 スタック