13.

C++で多次元配列の要素数を求める方法|sizeofと各次元の数え方

編集

C++で多次元配列の要素数を求めるには、配列全体のバイト数を1要素のバイト数で割るのが基本です。総要素数は sizeof(arr) / sizeof(arr[0][0])、行数は sizeof(arr) / sizeof(arr[0])、列数は sizeof(arr[0]) / sizeof(arr[0][0]) で求められます。sizeof はコンパイル時に確定する演算子なので、要素数も実行前に決まります。

この記事の要点
  • 2次元配列の総要素数は sizeof(arr) / sizeof(arr[0][0]) で求める。
  • 行数は sizeof(arr) / sizeof(arr[0])、列数は sizeof(arr[0]) / sizeof(arr[0][0])
  • C++17以降は std::size(arr) で各次元の要素数を安全に取得できる。
  • std::array なら .size()std::vector なら .size() で要素数がわかる。
  • 配列を関数に渡すとポインタへ減衰し、sizeof では要素数を計算できなくなる。

 

前提:配列のサイズは sizeof でわかる

C++の組み込み配列には「自分が何要素あるか」を返す機能はありません。そこで使うのが sizeof 演算子です。sizeof は対象が占めるメモリのバイト数を返します。配列に対して使うと、その配列が占める全バイト数が得られます。

たとえば int が4バイトの環境で int a[2][3]; を宣言すると、要素は全部で 2×3=6 個、合計で 6×4=24 バイトを占めます。このとき sizeof(a)24 を返します。要素1個分のバイト数 sizeof(a[0][0])(=4)で割れば、要素数が逆算できるという仕組みです。

sizeof はコンパイル時に評価される演算子なので、求まる要素数も実行前に確定する定数になります。ただし、この方法が成り立つのは配列の実体(要素数が型に含まれている状態)に対して sizeof を適用したときだけである点に注意してください(詳細は後述の「落とし穴」を参照)。

2次元配列の総要素数を求める

2次元配列の全要素数は、配列全体のバイト数を1要素のバイト数で割って求めます。

#include <iostream>

int main() {

    int arr[2][3] = {

        {1, 2, 3},

        {4, 5, 6}

    };

    // 総要素数 = 配列全体のバイト数 / 1要素のバイト数

    std::size_t total = sizeof(arr) / sizeof(arr[0][0]);

    std::cout << total << "\n"; // 6

}

 

sizeof(arr) は配列全体の24バイト、sizeof(arr[0][0])int 1個分の4バイトなので、24÷4=6 が得られます。割り算の結果の型は std::size_t(符号なし整数)になるため、受け取る変数も std::size_t にしておくと警告を避けられます。

各次元の要素数(行数・列数)を求める

各次元の要素数は、「1つ外側の要素のバイト数」を「1つ内側の要素のバイト数」で割ると求められます。int arr[2][3] の場合、arr[0] は「int 3個分の配列(=1行)」を表すことがポイントです。

#include <iostream>

int main() {

    int arr[2][3] = {

        {1, 2, 3},

        {4, 5, 6}

    };

    // 行数 = 配列全体 / 1行分

    std::size_t rows = sizeof(arr) / sizeof(arr[0]);

    // 列数 = 1行分 / 1要素分

    std::size_t cols = sizeof(arr[0]) / sizeof(arr[0][0]);

    std::cout << rows << "\n"; // 2(行数)

    std::cout << cols << "\n"; // 3(列数)

}

 

関係を整理すると次のとおりです。総要素数は「行数×列数」と一致します。

求めたいもの int arr[2][3] での値
総要素数 sizeof(arr) / sizeof(arr[0][0]) 6
行数(第1次元) sizeof(arr) / sizeof(arr[0]) 2
列数(第2次元) sizeof(arr[0]) / sizeof(arr[0][0]) 3

 

std::size(C++17)で書く

C++17以降では <iterator>(または多くの標準ヘッダ)で使える std::size を利用できます。組み込み配列に対しては各次元の要素数を返すため、sizeof の割り算よりも意図が読み取りやすくなります。

#include <iostream>

#include <iterator> // std::size

int main() {

    int arr[2][3] = { {1, 2, 3}, {4, 5, 6} };

    std::cout << std::size(arr) << "\n";      // 2(行数)

    std::cout << std::size(arr[0]) << "\n";  // 3(列数)

}

 

std::size(arr) が第1次元(行数)、std::size(arr[0]) が第2次元(列数)を返します。総要素数が必要なら、両者を掛け合わせるか、前述の sizeof 方式を使います。なお std::size はポインタに対しては利用できないため、配列がポインタへ減衰してしまう関数内などでは使えません(後述)。

std::array / std::vector での扱い

標準ライブラリのコンテナを使うと、要素数の取得はメンバ関数 .size() で完結します。組み込み配列の sizeof 計算と違い、ポインタへ減衰しないので関数に渡しても要素数を取り出せるのが大きな利点です。

#include <iostream>

#include <array>

#include <vector>

int main() {

    // std::array による2次元配列

    std::array<std::array<int, 3>, 2> a =

        { { {1, 2, 3}, {4, 5, 6} } };

    std::cout << a.size() << "\n";     // 2(行数)

    std::cout << a[0].size() << "\n"; // 3(列数)

    // std::vector による2次元配列

    std::vector<std::vector<int>> v =

        { {1, 2, 3}, {4, 5, 6} };

    std::cout << v.size() << "\n";     // 2(行数)

    std::cout << v[0].size() << "\n"; // 3(先頭行の列数)

}

 

std::array は要素数が型に含まれる固定長配列で、.size() はコンパイル時に決まる定数を返します。std::vector は実行時に伸縮できる可変長配列で、.size() はその時点の要素数を返します。std::vector による2次元配列は行ごとに長さが異なる場合があるため、列数は「行ごとに v[i].size() を見る」点に注意してください。

関数に渡すと sizeof が効かない

もっとも間違えやすいのが、配列を関数の引数に渡したときの挙動です。C++では配列を関数に渡すと、先頭要素へのポインタに変換(減衰)されます。その結果、関数側の sizeof は配列全体ではなくポインタ自身のサイズを返してしまい、要素数を正しく計算できません。

#include <iostream>

// 仮引数 arr は int(*)[3] というポインタに減衰している

void print_count(int arr[2][3]) {

    // sizeof(arr) はポインタのサイズ(例: 8)になる

    std::cout << sizeof(arr) / sizeof(arr[0][0]) << "\n";

    // → 6 にはならない(誤った値)

}

int main() {

    int arr[2][3] = { {1, 2, 3}, {4, 5, 6} };

    print_count(arr);

}

 

関数の引数で int arr[2][3] と書いても、先頭の次元のサイズは無視され、実際には int (*arr)[3](「int 3個の配列」へのポインタ)として扱われます。そのため sizeof(arr) は配列全体ではなくポインタのバイト数になります。

関数内で要素数を使いたい場合は、次のいずれかで対処します。

  • 要素数(行数・列数)を別の引数として一緒に渡す。
  • std::arraystd::vector を渡し、.size() で取得する。
  • テンプレートで配列を参照(const T (&arr)[N][M])として受け取り、NM を型から取り出す。

1次元配列の要素数について

1次元配列の要素数の求め方(sizeof(arr) / sizeof(arr[0]))や、ポインタ減衰の扱いについては、関連記事の 配列の要素数 でも詳しく解説しています。多次元配列は「1次元配列の各次元への応用」と捉えると理解しやすくなります。

落とし穴・注意点

注意点 内容
ポインタには sizeof が使えない 関数引数や new[] で確保した配列はポインタなので、sizeof ではポインタ自身のサイズしか得られず、要素数を計算できません。
可変長配列(VLA)は非標準 int n = 5; int arr[n][n]; のように実行時の変数でサイズを決める書き方は、C++標準には含まれません(一部コンパイラの拡張機能)。可変長が必要なら std::vector を使います。
size_t は符号なし sizeofstd::size の結果は std::size_t(符号なし整数)です。int と比較・演算すると暗黙変換による警告や予期しない結果を招くことがあります。
vector の列数は行ごとに異なりうる std::vector による2次元配列は各行の長さがそろっているとは限らないため、列数は v[0].size() だけで判断せず、必要な行ごとに確認します。

 

よくある質問(FAQ)

Q. なぜ sizeof(arr) / sizeof(arr[0][0]) で総要素数が求まるのですか?

A. sizeof(arr) は配列全体が占めるバイト数、sizeof(arr[0][0]) は1要素が占めるバイト数です。全体のバイト数を1要素のバイト数で割れば、要素が何個並んでいるか(=総要素数)が求まります。要素の型が int でも double でも、この比は要素数になります。

Q. sizeof 方式と std::size はどちらを使うべきですか?

A. C++17以降が使える環境なら、意図が明確で間違えにくい std::size が読みやすくおすすめです。総要素数を一発で求めたい場合や古い規格に合わせる必要がある場合は sizeof 方式が便利です。どちらも配列がポインタへ減衰していないことが前提です。

Q. 関数の中で多次元配列の要素数を正しく取得するには?

A. 配列は関数に渡すとポインタへ減衰するため、sizeof では取得できません。要素数を別の引数として渡すか、std::array / std::vector を渡して .size() を使うか、テンプレートで配列参照として受け取り型からサイズを取り出す方法を使います。

編集
Post Share
子ページ

子ページはありません

同階層のページ
  1. コメントアウト
  2. 文字列の結合/連結
  3. 変数の宣言
  4. 定数の宣言
  5. if文
  6. if文の論理演算子
  7. for文
  8. データ型(文字列以外)
  9. データ型(文字列)
  10. 配列とfor文
  11. 配列の要素数
  12. 多次元配列とfor文
  13. 多次元配列の要素数
  14. 関数の定義と呼び出し

最近更新/作成されたページ