論理演算 | Programming Place Plus C言語編 第15章

トップページC言語編

このページの概要 🔗

このページの解説は C99 をベースとしています

以下は目次です。


論理演算 🔗

論理演算 (logical operation) は、論理値を使って行う演算のことです。これに対し、通常の整数や実数を使った演算は、算術演算 (arithmetic operation) と呼ばれることがあります。

ここでは論理演算の中から、論理OR、論理AND、論理否定の3つを取り上げます。これらはそれぞれ専用の演算子が用意されています。

論理OR 🔗

2つの条件のいずれかを満たしていることを調べたいとします。たとえば、「値が 100以上、あるいは 5 の倍数」という条件を考えてみます。この場合、15 や 102 は条件を満たしますが、99 は条件を満たしません。また、100 や 105 は条件を満たします(2つの条件を両方ともを満たすケース)。

このような条件は、論理OR論理和 (logical OR) などと呼ばれます。論理OR は、‘||’ という表記の論理和演算子 (logical OR operator) を使って記述します。

条件式1 || 条件式2

まず、「条件式1」が評価されます。これが 0以外 だった場合、全体としての結果が 1 になります。「条件式1」が 0 に評価された場合には、「条件式2」を評価し、これが 0以外だった場合、全体としての結果が 1 に、そうでなければ 0 になります[1]

少々ややこしい書き方をしましたが、簡単にいえば「2つの条件式のどちらか一方だけでもいいので、真ならば 1。そうでないなら 0 になる」ということです。

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

実際のプログラムでは次のように使えます。

#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 value = get_input_integer();

    if (value >= 100 || value % 5 == 0) {
        puts("true");
    }
    else {
        puts("false");
    }
}

実行結果:

Please enter the integer.
102  <-- 入力された内容
true
Please enter the integer.
99  <-- 入力された内容
false

最初に少々ややこしい説明のしかたをしたのには理由があります。きちんと読むと、「条件式1」が 0以外になったときには「条件式2」が評価すらされないことが分かります。「2つの条件のどちらかを満たすかどうか」ということが知りたいわけなので、条件式の一方を調べてみて 0以外(真)なのであれば、もう一方の条件式を調べることに意味がないため、評価自体を省略してしまうのです。このような評価のルールを、短絡評価 (short-circuit evaluation) と呼びます。

短絡評価が行われるため、2つの条件の一方の側が、他方に比べてあきらかに処理に時間がかかるものであるなら、それを「条件式2」に置いたほうが効率的になります。


条件式を3つ以上組み合わせたい場合は、|| をつなげて条件式を書き並べていけばいいです。たとえば、「値が 100以上、あるいは 5 の倍数、あるいは 7 の倍数」としたければ、e >= 100 || e % 5 == 0 || e % 7 == 0 のように書けます。短絡評価なので、左側の条件式から順番に評価され、true になった時点で、それ以降の条件式は評価されません。

論理AND 🔗

今度は、2つの条件を両方とも満たしていることを調べたいとします。たとえば、「値が 100以上で、かつ、奇数である」という条件を考えてみます。この場合、101 や 103 は条件を満たしますが、99 や 100 は条件を満たしません(99 は奇数だが 100以上ではない。100 は 100以上だが奇数ではない)。

このような条件は、論理AND論理積 (logical AND) などと呼ばれます。論理ANDは、&& という表記の論理積演算子 (logical AND operator) を使って記述します。

条件式1 && 条件式2

「条件式1」と「条件式2」それぞれを評価した結果が 0以外になる場合にかぎって、結果が 1 になります。これ以外のパターンでは 0 になります[2]

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

論理AND演算子にも短絡評価のルールがあります。論理AND演算子では、必ず「条件式1」が先に評価され、もし 0(偽)であったなら、「条件式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 value = get_input_integer();

    if (value >= 100 && value % 2 != 0) {
        puts("true");
    }
    else {
        puts("false");
    }
}

実行結果:

Please enter the integer.
101  <-- 入力された内容
true
Please enter the integer.
99  <-- 入力された内容
false

論理積演算子における短絡評価は、「条件式1」を満たすことを前提とした「条件式2」を記述することに利用できます。たとえば、ある文字列について「2文字が ‘A’ であるか」を判定したいとします。この条件を s[1] == 'A' のように記述できますが、もし s が空文字列(““) だった場合には、不正な位置をアクセスしてしまいます。そのため、この条件式を評価する前に、空文字列でないことを確認しなければなりません。そこで、短絡評価を利用して、s[0] != '\0' && s[1] == 'A' と記述します。必ず、s[0] != '\0' が先に評価されるため、空文字列でない場合にだけ、s[1] == 'A' を評価させることができます。

#include <stdio.h>

int main(void)
{
    char s[] = "";
    if (s[1] == 'A') {  // 危険
        puts("true");
    }
    else {
        puts("false");
    }
}
#include <stdio.h>

int main(void)
{
    char s[] = "";
    if (s[0] != '\0' && s[1] == 'A') {  // OK
        puts("true");
    }
    else {
        puts("false");
    }
}

実行結果:

false

Visual Studio 2015 は、空文字列に対する s[1] へのアクセスを不正であると判断し、この2つのプログラムをいずれもコンパイルエラーにしてしまいます。Visual Studio 2017 では、後者のプログラムはコンパイルできます。

論理否定 🔗

最後に、条件を否定する、論理否定(論理NOT) (logical negation、logical NOT) を紹介します。論理AND、論理OR とちがって、登場する条件は1つだけで、その結果を反対にするというものです。

論理否定は、! という表記の論理否定演算子 (logical negation operator) を使って記述します。

!条件式

「条件式」が 0 の場合は 1 になり、0以外の場合は 1 になります[3]

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

xxx == false のような条件式は、!xxx と書いても同じ結果になります。

! が目立たず見落としやすいため、! xxx のように、空白をはさむ書き方が好まれることがあります。

たとえば、ある整数が偶数かどうかを判定する is_even関数を作ったとします。論理否定演算子を使えば、その関数を奇数の判定に利用できます。

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

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

// 偶数かどうか判定する
// num:判定する値
// 戻り値: 偶数なら true、偶数でなければ false を返す
bool is_even(int num)
{
    return num % 2 == 0;
}

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

    if (!is_even(value)) {
        printf("%d is odd number.\n", value);
    }
}

実行結果:

Please enter the integer.
3  <-- 入力された内容
3 is odd number.

is_even関数は、実引数が偶数ならば true を返すようになっているので、論理否定演算子で反転させることで、偶数ではない(=奇数である)ことを判定できます。

論理否定演算子が入ってくると途端にややこしく感じるかもしれませんが、その感覚は正しいです。人間は否定形をイメージしにくいと言われています(先に肯定形を思い浮かべてから、それを否定するという2段階の思考が必要だそうです)。ですから、否定形はできるだけ避けるべきです。もちろん完全には避けられませんが、たとえば関数の定義のほうに隠せば、あまり頭を悩ませずに済みます。今回のサンプルなら、奇数を判定する関数を定義するのが筋でしょう。

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

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

// 偶数かどうか判定する
// num:判定する値
// 戻り値: 偶数なら true、偶数でなければ false を返す
bool is_even(int num)
{
    return num % 2 == 0;
}

// 奇数かどうか判定する
// num:判定する値
// 戻り値: 奇数なら true、奇数でなければ false を返す
bool is_odd(int num)
{
    return !is_even(num);
}

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

    if (is_odd(value)) {
        printf("%d is odd number.\n", value);
    }
}

実行結果:

Please enter the integer.
3  <-- 入力された内容
3 is odd number.

優先順位 🔗

論理演算に使う3つの演算子はそれぞれ優先順位が異なります。論理否定演算子が一番強く、次に論理積演算子、一番弱いのが論理和演算子です。

すべての演算子の優先順位の一覧は、こちらを参照してください

論理否定演算子では、( ) を使って、優先順位を明確に指定しなければうまくいかないかもしれません。たとえば、値が奇数であることを判定しようとして、次のように書くと、正しい結果になりません。

#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 value = get_input_integer();

    if (!value % 2 == 0) {
        printf("%d is odd number.\n", value);
    }
    else {
        printf("%d is even number.\n", value);
    }
}

実行結果:

Please enter the integer.
4  <-- 入力された内容
4 is odd number.

論理否定演算子は優先順位が高いので、if (!value % 2 == 0) のように書くと、変数value の値を否定してから、2 の剰余を求めることになってしまいます。つまり、(!value) % 2 == 0 という扱いです。これでは value が 0 のときだけ奇数になり、0以外のときはすべて偶数になってしまいます。

求めている結果を得るには、!(value % 2 == 0) としなければなりません。


次のプログラムは論理積演算子と論理和演算子の優先順位を確認しています。

#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 value = get_input_integer();

    if ((value == 0) || (value % 2 != 0) && (value >= 100)) {
        puts("true");
    }
    else {
        puts("false");
    }
}

実行結果:

Please enter the integer.
103  <-- 入力された内容
true

論理和演算子よりも論理積演算子の方が優先されるので、(value % 2 != 0) && (value >= 100) が先に結び付きます。したがって、「奇数であること」と「100以上であること」は両方同時に満たす必要があります。この2つの結果を X と呼ぶとします。

続いて、(value == 0) || X の判定になります。したがって、「0 である」または「X」あるいはその両方が真になったとき、条件式全体が 1(真)になります。


練習問題 🔗

問題① 次のうち、結果が「真」になるものをすべて選んでください。a の値は 10、b の値は 20 とします。

  1. a == 10 && b == 20
  2. a == 10 && b == 10
  3. a != b || a == 0
  4. !(a == b)
  5. !(a == 10 || b == 10)
  6. a % 2 != 0 || a == 10 && b == 20

問題② 標準入力から受け取った整数が、次の条件をすべて満たすときに、標準出力へ「OK!」と出力するプログラムを作ってください。

問題③  標準入力から受け取った西暦年が、閏年かどうか判定するプログラムを作成してください。
※閏年の定義を正確に知っていますか?「4年に1度」だけでは不正解です。


解答ページはこちら

参考リンク 🔗


更新履歴 🔗



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

次の章へ (第16章 while文)

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

Programming Place Plus のトップページへ



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