简体   繁体   中英

Confusion about singly linked list

So I'm learning about linked lists recently. The functions are somewhat straightforward, but when I checked the output, it's always messy.

In the test file, from test 1 to test 3, I changed the position of std::cout lines, and in test 1 the output is not shown. I don't know how a simple cout line or the order of lines can affect how the linked list will be output. This is very confusing (details are provided in the output of each test)

My functions, specifically InsertHead, SearchList, InsertAfter, PreviousNode are sometimes correct in specific outputs.

For my InsertBefore function, I used a function called PreviousNode to get the pointer to previous node of current one, and used InsertAfter to insert a node after that previous node. However, the result is infinite. (I'm not allowed to used doubly linked list)

File node.h

#include <iostream>
using namespace std;

template <typename T>
struct Node{
    T _item;
    Node<T>* _next;

    Node() {
        _item = T();
        _next = NULL;
    }

    Node(T item){
        _item = item;
        _next = NULL;
    }

    // Print the value of a node
    friend std::ostream& operator <<(std::ostream& outs, const Node<T> &printMe){
        outs << "[" << printMe._item << "]";
    }
};

// Print the entire linked list
template <typename T>
void PrintList(Node<T>* head){
    Node<T>* walker = head;

    while(walker != NULL){
        cout << *walker;
        cout << "->";
        walker = walker->_next;
    }

    cout << "|||";
}

// Insert an item to the head
template <typename T>
Node<T>* InsertHead(Node<T>* &head, const T& item){
    Node<T>* temp = new Node<T>(item);
    temp->_next = head;
    head = temp;
    return head;
}

// Search an element in list, return the pointer to that node
template <typename T>
Node<T>* SearchList(Node<T>* head, const T& item){

    Node<T>* temp = head;

    // Iterate temp to find the match item
    while (temp->_item != item && temp->_next != NULL)
        temp = temp->_next;

    if (temp->_item == item) // If found, return temp
        return temp;
    else
        return NULL;
}

// find previous node
template <typename T>
Node<T>* PreviousNode(Node<T>* head, Node<T>* prevToThis) {
    if (prevToThis == head)
        return NULL;
    else {
        Node<T> *prev = head;

        // Iterate it until it reaches the one before prevToThis
        while(prev->_next != NULL && prev->_next != prevToThis)
            prev = prev->_next;
        return prev;
    }
}    

template <typename T>
Node<T>* InsertAfter(Node<T>* afterThis, const T& insertThis){
    // Create a temp node
    Node<T>* temp;
    temp->_item = insertThis;

    if (afterThis->_next == NULL){
        temp->_next = NULL;
        afterThis->_next = temp;
    }
    else {
        // Point temp to next node
        temp->_next = afterThis->_next;

        // Point mark node to temp
        afterThis->_next = temp;
    }

    return temp;
}


// Insert an item before a node
template <typename T>
Node<T>* InsertBefore(Node<T>*& head, Node<T>* beforeThis, T insertThis){
    Node<T> *prev = PreviousNode(head, beforeThis);

    Node<T>* temp;

    // If current node is head node
    if (beforeThis == head){
        temp->_item = insertThis;
        temp->_next = head;
        head = temp;
    }

    // Other nodes
    else {
        temp = InsertAfter(prev, insertThis);
    }

    return temp;
}

File main.cpp, test 1, run InsertAfter functions:

int main(){
    Node<int>* head = NULL;
    for (int i = 0; i < 10; i++)
        InsertHead(head, i * 10);
    PrintList(head);
    cout << endl;

    Node<int> *pos_50 = SearchList(head, 50);
    cout << "Insert 500 after 50: ";
    cout << endl;
    InsertAfter(pos_50, 500);
    PrintList(head);

    Node<int> *pos_0 = SearchList(head, 0);
    cout << "Insert 600 after 0: ";
    cout << endl;
    InsertAfter(pos_0, 600);
    PrintList(head);
}

Output, test 1, the rest of the code is not outputted

[90]->[80]->[70]->[60]->[50]->[40]->[30]->[20]->[10]->[0]->|||
Insert 500 after 50: 

File main.cpp, test 2: Run InsertAfter functions similarly like test 1 , but change the position of the std::cout lines:

int main(){
    Node<int>* head = NULL;
    for (int i = 0; i < 10; i++)
        InsertHead(head, i * 10);
    PrintList(head);

    cout << endl;
    cout << "Insert 500 after 50: ";
    cout << endl;
    Node<int> *pos_50 = SearchList(head, 50);

    InsertAfter(pos_50, 500);
    PrintList(head);

    cout << endl;
    cout << "Insert 600 after 0: ";
    cout << endl;
    Node<int> *pos_0 = SearchList(head, 0);
    InsertAfter(pos_0, 600);
    PrintList(head);
}

Output test 2: After changing the position of std::cout lines, the rest of the output is shown

[90]->[80]->[70]->[60]->[50]->[40]->[30]->[20]->[10]->[0]->|||
Insert 500 after 50: 
[90]->[80]->[70]->[60]->[50]->[500]->[40]->[30]->[20]->[10]->[0]->|||
Insert 600 after 0: 
[90]->[80]->[70]->[60]->[50]->[500]->[40]->[30]->[20]->[10]->[0]->[600]->|||


File main.cpp test 3, Run InsertAfter like test 1 , but run only once:

int main() {
    Node<int>* head = NULL;
    for (int i = 0; i < 10; i++)
        InsertHead(head, i * 10);
    PrintList(head);
    cout << endl;

    Node<int> *pos_50 = SearchList(head, 50);
    cout << "Insert 500 after 50: ";
    cout << endl;
    InsertAfter(pos_50, 500);
    PrintList(head);
}

Output test 3, the output is shown:

[90]->[80]->[70]->[60]->[50]->[40]->[30]->[20]->[10]->[0]->|||
Insert 500 after 50: 
[90]->[80]->[70]->[60]->[50]->[500]->[40]->[30]->[20]->[10]->[0]->|||

File main.cpp test 4, run test 4, put InsertAfter like test 3 , then check PreviousNode

int main() {
    Node<int>* head = NULL;
    for (int i = 0; i < 10; i++)
        InsertHead(head, i * 10);
    PrintList(head);
    cout << endl;

    cout << "Insert 500 after 50: ";
    cout << endl;
    Node<int> *pos_50 = SearchList(head, 50);
    InsertAfter(pos_50, 500);
    PrintList(head);


    cout << "Previous node before 50: " << *PreviousNode(head, pos_50);
}

Output: The previous node is 0, which is not correct

[90]->[80]->[70]->[60]->[50]->[40]->[30]->[20]->[10]->[0]->|||
Insert 500 after 50: 
[90]->[80]->[70]->[60]->[50]->[500]->[40]->[30]->[20]->[10]->[0]->|||
Previous node before 50: [0]


File main.cpp test 5, run InsertAfter and PreviousNode similarly like test 4 , but I run PreviousNode first.

int main(){
    Node<int>* head = NULL;
    for (int i = 0; i < 10; i++)
        InsertHead(head, i * 10);
    PrintList(head);
    cout << endl;


    Node<int> *pos_50 = SearchList(head, 50);
    cout << "Previous node before 50: " << *PreviousNode(head, pos_50);
    cout << endl;
    cout << "Insert 500 after 50: ";
    cout << endl;
    InsertAfter(pos_50, 500);
    PrintList(head);
}

Output test 5, similar to test 4, but the output is correct:

[90]->[80]->[70]->[60]->[50]->[40]->[30]->[20]->[10]->[0]->|||
Previous node before 50: [60]
Insert 500 after 50: 
[90]->[80]->[70]->[60]->[50]->[500]->[40]->[30]->[20]->[10]->[0]->|||

Main.cpp test 6, run only InsertBefore

int main(){
    Node<int>* head = NULL;
    for (int i = 0; i < 10; i++)
        InsertHead(head, i * 10);
    PrintList(head);
    cout << endl;


    Node<int> *pos_50 = SearchList(head, 50);
    cout << "Insert 700 before 50: " << endl;
    InsertBefore(head, pos_50, 700);
    PrintList(head);

}

Output test 6: The result appears infinitely

[700]->[700]->[700]->[700]->[700]->

I sincerely hope you can look at it and explain to me why test 1 doesn't show the rest of the output, why PreviousNode in 4 because of minor changes in cout lines, and why the InsertBefore has a loop, even though I used only the previous functions. Thanks a lot!

You must return the outs stream in operator<< . Currently you return nothing. It should be:

friend std::ostream& operator <<(std::ostream& outs, const Node<T> &printMe){
    outs << "[" << printMe._item << "]";
    return outs; // added missing return
}

Also, InsertAfter has a dangling pointer. Just watch gcc emit warnings (run all your compilations with -Wall on GCC and Clang, and with /w4 on Visual Studio):

prog.cc: In function 'Node<T>* InsertAfter(Node<T>*, const T&) [with T = int]':
prog.cc:83:5: warning: 'temp' is used uninitialized in this function [-Wuninitialized]
   83 |     temp->_item = insertThis;
      |     ^~~~

The offending code is:

template <typename T>
Node<T>* InsertAfter(Node<T>* afterThis, const T& insertThis){
    // Create a temp node
    Node<T>* temp;
    temp->_item = insertThis;

The temp variable is a pointer, not a node. Initially it points to nothing specific, and accessing it is undefined behavior. You must create a new node:

template <typename T>
Node<T>* InsertAfter(Node<T>* afterThis, const T& insertThis){
    // Create a temp node
    auto temp = new Node<T>;
    temp->_item = insertThis;

With InsertBefore it is more complicated, since sometimes a new object is needed and sometimes it is not:

template <typename T>
Node<T>* InsertBefore(Node<T>*& head, Node<T>* beforeThis, T insertThis){
    Node<T> *prev = PreviousNode(head, beforeThis);

    Node<T>* temp;

So the safest thing is to reorganize the code a little:

if (beforeThis != head){
    return InsertAfter(prev, insertThis);
}
auto temp = new Node<T>;

temp->_item = insertThis;
temp->_next = head;
head = temp;

General note : Better use std::unique_ptr and std::make_unique instead of raw pointers and new . If possible, avoid new altogether. If you use std::unique_ptr correctly, the chances for dangling pointers and memory leaks are considerably reduced.

Also, I strongly recommend to use C++ best practices. For example, hiding implementation details from the users of your class, using nullptr and not NULL , returning a reference when nullptr is impossible and there is no need to operate on the pointer, use return instead of modifying by reference parameters when possible, and so on.

edit

Added an advice to consult warnings, when developing code.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM