論理値 | Programming Place Plus C言語編 第13章

トップページC言語編

このページの概要

以下は目次です。


論理値

第12章で、for文を使って、同じコードを何度も繰り返し実行させられるようになりました。まだ「一定回数だけ」繰り返すという書き方にとどめていますが、for文の条件式はもっと柔軟な指定ができます。そこで、このページではまず、条件式というものをもう少し掘り下げて理解していこうと思います。

条件式を柔軟に設定できるようになると、たとえば電卓プログラムを、「“exit” と入力されるまで繰り返す」ように改造できます。

ここまでの電卓プログラムは、「整数 演算子 整数」というかたちの入力しか受け付けないので、“exit” のように文字列を1つだけ入力されたときの判定にはまだワンステップ必要です。

条件式を理解するにあたって、まず論理値を理解しておきましょう。論理値 (logical value) とは、ある条件が正しいか正しくないかという2択を表現する値のことです。「正しい」ことを「(しん) (true) 」といい、正しくないことを「(ぎ)(false)」といいます。

論理型

論理値を表現する型として、C言語には _Bool型 (_Bool type) という型があります。いつものように、変数を宣言するときの型に使えます。

_Bool 変数名 = 初期値;

_Bool型は論理値を表現するための型なので、ここに入れる値は真か偽の2択です。真や偽を分かりやすく表現するキーワードのようなものがあれば良いのですが、C言語には存在しません(分かりやすくする方法はあるので後で取り上げます)。古くからC言語では、偽を 0 であらわし、それ以外のすべての値を真とみなすという方針を採っています。_Bool型の変数に値を入れる場面であれば、普通、真を 1 で表現します。

_Bool is_completed = 1;  // 真
_Bool is_error = 0;      // 偽

_Bool型の変数に 0以外の値を与えた場合、自動的に 1 に変換されます1

【C89/95 経験者】int型や、typedef で作った BOOL型のようなものを使った場合との大きな違いがここにあります。_Bool型の変数には 0 か 1 しか入っていないことが保証できます(代入や初期化以外の方法で書き換えていなければ)。

_Bool型の変数には、それが論理値であることが分かりやすいような名前をつける習慣をもつ人が多いです。よくあるのは、先頭を「is」「has」「can」といった単語で始める名前です。is_completed は完了しているかどうかを、is_error はエラーが起きているかどうかを真か偽の2択で表現する変数であることが分かります。

bool、true、false

型名は _Bool という奇妙で入力しづらいですし、真や偽を 1 と 0 と表現するのは、単なる整数にみえてしまって何のことだか分かりづらいです。

そこで、_Bool型をもう少し使いやすい名前に変え、真や偽にも分かりやすい名前を与える方法があります。そのためには、ソースファイルに #include <stdbool.h> という記述を追加します。この記述があると、論理型を bool に、真を true、偽を false と記述できるようになります。

#include <stdbool.h>
#include <stdio.h>

int main(void)
{
    bool is_completed = true;
    bool is_error = false;
}

新しい名前が使えるようになっているだけであって、意味や動作に変化はありません。ソースコードが書きやすく、かつ読みやすくなることは利点ですから、C言語編ではこの方法を使っていくことにします。

論理型の出力

_Bool型(以降、断りを入れませんが、bool型でも同じことです)の値は、結局は整数(0 か 1)なので、printf関数を使って出力するときには “%d”変換指定子を使って整数として出力できます。

#include <stdbool.h>
#include <stdio.h>

int main(void)
{
    _Bool b1 = 1;
    printf("%d\n", b1);
    b1 = 0;
    printf("%d\n", b1);

    bool b2 = true;
    printf("%d\n", b2);
    b2 = false;
    printf("%d\n", b2);
}

実行結果:

1
0
1
0

しかし、_Bool型の値の意味としては論理値なので、“true” や “false” といった文字列表現で出力したいかもしれません。printf関数には、これを行う方法は用意されていないので、自力で文字列に直すプログラムを書くしかありません。switch文を使って次のようにできます。

#include <stdbool.h>
#include <stdio.h>

int main(void)
{
    bool b = true;

    switch (b) {
    case true:
        puts("true");
        break;
    case false:
        puts("false");
        break;
    }
}

実行結果:

true

ただ、この方法はさすがに面倒なので、もう少し簡潔な書き方を選ぶほうがいいでしょう。次の章で紹介する条件演算子を使うのが妥当です。

【上級】この方法を採ると、printf("%s\n", b ? "true" : "false"); のように書けます。これでも面倒なら、関数にしておくのもいいでしょう。

【C++プログラマー】std::boolalpha のような仕組みはありません。

論理型の入力

_Bool型の入力を受け取る場合も、整数として入力する必要があります。

#include <stdbool.h>
#include <stdio.h>

int main(void)
{
    puts("Please enter the integer.");
    char s[40];
    fgets(s, sizeof(s), stdin);
    int value;
    sscanf(s, "%d", &value);

    bool b = value;
    printf("%d\n", b);
}

実行結果:

Please enter the integer.
1  <-- 入力した内容
1

実行結果:

Please enter the integer.
0  <-- 入力した内容
0

sscanf関数にも、_Bool型の値を扱うための変換指定子はないので、まず “%d”変換指定子を使って int型として受け取り、そのあとで _Bool型の変数に入れ直すという方法を採ります。

しかし正直これでは、論理型の入力を扱っているとは言いがたいでしょう。_Bool型の値を、文字列の表現で入力して受け取りたいと思う(つまり、“true” や “false” と入力する)なら、そのようなプログラムを書く必要があります。これは次の章で説明する if文が必要です。

整数と論理型の値の変換

_Bool b = 1; のようなコードが書けているように、int型の値を _Bool型に変換できます。この結果は、元が 0 なら 0 のまま、元が 0 以外なら 1 です。

反対に、_Bool型の値を int型に変換することもできます。この場合、値に変化はありません。そもそも、_Bool型の値には 01 しかないので、int型の 01 になるだけです。

#include <stdbool.h>
#include <stdio.h>

int main(void)
{
    _Bool b1 = 1;
    int x = b1;      // 1
    printf("%d\n", x);

    b1 = 0;
    x = b1;          // 0
    printf("%d\n", x);

    bool b2 = true;  // 1
    x = b2;          // 1
    printf("%d\n", x);

    b2 = false;      // 0
    x = b2;          // 0
    printf("%d\n", x);
}

実行結果:

1
0
1
0

等価演算子

ここまで、ソースコードに 0(false) や 1(false) を書くか、入力によって受け取る例を挙げてきましたが、なんらかの条件を判定してみて、それが「正しい」か「正しくない」かによって論理値を取得することもできます。たとえば、「変数 value1 と変数 value2 の値が同じかどうか」を判定して、論理値を取得するといった具合です。

そのためには、等価演算子 (equality operator) という演算子を使用します。

式1 == 式2    // 左と右の値が同じ?
式1 != 式2    // 左と右の値が異なる?

== は一般的なイメージでの「イコール」にあたるものですが、C言語では「=」を2つ並べる必要があります。誤って = にすると、代入を意味することになり、間違ったプログラムになってしまうので注意してください。

!= の方は、「イコールでない」という意味になります。つまり、== とは得られる論理値が逆になります。

2つとも等価演算子として分類されています。2

具体的には、次のように記述します。

_Bool is_equal_value = value1 == value2;
_Bool is_different_value = value1 != value2;

== は、左右の式を評価した値が同じかどうかを判定し、同じであれば 1、同じでなければ 0 が得られます。!= は、左右の式を評価した値が異なるかどうかを判定し、異なるなら 1、同じなら 0 が得られます。3

【C++プログラマー】C++ では bool型の false か true になります。

このコードは少し読みづらいので、( ) を補うのもいいでしょう。

_Bool is_equal_value = (value1 == value2);
_Bool is_different_value = (value1 != value2);

重要な注意事項があって、文字列どうしを比較すると、想定した結果が得られません。

#include <stdio.h>

int main(void)
{
    char s1[] = "Hello";
    char s2[] = "Hello";

    printf("%d\n", s1 == s2);
    printf("%d\n", s1 != s2);
}

実行結果:

0
1

s1 と s2 はどちらも “Hello” ですが、== によって比較した結果は 0 になりました。!= だと 1 になっています。このように中身が同じなのに、異なっているかのような結果になっています。文字列を比較したいときは、別の手段を採る必要があります。これはあとで取り上げます

==!= による式を評価した時点で、0 や 1 という値になってしまうので、次のように3つ以上の値の比較をおこなうコードは正しく動作しません。

_Bool is_equal_values = (value1 == value2 == value3);

この場合、まず value1 == value2 が評価されて、0 か 1 が得られます。そのため、3つ目の値の比較は、1 == value30 == value3 としていることになります。

正しい方法は、value1 == value2 && value2 == value3 のように書くことです。第15章で説明しますが、&& が2つの条件式を結びつける役割を果たします。


実際に動くプログラムの例として、2つの整数を入力させ、同じかどうかを出力するプログラムを挙げてみます。

#include <stdio.h>

// 整数の入力を受け取る
// 戻り値: 標準入力から整数の入力を受け取り、その値を返す。
int get_input_integer(void)
{
    char s[40];
    fgets(s, sizeof(s), stdin);
    int value;
    sscanf(s, "%d", &value);
    return value;
}

int main(void)
{
    puts("Please enter the integer.");
    int value1 = get_input_integer();

    puts("Please enter the same integer again.");
    int value2 = get_input_integer();

    printf("%d\n", value1 == value2);
}

実行結果:

Please enter the integer.
1234  <-- 入力された整数
Please enter the same integer again.
1234  <-- 入力された整数
1

関係演算子

等価演算子は、2つの値が「同じ」か「同じではない」のいずれかを判定するものでした。これ以外の関係性を調べるために、関係演算子 (relational operator) があります。

関係演算子で調べられる関係性とは、「左の値の方が大きい(小さい)」「左の値は、右の値以上である(以下である)」の4つです。この4つに対応して、関係演算子は4種類あります。

式1 > 式2          // 左の方が大きい?
式1 < 式2          // 左の方が小さい?
式1 >= 式2         // 左が右以上?
式1 <= 式2         // 左が右以下?

「以上」「以下」は、左右の値が同じ場合を含みます。「大きい」「小さい」は、左右の値が同じ場合を含みません。

左右の式を評価した値を比較し、それぞれの関係演算子の意味と合致していれば 1、合致していなければ 0 になります。4

【C++プログラマー】C++ では bool型の false か true になります。

比較の結果がどうなるか確認するプログラムを挙げておきます。

#include <stdio.h>

int main(void)
{
    int value = 10;

    printf("%d\n", value > 0);
    printf("%d\n", value < 20);
    printf("%d\n", value >= 0);
    printf("%d\n", value <= 20);
    printf("%d\n", value > 10);
    printf("%d\n", value < 10);
    printf("%d\n", value >= 10);
    printf("%d\n", value <= 10);
}

実行結果:

1
1
1
1
0
0
1
1

第12章で、for文の「条件式」のところに書いていた i < 5 のような記述の正体は「左の方が小さい?」をあらわす関係演算子です。「変数 i の値が 5 より小さい」かどうかを調べて、そうであれば 1、そうでなければ 0 を得ていたということです。そして、for文は、「条件式」の値が 0 と等しくなるまでループを続行します。

以下の for文はいずれも有効です。

// 等価演算子や関係演算子を使う
for (int i = 0; i == x; ++i) { /* 繰り返すコード */ }
for (int i = 0; i != x; ++i) { /* 繰り返すコード */ }
for (int i = 0; i <= x; ++i) { /* 繰り返すコード */ }

// _Bool型の変数 is_equal_value があるとして
for (int i = 0; is_equal_value; ++i) { /* 繰り返すコード */ }

// int型の変数 value があるとして
for (int i = 0; value; ++i) { /* 繰り返すコード */ }

これで「n回繰り返す」使い方に限定されることはなくなりました。「ある数が入力されるまで」とか「ある変数の値と一致するまで」といった条件式でも記述できます。

文字列の比較

文字列の内容を比較するときには、strcmp関数を使います。この関数を使うには、#include <string.h> が必要です。

#include <stdio.h>
#include <string.h>

int main(void)
{
    char s1[] = "Hello";
    char s2[] = "Hello";

    printf("%d\n", strcmp(s1, s2));
}

実行結果:

0

strcmp関数には2つの実引数があり、比較したい文字列をそれぞれ渡します。もし両者がまったく同じ内容の文字列であれば、戻り値として 0 が返されます。一致しているときに 0、つまり「偽」になることは直観に反しますが、この関数は大小関係の比較にまで対応していることに理由があります。1つ目の実引数に渡した文字列のほうが、辞書順の比較で先に来る場合は 0 より小さい整数が、1つ目の実引数のほうが後ろに来る場合は 0 より大きい整数が返されます。

#include <stdio.h>
#include <string.h>

int main(void)
{
    char s1[] = "Hello";
    char s2[] = "Hot";

    printf("%d\n", strcmp(s1, s2));
    printf("%d\n", strcmp(s2, s1));
}

実行結果:

-1
1

Visual Studio 2015 では、-1 と 1 になりましたが、0 より小さいか、0 より大きいかということしか保証がないことに注意してください。実装によっては、-3 とか 6 とかが返ってくる可能性もあります。


練習問題

問題① 次のプログラムの実行結果はどうなりますか?

#include <stdio.h>

int main(void)
{
    int value1 = 10;
    int value2 = 20;

    printf("%d\n", value1 == value2);
    printf("%d\n", value1 != value2);
    printf("%d\n", value1 < value2);
    printf("%d\n", value1 <= value2);
    printf("%d\n", value1 > value2);
    printf("%d\n", value1 >= value2);
}

問題② 次のプログラムの実行結果はどうなりますか?

#include <stdbool.h>
#include <stdio.h>

int main(void)
{
    bool value = false;

    printf("%d\n", true);
    printf("%d\n", false);
    printf("%d\n", value == false);
    printf("%d\n", value != false);
}

問題③ 次の for文はどういう動作になるでしょうか?

for (int i = 0; 1; ++i) {
    // 繰り返す処理
}

問題④ “I am a programmer.” のように、「.」で終わる文字列があるとします。

この文字列の文字数(空白と最後のピリオドを含む)をカウントして出力するプログラムを作成してください。


解答ページはこちら

参考リンク


更新履歴



前の章へ (第12章 for文)

次の章へ (第14章 if文と条件演算子)

C言語編のトップページへ

Programming Place Plus のトップページへ



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