この文書の URL は http://www.cc.kyoto-su.ac.jp/~mtkg/lecture/comp_B/2012/11.html です。

if 文

数値の符号や大小関係など、様々な条件の違いに応じて、異なった処理を行いたいことがあります。 そのような場合には if 文を使います。

if 文にはいくつかの変種がありますが、一番単純なのは次の形の if 文です。

if (式) 文

「式」はある条件が成り立つか判定するためのもので、その値が非 0 であれば(つまり、値が 0 でなければ)「文」を実行します。 式の値が 0 であれば「文」は実行されず、これ以降に処理が移されます。 C では条件の成立(真)を非 0、条件の不成立(偽)を 0 で表します。

次のプログラムは、入力された値が正の場合だけ平方根を表示します。

/* work1101.c */
#include <stdio.h>
#include <math.h>               /* sqrt() */

int main(void)
{
    double dx;

    printf("Input a real: ");
    scanf("%lf", &dx);

    if (dx > 0.0) printf("%f\n", sqrt(dx));

    return 0;
}

キーボードからいろいろな実数を与えて動作を確認してみましょう。

等価演算子と関係演算子

if (...) の中の式 dx > 0.0 によって dx が 0.0 より大きいか判定します。 C には等号の成立・不成立を判断する「等価演算子」と、大小関係を判断する「関係演算子」があります。 等価演算子と関係演算子は条件が成り立つか判断し、成り立てば int 型の 1、成り立たなければ int 型の 0 を返します。

e1 == e2        e1 =  e2 ならば 1、そうでなければ 0
e1 != e2        e1 != e2 ならば 1、そうでなければ 0
e1 <  e2        e1 <  e2 ならば 1、そうでなければ 0
e1 <= e2        e1 <= e2 ならば 1、そうでなければ 0
e1 >= e2        e1 >= e2 ならば 1、そうでなければ 0
e1 >  e2        e1 >  e2 ならば 1、そうでなければ 0

等号は === が2つ)、 不等号は != であることに注意しましょう。

等価演算子・関係演算子と型

等価演算子・関係演算子で結ばれた両辺の2つの式の型が異なる場合、より広い方の型に変換されてから比較が行われます。 例えば dx が double 型の変数である場合、 dx > 1 では int 型の定数 1 が double 型の定数 1.0 に変換されてから大小が比較されます。

実数(浮動小数点数)はあくまでも近似値なので

if (dx == 1.0) ...

といったプログラムは意図したように動かないことがあります。 (計算には誤差が含まれることがあるため dx が厳密に 1.0 になるとは限りません。) 「dx が 1 に十分近い」といった、誤差を考慮した安全な比較方法を使いましょう。

if 文と else 節

入力された整数が偶数か奇数か判定して表示するプログラムを作成してみましょう。 偶奇の判定には整数を 2 で割った余りを調べればよいですね。

/* work1102.c */
#include <stdio.h>

int main(void)
{
    int nx;

    printf("Input an integer: ");
    scanf("%d", &nx);

    if (nx % 2 == 0)
        printf("%d is even.\n", nx);
    else
        printf("%d is odd.\n", nx);

    return 0;
}

if 文の構造

条件の成立・不成立に応じて異なる文を実行するには else 節を利用します。 else 節付きの if 文は次のような構造をしています。

if (式)
   文1;
else
   文2;

式が非 0 であれば文1が実行され、 0 であれば文2が実行されます。 次に示すように、文1または文2を空文(くうぶん) ; とすることも可能です。 空文は実行されても何もしません。

if (式)
   ;
else
   文2;

入れ子になった if 文

もっと複雑な条件判断の例として閏年 (leap year) の判定を考えてみましょう。 グレゴリオ暦では次の規則に従って閏年が設けられています。

  1. 西暦年が 4 で割り切れる年は閏年である。
  2. ただし、西暦年が 4 で割り切れる年のうち、100 で割り切れる年は平年とする。
  3. ただし、西暦年が 100 で割り切れる年のうち、400 で割り切れる年は閏年とする。

したがって、入力された西暦年が閏年か平年か判定するプログラムは次のようになります。

/*
  work1103.c
  閏年の判定
*/
#include <stdio.h>

int main(void)
{
    int year;

    if (year % 400 == 0)          /* 400 で割り切れるか */
        printf("leap year\n");
    else if (year % 100 == 0)     /* 100 で割り切れるか */
        printf("common year\n");
    else if (year % 4 == 0)       /* 4 で割り切れるか */
        printf("leap year\n");
    else
        printf("common year\n");

    return 0;
}

処理の流れ

このプログラムでは、まず int 型の変数 year が 400 で割り切れるか判定します。 割り切れる場合は leap year と表示し、処理は return に移されます。 割り切れない場合は if (year % 100 == 0) に処理が移され、100 で割り切れるかを判定します。 100 で割り切れるかどうかの判定は 400 で割り切れない場合だけ行われることに注意しましょう。

100 で割り切れる場合は common year と表示し、判定を終了します。 割り切れない場合は 4 で割り切れるか判定します。

4 で割り切れる場合は leap year と表示し、判定を終了します。 割り切れない場合は else に処理が移され common year と表示して終了します。

条件判断の順序

変数 year が 4 で割り切れるかどうかの判定から始めてしまうと、プログラムがかなり複雑になります。 条件判断はもっとも厳しいものから行うとすっきり書けることが多いようです。 ただし、効率を重視し、比較の回数をなるべく減らしたいなら、もっとも起こりやすい条件から比較すべきでしょう。

練習問題

キーボードから整数を入力し、符号を判定せよ。 正の場合は positive, 負の場合は negative, 0 の場合は zero と表示すること。

クリックして表示

複合条件と論理演算子

こんどは月の数を入力してその月の日数を表示するプログラムを考えてみましょう。 ただし、閏年は考慮せず、2月は常に28日とします。

/* work1105.c */
#include <stdio.h>

int main(void)
{
    int m;

    printf("Input month: ");
    scanf("%d", &m);

    if (m == 2)
        printf("28\n");
    else if (m == 4 || m == 6 || m == 9 || m == 11)
        printf("30\n");
    else
        printf("31\n");

    return 0;
}

論理演算子

2つ目の if 文の式

m == 4 || m == 6 || m == 9 || m == 11

は「m が 4 または 6 または 9 または 11」という条件を表します。 演算子 || は「または」(論理和)を表します。 「または」や「かつ」といった論理演算を行う演算子を「論理演算子」といいます。

論理演算子には &&|| の2種類があります。

e1 && e2  論理積(かつ)   e1, e2 がともに非 0 のときのみ 1、他は 0        (結果は int 型)
e1 || e2  論理和(または) e1, e2 のどちらかが非 0 なら 1、ともに 0 なら 0 (結果は int 型)

例えば、「x が正かつ y が負」という条件は論理積演算子 && を使って次のように表せます。

x > 0 && y < 0

練習問題

キーボードから2つの実数 x, y を読み込み、点 (x, y) が第1象限にあれば OK、 なければ NG と表示するプログラムを作成せよ。

クリックして表示

複合文(ブロック)

キーボードから2つの整数を読み込み、大きい方の値と小さい方の値を求めるプログラムを考えます。

/* work1107.c */
#include <stdio.h>

int main(void)
{
    int n1, n2, max, min;

    printf("Input two integers: ");
    scanf("%d %d", &n1, &n2);

    if (n1 > n2) {
        max = n1;
        min = n2;
    } else {
        max = n2;
        min = n1;
    }

    printf("max = %d\n", max);
    printf("min = %d\n", min);

    return 0;
}

複合文(ブロック)とスコープ

if 文に注目してください。 これまでの例では if 文は

if (文)
    文1;
else
    文2;

という形をしていましたが、今回の例では文1、文2に相当する部分が次のようになっています。

文1;  =>  { max = n1; min = n2; }
文2;  =>  { max = n2; min = n1; }

このように、複数の文を { } で囲んだものを「複合文」あるいは「ブロック」といいます。 複数の文を含むブロックは、構文上「単一の文」とみなされます。 ブロックの最後の } の後ろにコンマ ; は必要ないので注意しましょう。

ブロックの中には文だけでなく変数宣言を含むことができます。

{
    int n1, n2;
    :
    {
        int tmp;                /* 変数 tmp  はこのブロック内だけで使用可 */
        tmp = n1;
        n1 = n2;
        n2 = tmp;
    }
    :
}

ブロックの中で宣言された変数は、そのブロックの中でだけ使用することができ、ブロックの外では無効になります。 これを変数の「ブロック有効範囲(スコープ)」といいます。

switch 文

1つの式の値によってプログラムの処理が複数(3つ以上)に分岐するときは switch 文を使うと便利です。 例として 3 で割った余りを表示するプログラムを考えてみましょう。

/* work1108.c */
#include <stdio.h>

int main(void)
{
    int n;

    printf("Input an integer: ");
    scanf("%d", &n);

    switch (n % 3) {
    case 0:
        printf("amari = 0\n");
        break;
    case 1:
        printf("amari = 1\n");
        break;
    case 2:
        printf("amari = 2\n");
        break;
    }

    return 0;
}

switch 文の構造

switch 文は次のような構造をしています。 switch 文の直後の式を評価して、case に続けて書かれた値と一致するところに処理を移します。 そこから文が順番に実行されていきますが、途中に break 文があるとそこで処理が終了し、switch 文の外に脱出します。 break 文を忘れると以降の文がすべて実行されてしまうので注意しましょう。

switch (式)
case 値1:
    文;
     :
    break;
case 値2:
    文;
     :
    break;
 :
default:
    文;
     :
    break;
}

一致する値がみつからなかった場合は default: に処理が移されます。 default: は省略可能です。

フォールスルー (fall through)

break 文を意図的に省略して他の case の文を実行させることがあります。 これを fall through といいます。 次の例は 5 で割った余りが 0, 1, 2 の場合は OK、3, 4 の場合は NG と表示するプログラムです。

/* work1109.c */
#include <stdio.h>

int main(void)
{
    int n;

    printf("Input an integer: ");
    scanf("%d", &n);

    switch (n % 5) {
    case 0:
    case 1:
    case 2:
        printf("OK\n");
        break;
    case 3:
    case 4:
        printf("NG\n");
        break;
    }

    return 0;
}

fall through は便利ですが、うっかり break 文を付け忘れると見つけにくいバグを引き起こすので、あまり使わない方がよいという意見もあります。

練習問題

月の数を入力して季節を表示するプログラムを作成せよ。 ただし、月と季節の関係は

とする。 また、無効な月が入力された場合は invalid と表示すること。

クリックして表示

本日の課題

1. 大きい方の値

2つの整数を入力し、大きい方の値を表示するプログラムを作成せよ。

クリックして表示

2. 3つの数の最大値

3つの整数を入力し、最大値 (maximam value) を表示するプログラムを作成せよ。

クリックして表示

3. 和暦の計算

キーボードから西暦年を入力し、対応する和暦年を表示するプログラムを作成せよ。 例えば、西暦2000年の場合は Heisei 12 と表示する。 西暦と和暦は次のように対応する。

西暦1868年     明治1年
西暦1912年     大正1年
西暦1926年     昭和1年
西暦1989年     平成1年

元号(明治、大正、昭和、平成)の判定には厳密には月日が必要である。 例えば、1912年は7月29日までが明治45年、7月30日以降が大正1年であるが、ここではプログラムを単純化するため、ある元号の最終年度は次の元号の1年としてよい。 つまり、1912年は大正1年、1926年は昭和1年などとする。 また、1868年以前の西暦年が入力された場合は Not implemented と表示せよ。

クリックして表示

チャレンジ問題

1. 平方根の近似計算

正の実数 x の平方根 √(x) はつぎの式から r5 として求められる。

r1 = (1 + x) / 2
r2 = (r1 + x / r1) / 2
r3 = (r2 + x / r2) / 2
r4 = (r3 + x / r3) / 2
r5 = (r4 + x / r4) / 2

実数 x の値をキーボードから読み込み、 r1, r2, r3, r4, r5 の各値を画面に出力するプログラムを作成せよ。

クリックして表示

2. 曜日の計算

日曜日を 0、月曜日を 1、…、土曜日を 6 というように番号で表わすものとする。 このとき、今日の曜日を表わす番号 i と日数 n を読み込んで、n 日後の曜日を求め、番号で表示せよ。

クリックして表示

3. 三角形の判定

次のような機能をもったプログラムを作成せよ。

ヒント: 「三角形の各辺の長さは正で、2辺の和は他の1辺の長さよりも長い」という条件から

a > 0 かつ b > 0 かつ c > 0 かつ a + b > c かつ b + c > a かつ c + a > b

が三角形成立の条件となる。 後半3つの条件判定には、次のように定義される ms を利用する。

m = (a + b + c) / 2
s = m * (m - a) * (m - b) * (m - c)

s が 0 以下であれば三角形ができない。 s が正であれば三角形ができ、その面積は √(s) で与えられる。

クリックして表示

4. 2次方程式の解法

実数係数の2次方程式 a*x^2 + b*x + c = 0 の係数 a, b, c をキーボードから入力し、解を求めるプログラムを作成せよ。 判別式 D = b^2 - 4*a*c の正負を判定し、 方程式が実数解を持つ場合と虚数解を持つ場合、両方に対応すること。

クリックして表示