I am new to C++. I am trying to implement Linked Lists and doing so using classes. In order to allocate the Nodes, I use malloc()
and allocate the memory for each node inside a for
loop (for the 2nd to 2nd last node).
But, when I try to print the values, by using the printList()
function, the memory addresses change and I can no longer access the value stored earlier.
Code in question:
#include <stdio.h>
#include <stdlib.h>
class Node{
private:
// Attributes
int value;
Node* nextNode;
public:
// Empty Constructor
Node(){
value = 0;
}
// Value-and-address Constructor
Node(int localValue, Node* localAddress){
value = localValue;
nextNode = localAddress;
}
// Value Setter
void setValue(int localValue){
value = localValue;
}
// Address Setter
void setAddress(Node* localAddress){
nextNode = localAddress;
}
// Value Getter
int getValue(){
return value;
}
// Next Node Address Getter
Node* getNextNode(){
return nextNode;
}
};
class linkedList{
private:
Node* firstNode;
Node* lastNode;
int sizeOfNode = sizeof(Node);
int lengthOfList = 0;
int iter;
Node* container = (Node*)malloc(sizeof(Node)); // Container for last node
public:
// Constructor
linkedList(int sizeOfList, int* localArray){
// Creation of first Node
firstNode = (Node*)malloc(sizeOfNode);
*firstNode = Node(localArray[0], container);
Node* previousNode = previousNode; // Current Node
lengthOfList ++;
for(int i = 1; i < sizeOfList-1; i++){
iter = i;
Node* currentNode = (Node*)malloc(sizeOfNode);
printf("n Next Node Location: %p", currentNode);
(*previousNode).setAddress(currentNode);
printf("n Current Node Location: %p", (*previousNode).getNextNode());
*currentNode = Node(localArray[i], container);
previousNode = currentNode;
lengthOfList ++;
}
// Creation of last Node
iter++;
lastNode = (Node*)malloc(sizeOfNode);
*lastNode = Node(localArray[iter], container);
lengthOfList ++;
}
void printList(){
// First Print
Node* currentNode = firstNode;
for(int i = 0; i < lengthOfList; i++){
printf("n%d: %dn", i, (*currentNode).getValue());
printf("Next Node Location: %pn", (*currentNode).getNextNode());
currentNode = (*currentNode).getNextNode();
}
}
};
int main(){
int passedArray[] = {1, 2, 3, 4};
int sizeOfPassedArray = sizeof(passedArray)/sizeof(int);
linkedList list1 = linkedList(sizeOfPassedArray, passedArray);
list1.printList();
}
Upon running it, the output is:
Next Node Location: 00000261A1E6F940
Current Node Location: 00000261A1E6F940
Next Node Location: 00000261A1E6F700
Current Node Location: 00000261A1E6F700
0: 1
Next Node Location: 00000261A1E6F780
1: 0
Next Node Location: 0000000000000000
The reason I think the address is changed when the loop is exited is because:
-
The output prints a different address.
-
I can still access the
firstNode
normally.
What is the issue, and how do I solve it?
The debugger shows segmentation fault
at the return value
line in the getValue()
function, if that is relevant, but that is only from the second iteration of the printList()
loop and onwards.
17
I will try to summarize it for you:
malloc
will never call the constructor of your class as it is mainly used for C not for C++.- to dynamically allocate class instances in C++ you should use
new
instead of malloc - allocated memory using
malloc
ornew
must be freed withfree
ordelete
respectively - using
free
will never call your instance destructor
once you are done with your instance delete it.
1
All right, something constructive to think about here. You have the basic idea down, just a few things to help clarify stuff in your head.
Node
A “node” should be a very simple class. The idea of setters and getters is a good one, but you typically only need them when modifying them needs to involve functionality affecting the other parts of the class. In the case of a node in a linked list, the node itself should not be public facing, so no setters or getters are really necessary:
struct node
{
int value;
node * next;
};
But if you really want to add setters and getters, it is also a good idea to be careful with your vocabulary. An “address” is the value stored in a pointer. You aren’t storing any old address, you are storing the address of the next node, which we can abbreviate to just “next” (or not: “nextNode” works too).
In any case, try to stick to the same vocabulary word when dealing with an object:
class node
{
int _value;
node * _next;
public:
int getValue() const { return _value; }
void setValue( int value ) { _value = value; }
node * getNext() const { return _next; }
void setNext( node * next ) { _next = next; }
};
This keeps things simple and easy to track (meaning: it is less easy to confuse yourself when using the class later).
Names
Notice also that I did not capitalize the name of the class. You can, of course, but then be consistent: (Node
and LinkedList
) or (node
and linkedList
).
⟶ I personally prefer snake_case: linked_list
, but most people seem to prefer camelCase or PascalCase. Meh.
Also, you will notice that I added a leading underscore to private member variables. You do not need to do this, but a lot of people like to decorate them as such — it helps you (the programmer) to remember that this is a private field and should only be accessed by methods that have a good reason to be touching them directly. Other common ways to decorate them include m_value
and value_
.
⟶ Keep in mind that leading underscores are not valid for global object names.
Linked List
A doubly-linked list comes in two flavors:
- head and tail node pointers reference the actual head and tail nodes of the list
- head and tail nodes (actual node objects) are empty and are used to bracket the actual list. That is,
head
’s value is ignored, and it’s next pointer points to the actual first node in the list. This comes at the cost of using more space for every list (by a count of two nodes with unused values) but makes life a whole lot easier when writing the code to deal with inserting and deleting nodes from the list.
Yours is a singly-linked list, but things change only a little. The head still works the same as it does in a doubly-linked list (whichever option you choose), but the tail is now only useful for quickly appending data to the list, and therefore must always just be a pointer to the actual last node in the list regardless of whether or not head is a pointer.
⟶ Many singly-linked lists do not have a tail pointer, since their needs only require adding a node to the head and rarely the tail. Again, use case matters!
container vs tail/last node
Because the tail pointer must always point to the actual tail node of the list, there is no need to allocate a container
. Again, vocabulary here matters. The word “container” is expected to refer to the structure managing the entire collection. (You have already named that “linkedList”.)
Since you already have a tail
pointer, you do not need another magic empty node to manage the tail. Just make sure that tail
always points to the last node in the list and all is good.
sizeOfNode is…?
Beware to not use variables to store values that are easily obtained from the compiler. All that does is obfuscate what is really happening. Hence:
int sizeOfNode = sizeof(Node);
is totally unnecessary. If at any time you need the size of an object, just use sizeof
. Using sizeOfNode
makes the reader wonder if there is something unusual going on that he or she cannot immediately see, since you are taking the effort to store a value it is reasonable to assume that there is a reason for that, and the only reason would be if there is some spot where sizeOfNode
is not equal to sizeof(Node)
. That is not the case, hence doing that adds complexity for no reason.
iter doesn’t belong
The word “iter” refers to an iterator, which is a pointer (or pretends to be one). Further, the variable itself is only used in a function. Again, do not declare anything not strictly necessary to store the data in the class definition. Variables like this should be declared in the function they are used, as close to where they are used as possible.
For a simple integer index into an array, variable names like i
and j
and k
and n
are common.
Revised list
Your revised linked list class might look like this:
class linkedList
{
node * _firstNode; // or head or headNode etc
node * _lastNode; // or tail or tailNode etc
int _lengthOfList;
public:
linkedList() : _firstNode{nullptr}, _lastNode{nullptr}, _lengthOfList{0} { }
linkedList( int length, int * array )
: _firstNode{nullptr}, _lastNode{nullptr}, _lengthOfList{0}
{
...
}
int getLength() const { return _lengthOfList; }
};
Note that we have a default constructor (which just gives you an empty list) and a constructor that takes an array of integers to build a list. The :
indicates a “member initializer list”, which allows us to easily give all the class member objects an initial value, which we should. (Otherwise you would have to assign initial directly in the body of the construct, which also works, but whatever floats your boat.)
linkedList()
{
_firstNode = nullptr;
_lastNode = nullptr;
_lengthOfList = 0;
}
Notice also the addition of a getter to obtain the current list length. There is no setter. To change the length nodes must be added or deleted.
⟶ Standard C++ containers call this “size”. So you could name _lengthOfList
just _size
and the getter int size() const { return _size; }
and everyone who reads your code will understand that it refers to the number of elements in your linked list.
Adding nodes to the end of your list
Before you worry about the array ⟶ linked list constructor, you should have a method to simply append a single node to the end of the list. Something like:
class linkedList
{
public:
...
void append( int value )
{
...
}
...
};
This method should do nothing more than tack a new node onto the end of the list. It needs to:
- allocate memory for the new node (using either
malloc()
or even better, since this is C++, usingnew
) - set the newly-allocated node’s
_value
to the argument value - set the newly-allocated node’s
_nextNode
tonullptr
- set
_lastNode->_nextNode
to your new node (thus linking your node to the end of the list) - set
_lastNode
to point to the new node (in order to keep_lastNode
pointing to the actual last node in the list) - increment the
_lengthOfList
(in order to keep it accurate)
This kind of thing is something you should draw on a piece of paper. You should use a paper and pencil every time you do something to link or unlink nodes in your list.
Caveats for your singly-linked list:
- The
_firstNode
may benullptr
. If it is you must set both its value and_lastNode
’s value to point to the new node. - If
_firstNode
is notnullptr
, you only need to update_lastNode
.
Again, drawing this out on paper will help you to see these issues!
Now that you have a function to append nodes, your array ⟶ linked list constructor is super easy to write:
linkedList( int length, int * array )
: _firstNode{nullptr}, _lastNode{nullptr}, _lengthOfList{0}
{
for (int n = 0; n < length; n++)
append( array[n] );
}
That was surprisingly easy, no?
Final Thoughts
As you learn to use C++ and work your way through these kinds of structures life will get easier. Remember, it is always okay to build yourself a model of what you are trying to do, whether it be with paperclips or a pencil and paper (or whatever seems useful).
In this case, the most basic blocks of a linked list are:
- how to store the structure
- adding a node
- removing a node
- indexing a node
Everything else is just sweet, sweet sugar to help you do those things.
6