As a software developer, you’ll often work with lists—essential data structures for storing elements of the same type. In C++, lists differ from vectors because their data isn’t stored in contiguous memory. This difference has major implications for operations like finding or inserting elements. Let’s dive into these differences to help you better understand some fundamental programming concepts.
Understanding C++ Linked Lists
Before exploring linked lists, let’s compare them to a closely related data structure: the vector. Vectors store elements in contiguous memory, allowing fast data access. For example, finding the length of a vector is as simple as subtracting the first element’s address from the last. However, this adjacency in memory means that manipulating one part of the vector requires shifting other elements, which can be costly. That’s where linked lists come in.
What is a C++ Linked List?
A linked list is a more complex data structure than a vector. Each element in a linked list consists of the data itself and one or more pointers. A pointer is a variable that holds a memory address. In a singly linked list, each element points to the next one. In a doubly linked list, each element has pointers to both the previous and next elements. By default, C++ uses doubly linked lists, as seen in the C++ Standard Library.
To create a list in C++:
std::list<int> fibonacci = {0, 1, 1, 2, 3, 5, 8};
When defining a list, specify the data type in angle brackets (< >
), and all elements must be of the same type.
Linked List Operations
Think of a linked list as a chain of elements. This structure allows you to manipulate it more dynamically than a vector. When inserting or deleting an element, only the directly connected elements need to be adjusted by updating the pointers. In contrast, manipulating a vector is like shifting items in an egg carton—everything needs to be rearranged to maintain order.
Inserting an Element
Here’s how to insert an element at the beginning of a linked list:
#include <iostream>
#include <list>
using namespace std;
int main() {
list<int> fibo = {1, 1, 2, 3, 5, 8}; // create a Fibonacci sequence without zero
fibo.push_front(0); // insert 0 at the beginning
for (int n : fibo) {
cout << n << ' ';
}
}
Output:
0 1 1 2 3 5 8
To append an element to the end of the list, use push_back
.
Removing an Element
You can easily remove elements by value. In this example, you’ll delete elements with a value of 1 from the Fibonacci sequence:
#include <iostream>
#include <list>
using namespace std;
int main () {
list<int> fibo = {0, 1, 1, 2, 3, 5, 8};
fibo.remove(1); // remove all elements with value 1
for (int n : fibo) {
cout << n << ' ';
}
}
Output:
0 2 3 5 8
This removes all instances of the value. If you need to target a specific element, use an iterator.
Deleting by Position with an Iterator
An iterator points to the address of the element you want to erase:
#include <iostream>
#include <list>
using namespace std;
int main () {
list<int> fibo = {0, 1, 1, 2, 3, 5, 8};
list<int>::iterator it;
it = fibo.begin(); // point the iterator to the start
++it; // move the iterator one element forward
fibo.erase(it); // erase the element the iterator points to
for (int n : fibo) {
cout << n << ' ';
}
}
Output:
0 1 2 3 5 8
Inserting an Element by Position
You can also use an iterator to insert an element at a specific position:
include
include
using namespace std;
int main () {
list fibo = {0, 1, 2, 3, 5, 8};
list::iterator it;
it = fibo.begin(); // point the iterator to the start
++it; // move one element forward
fibo.insert(it, 1); // insert 1 at the current position
for (int n : fibo) {
cout << n << ' ';
}
}`
Output:
0 1 1 2 3 5 8
Reversing the List
To print the Fibonacci sequence in reverse, simply use reverse
on a doubly linked list:
`
include
include
using namespace std;
int main() {
list fibo = {0, 1, 1, 2, 3, 5, 8};
fibo.reverse(); // reverse the list
for (int n : fibo) {
cout << n << ' ';
}
}
`
Output:
8 5 3 2 1 1 0
When Not to Use Linked Lists
If you need to frequently access elements, such as with indexing, vectors are a better option. Linked lists, while flexible, take longer to compute because their elements aren’t stored in contiguous memory. To access an element, a linked list must traverse each node, which is computationally expensive.
Lists vs. Vectors
So when should you use lists, and when should you use vectors? It’s not always as simple as the theory suggests. Even for tasks involving a lot of insertion and deletion—actions typically suited for linked lists—a vector might perform better in some cases. Bjarne Stroustrup, the creator of C++, found that vectors outperform linked lists even when working with collections of over half a million elements. The takeaway: deciding between these data structures often requires empirical evidence.
Further Reading
To explore more about linked lists, check out the C++ specifications or find a manual implementation of a doubly linked list for deeper insights.
Top comments (2)
Good refresher for me
List pros:
List Cons:
Vector pros:
Vector cons:
Lots of things to consider when choosing this kind of data structure:
It can be challenging to know which to use especially if you're finding that you need features from both types.
Your first example of creating a list is wrong. The list values come after an
=
after the name.