C++で配列の要素数を取得する方法は、使っている配列の種類によって異なります。
固定長の生配列、関数に渡した配列、std::array、std::vector、動的確保した配列、文字列用の配列では、それぞれ考え方が違います。
特に重要なのは、配列そのものとして扱われている場合は要素数を取得できるが、ポインタになってしまうと元の要素数は分からなくなるという点です。
固定長の生配列で要素数を取得する方法
C++17以降なら std::size が分かりやすい
固定長の生配列であれば、C++17以降では std::size を使うのが分かりやすい方法です。
std::size は、配列全体の要素数を取得するための標準的な機能です。
配列の中身が整数でも文字でも、固定長の配列であれば要素数を取得できます。
読みやすく、意図も明確なので、現代C++ではこの方法が推奨されます。
sizeof を使う方法もある
C++17より前のコードや、昔からあるC++の書き方では、sizeof を使って要素数を計算する方法もよく使われます。
考え方は、配列全体のバイト数を、1要素あたりのバイト数で割るというものです。
たとえば、5個の整数を持つ配列があり、整数1つが4バイトなら、配列全体は20バイトになります。
これを1要素分の4バイトで割ると、要素数は5になります。
この考え方自体は正しいですが、注意点があります。
sizeof は配列そのものに対してだけ有効
sizeof を使った要素数の取得は、対象が本物の配列として扱われている場合にだけ正しく動作します。
配列が関数に渡されたあとや、ポインタとして扱われている場合には、正しい要素数を取得できません。
これはC++で非常に重要なポイントです。
関数に配列を渡した場合の注意点
関数の引数では配列がポインタになる
C++では、関数の引数として生配列を渡すと、配列そのものではなくポインタとして扱われます。
見た目としては配列を受け取っているように見えても、関数の中では実質的にポインタです。
そのため、関数の中で配列全体のサイズを調べようとしても、元の配列の要素数は分かりません。
関数内で sizeof を使っても正しい要素数は取れない
関数の中で sizeof を使うと、配列全体のサイズではなく、ポインタのサイズが取得されます。
多くの64bit環境ではポインタのサイズは8バイトです。
一方で、整数1つが4バイトなら、計算結果は2のような値になることがあります。
これは実際の配列の要素数とは関係ありません。
環境によってポインタのサイズは異なるため、結果が常に同じになるわけでもありません。
つまり、関数に渡された生配列に対して sizeof で要素数を求めるのは誤りです。
生配列を関数に渡すなら要素数も一緒に渡す
生配列を関数に渡す場合は、配列本体だけでなく、要素数も一緒に渡すのが基本です。
関数側では、渡された要素数を使って処理します。
これはC言語由来の書き方でもあり、C++でも生配列を扱う場合にはよく使われます。
ただし、現代C++では、生配列をそのまま関数に渡すよりも、std::array、std::vector、またはC++20以降なら std::span を使うほうが安全で扱いやすいです。
テンプレートを使って配列の要素数を取得する方法
配列参照として受け取れば要素数を保持できる
関数で生配列の要素数を取得したい場合でも、テンプレートを使って配列参照として受け取れば、要素数を取得できます。
この方法では、配列がポインタに変換されません。
配列の型に含まれている要素数を、テンプレート引数として受け取ることができます。
固定長の生配列に対して有効
この方法は、コンパイル時に要素数が決まっている固定長の生配列に対して有効です。
逆に、実行時にサイズが決まる動的配列や、ポインタとして渡されたものには使えません。
初心者にはやや難しいが、正確な方法
テンプレートを使う方法は、C++らしい正確な書き方です。
ただし、初心者にとっては少し難しく見えるかもしれません。
まずは「関数に生配列を渡すとサイズ情報が失われる」「配列参照として受け取ればサイズを保持できる」という考え方を理解するとよいです。
std::array の要素数を取得する方法
固定長配列なら std::array が便利
C++で固定長の配列を扱うなら、生配列よりも std::array を使うほうが便利です。
std::array は固定長の配列ですが、通常のクラスのように扱えるため、要素数を取得する機能が用意されています。
要素数は size で取得できる
std::array では、size を使って要素数を取得できます。
生配列のように、配列全体のバイト数を計算する必要はありません。
サイズ情報が型の一部として保持されているため、安全に要素数を取得できます。
関数に渡しても扱いやすい
std::array は、生配列よりも関数に渡しやすいです。
参照として渡せば、不要なコピーを避けながら、配列としての情報を保持できます。
ただし、std::array は要素数も型の一部です。
たとえば、要素数が3の std::array と要素数が5の std::array は、別の型として扱われます。
そのため、複数のサイズに対応したい場合は、テンプレートを使うことがあります。
std::vector の要素数を取得する方法
可変長配列なら std::vector が基本
要素数があとから増えたり減ったりする場合は、std::vector を使うのが基本です。
std::vector は、C++で最もよく使われる可変長配列のようなコンテナです。
要素数は size で取得できる
std::vector でも、要素数は size で取得できます。
要素を追加すれば要素数は増え、削除すれば要素数は減ります。
現在いくつの要素が入っているかを知りたい場合は、size を使います。
戻り値の型は std::size_t
std::vector の要素数を取得した場合、戻り値は通常 std::size_t です。
std::size_t は、サイズや個数を表すために使われる符号なし整数型です。
小さな配列ではあまり問題になりませんが、厳密に書くなら、要素数を受け取る変数には int よりも std::size_t や auto を使うほうが自然です。
動的確保した配列の要素数
new で確保した配列はサイズを自動取得できない
new を使って動的に配列を確保した場合、その変数は配列ではなくポインタです。
そのため、そこから元の要素数を標準的な方法で取得することはできません。
たとえば、5個分の領域を確保したとしても、ポインタだけを見ても、それが5個分なのか、10個分なのかは分かりません。
サイズは自分で管理する必要がある
動的確保した配列を使う場合は、要素数を別の変数として管理する必要があります。
ただし、現代C++では、直接 new で配列を確保する場面はかなり少なくなっています。
可変長の配列が必要なら、基本的には std::vector を使うほうが安全です。
std::vector なら、メモリ管理も自動で行われ、要素数も簡単に取得できます。
delete のための内部情報は利用できない
処理系によっては、動的配列を解放するために内部的なサイズ情報を持っている場合があります。
しかし、それはC++のプログラムから標準的に取得できる情報ではありません。
したがって、「内部的にはサイズを知っているかもしれないから取得できる」と考えるのは誤りです。
プログラム上では、動的配列の要素数は自分で管理する必要があります。
文字配列と文字列の長さの違い
文字配列の要素数と文字列の長さは別物
文字配列では、「配列の要素数」と「文字列の長さ」を区別する必要があります。
たとえば、hello という文字列を文字配列に入れると、実際には末尾にヌル文字が追加されます。
つまり、見た目の文字数は5文字でも、配列としては6要素になります。
ヌル文字を含むかどうかが違う
C文字列では、文字列の終わりを表すためにヌル文字が使われます。
そのため、文字配列の要素数を数える場合は、ヌル文字も含まれます。
一方で、C文字列の長さを調べる場合は、ヌル文字の前までを文字列として数えます。
つまり、文字配列の要素数と、C文字列としての長さは一致しないことがあります。
strlen はヌル終端された文字列にだけ使う
C文字列の長さを取得する機能は、対象がヌル終端されていることを前提にしています。
末尾にヌル文字がない文字配列に対して使うと、正しく動作しません。
場合によっては未定義動作になります。
そのため、文字配列に対して文字列長を取得する場合は、それが本当にC文字列として正しく終端されているかを確認する必要があります。
std::string の長さを取得する方法
C++の文字列なら std::string が基本
C++で文字列を扱う場合は、通常はC文字列よりも std::string を使います。
std::string は文字列を扱うための標準クラスで、長さの取得や文字列操作を安全に行いやすくなっています。
長さは size または length で取得できる
std::string では、文字列の長さを size または length で取得できます。
この2つは、std::string では基本的に同じ意味です。
日本語などでは見た目の文字数と一致しないことがある
注意点として、std::string の size や length が返すのは、基本的には格納されている char の個数です。
UTF-8で日本語を扱う場合、人間が見たときの文字数とは一致しないことがあります。
たとえば、見た目では1文字の日本語でも、内部的には複数バイトで表現されます。
そのため、std::string の長さは「見た目の文字数」ではなく、「内部的に格納されている文字データの数」と考えたほうが正確です。
std::ssize について
C++20以降では符号付きのサイズを取得できる
C++20以降では、std::ssize を使うことで、符号付き整数として要素数を取得できます。
通常の size や std::size は、符号なし整数型である std::size_t を返します。
一方、std::ssize は符号付き整数型を返します。
逆順ループなどで役立つことがある
符号付きのサイズは、逆順に処理したい場合などに便利なことがあります。
符号なし整数で逆順ループを書くと、0より小さくならないため、条件の書き方を間違えると無限ループになることがあります。
そのため、逆順処理では符号付きの値を使うほうが分かりやすい場合があります。
ただしループの書き方には注意が必要
std::ssize を使えば常に安全というわけではありません。
空のコンテナを扱う場合や、添字の条件を間違えた場合には、やはりバグの原因になります。
実務では、逆順処理にはリバースイテレータなどを使ったほうが安全で読みやすいことも多いです。
C++20以降なら std::span も便利
配列やコンテナを範囲として受け取れる
C++20以降では、関数の引数として std::span を使う方法もあります。
std::span は、配列や std::array、std::vector などの連続した要素列を、所有しないビューとして扱うための機能です。
サイズ情報を持ったまま関数に渡せる
std::span を使うと、配列の先頭だけでなく、要素数も一緒に扱えます。
そのため、関数内でも要素数を取得できます。
生配列を単に関数に渡すとサイズ情報が失われますが、std::span を使えば、範囲としてのサイズ情報を保ったまま渡せます。
生配列、std::array、std::vector を統一的に扱える
std::span の大きな利点は、生配列、std::array、std::vector などを同じように受け取れることです。
複数の種類の配列やコンテナを関数に渡したい場合、std::span は非常に便利です。
ただし、std::span 自体はデータを所有しません。元の配列やコンテナが存在している間だけ有効です。
要素数取得方法のまとめ
生配列の場合
固定長の生配列であれば、C++17以降は std::size を使うのが分かりやすく安全です。
C++17より前のコードでは、sizeof を使って、配列全体のサイズを1要素のサイズで割る方法もあります。
ただし、これは配列がポインタに変換されていない場合に限って正しく動作します。
関数に渡した生配列の場合
関数に渡された生配列は、基本的にポインタとして扱われます。
そのため、関数内で元の配列の要素数を自動的に取得することはできません。
生配列を使うなら、要素数も一緒に渡す必要があります。
または、テンプレートで配列参照として受け取る方法や、C++20以降なら std::span を使う方法があります。
std::array の場合
固定長配列をC++らしく扱いたい場合は、std::array が便利です。
要素数は size で取得できます。
生配列よりも安全で、標準ライブラリのコンテナとして扱えるため、固定長配列にはよく使われます。
std::vector の場合
要素数が変わる配列を扱う場合は、std::vector を使います。
要素数は size で取得できます。
現代C++では、可変長配列が必要な場面では、基本的に std::vector を選ぶのが自然です。
動的配列の場合
new で確保した動的配列から、要素数を標準的に取得することはできません。
サイズは自分で管理する必要があります。
ただし、現代C++では、直接 new を使うよりも std::vector を使うほうが安全です。
文字列の場合
文字配列では、配列の要素数と文字列の長さが異なる場合があります。
C文字列では、末尾にヌル文字が入るため、配列の要素数にはヌル文字も含まれます。
一方、文字列の長さとして数える場合は、ヌル文字は含まれません。
C++で文字列を扱うなら、基本的には std::string を使うのが扱いやすいです。
実務でのおすすめ
固定長なら std::array
要素数がコンパイル時に決まっていて、固定長でよい場合は、std::array を使うのがおすすめです。
生配列よりも扱いやすく、要素数も簡単に取得できます。
可変長なら std::vector
要素数が実行時に変わる場合は、std::vector を使うのが基本です。
メモリ管理を自動で行ってくれるため、生の動的配列より安全です。
生配列を使う必要があるなら std::size
既存コードや特別な理由で生配列を使う場合は、C++17以降なら std::size を使うとよいです。
ただし、関数に渡した後の生配列には使えない点に注意が必要です。
関数引数には std::span も検討する
C++20以降を使えるなら、配列やコンテナを関数に渡すときは std::span も有力な選択肢です。
サイズ情報を保ったまま範囲として受け取れるため、生配列とサイズを別々に渡すよりも分かりやすくなります。
最も重要なポイント
C++で配列の要素数を取得するときに、最も注意すべきなのは、配列とポインタは違うという点です。
配列そのものとして扱われている場合は、要素数を取得できます。
しかし、関数に渡されたり、動的確保によってポインタとして扱われたりすると、元の要素数は分からなくなります。
そのため、現代C++では次のように考えるのが分かりやすいです。
固定長なら std::array、可変長なら std::vector、生配列なら std::size、関数に範囲を渡すなら std::span を検討する。
この方針で書けば、C++の配列サイズに関する多くのトラブルを避けやすくなります。
以上、C++の配列の要素数を取得する方法についてでした。
最後までお読みいただき、ありがとうございました。
