A sequence container stores elements in order
One option: store elements contiguously in memory
Fast indexing pointer arithmetic under the hood
Increasing the capacity requires allocating a new array
Slow insert in the middle
We could also store locations with pointers
struct Node
{
int datum;
Node *next;
};
Each node of the list includes a datum
, but also a next
pointer containing the address of the next node
This structure is called a linked list
// One node in the list.
struct Node
{
int datum;
Node *next;
};
// The entire list.
class IntList
{
Node *first;
};
first
is iether null (empty list) or points to a validNode
- In the last
Node
,next
is always null (0) - For every other
Node
,next
points to anotherNode
class IntList
{
public:
// EFFECTS: constructs an empty list
IntList( );
//EFFECTS: returns true if the list is empty
bool IsEmpty( ) const;
//REQUIRES: list is not empty
//EFFECTS: Returns a reference to the first element
// in the list
int & Front( ) const;
//EFFECTS: inserts datum into the front of the list
void PushFront( int datum );
//REQUIRES: list is not empty
//EFFECTS: removes the item at the front of the list
void PopFront( );
};
class IntList
{
private:
struct Node
{
int datum;
Node *next;
};
Node *first;
};
No code outisde the class needs to know about the Node
type, so it is made private
This is an example of information hiding
We're not giving this class a Node
variable, but rather describing what a future Node
variable would look like
class IntList
{
private:
struct Node
{
int datum;
Node *next;
};
Node *first;
public:
// EFFECTS: constructs an empty list
IntList( ) : first( nullptr )
{
}
};
class IntList
{
private:
struct Node
{
int datum;
Node *next;
};
Node *first;
public:
//EFFECTS: returns true if the list is empty
bool IntList::IsEmpty( ) const
{
return first == nullptr;
}
};
class IntList
{
private:
struct Node
{
int datum;
Node *next;
};
Node *first;
public:
//REQUIRES: list is not empty
//EFFECTS: Returns a reference to the first
// element in the list
int &Front( ) const
{
assert( !IsEmpty( ) );
return first->datum;
}
};
We can both inspect and modify the item inside a node with Front( )
because it returns a reference
We can also use the reference variable x
int &Front( ) const
{
assert( !IsEmpty( ) );
return first->datum;
}
int main( )
{
IntList il;
il.PushFront( 1 );
int &x = il.Front( );
cout << x << endl; // 1
x = 17;
cout << x << endl; // 17
}
When we insert an integer, we start out with the first
field pointing to the current list
The current list might be empty, or not
The first thing we need to do is to create a new node to hold the new first element
Then, we need to establish teh invariants on the new node by setting datum
field to the new element and setting next
field to the beginning of the current list
Lastly, we need to fix the representation invariant by resetting first
void IntList::PushFront( int datum )
{
Node *p = new Node;
p->datum = datum;
p->next = first;
first = p;
}
First, check the REQUIRES
clause
If we are removing the front node, we must delete it to avoid a memory leak
Unofortunately, we can't delete it before advancing the first
pointer
But, after we advance first
, the removed node is an orphan, and can't be deleted
This is solved by using a local variable to remember the "old" first node, called victim
Store pointer in victim
, then delte the node after first
is updated
void IntList::PopFront( )
{
assert( !IsEmpty( ) );
Node *victim = first;
first = first->next;
delete victim;
}
We can use pointers to visit each node in a list
class IntList
{
public:
void Print( ) const;
//...
};
void IntList::Print( ) const
{
for ( Node *i = first; i != nullptr; i = i->next )
cout << i->datum << " ";
}
- Destructor
- Remove all nodes -
PopAll( )
- Remove all nodes -
- Copy constructor
- Initialize member variables
- Copy all nodes from other list -
PushAll( )
- Overloaded assignment operator
- Removed all nodes from this list -
PopAll( )
- Copy all nodes from other list -
PushAll( )
- Removed all nodes from this list -
We need the Big Three, because IntList
has a member variable that points to dynamic memory allocated by IntList
We already have a funciton to remove one node, PopFront( )
, let's reuse it
void IntList::PopAll( )
{
while ( !IsEmpty( ) )
PopFront( );
}
What happens if we try to reuse PushFront( )
to implement PushAll( )
?
void IntList::PushAll( const IntList &other )
{
for ( Node *p = other.first; p != nullptr; p = p->next )
PushFront( p->datum );
}
We get a backwards copy!
We could easily write PushAll( )
if we had a PushBack( )
function
With our current implementation, we would have to walk down the list to reach the last node and modify it's next
pointer
This necessitates that we add a last
pointer
class IntList
{
//...
private:
Node *first;
Node *last;
};
last
points to the last node of the list if it is not empty, and nullptr
otherwise
To implement PushBack( )
, we first create a new node and establish its invariants
Then we handle the empty and non-empty list cases
void IntList::PushBack int datum )
{
Node *p = new Node;
p->datum = datum;
p->next = nullptr;
if ( IsEmpty( ) )
first = last = p;
else
last = last->next = p;
}
Now that PushBack( )
is finished, PushAll( )
works too
void IntList::PushAll(const IntList &other )
{
for ( Node *p = other.first; p != nullptr; p = p->next )
PushBack( p->datum );
}
void IntList::PopAll( )
{
while ( !IsEmpty( ) )
PopFront( );
}
void IntList::PopFront( )
{
assert( !IsEmpty( ) );
Node *victim = first;
first = first->next;
delete victim;
}
IntList::~IntList( )
{
PopAll( );
}
We need to do two things:
- Initialize member varibles
- Copy all nodes from other list
The assignment operator must do three things:
- Remove all nodes form this list
- Copy all nodes from other list
- Ensure that there is no self assignment
IntList::IntList( const IntList &other ) :
first( nullptr ), last( nullptr )
{
PushAll( other );
}
void IntList::PushAll( const IntList &other )
{
for ( Node *p = other.first;
p != nullptr; p = p->next )
PushBack( p->datum );
}
void IntList::PushBack( int datum )
{
Node *p = new Node;
p->datum = datum;
p->next = nullptr;
if ( IsEmpty( ) )
first = last = p;
else
{
last->next = p;
last = p;
}
}
IntList & IntList::operator=( const IntList &rhs )
{
if ( this == &rhs )
return *this;
PopAll( );
PushAll( rhs );
return *this;
}