C++ の for ループには、伝統的に int が利用されることが多いです。
しかし、配列へのインデックスのレンジを指定する目的を考えると、size_t 型を使用するべき場面も多々あります。
size_t 型のインデックスで for 文を記述する場合、インクリメント方式であれば特段の問題なく表現することが可能です。
一方で、デクリメントのケースでは size_t の場合では、若干の工夫が必要です。
この記事では、size_t でのデクリメントの記述方法を紹介します。
size_t インデックスのインクリメント for ループ
まずは比較の参考として、典型的なインクリメント方式を掲載します。
正常に動作します。
filename: main.cc
#include <iostream>
#include <vector>
int main()
{
std::vector<int> v(5);
for (std::size_t i = 0; i < v.size(); ++i)
{
std::cout << i << std::endl;
}
return 0;
}
$ g++ main.cc && ./a.out
0
1
2
3
4
size_t インデックスのデクリメント for ループ
正常に動作しない例(無限ループ)
インクリメントの例と同じ配列に対してデクリメントループをしたい場合、4, 3, 2, 1, 0 とアクセスが必要です。
これを実直に表現しようとすると、次のようなコードになります。
filename: main.cc
#include <iostream>
#include <vector>
int main()
{
std::vector<int> v(5);
for (std::size_t i = v.size() - 1; i >= 0; --i)
{
std::cout << i << std::endl;
}
return 0;
}
しかし、この例では次のように無限ループが発生してしまいます。
$ g++ main.cc && ./a.out
4
3
2
1
0
18446744073709551615
18446744073709551614
18446744073709551613
・・・(省略)・・・
なぜならば、size_t 変数は int とは異なり、正の整数のみしか表現しないためです。
0 から 1 を差し引くと、-1 ではなく 18446744073709551615(= 2^64 – 1)と巨大な数字になってしまいます。i >= 0
の条件が常に True と判定されていまい、無限ループが発生することになります。
デクリメントループの記述例
以下、デクリメントループをスマートに記述する例です。
filename: main.cc
#include <iostream>
#include <vector>
int main()
{
std::vector<int> v(5);
for (std::size_t i = v.size(); i--;)
{
std::cout << i << std::endl;
}
return 0;
}
$ g++ main.cc && ./a.out
4
3
2
1
0
ポイントは以下3点です。
- 初期値を
size - 1
ではなくsize
そのものとする - 終了条件に後置デクリメント
i--
を指定する。 - for 第3項のインクリメント操作には何も記述しない。
終了条件でデクリメントを行うことにより、ループの初めにデクリメントが行われることになります。
これにより、初期値を size (= 5) と指定したことと辻褄が合い、デクリメントされた 4 からループが開始されます。
また、i--
と後置デクリメントを指定することで、終了判定のタイミングを1ループだけ遅延させることができます。
i が 0 になった翌ループで for 文を終了することになり、結果として 4, 3, 2, 1, 0 と適切なインデックスを取得できます。
なお、前置デクリメント --i
と指定すると、4, 3, 2, 1 と、1ループ早く終了してしまいます。
コメント