C++で配列のサイズを取得する方法は、配列の種類によって異なります。
一口に「配列」といっても、C++では昔ながらの固定長配列、std::array、std::vector、文字配列、ポインタなど、いくつかの形があります。
それぞれサイズの取得方法が違うため、使っているデータ構造に合わせて適切な方法を選ぶことが大切です。
特に注意したいのは、固定長配列ではサイズを取得できても、関数に渡した配列やポインタからは元の配列サイズを自動的に取得できない点です。
ここを理解していないと、意図しない値が返ってきたり、範囲外アクセスにつながったりする可能性があります。
配列のサイズには「要素数」と「バイト数」がある
要素数とは
配列のサイズを考えるときは、まず「要素数」と「バイト数」を分けて考える必要があります。
要素数とは、配列の中にいくつのデータが入るかを表す数です。
たとえば、5個の整数を格納できる配列であれば、要素数は5です。
通常、「配列のサイズを取得したい」と言う場合、多くはこの要素数を知りたいケースを指します。
バイト数とは
一方、バイト数とは、配列全体がメモリ上でどれくらいの容量を使っているかを表す値です。
たとえば、整数型の1要素が4バイトで、配列に5個の要素がある場合、配列全体のバイト数は20バイトになります。
ただし、型のサイズは環境によって異なる場合があります。
そのため、「整数型だから必ず4バイト」と決めつけるのではなく、あくまで多くの環境での一般的な例として理解しておくとよいでしょう。
固定長配列のサイズを取得する方法
固定長配列では要素数を取得できる
C++の固定長配列では、配列そのものに対して要素数を求めることができます。
固定長配列とは、宣言時点で要素数が決まっている配列のことです。
たとえば、整数を5個入れられる配列を作った場合、その配列の要素数は5になります。
昔ながらの方法では、配列全体のバイト数を1要素あたりのバイト数で割ることで、要素数を求めます。
この方法は、固定長配列そのものに対して使う場合は正しく動作します。
sizeof を使う方法の考え方
固定長配列では、配列全体のバイト数を取得し、それを1要素分のバイト数で割ることで、要素数を求められます。
考え方としては、次のようになります。
配列全体のバイト数を取得します。
次に、配列の1要素あたりのバイト数を取得します。
そして、配列全体のバイト数を1要素あたりのバイト数で割ると、配列に含まれる要素数が分かります。
たとえば、要素数が5個の整数配列があり、整数1つが4バイトの場合、配列全体のバイト数は20バイトです。
20バイトを4バイトで割ると、要素数は5になります。
このように、sizeof を利用する方法は古くから使われている基本的な手法です。
sizeof を使う方法の注意点
sizeof を使った方法は、配列そのものに対して使う場合は有効です。
しかし、関数に渡された配列に対して使うと、正しい要素数を取得できません。
これは、C++では配列を関数の引数として渡すと、多くの場合、配列そのものではなくポインタとして扱われるためです。
関数の中では、配列全体の情報が失われ、先頭要素のアドレスだけを持つような形になります。
そのため、関数の中で sizeof を使っても、元の配列全体のサイズではなく、ポインタのサイズが取得されてしまいます。
この点は、C++初心者が特につまずきやすい重要なポイントです。
C++17以降では std::size が便利
std::size を使うメリット
C++17以降では、固定長配列の要素数を取得する方法として std::size を使えます。
std::size は、配列や標準ライブラリのコンテナに対して、要素数を分かりやすく取得できる機能です。
sizeof を使う方法に比べて、「要素数を取得している」という意図が明確になります。
そのため、C++17以降の環境であれば、固定長配列の要素数取得には std::size を使うのがおすすめです。
std::size を使うと読みやすくなる
sizeof を使った方法では、配列全体のバイト数を1要素あたりのバイト数で割るという計算が必要です。
一方、std::size であれば、配列の要素数を取得していることがひと目で分かります。
コードを読む人にとっても理解しやすく、ミスも起こりにくくなります。
特に初心者向けのコードや、保守性を重視するコードでは、std::size の方が適しています。
std::size を使うときの注意点
std::size を使うには、C++17以降の環境が必要です。
また、標準ライブラリの機能であるため、適切なヘッダーを読み込む必要があります。
一般的には、std::size を使う場合は <iterator> をインクルードすると分かりやすいです。
ただし、開発環境や他のヘッダーの読み込み状況によっては、別の標準ライブラリヘッダー経由で使えることもあります。
記事や教材として説明する場合は、std::size を使うために <iterator> を読み込むと説明しておくと安全です。
std::array のサイズを取得する方法
std::array では size を使う
C++では、固定長配列をより安全に扱うために std::array が用意されています。
std::array は、要素数が固定された配列を扱うための標準ライブラリのコンテナです。
昔ながらの固定長配列と違い、メンバ関数を持っているため、サイズ取得や先頭要素・末尾要素へのアクセスなどを分かりやすく行えます。
std::array の要素数を取得する場合は、size を使います。
std::array は固定長だが扱いやすい
std::array は固定長の配列です。
つまり、作成した後に要素数を増やしたり減らしたりすることはできません。
しかし、通常の固定長配列よりも扱いやすく、標準ライブラリのコンテナとして他の機能とも組み合わせやすいというメリットがあります。
たとえば、要素数の取得、空かどうかの判定、先頭要素や末尾要素へのアクセスなどを、分かりやすい形で行えます。
std::array の関数引数では型とサイズに注意する
std::array を関数に渡す場合、要素型と要素数の両方が型の一部になります。
たとえば、整数を5個持つ std::array と、整数を3個持つ std::array は別の型として扱われます。
また、整数を5個持つ std::array と、小数を5個持つ std::array も別の型です。
そのため、特定の std::array だけを受け取る関数を書くと、他の要素数や他の型の std::array は受け取れません。
複数のサイズや型に対応したい場合は、テンプレートを使って汎用的に書く必要があります。
std::vector のサイズを取得する方法
std::vector では size を使う
C++で可変長配列としてよく使われるのが std::vector です。
std::vector は、要素数を後から増やしたり減らしたりできる動的な配列です。
固定長配列や std::array と違い、実行中に要素を追加したり削除したりできます。
std::vector の現在の要素数を取得する場合は、size を使います。
std::vector に sizeof を使ってはいけない
std::vector の要素数を取得するために sizeof を使うのは正しくありません。
sizeof は、オブジェクトそのもののバイト数を返します。
std::vector に対して sizeof を使っても、std::vector が管理している要素数は分かりません。
std::vector の中に何個の要素が入っているかを知りたい場合は、必ず size を使います。
std::size も使えるが size が一般的
C++17以降では、std::vector に対して std::size を使うこともできます。
ただし、std::vector だけを扱う場合は、メンバ関数である size を使うのが一般的です。
std::size は、固定長配列、std::array、std::vector などを統一的に扱いたい場合に便利です。
一方で、通常の std::vector の要素数取得では、vec.size() のようにメンバ関数を使う書き方が自然です。
std::size_t について
サイズを表す型には std::size_t を使う
配列やコンテナのサイズを扱う場合、戻り値の型には std::size_t がよく使われます。
std::size_t は、サイズや要素数を表すための符号なし整数型です。
配列の要素数やメモリサイズは負の値にならないため、こうした用途に向いています。
初心者向けのサンプルでは整数型で受け取っても動作する場合がありますが、正確性を重視するなら std::size_t を使う方が適切です。
ループでは範囲for文も便利
配列や std::vector の要素を順番に処理する場合、必ずしもインデックスを使う必要はありません。
単に全要素を順番に処理したいだけであれば、範囲for文を使う方が分かりやすく、安全です。
インデックス番号が必要な場合は std::size_t を使ったループが適しています。
一方で、要素の値を順番に取り出すだけなら、範囲for文を使うとコードが簡潔になります。
関数に配列を渡す場合の注意点
配列を関数に渡すとサイズ情報が失われる
C++では、固定長配列を関数の引数として渡すと、配列そのものではなくポインタとして扱われることがあります。
そのため、関数の中では元の配列が何個の要素を持っていたか分からなくなります。
これは非常に重要なポイントです。
関数の中で sizeof を使っても、元の配列全体のバイト数ではなく、ポインタのバイト数が取得されるため、要素数を正しく求めることはできません。
サイズを一緒に渡す方法
昔ながらの方法では、配列を関数に渡すときに、要素数も一緒に渡します。
この方法であれば、関数の中でも要素数を使って配列を処理できます。
ただし、配列とサイズを別々に渡すため、間違ったサイズを渡せてしまうという欠点があります。
実際の配列より大きなサイズを渡してしまうと、範囲外のメモリにアクセスする危険があります。
配列参照やテンプレートを使う方法
固定長配列のサイズを関数側で保持したい場合は、配列を参照として受け取る方法があります。
配列を参照として受け取れば、通常の関数引数のようにポインタへ変換されず、配列の要素数を型情報として保持できます。
さらにテンプレートを使えば、要素数が異なる固定長配列にも対応できます。
この方法は、固定長配列を安全に扱いたい場合に有効です。
C++20以降では std::span も便利
C++20以降では、配列や std::vector を関数に渡す方法として std::span も便利です。
std::span は、連続したデータ列を参照するための軽量なビューです。
固定長配列、std::array、std::vector などを、同じような形で関数に渡せます。
ただし、std::span はデータを所有しません。
あくまで元の配列や std::vector を参照しているだけです。
そのため、参照先の配列や std::vector が破棄された後に std::span を使うと危険です。
std::span を使う場合は、参照している元データの寿命に注意する必要があります。
ポインタから配列サイズは取得できない
ポインタが持っているのは先頭アドレスだけ
C++では、ポインタだけから元の配列サイズを自動的に取得することはできません。
ポインタが持っているのは、基本的にメモリ上のどこを指しているかという情報です。
つまり、配列の先頭要素の場所は分かりますが、その配列が何個の要素を持っているかは分かりません。
そのため、ポインタを使って配列のようにデータを扱う場合は、要素数を別途管理する必要があります。
ポインタを使う場合はサイズ管理が必要
ポインタで配列を扱う場合は、必ずサイズ情報も一緒に扱う必要があります。
たとえば、関数にポインタを渡す場合は、同時に要素数も渡すようにします。
そうしないと、どこまでが有効なデータなのか判断できません。
サイズ情報を持たないポインタを使って配列を処理すると、範囲外アクセスや未定義動作の原因になります。
多次元配列のサイズを取得する方法
2次元配列では行数と列数を分けて考える
2次元配列のサイズを取得する場合は、行数と列数を分けて考えます。
たとえば、3行4列の配列であれば、行数は3、列数は4です。
固定長の2次元配列であれば、配列そのものに対して行数や列数を求めることができます。
C++17以降であれば、std::size を使って行数や列数を取得することも可能です。
多次元配列も関数に渡すと注意が必要
通常の固定長配列と同じように、多次元配列も関数に渡すときには注意が必要です。
関数の引数として受け取った配列では、配列全体のサイズ情報が失われる場合があります。
そのため、関数内で sizeof を使っても、元の行数を正しく取得できないことがあります。
多次元配列を関数に渡す場合は、行数や列数を別途渡す、テンプレートを使って受け取る、std::array や std::vector を使うなど、より安全な方法を選ぶとよいでしょう。
文字配列のサイズ取得
文字配列では終端文字に注意する
文字配列を扱う場合は、通常の数値配列とは少し考え方が異なります。
C言語由来の文字列では、文字列の末尾に終端文字である '\0' が入ります。
たとえば、「Hello」という文字列は5文字ですが、C文字列として文字配列に格納すると、末尾に '\0' が追加されるため、配列の要素数は6になります。
つまり、文字列の文字数と、文字配列の要素数は必ずしも同じではありません。
文字数を知りたい場合は strlen を使う
C文字列の文字数を知りたい場合は、strlen を使います。
strlen は、終端文字 '\0' の手前までの文字数を数えます。そのため、「Hello」の場合、文字数は5になります。
ただし、strlen を使えるのは、文字配列が正しくヌル終端されている場合だけです。
末尾に '\0' がない文字配列に対して strlen を使うと、終端文字が見つかるまでメモリを読み続けてしまい、未定義動作につながる可能性があります。
C++では std::string が使いやすい
C++で文字列を扱う場合は、C文字列よりも std::string を使う方が一般的です。
std::string であれば、文字数を簡単に取得でき、メモリ管理も安全に行えます。
特別な理由がない限り、C++で文字列を扱う場合は std::string を使うのがおすすめです。
よくある間違い
関数内で sizeof を使って配列サイズを取得しようとする
よくある間違いの一つが、関数に渡された配列に対して sizeof を使い、要素数を求めようとすることです。
関数の引数として受け取った配列は、実際にはポインタとして扱われることがあります。
そのため、sizeof で取得できるのは元の配列全体のサイズではなく、ポインタのサイズです。
この方法では正しい要素数を取得できないため、関数に配列を渡す場合は、サイズも一緒に渡すか、配列参照、テンプレート、std::array、std::span などを使う必要があります。
std::vector に sizeof を使う
std::vector の要素数を取得するために sizeof を使うのも間違いです。
sizeof は、std::vector オブジェクトそのもののサイズを返すだけで、内部に格納されている要素数は返しません。
std::vector の要素数を知りたい場合は、必ず size を使います。
文字配列の要素数と文字数を混同する
文字配列では、終端文字 '\0' を含むかどうかに注意が必要です。
文字配列の要素数を数える場合は、終端文字を含むことがあります。
一方、文字列としての文字数を数える場合は、終端文字を含みません。
そのため、文字配列の要素数と文字列の文字数を混同しないようにしましょう。
配列サイズ取得の使い分け
固定長配列の場合
固定長配列の要素数を取得する場合、C++17以降であれば std::size を使うのがおすすめです。
古いC++環境では、sizeof を使って配列全体のバイト数を1要素あたりのバイト数で割る方法が使われます。
ただし、この方法は配列そのものに対して使う場合に限って有効です。
関数に渡された配列やポインタに対しては正しく使えません。
std::array の場合
std::array の要素数は、size で取得します。
固定長のデータを安全に扱いたい場合は、昔ながらの固定長配列よりも std::array を使う方が分かりやすく、C++らしい書き方になります。
std::vector の場合
std::vector の要素数は、size で取得します。
要素数が実行中に変わる可能性がある場合は、固定長配列や std::array ではなく、std::vector を使うのが一般的です。
文字列の場合
C文字列では、文字数を取得する場合に strlen を使います。
ただし、strlen はヌル終端された文字列に対して使う必要があります。
C++で文字列を扱う場合は、基本的には std::string を使い、文字数は size で取得するのが分かりやすいです。
ポインタの場合
ポインタだけから元の配列サイズを取得することはできません。
ポインタで配列を扱う場合は、サイズを別途管理する必要があります。
実務でおすすめの考え方
固定長なら std::array を使う
要素数があらかじめ決まっていて、後から増減しない場合は、std::array を使うのがおすすめです。
std::array は固定長でありながら、size などの便利な機能を使えます。
通常の固定長配列よりも扱いやすく、安全性も高くなります。
可変長なら std::vector を使う
要素数が後から変わる可能性がある場合は、std::vector を使うのが基本です。
std::vector であれば、要素の追加や削除ができ、現在の要素数も size で簡単に取得できます。
関数に渡すなら std::span も検討する
C++20以降の環境であれば、関数に配列や std::vector を渡すときに std::span を使うと便利です。
std::span を使うと、固定長配列、std::array、std::vector などを統一的に扱いやすくなります。
ただし、std::span はデータを所有しないため、参照先のデータが有効である間だけ使う必要があります。
まとめ
C++で配列のサイズを取得する方法は、配列の種類によって異なります。
固定長配列であれば、C++17以降では std::size を使うのがおすすめです。
古い方法では、sizeof を使って配列全体のバイト数を1要素あたりのバイト数で割ることで、要素数を求められます。
ただし、sizeof を使う方法は、配列そのものに対して使う場合に限って有効です。
関数に渡された配列やポインタに対して使っても、元の配列サイズは取得できません。
std::array では size、std::vector でも size を使います。
文字列を扱う場合、C文字列では strlen、C++の std::string では size を使うのが一般的です。
実務では、昔ながらの固定長配列よりも、固定長なら std::array、可変長なら std::vector を使う方が安全で分かりやすいです。
C++20以降で関数に配列やコンテナを渡す場合は、std::span も有力な選択肢になります。
以上、C++の配列のサイズを取得する方法についてでした。
最後までお読みいただき、ありがとうございました。
