Reputation: 77
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!
Upvotes: 1
Views: 109
Reputation: 8475
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.
Added an advice to consult warnings, when developing code.
Upvotes: 3