簡體   English   中英

鏈表:如何實現析構函數、復制構造函數和復制賦值運算符?

[英]Linked List: How to implement Destructor, Copy Constructor, and Copy Assignment Operator?

這是我的 C++ 代碼:

#include <iostream>

using namespace std;

typedef struct Node
{   
    int data;
    Node* next;
}Node;

class LinkedList
{
private:
    Node* first;
    Node* last;
public:
    LinkedList() {first = last = NULL;};
    LinkedList(int A[], int num);
    ~LinkedList();

    void Display();
    void Merge(LinkedList& b);
  
};

// Create Linked List using Array
LinkedList::LinkedList(int A[], int n)
{   
    Node* t = new Node;
    if (t == NULL)
    {
        cout << "Failed allocating memory!" << endl;
        exit(1);
    }
    t->data = A[0];
    t->next = NULL;
    first = last = t;

    for (int i = 1; i < n; i++)
    {
        t = new Node;
        if (t == NULL)
        {
            cout << "Failed allocating memory!" << endl;
            exit(1);
        }
        t->data = A[i];
        t->next = NULL;
        
        last->next = t;
        last = t;
    }
}

// Deleting all Node in Linked List
LinkedList::~LinkedList()
{
    Node* p = first;
    Node* tmp;

    while (p != NULL)
    {
        tmp = p;
        p = p->next;
        delete tmp;
    }
}

// Displaying Linked List
void LinkedList::Display()
{
    Node* tmp;

    for (tmp = first; tmp != NULL; tmp = tmp->next)
        cout << tmp->data << " ";
    cout << endl;    
}

// Merge two linked list
void LinkedList::Merge(LinkedList& b)
{
    // Store first pointer of Second Linked List
    Node* second = b.first;
    Node* third = NULL, *tmp = NULL;

    // We find first Node outside loop, smaller number, so Third pointer will store the first Node
    // Then, we can only use tmp pointer for repeating process inside While loop
    if (first->data < second->data)
    {
        third = tmp = first;
        first = first->next;
        tmp->next = NULL;
    }
    else
    {
        third = tmp = second;
        second = second->next;
        tmp->next = NULL;
    }

    // Use while loop for repeating process until First or Second hit NULL
    while (first != NULL && second != NULL)
    {
        // If first Node data is smaller than second Node data
        if (first->data < second->data)
        {
            tmp->next = first;
            tmp = first;
            first = first->next;
            tmp->next = NULL;
        }
        // If first Node data is greater than second Node data
        else
        {
            tmp->next = second;
            tmp = second;
            second = second->next;
            tmp->next = NULL;
        }
    }

    // Handle remaining Node that hasn't pointed by Last after while loop
    if (first != NULL)
        tmp->next = first;
    else
        tmp->next = second;

    // Change first to what Third pointing at, which is First Node
    first = third;    

    // Change last pointer from old first linked list to new last Node, after Merge
    Node* p = first;
    while (p->next != NULL)
    {
        p = p->next;
    }    
    last = p;
    
    // Destroy second linked list because every Node it's now connect with first linked list
    // This also prevent from Double free()
    b.last = NULL;
    b.first = NULL;
}

int main()
{
    int arr1[] = {4, 8, 12, 14, 15, 20, 26, 28, 30};
    int arr2[] = {2, 6, 10, 16, 18, 22, 24};
    int size1 = sizeof(arr1) / sizeof(arr1[0]);
    int size2 = sizeof(arr2) / sizeof(arr2[0]);
    
    LinkedList l1(arr1, size1);
    LinkedList l2(arr2, size2);

    l1.Display();
    l2.Display();
    
    // Merge two linked list, pass l2 as reference
    l1.Merge(l2);
    l1.Display();

    return 0;
}

我是 C++ 的初學者,在這段代碼中,我練習了如何合並兩個鏈表。 這實際上工作得很好。 我已經成功地按排序順序合並了兩個鏈表。

但是,有人說我應該遵循 C++的三原則 其中實現: DestructorCopy ConstructorCopy Assignment Operator

我看過很多關於這個的視頻。 我確實理解這基本上是處理淺復制,尤其是當我們不希望兩個不同的對象指向相同的內存地址時。 但是,對於我的問題是,我仍然不知道如何在處理鏈表的類上實現它,就像我上面的代碼一樣。

有人說,在我的main() ,這段代碼是: l1.Merge(l2); 不知何故不正確,因為我沒有明確的復制構造函數。

如果你看看我的Merge()函數,在最后一行,如果我沒有這樣做: b.last = NULL; b.first = NULL; ,它只是破壞第二個鏈表的指針,編譯器給我警告:檢測到雙空閑()

所以,我想我的問題是:

  1. 這段代碼如何: l1.Merge(l2); 和復制構造函數有關系嗎?
  2. Double free()是不是因為我沒有執行三規則而發生的? 如果是,如何解決它們?
  3. 如何根據我的代碼編寫三規則? 何時或如何使用它們?
  4. 基於此代碼,有什么問題嗎? 如果我的程序只想合並鏈表,我還需要三規則嗎?

謝謝你。 我希望有人能像我 10 歲一樣向我解釋。 並希望有人能給我寫一些代碼。

這段代碼中應用了幾個有問題的做法,還有一個錯誤。

首先,錯誤。 創建列表時,它會new所有節點並使用指針跟蹤它們。 當您將一個列表分配給另一個列表時,您實際上是復制指針值。 您現在不僅丟失了已分配列表的節點(因為您覆蓋了它們)並出現了內存泄漏(因為現在沒有指向已分配節點的指針),您現在還在兩個不同的列表上擁有相同的指針,指向相同的節點。 當列表被銷毀時,它們都試圖delete它們的節點,你最終釋放了兩次相同的內存。 哎呀。

解決此錯誤的方法是實現賦值運算符。

然后,有問題的做法:

  1. using namespace std; 為什么“使用命名空間標准;”被認為是不好的做法?
  2. 您在構造函數體中分配LinkedList的成員,而不是將值直接傳遞給它們在初始化列表中的構造函數。 構造函數中這個奇怪的冒號成員(“:”)語法是什么?
  3. 聲明一個數組參數( int[] )就是聲明一個指針。 請注意它。
  4. new不能返回NULL 檢查它的返回值是沒有用的。 如果它不能分配,它只會拋出一個異常。
  5. NULL是不適合使用的常量。 您可以使用nullptr ,它是NULL的 C++ 等價物,但它是類型安全的。
  6. 使用newdelete手動內存管理很難做到正確(正如您自己發現的那樣)。 您可能對使用std::unique_ptrstd::shared_ptr來減輕負擔感興趣。 他們會發現錯誤。

現在,請:不要像使用類的 C 那樣用 C++ 編寫。 我知道您可能沒有遇到我在這里介紹的所有功能,但無論如何現在您都知道它們了 :)

但是,對於我的問題是,我仍然不知道如何在處理鏈表的類上實現 [三規則],就像我上面的代碼一樣。

您只需實現復制構造函數和復制賦值運算符來迭代輸入列表,制作每個節點的副本並將它們插入到您的目標列表中。 你已經有了一個有效的析構函數。 在復制賦值運算符的情況下,您通常可以使用copy-swap 慣用語來使用復制構造函數來實現它,以避免重復自己

有人說,在我的main() ,這段代碼是: l1.Merge(l2); 不知何故不正確,因為我沒有明確的復制構造函數。

然后你被告知錯了。 您的Merge()代碼與復制構造函數無關

如果你看看我的Merge()函數,在最后一行,如果我沒有這樣做: b.last = NULL; b.first = NULL; ,它只是破壞第二個鏈表的指針,編譯器給我警告: Double free() detected.

正確的。 由於您節點從輸入列表移動到目標列表,您需要重置輸入列表,使其不再指向移動的節點。 否則,輸入列表的析構函數將嘗試釋放它們,目標列表的析構函數也將嘗試釋放它們。

這段代碼如何: l1.Merge(l2); 和復制構造函數有關系嗎?

它與它沒有任何關系。

Double free()是不是因為我沒有執行三規則而發生的?

不在您的特定示例中,因為您沒有執行任何復制操作。 但是,一般來說,不執行三規則會導致雙重釋放,是的。

如何根據我的代碼編寫三規則?

請參閱下面的代碼。

如果我的程序只想合並鏈表,我還需要三規則嗎?

否。僅當您想要制作列表副本時。

話雖如此,這是一個包含三規則的實現:

#include <iostream>
#include <utility>

struct Node
{
    int data;
    Node *next;
};

class LinkedList
{
private:
    Node *first;
    Node *last;
public:
    LinkedList();
    LinkedList(const LinkedList &src);
    LinkedList(int A[], int num);
    ~LinkedList();

    LinkedList& operator=(const LinkedList &rhs);

    void Display() const;
    void Merge(LinkedList &b);
};

// Create Linked List using default values
LinkedList::LinkedList()
    : first(NULL), last(NULL)
{
}

// Create Linked List using Array
LinkedList::LinkedList(int A[], int n)
    : first(NULL), last(NULL)
{
    Node **p = &first;

    for (int i = 0; i < n; ++i)
    {
        Node *t = new Node;
        t->data = A[i];
        t->next = NULL;

        *p = t;
        p = &(t->next);

        last = t;
    }
}

// Create Linked List by copying another Linked List
LinkedList::LinkedList(const LinkedList &src)
    : first(NULL), last(NULL)
{
    Node **p = &first;

    for (Node *tmp = src.first; tmp; tmp = tmp->next)
    {
        Node* t = new Node;
        t->data = tmp->data;
        t->next = NULL;

        *p = t;
        p = &(t->next);

        last = t;
    }
}

// Deleting all Node in Linked List
LinkedList::~LinkedList()
{
    Node *p = first;

    while (p)
    {
        Node *tmp = p;
        p = p->next;
        delete tmp;
    }
}

// Update Linked List by copying another Linked List
LinkedList& LinkedList::operator=(const LinkedList &rhs)
{
    if (&rhs != this)
    {
        LinkedList tmp(rhs);
        std::swap(tmp.first, first);
        std::swap(tmp.last, last);
    }
    return *this;
}

// Displaying Linked List
void LinkedList::Display() const
{
    for (Node *tmp = first; tmp; tmp = tmp->next)
        std::cout << tmp->data << " ";
    std::cout << std::endl;
}

// Merge two linked list
void LinkedList::Merge(LinkedList& b)
{
    if ((&b == this) || (!b.first))
        return;

    if (!first)
    {
        first = b.first; b.first = NULL;
        last = b.last; b.last = NULL;
        return;
    }

    // Store first pointer of Second Linked List
    Node *second = b.first;
    Node *third, **tmp = &third;

    // We find first Node outside loop, smaller number, so Third pointer will store the first Node
    // Then, we can only use tmp pointer for repeating process inside While loop
    // Use while loop for repeating process until First or Second hit NULL
    do
    {
        // If first Node data is smaller than second Node data
        if (first->data < second->data)
        {
            *tmp = first;
            tmp = &(first->next);
            first = first->next;
        }
        // If first Node data is greater than second Node data
        else
        {
            *tmp = second;
            tmp = &(second->next);
            second = second->next;
        }
        *tmp = NULL;
    }
    while (first && second);

    // Handle remaining Node that hasn't pointed by Last after while loop
    *tmp = (first) ? first : second;

    // Change first to what Third pointing at, which is First Node
    first = third;  

    // Change last pointer from old first linked list to new last Node, after Merge
    Node *p = first;
    while (p->next)
    {
        p = p->next;
    }   
    last = p;
    
    // Destroy second linked list because every Node it's now connect with first linked list
    // This also prevent from Double free()
    b.first = b.last = NULL;
}

int main()
{
    int arr1[] = {4, 8, 12, 14, 15, 20, 26, 28, 30};
    int arr2[] = {2, 6, 10, 16, 18, 22, 24};
    int size1 = sizeof(arr1) / sizeof(arr1[0]);
    int size2 = sizeof(arr2) / sizeof(arr2[0]);
    
    LinkedList l1(arr1, size1);
    LinkedList l2(arr2, size2);
    LinkedList l3(l1);
    LinkedList l4;

    l1.Display();
    l2.Display();
    l3.Display();
    l4.Display();
    
    // Merge two linked list, pass l2 as reference
    l3.Merge(l2);
    l4 = l3;

    l1.Display();
    l2.Display();
    l3.Display();
    l4.Display();

    return 0;
}

演示

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM