整数型 解答ページ | Programming Place Plus 新C++編

トップページ新C++編整数型

このページの概要 🔗

このページは、練習問題の解答例や解説のページです。



解答・解説 🔗

問題1 (確認★) 🔗

各種の標準整数型で扱える値の範囲を、std::numeric_limits<> を使って確認してみてください。


標準整数型とは、signed char、short int、int、long int、long long int および、unsigned char、unsigned short int、unsigned int、unsigned long int、unsigned long long int のことです(本編解説)。

#include <limits>
#include <iostream>

int main()
{
    std::cout << "signed char: " << static_cast<int>(std::numeric_limits<signed char>::min()) << " ~ " << static_cast<int>(std::numeric_limits<signed char>::max()) << "\n"
              << "unsigned char: " << static_cast<int>(std::numeric_limits<unsigned char>::min()) << " ~ " << static_cast<int>(std::numeric_limits<unsigned char>::max()) << "\n"
              << "short: " << std::numeric_limits<short>::min() << " ~ " << std::numeric_limits<short>::max() << "\n"
              << "unsigned short: " << std::numeric_limits<unsigned short>::min() << " ~ " << std::numeric_limits<unsigned short>::max() << "\n"
              << "int: " << std::numeric_limits<int>::min() << " ~ " << std::numeric_limits<int>::max() << "\n"
              << "unsigned int: " << std::numeric_limits<unsigned int>::min() << " ~ " << std::numeric_limits<unsigned int>::max() << "\n"
              << "long: " << std::numeric_limits<long>::min() << " ~ " << std::numeric_limits<long>::max() << "\n"
              << "unsigned long: " << std::numeric_limits<unsigned long>::min() << " ~ " << std::numeric_limits<unsigned long>::max() << "\n"
              << "long long: " << std::numeric_limits<long long>::min() << " ~ " << std::numeric_limits<long long>::max() << "\n"
              << "unsigned long long: " << std::numeric_limits<unsigned long long>::min() << " ~ " << std::numeric_limits<unsigned long long>::max() << "\n";
}

実行結果:

signed char: -128 ~ 127
unsigned char: 0 ~ 255
short: -32768 ~ 32767
unsigned short: 0 ~ 65535
int: -2147483648 ~ 2147483647
unsigned int: 0 ~ 4294967295
long: -2147483648 ~ 2147483647
unsigned long: 0 ~ 4294967295
long long: -9223372036854775808 ~ 9223372036854775807
unsigned long long: 0 ~ 18446744073709551615

標準整数型で扱える値の範囲は、signed char、unsigned char を除いて、処理系定義なので、実行結果は使っているコンパイラによって異なります。

signed char、unsigned char(char も)の値を出力するとき、普通にやると、文字として出力されます。整数値で出力するには、static_cast で int型にキャストします(本編解説)。

問題2 (確認★) 🔗

<cstdint> で定義されている型について、sizeof演算子がどのような大きさを返すか調べてみてください。


たとえば次のようにして確認できます。

#include <cstdint>
#include <iostream>

int main()
{
    std::cout << "int8_t: " << sizeof(std::int8_t) << "\n"
              << "int16_t: " << sizeof(std::int16_t) << "\n"
              << "int32_t: " << sizeof(std::int32_t) << "\n"
              << "int64_t: " << sizeof(std::int64_t) << "\n"
              << "uint8_t: " << sizeof(std::uint8_t) << "\n"
              << "uint16_t: " << sizeof(std::uint16_t) << "\n"
              << "uint32_t: " << sizeof(std::uint32_t) << "\n"
              << "uint64_t: " << sizeof(std::uint64_t) << "\n"
              << "int_least8_t: " << sizeof(std::int_least8_t) << "\n"
              << "int_least16_t: " << sizeof(std::int_least16_t) << "\n"
              << "int_least32_t: " << sizeof(std::int_least32_t) << "\n"
              << "int_least64_t: " << sizeof(std::int_least64_t) << "\n"
              << "uint_least8_t: " << sizeof(std::uint_least8_t) << "\n"
              << "uint_least16_t: " << sizeof(std::uint_least16_t) << "\n"
              << "uint_least32_t: " << sizeof(std::uint_least32_t) << "\n"
              << "uint_least64_t: " << sizeof(std::uint_least64_t) << "\n"
              << "int_fast8_t: " << sizeof(std::int_fast8_t) << "\n"
              << "int_fast16_t: " << sizeof(std::int_fast16_t) << "\n"
              << "int_fast32_t: " << sizeof(std::int_fast32_t) << "\n"
              << "int_fast64_t: " << sizeof(std::int_fast64_t) << "\n"
              << "uint_fast8_t: " << sizeof(std::uint_fast8_t) << "\n"
              << "uint_fast16_t: " << sizeof(std::uint_fast16_t) << "\n"
              << "uint_fast32_t: " << sizeof(std::uint_fast32_t) << "\n"
              << "uint_fast64_t: " << sizeof(std::uint_fast64_t) << "\n"
              << "intmax_t: " << sizeof(std::intmax_t) << "\n"
              << "uintmax_t: " << sizeof(std::uintmax_t) << "\n";
}

実行結果:

int8_t: 1
int16_t: 2
int32_t: 4
int64_t: 8
uint8_t: 1
uint16_t: 2
uint32_t: 4
uint64_t: 8
int_least8_t: 1
int_least16_t: 2
int_least32_t: 4
int_least64_t: 8
uint_least8_t: 1
uint_least16_t: 2
uint_least32_t: 4
uint_least64_t: 8
int_fast8_t: 1
int_fast16_t: 4
int_fast32_t: 4
int_fast64_t: 8
uint_fast8_t: 1
uint_fast16_t: 4
uint_fast32_t: 4
uint_fast64_t: 8
intmax_t: 8
uintmax_t: 8

この実行結果は Visual Studio 2015 で試したものです。使っているコンパイラによって異なる可能性があります。

この実行結果の中でいえば、int_fast16_t、uint_fast16_t の大きさが注目で、それぞれ 4 になりました。これらの型は、16ビット以上の大きさを持ち、もっとも高速に演算が行える整数型を表しています(本編解説)。この処理系では、16ビットの整数型よりも、32ビットの整数型のほうが高速であることがうかがえます。

問題3 (確認★) 🔗

次の変数 n1~n8 の型はそれぞれ何になりますか?

signed char sc {-10};
unsigned short us {10};

auto n1 = 10L;
auto n2 = 10LL;
auto n3 = 10LU;
auto n4 = 10 + n1;
auto n5 = n2 + n3;
auto n6 = sc * 2;
auto n7 = sc * us;
auto n8 = sc + 100LL / 2U;


n1 の初期化子は 10L です。整数リテラルに Lサフィックスを付加した場合、long int になります(表現できない値なら long long int や拡張整数型になるかもしれないが、10 は表現できる)(本編解説)。したがって、n1 の型は long int です。

n2 では 10LL を指定しています。整数リテラルに LLサフィックスを付加した場合、long long int になります(表現できない値なら拡張整数型になるかもしれないが、10 は表現できる)(本編解説)。したがって、n2 の型は long long int です。

n3 では 10LU を指定しています。LU は、Lサフィックスと Uサフィックスを両方指定したものです。Uサフィックスは符号無し整数型にすることを表しているので、n3 の型は unsigned long int です。

n4 では、10 + n1 を指定しています。10 は int、n1 は long int でした。したがってこれは、int と long int で計算を行っています。オペランドが2つある演算子で互いの型が異なる場合のルールが適用されます(本編解説)。「両方とも signed、あるいは両方とも unsigned である場合、ランクが低い側が、ランクが高い側の型に変換される」の部分が適用されるので、long int に合わせられます。したがって、4 は long int です。

n5 では、n2 + n3 を指定しています。n2 は long long int、n3 は unsigned long int です。さきほどと同様に一方の型に合わせる変換が起こります。long long int が 64ビット、unsigned long int が 32ビットの処理系であれば、「signed の側の型が、unsigned の側の型で表現できるすべての値を表現できるのなら、unsigned の側が signed の型に変換される」に該当するので、n5 は long long int になります。long long int と unsigned long int がともに 64ビットの処理系であれば、「両方とも、signed の側の型に対応する unsigned な型に変換される」に該当するので、n5 は unsigned long long int になります。

n6 では、sc * 2 を指定しています。sc は signed char、2 は int です。signed char は int よりもランクが低いため、まず整数拡張が行われ(本編解説)、int に変換されます。そのため、sc * 2 は int 同士での演算になり、その結果もまた int です。したがって、n6 は int です。

n7 では、sc * us を指定しています。sc は signed char、us は unsigned short です。さきほどと同様、それぞれに整数拡張が行われ、sc は int になります。unsigned short からの整数拡張は、unsigned short で表現できる値のすべてが int でも表現できるなら int、表現できない値があるのなら unsigned int になります(本編解説)。したがって、unsigned short が 16ビット、int が 32ビットの処理系ならば int になります。いずれも 16ビットであるような処理系では unsigned int になるでしょう。したがって、sc * us は int と int あるいは、int と unsigned int の乗算になります。前者なら n7 は int、後者なら「unsigned の側のランクが signed の側のランク以上の場合、signed の側が unsigned の側の型に変換される」が適用されるので、n7 は unsigned int になります。

n8 では、sc + 100LL / 2U を指定しています。sc は signed char、100LL は long long int、2U は unsigned int です。まず 100LL / 2U が計算されます。long long int が 64ビット、unsigned int が 32ビットとすると、「signed の側の型が、unsigned の側の型で表現できるすべての値を表現できるのなら、unsigned の側が signed の型に変換される」が適用されて、long long int に合わせられます。sc は整数拡張されて int になり、int と long long int の加算を行うことになり、「両方とも signed、あるいは両方とも unsigned である場合、ランクが低い側が、ランクが高い側の型に変換される」が適用されるので、n8 は long long int です。もし、100LL / 2U のとき、long long int と unsigned int が 64ビットであったとすれば、「両方とも、signed の側の型に対応する unsigned な型に変換される」が適用され、両方とも unsigned long long int に変換されます。そうなると、sc との加算は int と unsigned long long int の演算になるので、「unsigned の側のランクが signed の側のランク以上の場合、signed の側が unsigned の側の型に変換される」が適用され、n8 は unsigned long long int になります。

問題4 (基本★★) 🔗

トランプのマークをあらわす列挙型は、4通りの値が表現できれば十分なので、基底型を short や signed/unsigned char にできます。int の場合と比べて、メモリの使用量にどれだけ違いが出るでしょうか?


scoped enum では、デフォルトの基底型は int です。処理系によりますが、現代では int の大きさは 32ビットであることが多いです。short の大きさもまた処理系によりますが、多くの場合 16ビットです。signed char、unsigned char は 8ビットであると思われます。そのような処理系では、メモリ使用量は short にすれば半分に、signed char や unsigned char にすれば 4分の1 にまで減ると考えられます。

実用を考えると、この列挙型の変数が 52個ある可能性が高いでしょう(トランプ1セットの枚数。ジョーカーは除いた)。全体でのメモリ使用量は以下のようになります。

この削減効果に価値があるのかどうかは、プログラムの内容や、実行する環境で使えるメモリの容量などに左右されます。現代のほとんどの環境では、この程度のメモリ削減には意味がないかもしれません。


なお unscoped enum の場合、デフォルトの基底型は処理系定義で、定義したすべての列挙子の値を表現できる型が選ばれます。そのため、あえて short や signed char/unsigned char を指定しなくても、自動的に signed char/unsigned char 辺りが選択されている可能性はあります(Visual Studio 2015 では int でした)。

以下は、確認のためのプログラムです。

#include <iostream>

enum class CardMark1 {
    spade,
    club,
    diamond,
    heart,
};

enum class CardMark2 : short {
    spade,
    club,
    diamond,
    heart,
};

enum class CardMark3 : signed char {
    spade,
    club,
    diamond,
    heart,
};

enum CardMark4 {
    spade,
    club,
    diamond,
    heart,
};

int main()
{
    std::cout << "sizeof(CardMark1) = " << sizeof(CardMark1) << "\n"
              << "sizeof(CardMark2) = " << sizeof(CardMark2) << "\n"
              << "sizeof(CardMark3) = " << sizeof(CardMark3) << "\n"
              << "sizeof(CardMark4) = " << sizeof(CardMark4) << "\n";
}

実行結果:

sizeof(CardMark1) = 4
sizeof(CardMark2) = 2
sizeof(CardMark3) = 1
sizeof(CardMark4) = 4


参考リンク 🔗



更新履歴 🔗




はてなブックマーク に保存 Pocket に保存 Facebook でシェア
X で ポストフォロー LINE で送る noteで書く
rss1.0 取得ボタン RSS 管理者情報 プライバシーポリシー
先頭へ戻る