I would appreciate some help relative to my code solution, which deals with linked list management in C. I'll already declare the only strange thing with my request: I am writing a C++ file, but I am actually mostly leveraging C resources (malloc(), free(), etc.); that said, given the basic code I provide, I am confident no one will have trouble with that.
I want to write a function to add elements to the end of the list and one to delete elements from it, that work in any edge case. Given my desire, the removal function was the one that I struggled the most with, but also the one that made me realize how little I am understanding pointers.
I will now share the code I produced, that should be working fine, but:
It can surely be greatly improved both in terms of clarity and performance
I think that showing it to the community will highlight many of the flaws present in my solution
// The plan is to create a linked list and to be able to add and delete its elements
#include <iostream>
using namespace std; // I can write output lines as cout << "Hi!", rather than std::cout < "Hi!"
#include <cstdlib> // needed for malloc() in C++
struct node {
int data;
node* nextPtr; //"struct node* nextPtr;" : This would be the syntax for plain old C: you always have to type the "struct" keyword
};
node* createElement(int data) {
node* newElemPtr = (node*)malloc(sizeof(node)); // the "(node*)" cast is required by C++, and is not used in C
newElemPtr->data = data;
newElemPtr->nextPtr = NULL;
return newElemPtr;
}
void appendElement(int data, node** head) { // Adds a new node at the end of the list
// I pass as argument a pointer to pointer (double pointer) to node, so that I can edit the head node
// if the list is empty, without having to return a new node pointer as head: my function indeed features
// "void" in its signature
node* elemPtr = NULL;
elemPtr = createElement(data); // elemPtr is a pointer to the new node
if (*head == NULL) {
*head = elemPtr;
}
else {
node* currPtr = *head; // currPtr is the temporary variable that visits each node of the linked list
while (currPtr->nextPtr != NULL)
currPtr = currPtr->nextPtr;
currPtr->nextPtr = elemPtr; // Set last element's nextPtr to "elem", i.e., a pointer to the new element
}
};
void removeElement(int data, node** head) { // Remove all the nodes whose data content matches the "data" argument
int presence_flag = 0; // Flag used to check whether the required data is present at all in the linked list
if (*head == NULL) {
return;
}
else {
node* currPtr = *head;
node* prevPtr = *head;
while (currPtr != NULL) {
// This is the case in which I find a node to delete (it matches the "data" query), and it is not the first of the list
if (data == currPtr->data && currPtr != *head) {
prevPtr->nextPtr = currPtr->nextPtr; // Link the node ahead of the one to delete with the one behind
free(currPtr);
currPtr = prevPtr; // In the next loop, I will resume the analysis from the previous node, which now points to an unvisited one
presence_flag = 1;
}
// This is the case in which I find a node to delete and it is the first of the list
else if (data == currPtr->data && currPtr == *head) {
// This is the case in which I have to delete the first node, but the list features other nodes
if (currPtr->nextPtr != NULL){
*head = currPtr->nextPtr; // Move *head forward
currPtr = *head; // Do the same with currPtr, in order not to break the while() loop
free(prevPtr); // As *head has already been re-assigned, I leverage prevPtr to delete the old *head
presence_flag = 1;
}
// This is the case in which I have to delete the first and only node of the list
else {
*head = NULL;
currPtr = *head;
presence_flag = 1;
}
}
// This is the case in which the current node does not match the queried "data" value
else{
prevPtr = currPtr; // Update prevPtr
currPtr = currPtr->nextPtr; // Move currPtr forward
}
}
}
if (presence_flag == 0)
cout << "There is not any node with value " << data << " in the linked list.\n\n";
// Q1: Am I causing any memory leak by using *head == NULL instead of free(*head)?
// Q2: Should I free() everythin before ending the main(), at least as a good practice?
// Q3: Is there a way to make this function by not using a double pointer as input and by also keeping "void" as return value?
// Of course, it should still work in the tricky edge case of the last element in the list that has to be deleted
};
void printLinkedList(node* head) { // Here I return nothing, so I can freely edit "head" (i.e., there is no need for a temporary pointer)
if (head == NULL) {
cout << "The linked list is empty.\n";
}
else {
int elemCounter = 0;
while (head != NULL) {
elemCounter += 1;
cout << "elem N. " << elemCounter << ": data value = " << head->data << "\n"; // head->data is equal to (*head).data
head = head->nextPtr;
}
}
};
int main(int argc, char* argv[])
{
//cout << "Size of a single node of the list = " << sizeof(node) << "\n";
// == 16. On a 64 bits machine, an int ("data") requires 4 bytes.
// The pointer requires 8 bytes; the remaining 4 bytes are padding
node* head = NULL;
appendElement(1, &head);
appendElement(2, &head);
appendElement(3, &head);
printLinkedList(head);
cout << "\nRemoving element with data value = 1...\n\n";
removeElement(1, &head);
printLinkedList(head);
cout << "\nRemoving element with data value = 2...\n\n";
removeElement(2, &head);
printLinkedList(head);
cout << "\nRemoving element with data value = 3...\n\n";
removeElement(3, &head);
printLinkedList(head);
cout << "\nRemoving element with data value = 4...\n\n";
removeElement(4, &head);
printLinkedList(head);
cout << "\nRemoving element with data value = 1...\n\n";
removeElement(1, &head);
printLinkedList(head);
cout << "\nRemoving element with data value = 2...\n\n";
removeElement(2, &head);
printLinkedList(head);
return 0;
}
As you can see from the comments embedded in the code, I have 3 doubts that captured my interest while coding the node removal function:
Q1: Am I causing any memory leak by using *head == NULL instead of free(*head)?
Q2: Should I free() everything before ending the main(), at least as a good practice?
Q3: Is there a way to make this function by not using a double pointer as input and by also keeping "void" as return value? Of course, it should still work in the tricky edge case of the last element in the list that has to be deleted
I hope that featuring these "additional" questions is something reasonable to put here, as maybe someone in the future may have the same doubts I had.
I know there are plenty of ready-to-copy-and-paste solutions for my task, but I think I can really learn this stuff if I see why my precise design choices are not optimal/wrong.
I thank everyone for the time spent reading this.
There are many duplicated code. Also the function should not output any message. It is the caller of the function that decides whether to output a message. So the function should have the return type bool if you are considering the program as a C++ program or bool or int if you are considering the program as a C program.
The function removeElement invokes undefined behavior because in its paths of execution you are not always resetting correctly values of the pointers currPtr and prevPtr after deleting a node.
For example after this code snippet
if (data == currPtr->data && currPtr != *head) {
prevPtr->nextPtr = currPtr->nextPtr; // Link the node ahead of the one to delete with the one behind
free(currPtr);
currPtr = prevPtr; // In the next loop, I will resume the analysis from the previous node, which now points to an unvisited one
presence_flag = 1;
}
prevPtr and currPtr will be equal each other.
I would define the function the following way
int removeElement( node **head, int data )
{
int deleted = 0;
while ( *head )
{
if ( ( *head )->data == data )
{
deleted = 1;
node *current = *head;
*head = ( *head )->next;
free( current );
}
else
{
head = &( *head )->next;
}
}
return deleted;
}
As for your question
Q3: Is there a way to make this function by not using a double pointer
as input and by also keeping "void" as return value? Of course, it
should still work in the tricky edge case of the last element in the
list that has to be deleted
then in C you can not achieve this. In C++ you can pass the pointer to the first node by reference. In C passing by reference means passing an object indirectly through a pointer to it. So in C you have to use a double pointer in such a case.
Of course just setting a pointer to NULL without freeing data pointed to by the pointer that was dynamically allocated produces a memory leak. And you should free all the allocated memory then it is not required any more.
Related
The following code builds correctly but causes the program to crash when I run it. Can someone please tell me whats wrong with it. I suspect that there is something wrong with the DeleteNode function.
#include <iostream>
#include <cstdlib>
using namespace std;
class list {
private:
typedef struct node {
int data;
node* next;
}* nodePtr; //this means that 'nodePtr' will mean a pointer to the struct node
nodePtr head;
nodePtr current;
nodePtr temp;
public:
list() { //constuctor
head = NULL;
current = NULL;
temp = NULL;
};
void AddNode(int addData) //to add a particular data value
{
nodePtr n= new node;
n->next = NULL;
n->data = addData;
if (head != NULL) { //if a list is already set up
current = head;
while (current->next != NULL) { //to get to the last node in the list
current = current->next;
}
current->next = n;
}
else { // if list is not created
head = n; //new node is front of the list
}
}
void DeleteNode(int delData) //to delete a particular data value
{
nodePtr delPtr = NULL;
temp = head;
current = head;
while (current != NULL && current->data!=delData) { //pass through whole list && find value
temp = current;
current = current->next;
}
if (current = NULL) { //data value not found in list
cout << delData << " was not in the list." << endl;
delete delPtr; //to free up memory space
}
else {
delPtr = current;
current = current->next;
temp->next = current; //to reconnect list
if (delPtr == head) {
head = head->next;
temp = head;
}
delete delPtr;
cout << "The value " << delData << "was deleted." << endl;
}
}
void PrintList() //to print all the data values
{
current = head;
while (current != NULL) { //to go through the data valued of the list
cout << current->data << endl;
current = current->next;
}
}
};
int main()
{
list Shahzad;
Shahzad.AddNode(2);
Shahzad.AddNode(78);
Shahzad.AddNode(28);
Shahzad.AddNode(2398);
Shahzad.DeleteNode(78);
Shahzad.PrintList();
return 0;
}
Your first problem is with the following line:
if (current = NULL)
You're actually assigning null to current at this point.
This should actually be:
if (current == NULL)
Firstly, few code and file management remarks: consider separating your code into .h file where class members are declared and .cpp where class members are implemented, this will make your class easy to comprehend and possible errors will be easier to locate.
Secondly, a general advice when dealing with structures containing pointers is attention to proper resource management, i.e. pointer definitions, initialisations and deletions should be dealt with caution. If you are novice, consider the use of already provided smart pointer facilities like: std::unique_ptr which will "retain sole ownership of an object through a pointer and destroys that object when the unique_ptr goes out of scope"
Thirdly, use debugger to get rid of trivial errors like:
if (current = NULL)
which by the way contains additional inaccuracy expressed in the use of NULL instead of the pointer literal nullptr.
Lastly, check each of the member functions separately after you finish the initial implementation and only then proceed with further class expansion, otherwise you risk the accumulation of errors from multiple sources which will make your job very difficult
In your delete function in the case of which the node isn't found, you are deleting delPtr.
However, delPtr was never instantiated or assigned so you are trying to delete something that doesn't exist.
Always enclose pointer deletions in if statements to avoid this issue. Try this:
if (delPtr) delete delPtr;
Apart from all the suggestions here, you can use some safe programming practices to catch bugs early.
For ex: you wrote
if (current = NULL)
Instead, try writing the value being checked on the LHS and the variable on the RHS like this:
if ( NULL == current)
Here, if you mistyped
if (NULL = current)
the compiler will complain. You have a compile time bug now instead of a run-time one. This is far easier to find and debug.
I've checked the boards and could not find any help with this. I find it easy to implement recursive functions given base and general cases, but this doesn't work the way I do it. I'm supposed to iterate down a list until I reach the tail of a linked list. If the next node is NULL, then I have to store the value at the last node, remove that node, and return the value. So it's similar to a dequeue method, except it's performed recursively. What am I doing wrong?
int LinkedList::removeTailRec(Node *n)
{
// check for the base case(s)
if(n->next == NULL)
{
Node *tmp = new Node();
tmp = n;
int val = n->value;
tmp = NULL;
return val;
}
else
return removeTailRec(n->next);
// else call the recursive method
}
First, I recommend you use nullptr instead of NULL.
Then, onto your code. You're actually not removing anything from your list.
if(n->next == NULL)
{
Node *tmp = new Node();
^^^^^^^^^^
//Useless, and dangerous. This memory is never free'd
tmp = n;
int val = n->value;
tmp = NULL;
^^^^^^^^^^
//You just set a local variable to NULL, you're not deleting anything
return val;
}
If you want to remove the node, you'll have to keep a reference to the previous node (e.g. having a doubly linked list, that is, having a pointer to the next element and a pointer to the previous element in each node, or working on the previous node directly).
Set this previous node's next to nullptr, store the node's value and then delete the Node pointer.
One way to do this is to work with the pointer to the next node :
int LinkedList::removeTailRec(Node *n)
{
//EDIT: Adding a check for n validity
if(!n){
//Here, you should have a way of detecting
//a call to your method with a null pointer
return 0;
}
Node* nextNode = n->next;
// check for the base case(s)
if(nextNode->next == nullptr)
{
//Get the next node value
int val = nextNode->value;
//Set the current node next member to nullptr
n->next = nullptr;
//Free the last node
delete nextNode;
return val;
}
else{
return removeTailRec(n->next);
}
// else call the recursive method
}
You are storing the result but not deleting it from linked list. You can return result in another variable (pointer : result).
Node* getTail(Node *n,int *result){
//u can even free the memory
if(!n->next)
{
result=n->value;
return NULL;
}
n->next=getTail(n->next,result);
}
or you can do it other way
int getTail(Node *n)
{
if(!n) return 0;
if(n->next)
{
if(!n->next->next)
{
Node *frnode=n->next;
int result=n->next->value;
n->next=NULL;
delete frnode;
return result;
}
getTail(n->next);
}
You are not removing last node in your code, and you leak another (temporary) node here.
To remove last node you have to zero the link in the previous node.
Your code should look like
...
if (n == NULL || n->next == NULL)
throw std::out_of_range("node");
if(n->next->next == NULL)
{
int val = n->next->value;
delete n->next;
n->next = NULL;
return val;
}
else ...
Be aware of the fact that c++ is not a functional language and has no optimizations for tail recursion, so in real application as your lists grow big enough you'll eventually have failure with stack overflow =) use Haskell or Erlang for this style of programming, in c++ use for or while.
You should set the Node n's previous Node's next field to NULL when n is the tail Node.
I am writing a balancing binary tree for class, but I am having some confusion as to how to use pointers and references in C++ (coming straight from Java). The code below results in a segfault, because no node has actually been added to the tree, curr has just been switched to the new Node. How would I go about making it so that the new Node goes to where curr is pointing to on the tree, rather than just reassigning curr?
void BalancedTree::insert(int input)
{
cout << "Insert started\n"; //DEBUG
Node* trailingNode;
Node* curr;
curr = this->root;
while(curr != NULL){
cout << "Addloop\n"; //Debug
if(input < curr->data){ //input smaller than current
cout << "Left\n"; //DEBUG
trailingNode = curr;
curr = curr->left;
}else{ //input larger than current node
cout << "Right\n"; //DEBUG
trailingNode = curr;
curr = curr->right;
}
}
insert(curr, input);
cout << "test" << endl;
cout << root->data << " added\n"; //DEBUG
// curr->parent = trailingNode; //Set the parent
size++; //Increase size
}
//Helper method
void BalancedTree::insert(Node*& curr, int input)
{
curr = new Node(input);
}
If we have the following tree and attempt to insert the value 3:
2
/ \
1 <-- --> 4
/ \ / \
N N N N
(N is NULL) after the while loop as posted has completed:
trailingNode is pointing to the Node with value 4
curr is NULL (the left branch of Node with value 4)
then insert() assigns a new Node to curr but never attaches it to the left branch of the Node with value 4.
To attach you could change the call to insert():
insert(input < trailingNode->data ?
trailingNode->left :
trailingNode->right,
input);
You need to handle the case when the tree is empty. There are other ways that this could be achieved but hopefully this will point you in the right direction.
You can do it with a double pointer. Make cur actually reference (in C++ case point to) the last node reference you need to change:
Node ** curr;
curr = &this->root;
while(*curr != NULL){
cout << "Addloop\n"; //Debug
if(input < (*curr)->data){ //input smaller than current
cout << "Left\n"; //DEBUG
trailingNode = curr;
curr = &((*curr)->left);
}else{ //input larger than current node
cout << "Right\n"; //DEBUG
trailingNode = curr;
curr = &((*curr)->right);
}
}
insert(curr, input);
cout << "test" << endl;
cout << root->data << " added\n"; //DEBUG
// curr->parent = trailingNode; //Set the parent
size++; //Increase size
}
//Helper method
void BalancedTree::insert(Node** curr, int input)
{
*curr = new Node(input);
}
I think this should do the trick for you, but have not tried it - just edited in the edit here, so please forgive me if I made some simple coding error.
1) initialize the pointers to NULL:
Node* trailingNode = NULL;
Node* curr = NULL;
2) no need to explicitly check for NULL in the while loop:
while(curr){
3) The call to insert(curr, input) is always called with a NULL pointer! And even if it would not be NULL that pointer is still just a regular pointer having no relation with the node you retrieved it from. Taking a reference to it will not fix this. Instead you need to create a new node and assign it to either the left or right of the trailingNode, f.e.:
if (!curr->right) {
curr->right = new Node (input);
break; // exit while
}
Pointers are value types, similar to references or ints in Java.
Passing variables by reference creates an alias to that variable, so you can change its value in the function. When you do insert(curr, input);, it modifies the value of the pointer variable curr, making it point at a newly created node. The more or less equivalent situation in Java would be:
Node curr;
if([...]) {
[...]
curr = curr.left;
}
else {
[...]
curr=curr.right;
}
[...]
curr = new Node();
Now you can see how this doesn't actually insert anything into the tree. Now, if you want a variable to indicate an actual left or right member of a node, instead of just having the same value (pointing at the same object) as that member, you need to have a pointer to the member (field) itself - as the member is of a type 'pointer to Node', a pointer to the member would have to be a pointer to (pointer to Node), or in C++ notation Node**.
Now if you pass that 'pointer to pointer' to a function, you can modify through it the value of the referenced left or right member. This also means you don't need to modify the value of curr (and therefore pass it in by reference) - it will still point to the same left or right member (or possibly the root member of your BalancedTree object), only that member's value will be changed to point to the newly created element.
I am trying to create my own datatype that is like a vector or an array.
I am having troubles with my print function; When I go to print the list, it only prints the last item in the list.
// LinkedListClass.cpp : Defines the entry point for the console application.
#include "stdafx.h"
#include <iostream>
class Node
{
public:
int value;
Node* next;
Node::Node(int val)
{
value = val;
};
};
class List
{
public:
Node* firstNode;
Node* currentNode;
int size;
List::List()
{
firstNode = NULL;
currentNode = firstNode;
size = 0;
};
void push(Node* node)
{
if(firstNode == NULL)
{
firstNode = node;
firstNode->next = currentNode;
size++;
}
else
{
currentNode = node;
currentNode = currentNode->next;
size++;
}
};
void print()
{
if(firstNode != NULL)
{
Node* printNode = firstNode;
while(printNode->next != NULL)
{
std::cout << "List Item " << printNode->value << std::endl;
printNode = printNode->next;
}
}
};
};
int _tmain(int argc, _TCHAR* argv[])
{
List ll = List();
for(int i = 0; i < 10; ++i)
{
Node val = Node(i);
ll.push(&val);
}
std::cout << ll.firstNode->value << std::endl;
ll.print();
std::cout << "Size " << ll.size << std::endl;
std::cin.ignore();
return 0;
}
/* Output
9
Size 10
*/
I know this is nowhere near completed, but if you have any other pointers (lol), please feel free to suggest.
There are three important errors:
push() --- fixed
void push(Node* node)
{
if(firstNode == NULL)
{
firstNode = node;
currentNode = node;
// firstNode->next = currentNode; --> this does nothing useful!
size++;
}
else
{
currentNode->next = node;
currentNode = node;
//currentNode = node; -|
//currentNode = currentNode->next; -|----> why? what? Do explain.
size++;
}
}
I think by assigning firstNode->next = currentNode; you expected the next time currentNode was updated, it would update firstNode->next as well.
It doesn't work that way.
firstNode->next = currentNode; implies that the address stored in currentNode is now in firstNode->next. So next time you store something in currentNode = node; you're not storing it in firstNode->next. So you have a broken linked list --- which is why your output didn't go very far.
Also, this is really bad. By setting currentNode=node before setting the current node's next pointer to node, you've broken the list again. You should first point currentNode->next to node and then set the currentNode as node (node being the node which you're pushing onto your list).
Node val = Node(i);
The scope of val is only within that iteration of your loop. Once you loop around, it's off the stack and doesn't exist anymore. But you've copied the pointer of val to your list --- so now with the right push method, you're just adding a dangling pointer.
Node *val = new Node(i);
ll.push(val);
You need to put it on the heap so it stays on till you don't need it anymore.
... which leads us to your destructor!
Since you've allocated a node, you'll need to deallocate it. So do that in your destructor --- traverse your list and deallocate all those nodes.
The following lead to undefined behavior:
Node val = Node(i);
ll.push(&val); // take address of temporary
...
firstNode = node; // store address of temporary here
...
ll.print(); // temporary `val` was destroyed, but all nodes are point to it
You could change your code as follows:
Node* val = new Node(i);
ll.push( val );
And don't forget to delete all nodes later.
Your push() method is incorrect. The first time you push a node, it correctly assigns it to firstNode, but every subsequent push() just sets currentNode to the new node, and then sets currentNode to NULL -- you're not actually adding anything to your list.
I think it bears mentioning that pointers are not reference-by-name in C++. For instance, setting firstNode->next = currentNode doesn't make currentNode the next element in the list; it just makes firstNode->next point to the same address that currentNode does (in this case, NULL).
I'm not going to write the code for you, but here's how your push() function should work. The key is that you should be setting the 'next' field of an existing node to your new node, rather than currentNode to the new node:
In the case where firstNode is NULL,
set firstNode to the new node and
set firstNode->next to NULL (since
it has no next element). You can
also set currentNode = firstNode
here for convenience.
In the case where firstNode is not
NULL, we need to walk from firstNode
down the chain until we find a node
whose next field is NULL, then set
its next field to the new node.
Alternatively, we can use that
currentNode pointer to access the
last element in list and do the same
thing, being sure to set currentNode
to point to the new node when we're
done.
You basically have part 1 done, but part 2 still needs to be implemented. Feel free to ask for clarification/give criticism. :)
try it like Node* val=new Node(i)
previously u were storing the temporary variable. so no store the ndoe in dynamic memory so seprate memory can be given.
when u were creating the node it is create for temparary purpose
&temporary address were stored so when u traverse back the temporary memory had been released u will find there some garbage. value.
I'm a programming student in my first C++ class, and recently we covered linked lists, and we were given an assignment to implement a simple one. I have coded everything but my pop_back() function, which is supossed to return a pointer to the Node that needs to be deleted in Main(). No Node deletion is to be done in the actual function. So my question is:
Would you be willing to help point me in the right direction for my pop_back() function? Also, if you notice anything else that I'm doing wrong, let me know.
Also, this linked list is just to work with strings. In this case, a grocery list, so one string for the quantity of the item(1,2), and one string for the item type. (Milk, Eggs, etc.)
Below I've included my List & Node class implementations, so you can get an idea of what I've done so far.
Node.cpp
Node::Node(void)
{
descrip = " ";
quantity = " ";
previous = NULL;
next = NULL;
}
Node::Node(string q, string d)
{
descrip = d;
quantity = q;
previous = NULL;
next = NULL;
}
Node* Node::GetNext()
{
return next;
}
Node* Node::GetPrevious()
{
return previous;
}
void Node::SetNext(Node * setter)
{
next = setter;
}
void Node::SetPrevious(Node * setter)
{
previous = setter;
}
List.cpp
List::List(void)
{
first = NULL;
last = NULL;
numNodes = 0;
}
Node* List::GetFirst()
{
return first;
}
Node* List::GetLast()
{
return last;
}
void List::SetFirst(Node* setter)
{
first = setter;
}
void List::SetLast(Node* setter)
{
last = setter;
}
int List::GetNumNodes()
{
return numNodes;
}
void List::push_front(Node* item)
{
if (first == NULL)
{
first = item;
last = item;
}
else
{
Node* pFirst = first;
item->SetNext(pFirst);
first = item;
numNodes++;
}
}
void List::push_back(Node * item)
{
if (last == NULL)
{
first = item;
last = item;
}
else
{
last->SetNext(item);
last = item;
numNodes++;
}
}
Node* List::pop_front()
{
Node* temp = first;
first = first->GetNext();
if (first == NULL)
{
temp = first->GetNext();
first = p;
}
if (first == NULL)
{
last = NULL;
}
if (numNodes > 0)
{
numNodes--;
}
return temp;
}
Node* List::pop_back() // this whole function may be wrong, this is just my attempt at it
{
Node* temp;
temp = first;
while((temp->GetNext()) != NULL)
// im stuck here
}
Some pointers:
0x1243bfa3
0x45afc56e
0xdeadbeef
Some more pointers:
You should prefer to initialize your class members in the initialization list, not in the constructor's body.
In C++, unlike C89, we declare and define a function with no parameters as void f();, not void f(void);.
In C++ we commonly reset pointers with 0, not NULL.
See below for what I mean in code.
Good C++ code will try to take advantage of RAII. This implies avoiding primitive pointers for the most part. In this case plain old std::auto_ptr<> would make a perfectly sufficient substitute for the primitve Node* pointers. However, I do reckon part of the exercise here is pointer arithmetics, and so I just leave this as a side-note.
It would be useful for us if you'd attach the class declarations. I assumes all those accessors and mutators, GetFirst() and SetFirst() etc., are there because they are public. That's a bad idea. First, they expose the private pointers, which defeats the whole point of accessor. Second, they don't do anything special so they're just extra code -- which means extra room for bugs. This brings me to the next point.
Your mutators are incorrect. You blindly assign a new value to the private member pointer, without deleting what you had before. That's a memory leak.
Ever tried to pop_front() when the list is empty?
Finally, 8 being a round number it's time we get to the question at hand. pop_back(). My question to you is, why are you traversing the list all the way to the end if you so meticulously maintain a pointer to the last node of your list? Indeed, if you wouldn't bother with maintaining a pointer to the end of the list then you'd have to traverse all the way to the last node in order to pop it. And for that you were in the right direction. Except that ...
When you access members through pointers, as in first->GetNext(), always make sure first isn't a null pointer -- or else state in the function's documentation comment that you assume the pointer is not null.
These should get you started.
Points 1, 2 and 3 in code:
Node::Node()
: descrip(" "), quantity(" "), previous(0), next(0)
{
}
So if I understand this right you just want to run through your linked list until you get to the last node in the linked list and return the pointer to it?
I'm pretty sure what you have there will do it except
Node* List::pop_back() // this whole function may be wrong, this is just my attempt at it
{
Node* temp;
temp = first;
while(temp->GetNext() != NULL)
{
temp = temp->GetNext();
}
return temp;
}
So if I read it right, there it will continually loop around until it gets to the node with none in the line behind it, then return it.
I like the previous posters answer, but one thing you might want to keep in mind is if you have an empty list. Then your first pointer will equal NULL and you would be trying to call NULL->GetNext() basically and Seg Fault. I think you can edit the above code slightly and still get have it work like this:
Node* List::pop_back()
{
Node* temp;
temp = first;
while(temp != NULL && temp->GetNext() != NULL)
{
temp = temp->GetNext();
}
return temp;
}
This will have the function return NULL if there is nothing in the list and still work properly.
It would definitely have helped me if you also had posted your class declaration. I cannot guarantee that the below is correct but it makes sense to me
Node* List::pop_back()
{
Node *temp = NULL;
if(numNodes == 1)
{
temp = first;
// setting the list pointers to NULL
first = NULL;
// setting the list pointers to NULL
last = NULL;
//You should also probably remove the links from your node
//to the next and previous nodes but since you didn't specify
//this it is up to you
numNodes--;
}
else if(numNodes > 1) //more than one element
{
//the pointer you want to return
temp = last;
//For clarity I am creating another variable here
Node *newLast = temp->GetPrevious();
//Setting the new last node to point at nothing so now temp
//is "disconnected from the list"
newLast->next = NULL;
//the last pointer of the list is now pointing at the new last node
last = newLast;
//You should also probably remove the links from your node
//to the next and previous nodes but since you didn't specify this it is up to you
numNodes--; //decrement the counter
}
return temp;
}