データ型

基本的なデータ型

データ型とは、変数や関数の戻り値などで使用する、プログラムで実際に扱う「値」の形式のことです。
いままで登場したint型は整数を表す型、double型は小数を表す型です。

C言語には以下のようなデータ型が存在します。

char
1バイト符号付整数。
-128~127の値を記憶できる。
主に英数字を一字記憶させる用途に使用する。
short
2バイト符号付整数。
-32,768~32,767の値を記憶できる。
int
int型は環境により記憶容量が変化する。
32ビット環境では4バイト整数、64ビット環境では8ビット整数となる。
(Windowsの場合)
long
4バイト符号付整数。
-2,147,483,648~2,147,483,647の値を記憶できる。
long long
8バイト符号付整数。
–9,223,372,036,854,775,808~9,223,372,036,854,775,807の値を記憶できる。
float
4バイト単精度浮動小数点実数。
double
8バイト倍精度浮動小数点実数。 floatよりも高い精度の小数を表現できる。

バイトとは、コンピューターの情報量(記憶容量)の単位のひとつです。
1バイトで英数文字を一文字分記憶できます。
(1バイト=8ビット=2の8乗=256通りの情報を記憶できる)

整数のデータ型は、記憶できる数値の幅が違うだけで使い方は同じです。
大きい桁数の数値を使いたい場合には、より大きなバイト数を持つデータ型を使用します。

小数を含む値を使用する場合は、float型かdouble型のどちらかを使用します。
これらのデータ型が実際に記憶できる数値の幅はちょっと複雑なため、省略します。

符号なし整数

上記に加えて、整数のデータ型には以下の型が存在します。

signed
符号付き整数。
+-記号を付けた整数。
通常の整数型はすべて正負の数を表現でき、signedは省略できる。
unsigned
符号なし整数。
+-の記号を付けない整数。
正の数(0以上の数)しか表現できない。
その代わり、正方向への表現できる数が倍になる。
(unsigned charならば0~255の範囲を表現できる)

これらは各データ型の名称の前に記述します。

#include <stdio.h>

int main()
{
    //このふたつは同じ意味
    int _int = 1;
    signed int _sint = 1;
    
    //変数_uintはマイナス値を持てない
    unsigned int _uint = 1;
}

9行目の変数_uintは、マイナスの値を保存することができません。
そのかわり、プラス値は通常のint型の変数の倍の大きさの値を保存できます。

小数型には符号なし型はありません。

リテラル

コード中に直接記述される、数値や文字列などの値をリテラルといいます。

int num = 10;
printf("あいうえお");

このコード中の「10」や「"あいうえお"」がリテラルです。

文字列リテラル

上のコードの「"あいうえお"」のように、コード内に直接記述される文字列を文字列リテラルと呼びます。

文字列リテラルの改行

文字列リテラルの途中に円記号(バックスラッシュ)を書くと、コード中の文字列を途中で改行することができます。
実際に出力される文字列は改行されません。

printf("いろはにほへと ちりぬるを ¥
わかよたれそ つねならむ");
いろはにほへと ちりぬるを わかよたれそ つねならむ

文字列リテラルの連結

文字列リテラル同士を連続で書くと、文字列は結合しているものとみなされます。
途中の空白やタブ文字、改行は無視されます。

printf("ABC" "DEF");
//↑↓同じ
printf("ABC""DEF");
//↑↓同じ
printf("ABCDEF");

これを利用してコード中の文字列を改行することもできます。

printf("いろはにほへと ちりぬるを"
    " わかよたれそ つねならむ");

数値リテラル

コード中にそのまま記述される数値を数値リテラルといいます。

//「10」が数値リテラル
int num = 10;

サフィックス

数値リテラルは、整数値をそのまま書くとそれはint型の整数とみなされます。
小数値をそのまま書くとdouble型とみなされます。

これらの末尾に特殊な文字を加えることで、そのデータ型を変更することができます。
この特殊文字をサフィックスといいます。

int _int				= 10;
unsigned int _uint		= 10U;
long _long				= 10L;
unsigend long _ulong	= 10UL;
float _float 			= 10.0F;

サフィックスには以下があります。

U,u
unsigned
L,l
long
UL, ul
unsigned long
F, f
float

大文字、小文字はどちらでも構いませんが、Lの小文字は数字の1と紛らわしいため、大文字で統一したほうがいいでしょう。

プレフィックス(プリフィックス)

数値リテラルの先頭に特定の文字を追加することで8進数や16進数を表現することができます。
これをプレフィックス(プリフィックス)といいます。

#include <stdio.h>

int main()
{
    int oct = 010;
    int hex = 0x10;

    printf("%d\n", oct);
    printf("%d", hex);
    getchar();
}
8
16
0
8進数を表す
0x
16進数を表す

8進数は桁ひとつで「0~7」を表しますから、2桁目を「1」にすると「8」を表します。
16進数は桁ひとつで「0~15」を表しますから、2桁目を「1」にすると「16」を表します。

16進数は数字だけでは数が足りないので、10以降はA~Fのアルファベットを使用します。

#include <stdio.h>

int main()
{
	int hex9 = 0x9;
	int hex10 = 0xA;
	int hex11 = 0xB;
	int hex15 = 0xF;
	int hex16 = 0x10;

	printf("%d\n", hex9);
	printf("%d\n", hex10);
	printf("%d\n", hex11);
	printf("%d\n", hex15);
	printf("%d\n", hex16);
    getchar();
}
9
10
11
15
16

8進数、16進数をそのまま画面に表示する場合は、printf関数の変換指定子を以下のように書き換えます。

#include <stdio.h>

int main()
{
    int oct = 010;
    int hex = 0x10;

    //8進数表示
    printf("%o", oct);

    printf("\n");

    //16進数表示
    printf("%x", hex);

    getchar();
}

このコードはどちらも「10」と表示されます。

桁あふれ(オーバーフロー)

それぞれのデータ型には、保存できる値の幅に制限があります。
ではその幅を超えた値を変数に代入するとどうなるでしょうか。

#include <stdio.h>

int main()
{
    unsigned char _uchar = 255;
    _uchar = _uchar + 1;
    printf("%d", _uchar); //0
    getchar();
}

unsigned char型には0~255の値を保存できます。
そのため5行目の時点では問題はありません。

次の行で255に1を足すと256となり、unsigned charの限界を超えてしまいます。
これを桁あふれ(オーバーフロー)といいます。

このコードを実行すると、結果は「0」と表示されます。
255から一巡してまた0に戻ってきてしまったのです。

反対に、0から1を引くと255になります。

符号あり整数の場合

上記は符号なし整数(unsigned)で説明しましたが、符号あり整数の場合はこのような動作になる保証はありません。

#include <stdio.h>

int main()
{
    char _char = 127;
    _char = _char + 1;
    printf("%d", _char); //-128...?
    getchar();
}

char型の変数には-128~127の整数が保存できます。
すでに127が保存されている変数に1を足すとどうなるでしょうか。

実際にやってみると、おそらく「-128」と表示されると思います。
なんだ結局一巡するんじゃないか、と思われるかもしれませんが、この動作はただの偶然です。

符号付き整数でオーバーフローが発生した場合、どういった動作になるかはC言語では決められていません。
つまり一巡して最低値に戻るという保障はなく、無茶苦茶な値になったりプログラムがクラッシュしたり…という可能性もゼロではないのです。
(Visual Studioではたぶん一巡してくれますが、C言語の仕様上はオーバーフローは禁則)

符号付き整数でも符号なし整数でも、オーバーフローが発生すると意図しない値になることは変わらないので、オーバーフローはできるだけ発生させないようにプログラミングする必要があります。
(ただし、符号なし整数をあえてオーバーフローさせることは可能です)

だからと言って全てのデータ型をlong long型で用意するとメモリの無駄遣いとなります。
扱う値に対して適切なデータ型を使用しましょう。