C言語の基本|C言語の変数とデータ型

値を入れる箱と、その箱の種類を知れば、C言語のデータが見えてくる。

これまでの学習では、printfを使って文字や数値を表示し、scanfを使ってキーボードから整数を入力してきました。

たとえば、scanfで入力した年齢や学習日数は、入力された直後に消えてしまうわけではありません。プログラムの中に用意された場所へ保存され、その後のprintfや計算で利用されます。

この値を保存する場所が変数です。

そして、変数には、整数を保存するのか、小数を保存するのか、文字を保存するのかといった種類があります。この種類をデータ型と呼びます。

変数とデータ型は、C言語でデータを扱うための大切な土台です。ここでは、次の内容を順番に確認していきます。

  • 変数がどのような役割を持っているのか
  • データ型によって何が変わるのか
  • 変数の宣言と初期化をどのように書くのか
  • 定数がどのような値なのか
  • boolで真と偽を表す方法
  • 処理系依存とはどのような考え方なのか
  • 浮動小数点数で誤差が生じる理由
  • float、double、long doubleの違い

最初は、新しい用語が多いように感じるかもしれません。しかし、変数を値を入れる箱、データ型を箱の種類として考えると、内容を整理しやすくなります。

これから計算、条件分岐、繰り返しなどを学ぶときにも、変数とデータ型は繰り返し登場します。具体的なプログラムを動かしながら、値をどこへ保存し、どのように使うのかをつかんでいきましょう。

データはプログラムが扱う情報

プログラムの中で扱われる数値や文字などの情報を、データと呼びます。

たとえば、学習記録を管理するプログラムでは、次のようなデータを扱います。

データの内容値の例データの種類
学習した日数15整数
解いた問題数42整数
学習した時間21.5小数
評価を表す記号A文字
目標を達成したかtrue真偽値

人が見れば、それぞれの値が整数、小数、文字、真偽値のどれに当たるのかを自然に判断できます。

C言語では、プログラムを作る人が、どの種類のデータを扱うのかをデータ型によって明確に指定します。

変数は値を保存するための入れ物

変数とは、プログラムで使用する値を一時的に保存する場所です。

たとえば、年齢を保存する変数は次のように作れます。

int age;

この1行は、ageという名前の変数を用意することを表しています。

intは整数を扱うデータ型、ageは変数名です。

変数を作る基本的な書き方は、次のとおりです。

データ型 変数名;

代表的な例を見てみましょう。

int age;
double height;
char grade;

それぞれの変数には、次のような役割があります。

データ型変数名保存するデータの例
intage年齢
doubleheight身長
chargrade評価を表す1文字

このように、変数を用意することを変数の宣言と呼びます。

変数には分かりやすい名前を付ける

変数名は、基本的にはプログラムを作る人が決められます。

ただし、どのような値が入るのかを想像できる名前にすると、プログラムが読みやすくなります。

たとえば、次の変数名を比べてみましょう。

int x;
int study_days;

どちらもC言語として使用できますが、study_daysのほうが学習日数を保存する変数であることを理解しやすくなります。

変数名を付けるときは、次の点を意識するとよいでしょう。

  • 保存するデータの意味が伝わる名前にする
  • 英単語や複数の英単語を組み合わせる
  • 複数の単語はアンダースコアで区切る
  • 同じような役割の変数には統一した付け方をする
  • 意味が分かりにくい1文字の名前を必要以上に使わない

たとえば、学習記録を扱う変数は次のように表せます。

int study_days;
int solved_count;
double study_hours;

study_daysは学習日数、solved_countは解いた問題数、study_hoursは学習時間を表しています。

変数へ値を代入する

宣言した変数へ値を入れることを代入と呼びます。

int age;
age = 24;

1行目でint型の変数ageを宣言し、2行目で24を代入しています。

代入で使う=は、右側の値を左側の変数へ入れるという意味です。

age = 24;

この場合、右側の24が、左側のageへ保存されます。

数学で使う等号と形は同じですが、C言語では代入を表す記号として使われます。

変数の値は、後から変更することもできます。

int score;
score = 70;
score = 85;

最初に70を代入した後、85を代入しています。後から代入した85によって、それまで保存されていた70は置き換えられます。

この処理が終わった時点で、scoreに保存されている値は85です。

変数の宣言と同時に値を入れる

変数を宣言すると同時に、最初の値を入れることもできます。

int age = 24;

このように、変数へ最初の値を設定することを初期化と呼びます。

宣言してから代入する方法と、宣言と同時に初期化する方法を比べると、次のようになります。

方法書き方内容
宣言後に代入するint age;の後にage = 24;変数を作ってから値を入れる
宣言時に初期化するint age = 24;変数を作ると同時に値を入れる

最初から保存する値が決まっている場合は、宣言と同時に初期化すると分かりやすくなります。

int study_days = 15;
double study_hours = 21.5;
char grade = 'A';

初期化していない局所変数には、不定の値が入っている場合があります。その値を表示や計算に使うと、期待していない結果になるおそれがあります。

最初の値が決まっている変数は、できるだけ宣言時に初期化する習慣を付けておくと安心です。

図1:変数とデータ型を箱でイメージしよう

この図から分かること

変数は値を保存する箱のような存在です。変数名は箱の名前、データ型は箱へ入れられる値の種類を表しています。

int型の変数には整数、double型の変数には小数、char型の変数には1文字、bool型の変数には真偽値を保存します。

どのようなデータを扱うのかに合わせてデータ型を選ぶことが、C言語で値を正しく扱うための基本です。

データ型は変数へ保存する値の種類

データ型は、変数へどのような種類の値を保存するのかを決めます。

初心者のうちによく使用するデータ型は、次のとおりです。

データ型主に扱うデータ値の例
int整数10、25、-3
float小数3.5、0.25
double小数18.75、0.1
char1文字A、x、7
bool真偽値true、false

たとえば、年齢や個数のように小数部分を必要としないデータにはintが向いています。

int age = 24;
int item_count = 8;

時間や距離など、小数部分を含むデータにはdoubleやfloatを使用します。

double study_hours = 21.5;
float temperature = 23.5f;

評価を表すAなどの1文字にはcharを使用します。

char grade = 'A';

目標を達成したか、処理に成功したかといった二者択一の状態にはboolを使用できます。

bool completed = true;

データ型によって書き方が変わる

C言語では、データの種類によって値の書き方が異なります。

int count = 10;
double distance = 12.5;
char rank = 'A';
bool finished = true;

それぞれの値には、次のような違いがあります。

データ書き方説明
整数10小数点を付けずに書く
小数12.5小数点を含めて書く
文字'A'1文字を単一引用符で囲む
真偽値truetrueまたはfalseを書く

char型へ代入する文字は、C言語の文法上、単一引用符で囲む必要があります。これは命令や引数を強調するための引用ではなく、1文字を表す文字定数の記法です。

一方、printfで表示する文章は文字列なので、二重引用符で囲みます。

printf("データを表示します。\n");

文字と文字列は書き方が異なるため、区別しておきましょう。

printfではデータ型に合った変換指定を使う

変数の値をprintfで表示するときは、データ型に対応する変換指定を使用します。

データ型printfで主に使う変換指定表示例
int%d15
double%f21.500000
char%cA
bool%d0または1

doubleの小数点以下を1桁だけ表示したい場合は、%.1fを使用できます。

double study_hours = 21.5;

printf("学習時間は%.1f時間です。\n", study_hours);

実行結果は次のようになります。

学習時間は21.5時間です。

データ型と変換指定が一致していないと、正しい値が表示されない可能性があります。変数を表示するときは、その変数のデータ型を確認することが大切です。

定数は変更しない値

変数は、プログラムの途中で値を変更できます。

int score = 70;
score = 85;

一方、プログラムの途中で変更しない値を定数として扱うことがあります。

C言語では、constを付けて、変更しない変数を宣言できます。

const int target_score = 80;

この宣言では、target_scoreを初期値80で作成し、その後は変更しないものとして扱います。

次のように別の値を代入することはできません。

target_score = 90;

constで宣言した値を変更しようとすると、コンパイル時にエラーとして検出されます。

変数とconstを付けた値の違いは、次のとおりです。

種類値の変更使用例
通常の変数後から変更できる現在の点数、残り回数
constを付けた値初期化後に変更できない合格点、上限値、固定の倍率

変更してはいけない値へconstを付けると、誤って値を書き換えることを防ぎやすくなります。

複数のデータ型を使ったプログラム

次のプログラムでは、整数、小数、文字、真偽値、変更しない値を使って、運動記録を表示します。

複数のデータ型で運動記録を表示するプログラム

ファイル名:4_1_1.c

// 複数のデータ型で運動記録を表示するプログラム
#include <stdio.h>
#include <stdbool.h>

int main(void)
{
    int exercise_days = 14;          // 運動を続けた日数
    int completed_sets = 36;         // 完了したセット数
    double exercise_hours = 12.5;    // 合計運動時間
    char evaluation = 'A';           // 今回の評価
    bool goal_reached = false;       // 目標を達成したか
    const int target_sets = 50;      // 目標のセット数

    printf("運動を続けた日数:%d日\n", exercise_days);
    printf("完了したセット数:%dセット\n", completed_sets);
    printf("合計運動時間:%.1f時間\n", exercise_hours);
    printf("今回の評価:%c\n", evaluation);
    printf("目標セット数:%dセット\n", target_sets);
    printf("目標達成状態:%d\n", goal_reached);

    return 0;
}
実行結果の例
運動を続けた日数:14日
完了したセット数:36セット
合計運動時間:12.5時間
今回の評価:A
目標セット数:50セット
目標達成状態:0

このプログラムでは、データの内容に合わせて複数のデータ型を使い分けています。

int型の変数を確認しよう

次の2つの変数はint型です。

int exercise_days = 14;
int completed_sets = 36;

exercise_daysには運動を続けた日数、completed_setsには完了したセット数を保存しています。

どちらも小数部分を必要としないため、整数を扱うintが適しています。

printfでは、int型の値を%dで表示します。

printf("運動を続けた日数:%d日\n", exercise_days);
printf("完了したセット数:%dセット\n", completed_sets);

double型の変数を確認しよう

合計運動時間には、小数部分を含む12.5を使用しています。

double exercise_hours = 12.5;

小数を含む値なので、double型の変数へ保存しています。

表示には%.1fを使用しています。

printf("合計運動時間:%.1f時間\n", exercise_hours);

.1は、小数点以下を1桁表示する指定です。そのため、12.5と表示されます。

char型の変数を確認しよう

評価を表す1文字は、char型へ保存しています。

char evaluation = 'A';

char型の値を表示するときは、%cを使用します。

printf("今回の評価:%c\n", evaluation);

文字定数Aは、C言語の文法に従って単一引用符で囲みます。

bool型の変数を確認しよう

目標を達成したかどうかは、bool型で表しています。

bool goal_reached = false;

falseは偽を表します。今回は目標をまだ達成していない状態です。

bool型の値を%dで表示すると、falseは0として表示されます。

printf("目標達成状態:%d\n", goal_reached);

constを付けた値を確認しよう

目標のセット数は、プログラムの途中で変更しない値として宣言しています。

const int target_sets = 50;

target_setsのデータ型はintですが、constが付いているため、初期化後に別の値を代入できません。

目標値や上限値など、誤って変更したくない値にはconstを付けると、値の役割が伝わりやすくなります。

論理値型boolを理解しよう

論理値とは、真と偽のどちらかを表す値です。

C17では、stdbool.hを読み込むとbool、true、falseを使用できます。

#include <stdbool.h>

bool型の基本的な宣言は、次のとおりです。

bool completed = true;
bool has_error = false;

trueは真、falseは偽を表します。

論理値は、次のような状態を表すのに向いています。

  • 学習目標を達成したか
  • 入力された値が正しいか
  • エラーが発生したか
  • 処理を続けるか
  • ファイルを開けたか
  • 条件を満たしているか

たとえば、目標を達成した状態は次のように表せます。

bool goal_reached = true;

目標を達成していない状態は、次のように表せます。

bool goal_reached = false;

boolを使うためにstdbool.hを読み込む

bool、true、falseを使用するプログラムでは、次の記述が必要です。

#include <stdbool.h>

C17における標準の論理型は_Boolです。stdbool.hでは、初心者にも読みやすいboolという名前で_Boolを使えるようにしています。

同じように、trueとfalseも利用できるようになります。

表記意味
_BoolC言語が持つ論理型
bool_Boolを読みやすく使うための名前
true真を表す値
false偽を表す値

C17で学習する段階では、stdbool.hを読み込み、boolを使用すると分かりやすくなります。

0と0以外の値の考え方

C言語では、条件を判断するときに、0を偽、0以外を真として扱います。

条件としての扱い
0
1
-1
25

bool型へ値を保存すると、0は0として保存され、0以外の値は1として保存されます。

bool first = 0;
bool second = 8;

firstはfalseに相当し、secondはtrueに相当します。

boolを使うと、0や1を直接書くよりも、値の意味を読み取りやすくなります。

bool completed = true;

この書き方なら、completedが完了状態を表していることが分かりやすくなります。

bool型で達成状態を表示するプログラム

次のプログラムでは、読書目標を達成した状態をbool型で表します。

bool型で読書目標の達成状態を表示するプログラム

ファイル名:4_1_2.c

// bool型で読書目標の達成状態を表示するプログラム
#include <stdio.h>
#include <stdbool.h>

int main(void)
{
    bool is_reading_goal_reached = true;   // 読書目標を達成したか

    printf("読書目標の達成状態:%d\n", is_reading_goal_reached);

    return 0;
}
実行結果の例
読書目標の達成状態:1

is_reading_goal_reachedにはtrueを代入しています。

bool is_reading_goal_reached = true;

trueは真を表します。printfでは%dを使っているため、実行結果には1と表示されます。

値をfalseに変更してみましょう。

bool is_reading_goal_reached = false;

この場合の表示は次のようになります。

読書目標の達成状態:0

bool型を使うことで、達成した状態と達成していない状態を明確に表せます。

処理系依存とは環境によって違いが生じること

C言語には標準規格があります。標準規格によって、基本的な文法やライブラリの使い方が定められています。

ただし、C言語のすべての動作が、あらゆる環境で完全に同じになるわけではありません。

コンパイラ、OS、CPUなどの環境によって、データ型の大きさや一部の動作が異なることがあります。このような環境による違いを、広い意味で処理系依存と呼びます。

ここでいう処理系には、主に次の要素が関係します。

  • GCCやVisual C++などのコンパイラ
  • WindowsやLinuxなどのOS
  • 使用しているCPU
  • 文字コード
  • 標準ライブラリの実装
  • コンパイル時の設定

たとえば、int型が使用するメモリの大きさは、すべての環境で必ず同じとは限りません。

C言語の規格では、それぞれの整数型が最低限表せる範囲や型同士の大小関係は決められていますが、必ず何バイトにするかまでは、すべて固定されていません。

処理系によって違いが出る代表例

項目違いが出る可能性がある内容
intなどの大きさ使用するバイト数や表現できる範囲
charの符号符号付きとして扱うか、符号なしとして扱うか
long doubleの精度doubleより高精度か、同程度か
文字コードUTF-8、Shift_JISなど
ライブラリの実装関数の内部的な処理や結果の性質
実行ファイルOSやCPUに対応した形式

初心者の段階で、環境ごとの違いをすべて暗記する必要はありません。

まずは、同じC言語のソースコードでも、使用する環境によって細かな違いが生じることがあると理解しておけば大丈夫です。

図2:C言語における処理系依存のイメージ

この図から分かること

C言語には共通の標準規格がありますが、データ型の大きさ、文字コード、一部の処理などは、使用する環境によって違いが生じることがあります。

異なる環境でも動かすプログラムを作るときは、特定のコンパイラやOSだけを前提にしすぎないことが大切です。

学習中は、まず自分が使用しているWindows 11、GCC、C17という環境を意識しながら進めましょう。

処理系による違いへ対応する考え方

処理系依存による問題を減らすには、標準規格に沿った機能を使い、必要に応じて環境の情報を確認します。

標準ライブラリを活用する

C言語には、さまざまな処理を行う標準ライブラリが用意されています。

文字の種類を調べるisalnumなどを利用すれば、独自の判断処理をすべて自分で作る必要がありません。

#include <ctype.h>

標準ライブラリを適切に利用することで、読みやすさや移植しやすさを高められます。

大きさが明確な整数型を検討する

整数のビット数を明確にしたい場合は、stdint.hで定義される整数型を利用できます。

代表例は次のとおりです。

  • int32_tは符号付き32ビット整数を表す
  • uint32_tは符号なし32ビット整数を表す
  • uint8_tは符号なし8ビット整数を表す

これらの型を使用する場合は、stdint.hを読み込みます。

#include <stdint.h>

ただし、int32_tなどの正確な幅を持つ型は、その幅の整数型を処理系が提供できる場合に定義されます。詳しい使い方は、整数型を深く学ぶ段階で確認すれば大丈夫です。

sizeofで型の大きさを確認する

現在の環境でデータ型が何バイト使用するかは、sizeofで確認できます。

printf("int型の大きさ:%zuバイト\n", sizeof(int));

sizeof(int)は、現在の処理系におけるint型の大きさをバイト単位で表します。

環境によって変わる可能性がある情報は、決めつけずに調べる姿勢が大切です。

コンパイラの資料を確認する

処理系固有の動作を確認したいときは、使用しているコンパイラやライブラリの資料を確認します。

実務では、次のような情報を確認する場面があります。

  • 各データ型の大きさ
  • コンパイラが対応するC言語規格
  • 使用できるコンパイルオプション
  • 文字コードの扱い
  • 警告の内容
  • 標準ライブラリ関数の仕様

すべてを暗記するのではなく、必要になったときに正しい資料を調べることも、プログラミングでは大切な力です。

浮動小数点数には誤差が生じることがある

float、double、long doubleは、小数を含む値を扱う浮動小数点型です。

float temperature = 23.5f;
double distance = 12.75;
long double precise_value = 0.125L;

小数は整数と同じように正確に保存されるように見えますが、実際には誤差を含むことがあります。

特に、0.1や0.2のような10進数の小数は、コンピューターが内部で使用する2進数では有限の桁数で正確に表せない場合があります。

そのため、実際には目的の値に非常に近い値へ丸めて保存されます。

0.1や0.2を正確に表せない理由

10進数では、0.1や0.2を短く表せます。

しかし、2進数に変換すると、これらは終わりなく続く小数になります。コンピューターのメモリには保存できるビット数の上限があるため、途中で丸めなければなりません。

たとえば、次の計算を考えてみましょう。

double result = 0.1 + 0.2;

数学では結果は0.3です。

ところが、内部では0.1と0.2がそれぞれ近似値として保存されるため、多くの環境で桁数を増やして表示すると次のような値が見えることがあります。

0.30000000000000004

通常の表示桁数では0.300000と表示されるため、誤差に気づかないこともあります。

浮動小数点数を扱うときは、表示された値が見た目どおりの正確な値とは限らないことを覚えておきましょう。

浮動小数点数で見られる主な誤差

種類起こりやすい処理内容
丸め誤差正確に表せない小数の保存表現できる近い値へ丸められる
桁落ち非常に近い値同士の減算有効な桁が失われる
情報落ち非常に大きい値と小さい値の加算小さい値の影響が結果に現れなくなる

丸め誤差

丸め誤差は、限られたビット数では正確に表せない値を、最も近い値として保存することで生じます。

0.1や0.2をdouble型で扱ったときに見られる誤差は、丸め誤差の代表例です。

桁落ち

桁落ちは、値が非常に近い2つの数を引き算したときに、有効な桁が失われる現象です。

たとえば、非常に近い測定値の差を求める処理では、計算結果の精度が大きく下がることがあります。

情報落ち

情報落ちは、非常に大きな値と非常に小さな値を加算したときに、小さい値の影響が結果へ反映されなくなる現象です。

保存できる桁数には限界があるため、大きな値を表すために多くの桁を使うと、小さな値を保持できなくなることがあります。

これらの詳しい対処方法を、今すぐすべて覚える必要はありません。まずは、小数の計算結果には誤差が含まれる可能性があるという前提を持つことが大切です。

float、double、long doubleの違い

C言語には、主に3種類の浮動小数点型があります。

データ型大まかな特徴値の書き方の例
float精度と表現範囲が比較的小さい3.5f
double一般的な小数計算で使いやすい3.5
long doubledouble以上の精度を持つ3.5L

通常の小数定数はdouble型として扱われます。

double value = 3.5;

float型の小数定数には、末尾にfを付けられます。

float value = 3.5f;

long double型の小数定数には、末尾にLを付けます。

long double value = 3.5L;

一般的には、floatよりdouble、doubleよりlong doubleのほうが、同等以上の精度を持ちます。

ただし、long doubleが実際にどの程度の精度を持つかは処理系によって異なります。環境によっては、doubleとlong doubleの精度が同じ場合もあります。

また、精度の高い型を使っても、浮動小数点数の誤差が完全になくなるわけではありません。

浮動小数点数の誤差を確認するプログラム

次のプログラムでは、double型の0.1と0.2を加算し、結果を小数点以下17桁まで表示します。

0.1と0.2の計算結果を詳しく表示するプログラム

ファイル名:4_1_3.c

// 浮動小数点数の計算結果を詳しく表示するプログラム
#include <stdio.h>

int main(void)
{
    double first_value = 0.1;                 // 1つ目の小数
    double second_value = 0.2;                // 2つ目の小数
    double total = first_value + second_value;

    printf("通常の表示:%f\n", total);
    printf("小数点以下17桁の表示:%.17f\n", total);

    return 0;
}
実行結果の例

環境によって細かな表示は異なる可能性がありますが、多くの環境では次のような結果になります。

通常の表示:0.300000
小数点以下17桁の表示:0.30000000000000004

小数点以下の表示桁数で見え方が変わる

1つ目のprintfでは、%fを使っています。

printf("通常の表示:%f\n", total);

%fは、標準では小数点以下を6桁表示します。そのため、結果は次のように見えます。

0.300000

この表示だけを見ると、計算結果は正確に0.3であるように感じられます。

2つ目のprintfでは、%.17fを使っています。

printf("小数点以下17桁の表示:%.17f\n", total);

小数点以下を17桁まで表示すると、内部に保存されている値が0.3とわずかに異なることを確認できます。

0.30000000000000004

これは、プログラムが計算に失敗したわけではありません。0.1と0.2を2進数で正確に表せないため、それぞれを近い値として保存した結果です。

図3:浮動小数点数に誤差が生じる仕組み

この図から分かること

10進数では簡単に表せる0.1や0.2でも、2進数では有限の桁数で正確に表せない場合があります。

コンピューターは保存できるビット数に限りがあるため、表現できる近い値へ丸めて保存します。その近似値同士を計算すると、数学上の0.3とはわずかに異なる結果になることがあります。

floatからdoubleへ変更して精度を高めても、すべての小数を完全に正確に表せるわけではありません。小数は誤差を含む可能性があることを意識して扱う必要があります。

小数を単純に等しいか比較するときは注意する

浮動小数点数には誤差が含まれることがあるため、計算結果が特定の小数と完全に一致することを前提にすると、期待と異なる判断になる可能性があります。

たとえば、次の計算結果は、内部では0.3と完全には一致しない場合があります。

double total = 0.1 + 0.2;

そのため、小数を扱うプログラムでは、誤差があることを考慮して設計する必要があります。

詳しい比較方法は条件分岐や数学関数を学ぶ段階で扱います。今は、浮動小数点数の計算では見た目と内部の値がわずかに異なる場合があると理解しておきましょう。

変数とデータ型のつながりを整理しよう

これまでprintfやscanfで使ってきた値は、変数とデータ型によって管理されています。

scanfで整数を受け取る処理を見てみましょう。

int age;

scanf("%d", &age);

intによって、ageが整数を保存する変数であることを宣言しています。

scanfは、キーボードから読み取った整数をageへ保存します。その後、printfでageの値を表示できます。

printf("年齢は%d歳です。\n", age);

この流れを整理すると、次のようになります。

  1. データ型を決める
  2. 変数を宣言する
  3. 初期値や入力された値を変数へ保存する
  4. 保存された値を計算や表示に使う

整数、小数、文字、真偽値には、それぞれ適したデータ型があります。

扱いたいデータ主なデータ型主な使用例
整数int年齢、個数、日数
小数double、float時間、距離、温度
1文字char評価、選択記号
真偽値bool成功、失敗、達成状態
変更しない値constを付けた型目標値、上限値

最初からすべての型の細かな違いを覚える必要はありません。

まずは、変数は値を保存する場所であり、データ型はその変数へ保存できる値の種類を決めるものだと理解しておきましょう。

そのうえで、boolを使うにはstdbool.hが必要なこと、環境によって型の大きさなどが異なる場合があること、小数には誤差が含まれる可能性があることを押さえておけば、この先の変数、定数、演算、条件分岐の学習へスムーズにつなげられます。