In this tutorial, we explore multi-dimensional collections in C++, including multi-dimensional arrays and nested std::vector structures, which are commonly used to represent data in multiple dimensions, such as matrices or grids.
Throughout this tutorial, we will:
- Introduce multi-dimensional C-style arrays and nested std::vector structures.
- Demonstrate how to declare and use them with basic examples.
- Explain how to pass them to functions.
- Present a practical problem and solve it using both approaches.
1- Basics of Multi-Dimensional Arrays and Nested Vectors
1.1 Multi-Dimensional C-Style Arrays
A multi-dimensional array is an array of arrays. For example, a 2D array can represent a matrix with rows and columns. Its size is fixed at compile time.
Declaration:
int arr[rows][cols]; // e.g., int arr[2][3] for 2 rows, 3 columns
1.2 Nested std::vector (Vector of Vectors)
A nested std::vector is a dynamic structure where each element of the outer vector is itself a vector. There are two common ways to declare it:
- Pre-sized Declaration:
vector<vector<int>> vec(rows, vector<int>(cols)); // e.g., 2 rows, 3 columns
Creates a vector with rows rows, each containing a vector of cols elements, initialized to 0.
- Empty Declaration with Assignment:
vector<vector<int>> vec; // Starts empty
vec = {{1, 2, 3}, {4, 5, 6}}; // Assigns a 2x3 structure
Comparison with Examples:
Let’s see these in action to understand how they differ.
-Pre-sized Example:
#include <iostream>
#include <vector>
using namespace std;
int main() {
// Declare a 2x3 nested vector (not empty, initialized with 0s)
vector<vector<int>> vec(2, vector<int>(3));
// Print initial contents
cout << "Pre-sized 2x3 Nested Vector (initial):\n";
for (int i = 0; i < vec.size(); i++) {
for (int j = 0; j < vec[i].size(); j++) {
cout << vec[i][j] << " ";
}
cout << endl;
}
// Assign specific values
vec[0] = {1, 2, 3}; // Replace row 0
vec[1] = {4, 5, 6}; // Replace row 1
// Print updated contents
cout << "\nPre-sized 2x3 Nested Vector (updated):\n";
for (int i = 0; i < vec.size(); i++) {
for (int j = 0; j < vec[i].size(); j++) {
cout << vec[i][j] << " ";
}
cout << endl;
}
return 0;
}
Output:
Pre-sized 2x3 Nested Vector (initial):
0 0 0
0 0 0
Pre-sized 2x3 Nested Vector (updated):
1 2 3
4 5 6
-Empty with Assignment Example:
#include <iostream>
#include <vector>
using namespace std;
int main() {
// Declare an empty nested vector first
vector<vector<int>> vec;
// Print initial size
cout << "Initial size of empty vector: " << vec.size() << endl;
// Assign the 2x3 structure in one step
vec = {{1, 2, 3}, {4, 5, 6}};
// Print the result
cout << "\nAssigned 2x3 Nested Vector:\n";
for (int i = 0; i < vec.size(); i++) {
for (int j = 0; j < vec[i].size(); j++) {
cout << vec[i][j] << " ";
}
cout << endl;
}
return 0;
}
Output:
Initial size of empty vector: 0
Assigned 2x3 Nested Vector:
1 2 3
4 5 6
Explanation of Differences:
-Pre-sized (vec(rows, vector<int>(cols))):
- Immediately creates a 2x3 structure with all elements set to 0.
- Size is fixed at declaration (vec.size() == 2, vec[0].size() == 3).
- You replace values afterward (e.g., vec[0] = {1, 2, 3}).
- Best when you need a specific size upfront and plan to fill it later.
-Empty with Assignment (vec; vec = {…}):
- Starts empty (vec.size() == 0), with no structure.
- Assignment (vec = {{1, 2, 3}, {4, 5, 6}}) sets the size and values in one step.
- Simpler if you have all the data ready and want to initialize it directly.
-Key Difference: The pre-sized method gives you a “blank slate” with a defined shape, while the empty method starts with nothing until you assign the full structure.
2- Key Examples with Explanations
2.1 Basic Usage with Loops
Let’s compare a 2D array and both nested vector declarations, initializing them with {1, 2, 3}, {4, 5, 6} and printing with loops.
Code:
#include <iostream>
#include <vector>
using namespace std;
int main() {
// 2D C-style array: 2 rows, 3 columns
int arr[2][3] = {{1, 2, 3}, {4, 5, 6}};
// Nested vector (pre-sized): 2 rows, 3 columns
vector<vector<int>> vec1(2, vector<int>(3));
vec1[0] = {1, 2, 3};
vec1[1] = {4, 5, 6};
// Nested vector (empty then assigned): 2 rows, 3 columns
vector<vector<int>> vec2;
vec2 = {{1, 2, 3}, {4, 5, 6}};
// Printing 2D array
cout << "2D Array:\n";
for (int i = 0; i < 2; i++) {
for (int j = 0; j < 3; j++) {
cout << arr[i][j] << " ";
}
cout << endl;
}
// Printing pre-sized nested vector
cout << "\nPre-sized Nested Vector:\n";
for (int i = 0; i < vec1.size(); i++) {
for (int j = 0; j < vec1[i].size(); j++) {
cout << vec1[i][j] << " ";
}
cout << endl;
}
// Printing assigned nested vector
cout << "\nAssigned Nested Vector:\n";
for (int i = 0; i < vec2.size(); i++) {
for (int j = 0; j < vec2[i].size(); j++) {
cout << vec2[i][j] << " ";
}
cout << endl;
}
return 0;
}
Output:
2D Array:
1 2 3
4 5 6
Pre-sized Nested Vector:
1 2 3
4 5 6
Assigned Nested Vector:
1 2 3
4 5 6
Explanation:
- 2D Array: Fixed at 2x3, initialized directly. Loops use hardcoded bounds.
- Pre-sized Vector (vec1): Starts as a 2x3 grid of 0s, then updated with values. Size is set at declaration.
- Assigned Vector (vec2): Starts empty, then assigned the full structure. Size is set by the assignment.
2.2 Passing to Functions
Let’s pass a 2D array and a nested vector (assigned method) to functions to sum their elements.
Code:
#include <iostream>
#include <vector>
using namespace std;
// Function for 2D array
int sumArray(int arr[][3], int rows) {
int sum = 0;
for (int i = 0; i < rows; i++) {
for (int j = 0; j < 3; j++) {
sum += arr[i][j];
}
}
return sum;
}
// Function for nested vector
int sumVector(const vector<vector<int>>& vec) {
int sum = 0;
for (int i = 0; i < vec.size(); i++) {
for (int j = 0; j < vec[i].size(); j++) {
sum += vec[i][j];
}
}
return sum;
}
int main() {
// 2D array
int arr[2][3] = {{1, 2, 3}, {4, 5, 6}};
// Nested vector (assigned method)
vector<vector<int>> vec;
vec = {{1, 2, 3}, {4, 5, 6}};
// Call functions
cout << "Sum of 2D array: " << sumArray(arr, 2) << endl;
cout << "Sum of nested vector: " << sumVector(vec) << endl;
return 0;
}
Output:
Sum of 2D array: 21
Sum of nested vector: 21
Explanation:
2D Array: Requires column size in the parameter (int arr[][3]).
When passing a 2D C-style array to a function, the syntax int arr[][3] is used. Here’s what this means and why it’s required:
- Column Size (Fixed): The 3 in int arr[][3] specifies the number of columns, and it must be provided at compile time. This is because a 2D array in C++ is stored in memory as a contiguous block, and the compiler needs to know the column size to calculate the memory offset for each element. For example, to access arr[i][j], the compiler computes the address as base_address + (i * columns + j) * sizeof(int). Without the column size (3), it can’t determine where each row ends and the next begins.
- Row Size (Flexible): The number of rows (the first dimension) is not fixed in the function parameter and can be passed as a separate argument (e.g., int rows). The empty brackets [] indicate that the function can accept a 2D array with any number of rows, as long as each row has exactly 3 columns. This makes the function reusable for arrays like int arr1[2][3] or int arr2[5][3], but the column size remains constant.
- Why Fixed Columns? Unlike std::vector, a C-style array’s size is determined at compile time and can’t change at runtime. The column size being fixed in the parameter reflects this static nature. If you tried to use int arr[][] (no sizes specified), the compiler would throw an error because it lacks the information needed to navigate the array’s memory layout.
- Example Context: In the function sumArray(int arr[][3], int rows), the 3 ensures the function knows each row has 3 elements, while rows (passed as 2 in the example) tells it how many rows to process. This combination allows the function to work with the fixed 2x3 array {{1, 2, 3}, {4, 5, 6}}.
Nested Vector: Passed by reference, works with either method after initialization.
When passing a nested std::vector (e.g., vector<vector<int>>) to a function, it’s typically passed by const reference (e.g., const vector<vector<int>>& vec). Here’s a deeper look at how this works and what vec[i].size() means:
- Passed by Reference: The & in const vector<vector<int>>& vec means the vector is passed by reference, not copied. This is crucial because a nested vector can be large, and copying it would be slow and memory-intensive. The const ensures the function won’t modify the vector, making it safe for read-only operations like summing elements.
- Dynamic Sizes: Unlike a 2D array, a nested vector’s sizes (both rows and columns) are dynamic and determined at runtime. The outer vector’s size (vec.size()) is the number of rows, and each inner vector’s size (vec[i].size()) is the number of columns in row i. These sizes aren’t fixed at compile time — they’re stored as part of the vector’s internal data and can vary.
- What is vec[i].size()?
- vec is the outer vector, and vec[i] accesses the i-th inner vector (a row).
- vec[i].size() returns the number of elements (columns) in that specific row. For example, in vec = {{1, 2, 3}, {4, 5, 6}}, vec[0].size() is 3 because the first row has 3 elements (1, 2, 3).
3- Problem and Solution
Problem: Transpose a 2x3 matrix into a 3x2 matrix using a 2D array and a nested vector (pre-sized method).
Solution:
#include <iostream>
#include <vector>
using namespace std;
int main() {
// 2D array: 2x3
int arr[2][3] = {{1, 2, 3}, {4, 5, 6}};
int arrT[3][2];
// Nested vector (pre-sized): 2x3
vector<vector<int>> vec(2, vector<int>(3));
vec[0] = {1, 2, 3};
vec[1] = {4, 5, 6};
vector<vector<int>> vecT(3, vector<int>(2));
// Transpose 2D array
for (int i = 0; i < 2; i++) {
for (int j = 0; j < 3; j++) {
arrT[j][i] = arr[i][j];
}
}
// Transpose nested vector
for (int i = 0; i < vec.size(); i++) {
for (int j = 0; j < vec[i].size(); j++) {
vecT[j][i] = vec[i][j];
}
}
// Print results
cout << "Transposed 2D Array (3x2):\n";
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 2; j++) {
cout << arrT[i][j] << " ";
}
cout << endl;
}
cout << "\nTransposed Nested Vector (3x2):\n";
for (int i = 0; i < vecT.size(); i++) {
for (int j = 0; j < vecT[i].size(); j++) {
cout << vecT[i][j] << " ";
}
cout << endl;
}
return 0;
}
Output:
Transposed 2D Array (3x2):
1 4
2 5
3 6
Transposed Nested Vector (3x2):
1 4
2 5
3 6
Explanation:
- 2D Array: Fixed sizes, transposed by swapping indices.
- Nested Vector: Pre-sized method used for consistency, transposed similarly.
'C++ Beginner' 카테고리의 다른 글
cpp_021: Const Modifier in C++ (0) | 2025.04.05 |
---|---|
cpp_019: Writing to Files with ofstream in C++ (0) | 2025.04.05 |
cpp_019: Basics of Functions, Methods, Static Methods, and Constructors in C++ (0) | 2025.04.05 |
cpp_018: Switch and Enum in C++ (0) | 2025.04.05 |
Cpp_017: Range-Based For Loops in C++ (0) | 2025.04.05 |