C++single linked listシングルチェーンテーブルの作成と実行(ダイナミックストレージやメモリ漏洩などの問題を考慮したヘッダノードあり)(1)


これはC++programming IIという授業で配置された小さな作業であり、核心は動的記憶とポインタの理解(特にnewとdeleteの使用)である.時間をかけて、途中でcsdnに来て資料を探しても、関連内容を見た経験が共有されていないので、ブログを書いて成果と心得を記録します.コードのそばには簡単な注釈があり、中英が混在している.重要ではない私は翻訳していません.問題があればコメントを歓迎します.
まず、head:ヘッダノードtail:テールノード/テールを宣言します.
作成(ヘッダファイル)
LinkedList.h:
#ifndef INTLIST_H
#define INTLIST_H

#include 
using namespace std;

struct IntNode {
	int value;
	IntNode *next;
	IntNode(int value) : value(value), next(nullptr) {}
};

class IntList {
 private:
	IntNode *head;
	IntNode *tail;
 public:
	IntList();
	IntList(const IntList &cpy);
	~IntList();
	void push_front(int);
	void pop_front();
	bool empty() const;
	const int & front() const;
	const int & back() const;
	IntList& operator=(const IntList &rhs);
	void push_back(int);
	void clear();
	void selection_sort();
	void insert_ordered(int);
	void remove_duplicates();
	friend ostream & operator<

LinkedList.cppの構造関数:
IntList::IntList(): head(nullptr), tail(nullptr) {}

こうぞうかんすう
肝心なのは来て、注意しないとこの一歩からすでにmemory leakとdangling pointerが生まれています
IntList::~IntList(){
   if (head != nullptr){
     // 1. go to head 
     // 2. delete head and set it to null
     // 3. use the next of head to go to the next node in the IntList
     // 4. delete that node and set it to null
     // 5. repeat 3 and 4 until it reaches tail
     while(head != nullptr){
         IntNode *tmp = head->next;  //      next    nullptr
         delete head;
         head = tmp; //          nullptr
     }
   }
  
}

2つのpush関数、先頭と末尾からノードを追加
  • ノードを追加する唯一の方法.
  • このときの新しいノードはすべて手動で新しいメモリ(new)を割り当てる必要があることに注意して、削除する時手動で削除する(delete)
  • が必要です
  • は、この値num、next nullptrのノードに新しいアドレスを動的に割り当てます.
  • はその後、チェーンテーブルに追加される.

  • この場合、3つの状況に注意してください.
  • チェーンテーブルが空です:head=新しいノード+tail=新しいノード
  • チェーンテーブルには1つのノードしかありません:前からpushするとhead=新しいノード、後ろからtail=新しいノード
  • とします.
  • チェーンテーブルには、上の
  • と同じ2つ以上のノードがあります.
    push_front(int)
    void IntList::push_front(int num){  
        IntNode* node = new IntNode(num); 
        if (empty()){ 
        //consider three different senarios: 1. empty; 2. only one node(head == tail == nullptr); 3. two or more than two
            tail = node; 
            head = node;
        }else{
            node->next = head;
            head = node;
            }
        } //           ?  
    }
    

    push_back(int)
    void IntList::push_back(int value_){
        IntNode* node = new IntNode(value_);
        if (head == nullptr){
            push_front(value_);
        }
        else{
            tail->next = node;
            tail = node;
        }
    }
    

    先頭ノードと末尾ノードの値を返します.
    front() & back()
    const int & IntList::front() const{
        return head->value;
    }    
    const int & IntList::back() const{
        return tail->value;
    }
    

    empty()
    bool IntList::empty() const{
        return (head == nullptr); // if checking both nodes, when deleting, set  to null
    }
    

    ノードを最初から削除
  • ノードを削除する第1の態様
  • .
  • ノードが1つしかない場合を考慮してください.そうしないと、プログラムはcrashになります.head&tailを手動で削除しnullptr(nullを勝手に設定する良い習慣)
  • に設定してください.
    pop_front()
    void IntList::pop_front(){
        // the same three senarios
        if (!empty()){ 
            if (head != nullptr){
                IntNode* node = head->next;
                delete head;
                head = node;
            }
            else if(head == tail){
                delete head;
                delete tail;
                head = nullptr;
                tail = nullptr; // avoid the dangling pointer
            }
        }
    }
    

    すべてのノードを削除
  • ノードを削除する第2の態様
  • .
  • は、構造関数の実行内容と同じ
  • である.
  • 目的:すべてのノードをクリアし、チェーンテーブル
  • をリフレッシュする
    clear()
    	void IntList::clear(){
        if (head != nullptr){
         // 1. delete head and set it to null
         // 2. use the next of head to go to the next node in the IntList
         // 3. delete that node and set it to null
         // 4. repeat 2 and 3 until it reaches tail
         while(head != nullptr){
             IntNode *tmp = head->next;
             delete head;
             head = tmp;
         }
       }
    }
    

    <<を使用してチェーンテーブルコンテンツの印刷を行う
    ここではノードのnextがnullptrであるか否かで末尾に達したか否かを判断する(次の部分のremove_duplicates()関数に1つしか残っていない場合tailがどのように処理するか、ノード==tailで印刷エラーが発生するかをチェックするため)
    friend function: operator<<
    ostream & operator<value;
            if (node->next == nullptr){
             // test deleting tail success or not; since I cannot handle the positon of tail node properly, this is a good practice
                break;
            }
            out << " ";
            node = node->next;
        }
        return out;
    }
    

    コピーコンストラクタ
    deep copyを行うには、手動でノードを1つずつ追加する必要があります
    IntList::IntList(const IntList &cpy){
        head = nullptr; // !
        tail = nullptr; // !
        if (this != &cpy){
            if (!cpy.empty()){
                IntNode* tmp = cpy.head;
                while(tmp != nullptr){
                    push_back(tmp->value);
                    tmp = tmp->next;
                }
            }
        }  //             ......  
    }
    

    チェーンテーブルのコピー(=)
    この場合、5つの状況を考慮する必要があります.
  • 空チェーンテーブルコピー空チェーンテーブル
  • 空チェーンテーブルコピー非空チェーンテーブル
  • 非空チェーンテーブルコピー空チェーンテーブル
  • 非空チェーンテーブルコピー非空チェーンテーブル
  • 自己コピー自己(self-assignment)
  • IntList& IntList::operator=(const IntList &rhs){
        if (rhs.empty()){
            head = nullptr;
            tail = nullptr;
        }
        else if (&rhs != this){
            if (head != nullptr){
                clear();
                head = nullptr;
                tail = nullptr; // clear() does not set head and tail to null which can be used later
            }
            IntNode* tmp = rhs.head;
            while(tmp != nullptr){
                push_back(tmp->value);
                tmp = tmp->next;
            }
        }else if (&rhs == this){
            return *this;
        }
        return *this;
    }
    

    ここで私は2つの間違いを犯したことがあります.
  • segmentation fault
  •        IntNode* tmp = rhs.head;
           // do not assign head with rhs's head, the push_back function does the same
            //      head = rhs.head;,    segmentation fault (  dangling pointer   ;
            // dangling pointer: deallocates the data the pointer pointing to without modifying the value of the pointer)
            while(tmp != nullptr){
                push_back(tmp->value);
                tmp = tmp->next;
            }
    
  • 非空チェーンテーブルが空チェーンテーブルをコピーしたときclear()が終了しtailを手当たり次第にnullptr(clear()はheadからdeleteのみ、最後にheadをnullとしたため)
  • あとはselection_sort(), insert_ordered(int), remove_duplicates()および宿題を書くときにプログラムが報告したすべてのerror;今日は宿題が多すぎて、次の編に書く時間があります.
    復習後の3つの関数の実現と誤報分析のノートリンクを添付し、英語全体で障害のない友达が見に来ることができます.https://www.notion.so/lavendershuo/Review-Linked-List-19911524ae42403b8cab305ff36c1943