[C++] 二次元配列のvectorに要素を追加する方法
C++で二次元配列を表現する際、std::vector
を使用することが一般的です。二次元配列はstd::vector<std::vector<T>>
として定義されます。
要素を追加するには、まず外側のvector
に対してpush_back
を使用して新しいvector
を追加します。
その後、内側のvector
に対してpush_back
を使用して要素を追加します。
この方法により、動的に行や列を追加することが可能です。
二次元vectorに要素を追加する方法
C++の二次元vectorは、動的にサイズを変更できる便利なデータ構造です。
ここでは、二次元vectorに要素を追加する方法をいくつか紹介します。
push_backを使った追加方法
push_back
は、vectorの末尾に新しい要素を追加するためのメソッドです。
二次元vectorの場合、各行に対してpush_back
を使用して要素を追加できます。
#include <iostream>
#include <vector>
int main() {
// 二次元vectorの宣言
std::vector<std::vector<int>> matrix;
// 新しい行を追加
matrix.push_back(std::vector<int>());
// 行の末尾に要素を追加
matrix[0].push_back(1);
matrix[0].push_back(2);
matrix[0].push_back(3);
// 結果を表示
for (const auto& row : matrix) {
for (int value : row) {
std::cout << value << " ";
}
std::cout << std::endl;
}
return 0;
}
1 2 3
この例では、まず新しい行を追加し、その行に対してpush_back
を使って要素を追加しています。
これにより、行ごとに異なる長さのvectorを持つことができます。
insertを使った特定位置への追加
insertメソッド
を使用すると、vectorの特定の位置に要素を挿入することができます。
二次元vectorでも同様に、特定の行の任意の位置に要素を挿入できます。
#include <iostream>
#include <vector>
int main() {
// 二次元vectorの宣言と初期化
std::vector<std::vector<int>> matrix = {{1, 2, 3}, {4, 5, 6}};
// 1行目の2番目の位置に10を挿入
matrix[0].insert(matrix[0].begin() + 1, 10);
// 結果を表示
for (const auto& row : matrix) {
for (int value : row) {
std::cout << value << " ";
}
std::cout << std::endl;
}
return 0;
}
1 10 2 3
4 5 6
この例では、1行目の2番目の位置に10
を挿入しています。
insert
を使うことで、任意の位置に要素を追加することが可能です。
emplace_backを使った効率的な追加
emplace_back
は、push_back
と似ていますが、オブジェクトを直接構築するため、効率的に要素を追加できます。
特に、オブジェクトのコピーが発生しないため、パフォーマンスが向上します。
#include <iostream>
#include <vector>
int main() {
// 二次元vectorの宣言
std::vector<std::vector<int>> matrix;
// 新しい行を追加
matrix.emplace_back(); // ここで新しい行を直接構築
// 行の末尾に要素を追加
matrix[0].emplace_back(7);
matrix[0].emplace_back(8);
matrix[0].emplace_back(9);
// 結果を表示
for (const auto& row : matrix) {
for (int value : row) {
std::cout << value << " ";
}
std::cout << std::endl;
}
return 0;
}
7 8 9
この例では、emplace_back
を使って新しい行を追加し、その行に要素を追加しています。
emplace_back
は、特にオブジェクトの構築が必要な場合に有効です。
二次元vectorの要素アクセス
二次元vectorの要素にアクセスする方法は複数あります。
ここでは、インデックス、atメソッド
、イテレータを使ったアクセス方法を紹介します。
インデックスを使ったアクセス方法
インデックスを使ったアクセスは、最も一般的で簡単な方法です。
行と列のインデックスを指定して要素にアクセスします。
#include <iostream>
#include <vector>
int main() {
// 二次元vectorの宣言と初期化
std::vector<std::vector<int>> matrix = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
// インデックスを使って要素にアクセス
int value = matrix[1][2]; // 2行目の3列目の要素を取得
// 結果を表示
std::cout << "取得した要素: " << value << std::endl;
return 0;
}
取得した要素: 6
この方法はシンプルで高速ですが、範囲外のインデックスを指定すると未定義の動作を引き起こす可能性があります。
atメソッドを使った安全なアクセス
atメソッド
を使うと、範囲外アクセスを防ぐことができます。
範囲外のインデックスを指定した場合、例外がスローされます。
#include <iostream>
#include <vector>
int main() {
// 二次元vectorの宣言と初期化
std::vector<std::vector<int>> matrix = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
try {
// atメソッドを使って要素にアクセス
int value = matrix.at(1).at(2); // 2行目の3列目の要素を取得
// 結果を表示
std::cout << "取得した要素: " << value << std::endl;
} catch (const std::out_of_range& e) {
std::cerr << "範囲外アクセス: " << e.what() << std::endl;
}
return 0;
}
取得した要素: 6
atメソッド
を使うことで、範囲外アクセスを安全に防ぐことができ、デバッグ時に役立ちます。
イテレータを使ったアクセス
イテレータを使うと、vectorの要素を順番に処理することができます。
特に、全要素を走査する場合に便利です。
#include <iostream>
#include <vector>
int main() {
// 二次元vectorの宣言と初期化
std::vector<std::vector<int>> matrix = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
// イテレータを使って要素にアクセス
for (auto rowIt = matrix.begin(); rowIt != matrix.end(); ++rowIt) {
for (auto colIt = rowIt->begin(); colIt != rowIt->end(); ++colIt) {
std::cout << *colIt << " ";
}
std::cout << std::endl;
}
return 0;
}
1 2 3
4 5 6
7 8 9
イテレータを使うことで、vectorの要素を柔軟に操作することができ、特に複雑な操作を行う際に有用です。
二次元vectorの応用例
二次元vectorは、行列のようなデータ構造を扱うのに非常に便利です。
ここでは、二次元vectorを用いた行列の転置、加算、スカラー倍の応用例を紹介します。
行列の転置
行列の転置とは、行と列を入れ替える操作です。
二次元vectorを使って行列の転置を行う方法を示します。
#include <iostream>
#include <vector>
std::vector<std::vector<int>> transpose(const std::vector<std::vector<int>>& matrix) {
if (matrix.empty()) return {};
std::vector<std::vector<int>> transposed(matrix[0].size(), std::vector<int>(matrix.size()));
for (size_t i = 0; i < matrix.size(); ++i) {
for (size_t j = 0; j < matrix[i].size(); ++j) {
transposed[j][i] = matrix[i][j];
}
}
return transposed;
}
int main() {
// 二次元vectorの宣言と初期化
std::vector<std::vector<int>> matrix = {{1, 2, 3}, {4, 5, 6}};
// 行列の転置
std::vector<std::vector<int>> transposedMatrix = transpose(matrix);
// 結果を表示
for (const auto& row : transposedMatrix) {
for (int value : row) {
std::cout << value << " ";
}
std::cout << std::endl;
}
return 0;
}
1 4
2 5
3 6
この例では、元の行列の行と列を入れ替えて新しい行列を作成しています。
行列の加算
行列の加算は、同じ位置の要素同士を足し合わせる操作です。
二次元vectorを使って行列の加算を行う方法を示します。
#include <iostream>
#include <vector>
std::vector<std::vector<int>> addMatrices(const std::vector<std::vector<int>>& matrix1, const std::vector<std::vector<int>>& matrix2) {
std::vector<std::vector<int>> result(matrix1.size(), std::vector<int>(matrix1[0].size()));
for (size_t i = 0; i < matrix1.size(); ++i) {
for (size_t j = 0; j < matrix1[i].size(); ++j) {
result[i][j] = matrix1[i][j] + matrix2[i][j];
}
}
return result;
}
int main() {
// 二次元vectorの宣言と初期化
std::vector<std::vector<int>> matrix1 = {{1, 2, 3}, {4, 5, 6}};
std::vector<std::vector<int>> matrix2 = {{7, 8, 9}, {10, 11, 12}};
// 行列の加算
std::vector<std::vector<int>> sumMatrix = addMatrices(matrix1, matrix2);
// 結果を表示
for (const auto& row : sumMatrix) {
for (int value : row) {
std::cout << value << " ";
}
std::cout << std::endl;
}
return 0;
}
8 10 12
14 16 18
この例では、同じ位置の要素を足し合わせて新しい行列を作成しています。
行列のスカラー倍
行列のスカラー倍は、行列のすべての要素に同じスカラー値を掛ける操作です。
二次元vectorを使って行列のスカラー倍を行う方法を示します。
#include <iostream>
#include <vector>
std::vector<std::vector<int>> scalarMultiply(const std::vector<std::vector<int>>& matrix, int scalar) {
std::vector<std::vector<int>> result(matrix.size(), std::vector<int>(matrix[0].size()));
for (size_t i = 0; i < matrix.size(); ++i) {
for (size_t j = 0; j < matrix[i].size(); ++j) {
result[i][j] = matrix[i][j] * scalar;
}
}
return result;
}
int main() {
// 二次元vectorの宣言と初期化
std::vector<std::vector<int>> matrix = {{1, 2, 3}, {4, 5, 6}};
// 行列のスカラー倍
int scalar = 3;
std::vector<std::vector<int>> scaledMatrix = scalarMultiply(matrix, scalar);
// 結果を表示
for (const auto& row : scaledMatrix) {
for (int value : row) {
std::cout << value << " ";
}
std::cout << std::endl;
}
return 0;
}
3 6 9
12 15 18
この例では、行列のすべての要素にスカラー値を掛けて新しい行列を作成しています。
二次元vectorのメモリ管理
C++の二次元vectorは、動的にメモリを管理するため、プログラマが直接メモリ管理を行う必要がありません。
しかし、効率的なプログラムを作成するためには、メモリ管理の基本を理解しておくことが重要です。
メモリの自動管理
二次元vectorは、C++のSTL(Standard Template Library)の一部であり、メモリの自動管理を行います。
vectorは必要に応じてメモリを確保し、スコープを抜けると自動的に解放されます。
#include <iostream>
#include <vector>
int main() {
// 二次元vectorの宣言
std::vector<std::vector<int>> matrix;
// メモリの自動管理
matrix.push_back({1, 2, 3});
matrix.push_back({4, 5, 6});
// vectorがスコープを抜けるときにメモリが自動的に解放される
return 0;
}
この例では、matrix
がスコープを抜けるときに、vectorが自動的にメモリを解放します。
プログラマはメモリの解放を心配する必要がありません。
メモリの再確保とその影響
vectorは、要素が追加されると必要に応じてメモリを再確保します。
再確保はコストがかかる操作であり、頻繁に発生するとパフォーマンスに影響を与える可能性があります。
#include <iostream>
#include <vector>
int main() {
// 二次元vectorの宣言
std::vector<std::vector<int>> matrix;
// メモリの再確保を最小限にするために、事前に容量を予約
matrix.reserve(10); // 10行分のメモリを予約
for (int i = 0; i < 10; ++i) {
matrix.push_back({i, i + 1, i + 2});
}
std::cout << "行数: " << matrix.size() << std::endl;
return 0;
}
この例では、reserve
を使って事前にメモリを予約することで、再確保の回数を減らし、パフォーマンスを向上させています。
メモリリークを防ぐ方法
二次元vectorを使用する場合、通常はメモリリークの心配はありませんが、他の動的メモリ管理と組み合わせる場合は注意が必要です。
例えば、ポインタをvectorに格納する場合、適切にメモリを解放する必要があります。
#include <iostream>
#include <vector>
int main() {
// ポインタを格納する二次元vectorの宣言
std::vector<std::vector<int*>> matrix;
// メモリの割り当て
for (int i = 0; i < 3; ++i) {
std::vector<int*> row;
for (int j = 0; j < 3; ++j) {
row.push_back(new int(i * j)); // 動的にメモリを割り当て
}
matrix.push_back(row);
}
// メモリの解放
for (auto& row : matrix) {
for (int* ptr : row) {
delete ptr; // 動的に割り当てたメモリを解放
}
}
return 0;
}
この例では、new
で動的に割り当てたメモリをdelete
で解放することで、メモリリークを防いでいます。
ポインタを使用する場合は、必ずメモリを適切に解放することが重要です。
まとめ
この記事では、C++の二次元vectorにおける要素の追加方法やアクセス方法、応用例、メモリ管理について詳しく解説しました。
二次元vectorを効果的に活用するための基本的な操作から応用的なテクニックまでをカバーし、プログラムの効率化や安全性の向上に役立つ情報を提供しました。
これを機に、実際のプログラムで二次元vectorを活用し、より複雑なデータ構造の操作に挑戦してみてください。