Coming Soon

This lesson is currently being developed

Introduction to containers and arrays

Introduction to storing collections of data.

Dynamic arrays: std::vector
Chapter
Beginner
Difficulty
40min
Estimated Time

What to Expect

Comprehensive explanations with practical examples

Interactive coding exercises to practice concepts

Knowledge quiz to test your understanding

Step-by-step guidance for beginners

Development Status

In Progress

Content is being carefully crafted to provide the best learning experience

Preview

Early Preview Content

This content is still being developed and may change before publication.

16.1 — Introduction to containers and arrays

In this lesson, you'll learn about containers in C++, with a focus on why they're essential and how they compare to C-style arrays. This foundation will prepare you for working with std::vector, one of the most important containers in C++.

What are containers?

A container is a data structure that holds a collection of elements. Think of containers like different types of storage boxes:

  • A shoe box holds multiple pairs of shoes
  • A bookshelf holds multiple books in order
  • A filing cabinet holds multiple documents organized by category

In C++, containers hold multiple values of the same type and provide ways to access, add, remove, and manipulate those values.

Why do we need containers?

So far, you've worked with individual variables:

int score1 = 85;
int score2 = 92;
int score3 = 78;
int score4 = 90;
int score5 = 88;

But what if you need to store 100 test scores? Or 1000? Creating individual variables becomes impractical. This is where containers shine - they let you store many values in a single, manageable structure.

C-style arrays: The foundation

Before diving into modern C++ containers, let's understand C-style arrays - the foundation upon which containers are built.

Declaring and initializing arrays

Here's how to create a C-style array:

#include <iostream>

int main()
{
    // Create an array of 5 integers
    int scores[5];
    
    // Initialize values
    scores[0] = 85;
    scores[1] = 92;
    scores[2] = 78;
    scores[3] = 90;
    scores[4] = 88;
    
    // Print the scores
    std::cout << "Test scores: ";
    for (int i = 0; i < 5; ++i)
    {
        std::cout << scores[i] << " ";
    }
    std::cout << std::endl;
    
    return 0;
}

Output:

Test scores: 85 92 78 90 88 

You can also initialize arrays when you declare them:

#include <iostream>

int main()
{
    // Initialize array with values
    int scores[5] = {85, 92, 78, 90, 88};
    
    // Or let the compiler count the elements
    int grades[] = {95, 87, 92, 78, 85, 90}; // Size is 6
    
    std::cout << "Scores: ";
    for (int i = 0; i < 5; ++i)
    {
        std::cout << scores[i] << " ";
    }
    std::cout << std::endl;
    
    std::cout << "Grades: ";
    for (int i = 0; i < 6; ++i)
    {
        std::cout << grades[i] << " ";
    }
    std::cout << std::endl;
    
    return 0;
}

Output:

Scores: 85 92 78 90 88 
Grades: 95 87 92 78 85 90 

Problems with C-style arrays

While C-style arrays work, they have several significant limitations:

1. Fixed size at compile time

int size = 10;
int scores[size]; // Error! Size must be known at compile time

const int SIZE = 10;
int scores[SIZE]; // OK - SIZE is compile-time constant

2. No size information

Arrays don't know their own size:

#include <iostream>

int main()
{
    int numbers[] = {1, 2, 3, 4, 5};
    
    // This doesn't give you the number of elements!
    // It gives you the total bytes used by the array
    std::cout << "sizeof(numbers): " << sizeof(numbers) << std::endl;
    
    // To get the number of elements, you need:
    int count = sizeof(numbers) / sizeof(numbers[0]);
    std::cout << "Number of elements: " << count << std::endl;
    
    return 0;
}

Output:

sizeof(numbers): 20
Number of elements: 5

3. No bounds checking

Arrays don't prevent you from accessing invalid indices:

#include <iostream>

int main()
{
    int numbers[3] = {10, 20, 30};
    
    std::cout << numbers[0] << std::endl; // OK
    std::cout << numbers[2] << std::endl; // OK
    std::cout << numbers[5] << std::endl; // Undefined behavior! No error message
    
    return 0;
}

Output:

10
30
-858993460

The third output is unpredictable - it could be any value!

4. Difficult to resize

You cannot change the size of a C-style array after creation:

int scores[5] = {85, 92, 78, 90, 88};
// No way to add a 6th score without creating a new array

When arrays are useful

Despite their limitations, C-style arrays are still useful in certain situations:

1. When size is known and fixed

#include <iostream>

int main()
{
    // Days in each month (non-leap year)
    int daysInMonth[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
    
    std::cout << "January has " << daysInMonth[0] << " days" << std::endl;
    std::cout << "February has " << daysInMonth[1] << " days" << std::endl;
    
    return 0;
}

Output:

January has 31 days
February has 28 days

2. For performance-critical code

Arrays have minimal overhead and provide direct memory access.

3. Working with C libraries

Many C libraries expect C-style arrays.

Modern C++ containers

Modern C++ provides powerful container classes that solve the problems of C-style arrays:

  • std::vector: Dynamic array that can grow and shrink
  • std::array: Fixed-size array with bounds checking
  • std::string: Specialized container for characters
  • std::deque: Double-ended queue
  • std::list: Linked list
  • std::set: Sorted collection of unique elements
  • std::map: Key-value pairs

Why std::vector is special

std::vector is the most commonly used container because it:

  • Grows and shrinks automatically
  • Provides bounds checking (in debug mode)
  • Knows its own size
  • Offers many useful member functions
  • Has performance similar to C-style arrays
  • Is compatible with standard algorithms

Here's a preview of what std::vector looks like:

#include <iostream>
#include <vector>

int main()
{
    std::vector<int> scores = {85, 92, 78, 90, 88};
    
    std::cout << "Number of scores: " << scores.size() << std::endl;
    
    // Add a new score
    scores.push_back(95);
    
    std::cout << "After adding a score: " << scores.size() << std::endl;
    
    // Print all scores
    std::cout << "All scores: ";
    for (int score : scores) // Range-based for loop
    {
        std::cout << score << " ";
    }
    std::cout << std::endl;
    
    return 0;
}

Output:

Number of scores: 5
After adding a score: 6
All scores: 85 92 78 90 88 95 

Memory layout and performance

Array memory layout

Arrays store elements in contiguous memory:

Array: [10][20][30][40][50]
Memory: |10|20|30|40|50|
        ^  ^  ^  ^  ^
     addr addr+4 addr+8 addr+12 addr+16

This layout provides:

  • Fast access: Elements are accessed by simple arithmetic
  • Cache efficiency: Nearby elements are likely in CPU cache
  • Predictable performance: Access time is constant

Container overhead

Modern containers like std::vector maintain this performance while adding useful features:

std::vector<int> numbers = {10, 20, 30, 40, 50};
// Internally stores:
// - Pointer to data: points to [10][20][30][40][50]
// - Size: 5 (current number of elements)
// - Capacity: 5 (space allocated)

Best practices for choosing containers

Use std::vector when:

  • You need a dynamic array
  • You frequently access elements by index
  • You need to add/remove elements at the end
  • You want good general-purpose performance

Use C-style arrays when:

  • Size is fixed and known at compile time
  • You need minimal memory overhead
  • You're interfacing with C code

Use other containers when:

  • std::array: Fixed size with bounds checking
  • std::deque: Frequent insertion/deletion at both ends
  • std::list: Frequent insertion/deletion in the middle
  • std::string: Working with text

Summary

Containers are essential tools for managing collections of data in C++. While C-style arrays provide the foundation and are still useful in specific scenarios, modern C++ containers like std::vector solve many of their limitations:

  • C-style arrays: Fixed size, no bounds checking, minimal overhead
  • Modern containers: Dynamic sizing, bounds checking, rich functionality
  • std::vector: The go-to container for most situations

In the next lesson, you'll dive deep into std::vector and learn how to create, initialize, and use vectors effectively.

Quiz

  1. What are the main limitations of C-style arrays compared to modern containers?
  2. When would you still choose a C-style array over std::vector?
  3. How do you find the number of elements in a C-style array?
  4. What happens when you access an array element beyond its bounds?
  5. What are the advantages of contiguous memory layout in arrays?

Practice exercises

Try these exercises to practice working with arrays and containers:

  1. Grade calculator: Create a C-style array to store 5 test scores, calculate the average, and display all scores along with the average.

  2. Array comparison: Write a program that creates two integer arrays and compares them element by element, reporting whether they are identical.

  3. Container preview: Create a simple std::vector of your favorite numbers, add a few more numbers using push_back(), and display the final list with its size.

  4. Memory exploration: Use the sizeof operator to explore how much memory different array sizes consume and calculate the bytes per element for different data types.

Continue Learning

Explore other available lessons while this one is being prepared.

View Course

Explore More Courses

Discover other available courses while this lesson is being prepared.

Browse Courses

Lesson Discussion

Share your thoughts and questions

💬

No comments yet. Be the first to share your thoughts!

Sign in to join the discussion