简体   繁体   English

链表:如何实现析构函数、复制构造函数和复制赋值运算符?

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

This is my C++ code:这是我的 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;
}

I'm beginner on C++ and in this code, I practice how to Merge two linked list.我是 C++ 的初学者,在这段代码中,我练习了如何合并两个链表。 This actually works perfectly.这实际上工作得很好。 I've successfully Merge the two Linked List in sorted order.我已经成功地按排序顺序合并了两个链表。

But, there's people said that I should've follow the Rule of Three on C++.但是,有人说我应该遵循 C++的三原则 Which implement: Destructor , Copy Constructor , and Copy Assignment Operator .其中实现: DestructorCopy ConstructorCopy Assignment Operator

I've watched many videos about that.我看过很多关于这个的视频。 I do understand that is basically handle Shallow Copy especially when we don't want two different object point to the same address of memory.我确实理解这基本上是处理浅复制,尤其是当我们不希望两个不同的对象指向相同的内存地址时。 But, for my problem is, I still don't know how to Implement it on a Class that working on a Linked List just like my code above.但是,对于我的问题是,我仍然不知道如何在处理链表的类上实现它,就像我上面的代码一样。

Someone said, in my main() , this code: l1.Merge(l2);有人说,在我的main() ,这段代码是: l1.Merge(l2); is somehow incorrect because I don't have explicit Copy Constructor.不知何故不正确,因为我没有明确的复制构造函数。

And if you look at my Merge() function, in Last line, if I didn't to this: b.last = NULL;如果你看看我的Merge()函数,在最后一行,如果我没有这样做: b.last = NULL; and b.first = NULL;b.first = NULL; , which simply destroy pointer of Second Linked list, the Compiler give me warning: Double free() detected. ,它只是破坏第二个链表的指针,编译器给我警告:检测到双空闲()

So, I think my question is:所以,我想我的问题是:

  1. How can this code: l1.Merge(l2);这段代码如何: l1.Merge(l2); is have something to do with Copy Constructor?和复制构造函数有关系吗?
  2. Is Double free() happened because I don't implement the Rule of Three? Double free()是不是因为我没有执行三规则而发生的? If yes, how to address them?如果是,如何解决它们?
  3. How to write the Rule of Three based on my code?如何根据我的代码编写三规则? When or How to use them?何时或如何使用它们?
  4. Based on this Code, is there something wrong?基于此代码,有什么问题吗? Do I still need the Rule of Three if my Program only want to Merge Linked List?如果我的程序只想合并链表,我还需要三规则吗?

Thank You.谢谢你。 I hope someone can explain to me like I'm 10 years old.我希望有人能像我 10 岁一样向我解释。 and hope someone can write me some Code.并希望有人能给我写一些代码。

There are several questionable practices applied in this code, and there is also a bug.这段代码中应用了几个有问题的做法,还有一个错误。

First, the bug.首先,错误。 When you create a list, it new s all its nodes and keeps track of them using pointers.创建列表时,它会new所有节点并使用指针跟踪它们。 When you assign a list to another, you literally copy the pointer values.当您将一个列表分配给另一个列表时,您实际上是复制指针值。 Not only have you now lost the nodes of the assigned list (because you overwrote them) and got a memory leak (because now there's no pointer pointing to the allocated nodes), you also now have the same pointers on two different lists, pointing to the same nodes.您现在不仅丢失了已分配列表的节点(因为您覆盖了它们)并出现了内存泄漏(因为现在没有指向已分配节点的指针),您现在还在两个不同的列表上拥有相同的指针,指向相同的节点。 When the lists are destroyed, both of them try to delete their nodes, and you end up freeing the same memory twice.当列表被销毁时,它们都试图delete它们的节点,你最终释放了两次相同的内存。 Yuk.哎呀。

The solution to this bug is to implement the assignment operator.解决此错误的方法是实现赋值运算符。

Then, the questionable practices:然后,有问题的做法:

  1. using namespace std; ( Why is "using namespace std;" considered bad practice? ) 为什么“使用命名空间标准;”被认为是不好的做法?
  2. You're assigning the members of LinkedList in the constructor body, instead of passing the values directly to their constructor in the initialization list.您在构造函数体中分配LinkedList的成员,而不是将值直接传递给它们在初始化列表中的构造函数。 ( What is this weird colon-member (" : ") syntax in the constructor? ) 构造函数中这个奇怪的冒号成员(“:”)语法是什么?
  3. Declaring an array parameter ( int[] ) is declaring a pointer.声明一个数组参数( int[] )就是声明一个指针。 Just be aware of it.请注意它。
  4. new cannot return NULL ! new不能返回NULL It's useless to check its return value.检查它的返回值是没有用的。 If it can't allocate, it will simply throw an exception.如果它不能分配,它只会抛出一个异常。
  5. NULL is the inappropriate constant to use. NULL是不适合使用的常量。 You can use nullptr , it's the C++ equivalent of NULL , except it's type safe.您可以使用nullptr ,它是NULL的 C++ 等价物,但它是类型安全的。
  6. Manual memory management with new and delete is tricky to get right (as you figured out yourself).使用newdelete手动内存管理很难做到正确(正如您自己发现的那样)。 You might be interested in using std::unique_ptr or std::shared_ptr to alleviate the burden.您可能对使用std::unique_ptrstd::shared_ptr来减轻负担感兴趣。 They would have caught the bug.他们会发现错误。

Now, please: do not write in C++ like it's C with classes.现在,请:不要像使用类的 C 那样用 C++ 编写。 I understand that you may not have encountered all of the features I presented here, but anyway now you know about them :)我知道您可能没有遇到我在这里介绍的所有功能,但无论如何现在您都知道它们了 :)

But, for my problem is, I still don't know how to Implement [Rule of Three] on a Class that working on a Linked List just like my code above.但是,对于我的问题是,我仍然不知道如何在处理链表的类上实现 [三规则],就像我上面的代码一样。

You simply implement the copy constructor and copy assignment operator to iterate the input list, making a copy of each node and inserting them into your target list.您只需实现复制构造函数和复制赋值运算符来迭代输入列表,制作每个节点的副本并将它们插入到您的目标列表中。 You already have a working destructor.你已经有了一个有效的析构函数。 In the case of the copy assignment operator, you can usually use the copy-swap idiom to implement it using the copy constructor to avoid repeating yourself .在复制赋值运算符的情况下,您通常可以使用copy-swap 惯用语来使用复制构造函数来实现它,以避免重复自己

Someone said, in my main() , this code: l1.Merge(l2);有人说,在我的main() ,这段代码是: l1.Merge(l2); is somehow incorrect because I don't have explicit Copy Constructor.不知何故不正确,因为我没有明确的复制构造函数。

Then you were told wrong.然后你被告知错了。 Your Merge() code has nothing to do with a copy constructor.您的Merge()代码与复制构造函数无关

And if you look at my Merge() function, in Last line, if I didn't to this: b.last = NULL;如果你看看我的Merge()函数,在最后一行,如果我没有这样做: b.last = NULL; and b.first = NULL;b.first = NULL; , which simply destroy pointer of Second Linked list, the Compiler give me warning: Double free() detected. ,它只是破坏第二个链表的指针,编译器给我警告: Double free() detected.

Correct.正确的。 Since you are moving the nodes from the input list to the target list, you need to reset the input list so it doesn't point at the moved nodes anymore.由于您节点从输入列表移动到目标列表,您需要重置输入列表,使其不再指向移动的节点。 Otherwise, the destructor of the input list will try to free them, as will the destructor of the target list.否则,输入列表的析构函数将尝试释放它们,目标列表的析构函数也将尝试释放它们。

How can this code: l1.Merge(l2);这段代码如何: l1.Merge(l2); is have something to do with Copy Constructor?和复制构造函数有关系吗?

It doesn't have anything to do with it.它与它没有任何关系。

Is Double free() happened because I don't implement the Rule of Three? Double free()是不是因为我没有执行三规则而发生的?

Not in your particular example, as you are not performing any copy operations.不在您的特定示例中,因为您没有执行任何复制操作。 But, in general, not implementing the Rule of Three can lead to double frees, yes.但是,一般来说,不执行三规则会导致双重释放,是的。

How to write the Rule of Three based on my code?如何根据我的代码编写三规则?

See the code below.请参阅下面的代码。

Do I still need the Rule of Three if my Program only want to Merge Linked List?如果我的程序只想合并链表,我还需要三规则吗?

No. Only when you want to make copies of lists.否。仅当您想要制作列表副本时。

With that said, here is an implementation that includes the Rule of Three:话虽如此,这是一个包含三规则的实现:

#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;
}

Demo演示

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 在赋值运算符中使用复制构造函数获取链表 - Using copy constructor in assignment operator for a linked list 对链表副本构造函数和赋值运算符使用copy()方法 - Using a copy() method for a linked list copy constructor and assignment operator 复制构造函数,赋值运算符和析构函数代码重复 - Copy constructor, assignment operator, and destructor code duplication 继承时如何正确编写副本构造函数,析构函数和赋值运算符? - How correctly to write a copy constructor, destructor, and assignment operator when inheriting? 实现复制构造函数、析构函数以及如何为队列重载赋值运算符 - implementing a copy constructor, destructor, and how to overload assignment operator for queues 如何在双向链表上实现复制赋值? - How to implement a copy assignment on a doubly linked List? 双链列表模板副本构造函数赋值运算符 - Doubly Linked List Template Copy Constructor Assignment Operator 使用shared_ptr时需要实现析构函数,复制构造函数,赋值运算符 - Need to implement destructor, copy constructor, assignment operator when using shared_ptr 如何在临时容器类中实现复制构造函数和赋值运算符? - How to implement copy constructor and assignment operator in a templatic container class? 我应该如何为矩阵类实现复制构造函数和赋值运算符? - How should I implement a copy constructor & assignment operator for a matrix class?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM