2D Vector in C++: How to Create, Initialize, and Traverse Grids

Looking for a clear, beginner-friendly guide to the 2D Vector in C++? This article explains what a 2D vector is, how to create and use one, and why it’s a powerful tool for handling grid-like data—with commented code, outputs, and “Try it yourself” challenges after each section to build hands-on skills.

🔹 What is a 2D Vector in C++?

A 2D Vector in C++ is a “vector of vectors.” Think of it as a dynamic grid or table where you can change the number of rows and columns at runtime. Unlike a fixed 2D array, a 2D vector manages its own memory, making it a flexible and safe choice for representing matrices, game boards, or any two-dimensional data structure.

Each element in the outer vector is another vector, representing a row. This structure allows for rows of varying lengths, which is known as a “jagged array”.

🔹 How to Declare and Initialize a 2D Vector

There are several common ways to create a 2D Vector in C++, from an empty one to a fully initialized grid.

// Snippet 1: Different ways to initialize a 2D Vector
#include <iostream>
#include <vector>
#include <string>
using namespace std;
// Helper function to print any 2D vector of integers
void printGrid(const vector<vector<int>>& grid) {
    for (const auto& row : grid) {
        for (int cell : row) {
            cout << cell << " ";
        }
        cout << endl;
    }
    cout << "------------------" << endl;
}
int main() {
    // 1. An empty 2D vector
    vector<vector<int>> emptyGrid;
    // 2. A 3x4 grid initialized with zeros
    int rows = 3;
    int cols = 4;
    vector<vector<int>> gridWithZeros(rows, vector<int>(cols, 0));
    cout << "3x4 Grid of Zeros:" << endl;
    printGrid(gridWithZeros);
    // 3. A 2D vector with an initializer list (Jagged)
    vector<vector<int>> initializedGrid = { {1, 2, 3}, {4, 5}, {6, 7, 8, 9} };
    cout << "Grid from Initializer List (Jagged):" << endl;
    printGrid(initializedGrid);
}

Output

3x4 Grid of Zeros:
0 0 0 0 
0 0 0 0 
0 0 0 0 
------------------
Grid from Initializer List (Jagged):
1 2 3 
4 5 
6 7 8 9 
------------------

Explanation: The most common method for creating a grid of a known size is vector<vector<int>> grid(rows, vector<int>(cols, initial_value)). This creates an outer vector with rows elements, where each element is another vector of size cols, pre-filled with initial_value.

Try it yourself

  • Create a 2×2 2D Vector in C++ to represent a Tic-Tac-Toe board, initialized with a character like ‘-‘.
  • Modify the initializedGrid to make all rows have the same number of columns.

🔹 Accessing and Modifying Elements

You can access elements in a 2D Vector in C++ using the same double-bracket notation as 2D arrays, like grid[row][col]. This makes the transition from arrays to vectors very intuitive.

// Snippet 2: Accessing and modifying elements
#include <iostream>
#include <vector>
using namespace std;
int main() {
    // Create a 3x3 matrix
    vector<vector<int>> matrix(3, vector<int>(3, 0));
    // Modify a few elements
    matrix[0][0] = 1; // Top-left
    matrix[1][1] = 2; // Center
    matrix[2][2] = 3; // Bottom-right
    // Access and print the center element
    cout << "Center element: " << matrix[1][1] << endl;
    // Use at() for safe, bounds-checked access
    try {
        matrix.at(1).at(1) = 5; // Safe modification
        cout << "New center element: " << matrix.at(1).at(1) << endl;
        // The following line would throw an exception
        // cout << matrix.at(5).at(5) << endl;
    } catch (const out_of_range& e) {
        cerr << "Error: " << e.what() << endl;
    }
}

Output

Center element: 2
New center element: 5

Try it yourself

  • Set all elements on the main diagonal of the matrix to 10.
  • Uncomment the line that causes an out-of-range error to see how .at() protects your program.

🔹 Traversing a 2D Vector

Traversing a 2D Vector in C++ typically requires nested loops: an outer loop for the rows and an inner loop for the columns. Range-based for loops make this code clean and less error-prone.

// Snippet 3: Traversing with nested for loops
#include <iostream>
#include <vector>
using namespace std;
int main() {
    vector<vector<int>> grid = {
        {10, 20, 30},
        {40, 50, 60},
        {70, 80, 90}
    };
    cout << "Traversing with range-based for loops:" << endl;
    // Outer loop for each row
    for (const auto& row : grid) {
        // Inner loop for each cell
        for (int cell : row) {
            cout << cell << "\t";
        }
        cout << endl;
    }
    cout << "\nTraversing with traditional index-based for loops:" << endl;
    for (size_t i = 0; i < grid.size(); ++i) {
        for (size_t j = 0; j < grid[i].size(); ++j) {
            cout << grid[i][j] << "\t";
        }
        cout << endl;
    }
}

Output

Traversing with range-based for loops:
10	20	30
40	50	60
70	80	90
Traversing with traditional index-based for loops:
10	20	30
40	50	60
70	80	90

Try it yourself

  • Write a function that takes a 2D Vector in C++ and returns the sum of all its elements.
  • Modify the index-based loop to only print the elements on the border of the grid.

🔹 Dynamically Adding Rows and Columns

The real power of a 2D Vector in C++ is its dynamic nature. You can easily add or remove rows, and even add elements to existing rows.

// Snippet 4: Adding rows and columns dynamically
#include <iostream>
#include <vector>
using namespace std;
// Helper function to print a 2D vector
void printGrid(const vector<vector<int>>& grid);
int main() {
    vector<vector<int>> grid;
    // Add a new row
    grid.push_back({1, 1, 1});
    cout << "After adding first row:" << endl;
    printGrid(grid);
    // Add another new row
    vector<int> newRow = {2, 2};
    grid.push_back(newRow);
    cout << "After adding second row:" << endl;
    printGrid(grid);
    // Add an element to an existing row (the first row)
    grid[0].push_back(1);
    cout << "After adding element to first row:" << endl;
    printGrid(grid);
}
void printGrid(const vector<vector<int>>& grid) {
    for (const auto& row : grid) {
        for (int cell : row) {
            cout << cell << " ";
        }
        cout << endl;
    }
    cout << "------------------" << endl;
}

Output

After adding first row:
1 1 1
------------------
After adding second row:
1 1 1
2 2
------------------
After adding element to first row:
1 1 1 1
2 2
------------------

Try it yourself

  • Write a loop that adds 5 rows to an empty grid, where each row i contains the numbers from 0 to i.
  • Use grid.pop_back() to remove the last row you added.

🔹 Mini Project: Student Grades

Let’s use a 2D Vector in C++ to store grades for multiple students across multiple assignments. This is a classic use case for a grid-like data structure.

// Snippet 5: Storing and calculating student grades
#include <iostream>
#include <vector>
#include <string>
#include <numeric> // For std::accumulate
#include <iomanip> // For std::fixed and std::setprecision
using namespace std;
int main() {
    vector<string> studentNames = {"Alice", "Bob", "Charlie"};
    vector<vector<int>> grades = {
        {85, 92, 78},  // Alice's grades
        {90, 88, 94},  // Bob's grades
        {72, 80, 68}   // Charlie's grades
    };
    cout << fixed << setprecision(2); // Format output for decimals
    // Calculate and print the average for each student
    for (size_t i = 0; i < grades.size(); ++i) {
        double sum = accumulate(grades[i].begin(), grades[i].end(), 0.0);
        double average = sum / grades[i].size();
        cout << studentNames[i] << "'s average: " << average << endl;
    }
}

Output

Alice's average: 85.00
Bob's average: 90.67
Charlie's average: 73.33

Try it yourself

  • Add a new student, “David,” with his grades to both vectors.
  • Write code to find the highest grade across all students and all assignments.

🔹 FAQ: 2D Vector in C++

Q1. What is the difference between a 2D vector and a 2D array?
A 2D vector is dynamic; it can change size at runtime. A 2D array has a fixed size determined at compile time. Vectors also provide helpful member functions like .size() and .push_back().

Q2. How do I get the number of rows and columns?
The number of rows is grid.size(). The number of columns for a specific row i is grid[i].size(). For a non-jagged grid, all rows will have the same column count.

Q3. Is a 2D vector’s memory contiguous?
Not entirely. Each row (each inner vector) stores its elements contiguously. However, the rows themselves might be located in different places in memory. If you need a fully contiguous grid for performance reasons, consider using a single 1D vector and calculating indices manually (e.g., index = row * num_cols + col).

Q4. How can I pass a 2D vector to a function?
Pass it by constant reference (const vector<vector<T>>& grid) to avoid making a slow and memory-intensive copy, especially for large grids.

Q5. Can a 2D vector have rows of different lengths?
Yes. This is called a “jagged array” and is one of the key advantages of using a 2D Vector in C++ over a traditional 2D array.

🔹 Wrapping Up

The 2D Vector in C++ is an essential tool for any C++ programmer working with grid-based data. Its dynamic nature, safety features, and ease of use make it a far superior choice to C-style 2D arrays for most applications. By understanding how to initialize, access, and manipulate them, you can write cleaner, more flexible, and more powerful code.

Leave a Comment

About RadiantRiva

Your go-to resource for coding tutorials, developer guides, and programming tips.

Learn More

Quick Links

Follow Us

Newsletter

Get coding tips, tutorials, and updates straight to your inbox.