C++ 链表追加和删除后面的节点

huangapple go评论72阅读模式
英文:

C++ linked list append and delete after

问题

我创建了一个代码,它会要求用户输入两个列表,然后将第二个列表附加到第一个列表后面,附加的位置是用户输入的第一个列表中的数字的位置,例如:
```cpp
第一个列表:
1
2
3
4
5
第二个列表:
11
22
33
44
55
要附加的位置:2
1
2
11
22
33
44
55
3
4
5

我遇到的问题是当我删除这些列表时,它尝试两次删除相同的元素,导致出现这个错误:(0xC0000374)。
我添加了一个带有cout的调试测试,并意识到被删除两次的元素是第二个列表的第一个元素。

我的代码:

struct elem
{
    int num;
    elem *next;
};

void add_element (elem*&first1, elem*&last1, int i)
{
    elem *p = new elem;
    p->num = i;
    p->next = NULL;
    if (first1 == NULL) first1 = p;
    else last1->next = p;
    last1 = p;
};

void print_list (elem *first1)
{
    for (elem *p = first1; p!=NULL; p=p->next)
    {
        cout << p->num << endl;
    };
}

void delete_list (elem*&first1)
{
    elem* p = first1;
    while (p != NULL) {
        first1 = first1->next;
        p->next = NULL;
        cout << "删除地址为 " << p << endl;
        delete p;
        p = first1;
    }
}

void insert_list(elem*& first1, elem*& last1, elem* p, elem* first2, elem* last2) {
    // 更新第二个列表的最后一个元素的next指针指向NULL
    last2->next = NULL;

    if (p == NULL) { // 在列表开始插入
        if (first1 == NULL) {
            first1 = first2;
        } else {
            last2->next = first1;
            first1 = first2;
        }
        if (last1 == NULL) {
            last1 = last2;
        } else {
            last1 = last2;
            // 更新last1指针指向第二个列表的最后一个元素
            while (last1->next != NULL) {
                last1 = last1->next;
            }
        }
    } else if (p == last1) { // 在列表末尾插入
        last1->next = first2;
        last1 = last2;
    } else { // 在列表中间插入
        last2->next = p->next;
        p->next = first2;
    }
}

int main()
{
    elem *first1=NULL, *last1=NULL, *p;
    int i;
    cout << "输入元素的值(以 '0' 结束输入): " << endl;
    cin >> i;
    while (i != 0)
    {
        add_element (first1, last1, i);
        cin >> i;
    };

    cout << "\n列表: " << endl;
    print_list (first1);

    elem* first2 = NULL;
    elem* last2 = NULL;
    cout << "\n输入第二个列表的元素值(以 '0' 结束输入): " << endl;
    cin >> i;
    while (i != 0) {
        add_element(first2, last2, i);
        cin >> i;
    }

    cout << "\n输入要插入第二个列表的元素后的位置: ";
    int pos;
    cin >> pos;

    // 查找指定位置的元素
    p = first1;
    for (int i = 1; i < pos && p != NULL; i++) {
        p = p->next;
    }

    // 检查指定的位置是否有效
    if (p == NULL) {
        cout << "\n错误: 位置超出范围。" << endl;
        return 0;
    }

    // 在指定元素后插入第二个列表
    insert_list(first1, last1, p, first2, last2);

    cout << "\n更新后的列表: " << endl;
    print_list(first1);
    cout << endl;

    delete_list(first1);
    delete_list(first2);
    cout << endl;
    if(first2->next) cout << "first2 pastaav " << endl;

    return 0;
}
英文:

So I created a code that asks user for two lists, and then appends second list to the first one, it gets appended after the position of user inputted number on the first list, example:

First list:
1
2
3
4
5
Second list:
11
22
33
44
55
Position after which to be appended: 2
1
2
11
22
33
44
55
3
4
5

The problem I'm getting is that when I delete these lists, it tries to delete the same element twice, giving this execution ?code? - (0xC0000374).
I added debugging test with cout, and realized that the element that is getting deleted twice is the first element of the second list.

My code:

struct elem
{
int num;
elem *next;
};
void add_element (elem*&first1, elem*&last1, int i)
{
elem *p = new elem;
p->num = i;
p->next = NULL;
if (first1 == NULL) first1 = p;
else last1->next = p;
last1 = p;
};
void print_list (elem *first1)
{
for (elem *p = first1; p!=NULL; p=p->next)
{
cout << p->num << endl;
};
}
void delete_list (elem*&first1)
{
elem* p = first1;
while (p != NULL) {
first1 = first1->next;
p->next = NULL;
cout << "Deleting element at address " << p << endl;
delete p;
p = first1;
}
}
void insert_list(elem*& first1, elem*& last1, elem* p, elem* first2, elem* last2) {
// Update the next pointer of the last element of the second list to point to NULL
last2->next = NULL;
if (p == NULL) { // Insert at the beginning of the list
if (first1 == NULL) {
first1 = first2;
} else {
last2->next = first1;
first1 = first2;
}
if (last1 == NULL) {
last1 = last2;
} else {
last1 = last2;
// Update the last1 pointer to point to the last element in the second list
while (last1->next != NULL) {
last1 = last1->next;
}
}
} else if (p == last1) { // Insert at the end of the list
last1->next = first2;
last1 = last2;
} else { // Insert in the middle of the list
last2->next = p->next;
p->next = first2;
}
}
int main()
{
elem *first1=NULL, *last1=NULL, *p;
int i;
cout << "Enter the values of elements (end input with '0'): " << endl;
cin >> i;
while (i != 0)
{
add_element (first1, last1, i);
cin >> i;
};
cout << "\nThe list: " << endl;
print_list (first1);
elem* first2 = NULL;
elem* last2 = NULL;
cout << "\nEnter the values of elements for the second list (end input with '0'): " << endl;
cin >> i;
while (i != 0) {
add_element(first2, last2, i);
cin >> i;
}
cout << "\nEnter the position of the element after which to insert the second list: ";
int pos;
cin >> pos;
// Find the element at the specified position
p = first1;
for (int i = 1; i < pos && p != NULL; i++) {
p = p->next;
}
// Check if the specified position is valid
if (p == NULL) {
cout << "\nError: Position out of range." << endl;
return 0;
}
// Insert the second list after the specified element
insert_list(first1, last1, p, first2, last2);
cout << "\nThe updated list: " << endl;
print_list(first1);
cout << endl;
delete_list(first1);
delete_list(first2);
cout << endl;
if(first2->next) cout << "first2 pastaav " << endl;
return 0;
}

I've tried ChatGPT, but that didn't work really, maybe I don't know how to use it properly :D.
I'll add picture with example: https://prnt.sc/gBCe0WCfHF5q

答案1

得分: 2

正如评论中指出的,你在第一个列表中插入的是节点而不是元素。这导致第二个列表指向了第一个列表。

初始状态如下:

main::p1 ->  1 -  2 -  3 -  4 -  5  
main::p2 -> 11 - 22 - 33 - 44 - 55

在插入后,情况如下:

main::p1 ->  1 -  2 \                          / 3 -  4 -  5  
main::p2 -> ---------\ 11 - 22 - 33 - 44 - 55 /

当你试图删除两个列表时,会发生双重删除,因为主程序仍然通过 p2 拥有对 11 的引用,即使现在 p1 "拥有" 它。

一个快速的修复方法是,当你用第二个列表的最后一个元素的下一个指针覆盖第一个列表的第一个指针时。这可能有一个缺点,如果指针在复制之前已被复制,那么它将什么也不做。

假设以下情况,并将前5个元素插入到 p1 中:

main::p1 ->  1 -  2 -  3 -  4 -  5  
main::p2 -> 11 - 22 - 33 - 44 - 55 - 66

然后情况将如下:

main::p1 ->  1 -  2 - 11 - 22 - 33 - 44 - 55 -  3 -  4 -  5  
main::p2 -> 66

然后你可以安全地删除两个列表。

请注意,代码看起来更像是 C 而不是 C++。

首先,你应该使用 nullptr 而不是 NULL
(对 FALSE 使用 false,对 TRUE 使用 true)。这些宏都是纯C的。

其次,除非这是一个学校或大学的任务,否则你应该避免使用裸指针(T*)。相反,使用智能指针如 std::unique_ptr<T>std::shared_ptr<T>

在C++中,几乎所有东西都是通过间接的动态内存管理来处理的。这意味着你通过值或 const& 传递控制结构。它们跟踪自己的内部动态内存。一旦它们超出范围,它们就会清除自己分配的资源。这个原则被称为RAII

使用 unique_ptr 可能有点复杂,因为你不能复制它们,但可以使用 std::move 来移动它们,这基本上是窃取所有权:auto new_ptr_owner = std::move(varname_of_old_unique_ptr);

这迫使你正确处理内存管理。而且与使用裸指针一样高效。

如果你不关心效率,你可以使用 shared_ptr。它们是可复制的,因为它们计算它们的所有拷贝。当计数达到零时,指针将删除自己的资源。

第三,你可能希望定义你的链表,使其始终包含一个无效的第一个元素,这基本上就是你的列表。当你的第一个列表元素没有下一个值时,它是空的。这将减少代码开销,因为你不需要检查 p 是否为 nullptr,因为它将始终是一个有效的元素。

或者,编写一个包装类:

class LinkedList {
    struct Node {
        int value{};
        std::unique_ptr<Node> next;
        Node* prev {}; // 可选,方便
    };
    std::unique_ptr<Node> first; // 必需
    Node* last;  // 可选,可以快速插入到末尾。
    size_t size;    // 可选,方便。

public:

    void insert(Node begin_n, Node last_n); // 待实现
    void emplace_last(int value); // 待实现
};

请注意,每个节点在同一时刻只能通过一个智能指针访问。使用 unique_ptr 将无法编译,使用 shared_ptr 将获得一个依赖循环(它们将相互保持活动状态)。

英文:

As the comments already pointed out, you are inserting the nodes, not the elements, into your first list. This causes the second list to point into the first list.

It looks like this:

main::p1 ->  1 -  2 -  3 -  4 -  5  
main::p2 -> 11 - 22 - 33 - 44 - 55

after your insertion, it looks like this:

main::p1 ->  1 -  2 \                          / 3 -  4 -  5  
main::p2 -> ---------\ 11 - 22 - 33 - 44 - 55 /

When you now try to delete both lists, you will get a double deletion, because main still has a reference to 11 via p2, even p1 now "owns" it.

A quick fix would be, when you override the first pointer of the second list with the next pointer of the last element of the second list. This may have the disadvantage, that it does nothing, when the pointer was copied before.

Imagine the following, and you insert the first 5 elements into p1:

main::p1 ->  1 -  2 -  3 -  4 -  5  
main::p2 -> 11 - 22 - 33 - 44 - 55 - 66

Then it would look like this:

main::p1 ->  1 -  2 - 11 - 22 - 33 - 44 - 55 -  3 -  4 -  5  
main::p2 -> 66

And you can safely delete both lists.

void insert_list(elem*& first1, elem* p, elem*& first2, elem* last2) {
// Store the next pointer of last2 for later deletion.
auto last2next = last2->next;
// Update the next pointer of the last element of the second list to point to NULL
// last2->next = NULL; // Redundant, must be allways overridden with p->next or first anyway.
// Assuming you insert after p
// Insert at the beginning of the list, when p == nullptr
auto*& begin_pointer = (p == nullptr) ? first1 : p->next;
last2->next = begin_pointer;
begin_pointer = first2;
// override the original first2 pointer with the saved last2.next pointer.
first2 = last2next;
}

===

But to give you general feedback: Your code looks more like C than C++.
First, you should use nullptr instead of NULL.
(Same applies for FALSE -> false and TRUE true). Those makros are all pure C.

Second, unless this is a school or university task, you should avoid raw pointers (T*). Instead, use smart pointers like std::unique_ptr<T> or std::shared_ptr<T>.

In c++ mostly everything is handled via indirect dynamic memory management. This means, that you pass control structures either by value or by const&. They keep track of their own internal dynamic memory. As soon they go out of scope, they clear their own allocated resources. This principle is called RAII.

Using unique_ptr might be a bit complicated, because you can't copy them, but you can std::move them, which basically steals the ownership: auto new_ptr_owner = std::move(varname_of_old_unique_ptr);

This forces you, to do memory management correct. And is also as efficient as the use of raw pointers.
If you don't care for efficiency, you could just use shared_ptr. They are copyable, because they count all copies of it. When the count reaches zero, the pointer deletes the own resources.

Third, you might want to define your linked list, to always contain an invalid first element, which is basically your list. It is empty, when your first list element has no next value. This will reduce code overhead, because you are not required to check if p is nullptr, since it will always be a valid element.

Alternatively, write a wrapper class:

class LinkedList {
struct Node {
int value{};
std::unique_ptr<Node > next;
Node* prev {}; // Not required, convenience
};
std::unique_ptr<Node> first; // Required
Node* last;  // Optional, to quickly insert at the end.
size_t size;    // Optional, convenience.
public:
void insert(Node begin_n, Node last_n); // Todo implement
void emplace_last(int value); // Todo implement
}; 

Note, that each node can only be reached over one smart pointer at the same time. With a unique_ptr it won't compile, with a shared_ptr you will get a dependency loop (They will keep them alive reciprocally).

huangapple
  • 本文由 发表于 2023年3月31日 20:08:15
  • 转载请务必保留本文链接:https://go.coder-hub.com/75898368.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定