C++で型が重要な理由
変数のページでは、int age = 30; のように明示的な型で値を宣言しました。この型は単なるラベルではありません - コンパイラに何バイト確保するか、それらのバイトをどう解釈するか、どの演算が許されるかを伝えます。型を間違えると、気づかないうちに精度を失ったり、オーバーフローしたり、未定義動作を引き起こしたりします。
C++は組み込み型をいくつかのファミリーにまとめています。整数、浮動小数点数、文字型、そして真偽型です。それぞれを見たうえで、人がつまずくルールを確認しましょう。
基本型
1つのプログラムに各コア型を1つずつ示します。リテラルのサフィックス(L、f、u)と char のシングルクォートに注目してください。
bool はデフォルトで true/false ではなく 1 または 0 として出力されます。char はシングルクォートを使います - 'A' は1文字ですが、"A"(ダブルクォート)は文字列リテラルで、まったく別の型です。この2つの間違いは初期段階で非常によくあります。
サイズは固定ではない
これはJavaのような言語から来た人にとって最大の驚きです。C++規格は最小サイズと相対的な順序(short ≤ int ≤ long ≤ long long)だけを保証します。実際のサイズはコンパイラとプラットフォームに依存します。常に sizeof で確認してください。
典型的な64ビットLinuxビルドでは int = 4、long = 8 と表示されます。しかし64ビットWindowsでは long はわずか4バイトです。この移植性のギャップこそが、long が64ビットだと仮定するコードを書くべきでない理由です。
正確な幅が必要なときは、<cstdint> の固定幅整数型に頼りましょう。
ファイル形式、ネットワークプロトコル、あるいはマシン間で同一に振る舞う必要のあるものには int32_t/int64_t を使ってください。キャスト (int)a に注目 - 8ビット型をストリームに送ると数値ではなく文字として出力されるので、先にキャストしましょう。
Signed と unsigned
すべての整数型には2つの種類があります。signed 型は負の値を保持できますが、unsigned 型は保持できず、負の範囲を引き換えにより大きな正の最大値を得ます。素の int はデフォルトで signed です。
符号なしの 0 から減算すると、負になるのではなく巨大な正の数に巻き戻ります。これは人を絶えず引っかけます - 特に .size() が返す size_t(符号なし型)で起こります。
vector<int> v = {1, 2, 3};
// 危険: v.size() は符号なしです。v が空だと v.size() - 1 は巨大な数に
// 巻き戻り、ループはほぼ永遠に回り続けます。
for (size_t i = 0; i <= v.size() - 1; i++) { /* ... */ }
i < v.size() を使い(決して <= size() - 1 を使わない)、あるいは範囲ベースの for ループで問題そのものを回避しましょう。
整数オーバーフローは未定義動作
符号なしの巻き戻り(これは明確に定義されている)とは異なり、符号付き整数のオーバーフローはC++では未定義動作です。コンパイラは何でもできます - ゴミを返す、最適化でチェックを消す、クラッシュする、など。
対処法はどの言語でもオーバーフローの罠と同じです。より広い型で算術を行うことです。加算が64ビットで起こるように、+ の前に片方のオペランドを long long にキャストしましょう。結果を後からキャストしても遅すぎます - オーバーフローはすでに起きています。
適切な型を選ぶ
ほとんどのコードではデフォルトで問題ありません。整数には int、小数には double です。理由があるときだけ別のものに手を伸ばしましょう。
| 型 | 一般的なサイズ | 使う場面 |
|---|---|---|
int | 32ビット | 整数のデフォルト |
long long | 64ビット | 約20億を超える値: タイムスタンプ、大きなカウンター |
double | 64ビット | 小数のデフォルト - 良い精度 |
float | 32ビット | 精度を犠牲にできる、メモリの厳しい配列 |
bool | 1バイト | true/false のフラグ |
int32_t / int64_t | 正確 | クロスプラットフォーム形式、プロトコル、ビット操作 |
覚えておくべき落とし穴がいくつかあります。float は有効な十進数で約7桁しかないため、0.1f + 0.2f は正確には 0.3 になりません - 本当にメモリを節約する必要がない限り double を選びましょう。また char はプラットフォームによって signed か unsigned のどちらにもなり得るので、生のバイトに対して算術を行う場合は signed char か unsigned char と明示してください。
次へ: auto キーワード
毎回型を書き出すのは面倒で、型が長かったり名付けにくかったりすることもあります。C++では auto キーワードでコンパイラに型を推論させられます - auto x = 42; は x を int にし、auto it = v.begin(); は冗長なイテレータ型を打つ手間を省きます。次のページでは、auto がコードを明確にする場面と、隠しすぎてしまう場面を扱います。
よくある質問
C++の基本的なデータ型は何ですか?
基本型は整数(short、int、long、long long)、浮動小数点数(float、double、long double)、文字型の char、真偽型の bool です。各整数型はさらに signed または unsigned になり得ます。それ以外のすべて - std::string、配列、自作のクラス - はこれらの上に構築されています。
C++における int と long の違いは何ですか?
どちらも整数を格納しますが、long は少なくとも int と同じ幅を持つことが保証されています(64ビットプラットフォームではしばしば64ビットですが、Windowsでは32ビットのみ)。規格は最小サイズだけを定めているので、幅を保証したい場合は <cstdint> の int32_t や int64_t のような固定幅型を使ってください。
C++の int は何ビットですか?
規格は int が少なくとも16ビットであることだけを保証しますが、現代のほぼすべてのデスクトップとサーバーでは32ビットです。サイズはプラットフォーム依存なので、決して決めつけないでください - 確認するには sizeof(int) を出力するか、正確な幅が必要なときは int32_t のような <cstdint> の型を使いましょう。