これはかなりばかげた質問ですが、CまたはC++で配列のforループを定義するときにunsigned int
ではなくint
が一般的に使用されるのはなぜですか?
for(int i;i<arraySize;i++){}
for(unsigned int i;i<arraySize;i++){}
配列のインデックス作成以外のことを行うときにint
を使用する利点と、C++コンテナーを使用するときにイテレーターを使用する利点を認識しています。配列をループするときに問題にならないからですか?それとも一緒に避けて、size_t
などの異なるタイプを使用する必要がありますか?
これはより一般的な現象であり、多くの場合、整数に正しい型を使用しません。現代のCには、プリミティブ整数型よりもはるかに望ましいセマンティックtypedefがあります。たとえば、「サイズ」であるものはすべてsize_t
と入力するだけです。アプリケーション変数に体系的にセマンティックタイプを使用する場合、これらのタイプを使用するとループ変数もはるかに簡単になります。
また、int
を使用したことが原因で検出が困難なバグをいくつか目にしました。大きな行列などで突然クラッシュしたコード。正しい型で正しくコーディングするだけでそれを回避できます。
int
の使用は、配列のインデックス作成の論理的な観点からはより正確です。
CおよびC++のunsigned
セマンティックは、実際には「負ではない」という意味ではありませんが、「ビットマスク」または「モジュロ整数」に似ています。
unsigned
が「非負」の数値に適した型ではない理由を理解するには、考慮してください
明らかに上記のフレーズはどれも意味がありません...しかし、CとC++のunsigned
セマンティックが実際に機能する方法です。
コンテナのサイズに実際にunsigned
型を使用することは、C++の設計ミスであり、残念ながら、(下位互換性のために)この間違った選択を永久に使用する運命にあります。 「unsigned」という名前は「non-negative」に似ているので好きかもしれませんが、名前は無関係であり、意味が重要です...そしてunsigned
は「non-negative」からはかなり離れています。
このため、ほとんどのループをベクトルにコーディングするとき、私の個人的に好ましい形式は次のとおりです。
_for (int i=0,n=v.size(); i<n; i++) {
...
}
_
(もちろん、ベクトルのサイズが反復中に変更されておらず、実際には本体のインデックスが必要であると仮定します。そうでない場合はfor (auto& x : v)...
の方が優れています)。
これは、unsigned
からできるだけ早く実行され、プレーン整数を使用することで、_unsigned size_t
_の設計ミスの結果であるトラップを回避できるという利点があります。たとえば、次のことを考慮してください。
_// draw lines connecting the dots
for (size_t i=0; i<pts.size()-1; i++) {
drawLine(pts[i], pts[i+1]);
}
_
上記のコードでは、pts
ベクトルが空の場合、pts.size()-1
が巨大な無意味な数値であるため、問題が発生します。 _a < b-1
_が_a+1 < b
_と同じではない式を扱うことは、一般的に使用される値であっても地雷原で踊るようなものです。
歴史的に、_size_t
_を符号なしにすることの正当化は、値に追加のビットを使用できるようにすることです。 16ビットプラットフォームでは32767だけではなく、65535要素を配列に含めることができます。その時でさえ私の間違ったセマンティック選択の追加コストは利益の価値がありませんでした(そして32767要素が今十分ではない場合、とにかく65535は長い間十分ではありません)。
符号なしの値は非常に便利ですが、コンテナーのサイズやインデックスには使用できません。サイズとインデックスの場合、通常の符号付き整数は、セマンティックが期待どおりであるため、はるかにうまく機能します。
符号なしの値は、モジュロ演算プロパティが必要な場合、またはビットレベルで作業する場合に理想的なタイプです。
それは純粋に怠惰と無知です。インデックスには常に正しいタイプを使用する必要があります。可能なインデックスの範囲を制限する詳細情報がない限り、size_t
が正しいタイプです。
もちろん、ディメンションがファイル内のシングルバイトフィールドから読み取られた場合、その範囲は0〜255であり、int
は完全に妥当なインデックスタイプです。同様に、int
は、0から99のように一定回数ループする場合は問題ありません。ただし、int
を使用しない別の理由があります。i%2
を使用する場合ループボディで偶数/奇数のインデックスを異なる方法で処理する場合、i
が署名されている場合は、i
が署名されていない場合よりもi%2
の方がはるかに高価です...
大した違いはありません。 int
の1つの利点は、署名されていることです。したがって、int i < 0
は理にかなっていますが、unsigned i < 0
はあまりありません。
インデックスが計算される場合、それは有益である可能性があります(たとえば、結果が負の場合にループに入ることができない場合があります)。
そして、はい、書くことは少なくなります:-)
int
を使用して配列にインデックスを付けることはレガシーですが、それでも広く採用されています。 int
は単なる一般的な数値タイプであり、プラットフォームのアドレス指定機能に対応していません。それより短いか長い場合は、それを超える非常に大きな配列にインデックスを付けようとすると、奇妙な結果が発生する可能性があります。
最新のプラットフォームでは、off_t
、ptrdiff_t
およびsize_t
より多くの移植性を保証します。
これらのタイプのもう1つの利点は、コードを読む人にcontextを与えることです。上記のタイプを見ると、コードが計算だけでなく、配列の添字やポインタの計算を行うことがわかります。
そのため、防弾仕様で移植性があり状況に応じたコードを記述したい場合は、いくつかのキーストロークを犠牲にして行うことができます。
GCCはtypeof
拡張子もサポートしているため、あちこちに同じタイプ名を入力する必要がありません。
typeof(arraySize) i;
for (i = 0; i < arraySize; i++) {
...
}
次に、arraySize
のタイプを変更すると、i
のタイプが自動的に変更されます。
それは本当にコーダーに依存します。一部のコーダーは型の完全主義を好むため、比較対象の型を使用します。たとえば、C文字列を反復処理している場合は、次のように表示されます。
size_t sz = strlen("hello");
for (size_t i = 0; i < sz; i++) {
...
}
一方、彼らが何かを10回行っているだけなら、おそらくint
が表示されます。
for (int i = 0; i < 10; i++) {
...
}
私はint
を使用します。これは、物理的なタイピングが少なくて済むので問題ではありません。それらは同じ量のスペースを使用し、配列に数十億の要素がない限り、使用していない場合でもオーバーフローしません16ビットコンパイラ。通常はそうではありません。
タイプchar
の2ギガバイト、タイプshort
の4ギガバイト、タイプint
の8ギガバイトなどのサイズの配列がない限り、実際には変数が署名されているかどうかは問題です。
では、タイプを少なくできるのに、なぜタイプを増やすのでしょうか?
タイプする方が短いという問題は別として、負の数を許可することが理由です。
値が負になる可能性があるかどうかを事前に言うことはできないため、整数の引数を取るほとんどの関数は、符号付きの変数を取ります。ほとんどの関数は符号付き整数を使用するため、ループのようなものに符号付き整数を使用する方が作業が少ない場合があります。そうしないと、型キャストの束を追加しなければならない可能性があります。
64ビットプラットフォームに移行すると、符号付き整数の符号なし範囲は、ほとんどの目的にとって十分なものになります。これらの場合、符号付き整数を使用しない理由はそれほど多くありません。
次の簡単な例を考えます。
int max = some_user_input; // or some_calculation_result
for(unsigned int i = 0; i < max; ++i)
do_something;
max
が負の値、たとえば-1の場合、-1
はUINT_MAX
と見なされます(samランクが異なるが符号が異なる2つの整数を比較すると、署名されたものは、署名されていないものとして扱われます。一方、次のコードではこの問題は発生しません。
int max = some_user_input;
for(int i = 0; i < max; ++i)
do_something;
負のmax
入力を与えると、ループは安全にスキップされます。