入門書でのおさらい9・switch文

今日は66ページのswitch文を勉強したいと思います。

66ページ・switch文

例題3-20

例題3-19をスイッチ使って書き直せというのがあるので一度何も見ずにやってみますね。答えはまだ言わないで。もちろんヒントも言わないで自力でやってみるから

了解しました!その意気ですね。ヒントも答えも一切出しません。納得がいくまで、自力で組み上げてみてください。

書き終わったり、動かしてみて「できた!」となったら、ぜひそのコードを見せてくださいね。待っています!

おっと、その前にスイッチの基礎構文というか書き方の公式教えて

承知いたしました!switch文の「公式」はこちらです。

switch (変数) {
    case 値1:
        // 値1のときの処理
        break;

    case 値2:
        // 値2のときの処理
        break;

    default:
        // どれにも当てはまらないときの処理
        break;
}
【ルールのおさらい】
  • switch ( ) の中には、判定したい「変数」を入れます。
  • case 値: の後ろには「コロン(:)」を書きます。
  • break; を忘れると、次の case まで突き抜けて実行されてしまうので注意です。

では、自力での書き換え、いってらっしゃい!
コードが書けたら(あるいは詰まったら)いつでも呼んでくださいね。

できました!

#include<stdio.h>
int main(void){
    
    int no;
    printf("整数値:"); scanf("%d",&no);

    switch (no % 3)
    {
        case 0:
            puts("3で割り切れます");
            break;
        
        case 1:
            puts("3で割った剰余は1です");
            break;

        case 2:
            puts("3で割った剰余は2です");
            break;
   
        default:
            break;
    }

    return 0;
}

完璧です!正解です。

switch のカッコの中に、変数だけでなく no % 3 という計算式を直接入れたのがナイスですね。こうすることで、計算結果(0, 1, 2)をそのまま case で仕分けることができます。

  • 各 case の後にちゃんと break; が入っているので、余計な処理を実行せずに済みます。
  • default: もしっかり書けていますね(今回は余りが 0, 1, 2 以外になることはないので、空っぽでも問題ありません)。

答えを見たけどdefaultは入ってなかったよ。これはelseみたいなもの?

その通り、default: は if 文でいうところの else と全く同じ役割です。「どの case にも当てはまらなかったらここに来い」という最終地点ですね。

今回の問題で回答に載っていなかったのは、主に2つの理由が考えられます。

  1. 論理的に不要だから
    数学的に「3で割った余り」は 0, 1, 2 の3パターンしか絶対にありえないですよね。すべてのパターンが case で網羅されているので、default にたどり着く可能性が 0% なんです。
  2. コードを短くするため
    学習用のサンプルコードでは、本質ではない部分(今回なら絶対に実行されない default)を削ってシンプルに見せることがあります。
実務では書くことも多い

ただ、実際の仕事でプログラムを書くときは、今回のように不要に見えても default: をあえて書く人が多いです。

  • 「想定外」への備え: 万が一、バグなどで変な値が入ったときに「エラーです」と表示させるため。
  • 「書き忘れじゃないよ」という合図: 「他のケースは考えていないわけじゃなく、これで全部だよ」と他のプログラマに伝えるため。

なくても動きますが、「else 的なセーフティネット」として覚えておくと完璧です!

じゃあ今回のはdefaultなしプログラムが正解だね

switch (no % 3) {
    case 0:
        puts("3で割り切れます");
        break; // ここで一歩下がる
        
    case 1:
        puts("3で割った剰余は1です");
        break; // case 0 と縦を揃える

    case 2:
        puts("3で割った剰余は2です");
        break;
}

解説書に最後のcaseにbreak;つけてないのがあったんだけど、例えば下のようなのはbreak;書かなくていいの???

    switch (sw)
    {
        case 1:
            puts("赤"); break;
        
        case 2:
            puts("青"); break;
            
        case 3:
            puts("白");

    }

結論から言うと、そのコードの場合、動作としては break がなくても問題ありません。

理由は、case 3 が switch 文の最後(一番下)だからです。

なぜ無くてもいいのか?

break の役割は「switch の外へ脱出すること」でしたよね。
case 3 の処理が終わると、その下にはもう何もありません。つまり、break を書かなくても勝手に switch の外に出るしかないので、結果的に書いたときと同じ動きになります。

でも、普通は書く!

動作には影響しませんが、多くのプログラマーは最後にも break; を書きます。それには2つの理由があります。

  1. 「うっかり」を防ぐため(安全策)
    あとで「4番目の選択肢(case 4)」を一番下に追加したくなったとします。もし case 3 に break を書き忘れていると、追加した瞬間に、3番を選んだ人が4番へ「突き抜けて(詐欺にあって)」しまいます。
  2. 見た目の統一感
    すべての case に break がついていたほうが、リズムが良くて読みやすいからです。

なるほどー。書くのが当たり前だね。解説書はこのあとcaseの増減に柔軟に対応できるから書いておこうという趣旨でした!

69ページ・switchとif文

このページの例題に盛大にだまされました。

    if(p % 5 == 1)
    c = 3;
    else if (p % 5 == 2)
    c = 5;
    else if (p % 5 == 3)
    c = 7;
    else if (q % 5 == 4)
    c = 9;

このif文をswitch文にするという問題。答えはこうだろう!!

switch(p % 5){
        case 1: c=3; break;
        case 2: c=5; break;
        case 3: c=7; break;
        case 4: c=9; break;
        default: break;
    }

でも。。。答えはこうでした

switch(p % 5){
        case 1: c=3; break;
        case 2: c=5; break;
        case 3: c=7; break;    
        default: if(q % 5 == 4) c=9; break;
    }

え?なんでなんで?なんでこんな書き方なの??(私は読み間違えていました!完全に!)

最後の「else if (q % 5 == 4)」部分だけ変数がpでなくqだったのです!

もちろん私は全部pだと思ってましたよ。脳内変換で。

これならこの書き方が答えなのも納得です。if文だとこんな紛らわしいのが潜んでるのがわかりにくいという注意喚起の説明でした。一方switch文だと(このように異質な感じでプログラムに表れてくれるのでかな?)わかりやすい場合が多いということでした。

まんまとやられてしまいました。

演習3-12

このプログラムをswitch文になおすのね。よし!簡単簡単!

    int n;
    printf("整数を入力せよ:"); scanf("%d",&n);
    if(n % 2)
        puts("その数は奇数です");
    else
        puts("その数は偶数です");

できたーー!余りが1の時は奇数でそれ以外は偶数だな!ジェミニ先生に診てもらおう

#include<stdio.h>
int main(void){
    
    int n;
    printf("整数を入力せよ:"); scanf("%d",&n);

    switch(n % 2){
        case 1: puts("その整数は奇数です"); break;
        default: puts("その整数は偶数です");  break;
    }

    return 0;  
}

負の数への対応: 実はC言語では、負の奇数(-3など)を % 2 すると結果が -1 になることがあります。負の奇数が来たときにおかしい結果になってしまいます。

うあ、そっかマイナスもあるんだ。ということは奇数の余りは1と-1ってことか。偶数の時は余りが0だからdefaultで拾うのを奇数のほうにしてみる??
私のプログラム実行したら-3が偶数だって・・・。これはやばい。もう一度作ってきます!

書き直し1つ目

    switch(n % 2){
        case 1: puts("その整数は奇数です"); break;
        case -1: puts("その整数は奇数です"); break;
        default: puts("その整数は偶数です");  break;
    }

お見事!どちらも正解ですが、case 1: と case -1: を両方書くことで、負の数までしっかりケアできています。フォールスルーの話を活かして、こう書くともっとスッキリしますね。

case 1:
case -1: 
    puts("奇数です"); break;

あー!その技術あったね!break;とばすやつだね。もう一回かいてくる。

    switch(n % 2){
        case 1: 
        case -1: puts("その整数は奇数です"); break;
        default: puts("その整数は偶数です");  break;
    }

完璧です!それが一番「忠実」かつ「丁寧」な正解ですね。

元の if 文が「n % 2 が成り立つ(0以外である)こと」を奇数の条件にしていたので、それを case 1: と case -1: の両方で受けて、それ以外(0)を default に流す。

  1. 負の数が来ても嘘をつかない
  2. 0 もしっかり偶数に仕分けられる
  3. フォールスルー(あえてbreakを書かずに合流させる技)を正しく使っている

と、今日学んだことがすべて詰まっています。素晴らしい着地です!

書き直し2つ目

もう一つ作ったので見てください

    switch(n % 2){
        case 0: puts("その整数は偶数です"); break;
        default: puts("その整数は奇数です");  break;
    }

これは天才的な解決策です!
なぜなら、3で割った時や5で割った時と違い、2で割った余りが「0」になるのは偶数だけだからです。

  • 余りが 0 なら絶対偶数。
  • それ以外(1 でも -1 でも)なら絶対奇数。

「0か、それ以外か」という if の強みを、見事に switch の default で再現できています。

ジェミニ先生のマニアックなコード

switch(n % 2 == 0) { // 「2で割り切れるか?」の結果(0か1)で判定
    case 1: puts("偶数"); break; // 割り切れた(真)なら偶数
    case 0: puts("奇数"); break; // 割り切れなかった(偽)なら奇数
}

そのコードは「条件式そのものをスイッチに入れる」というちょっとマニアックな手法です。

あえてcaseを0か1の二つにしてしまうんですね?

n % 2 == 0 という「質問」を switch のカッコに入れることで、答えを無理やり 「1(はい/真)」か「0(いいえ/偽)」の2択 に絞り込んでしまうテクニックです。

これなら、余りが 1 だろうが -1 だろうが、一括で「0(いいえ)」の箱に放り込めるので、見た目がとてもスッキリします

  • 「計算結果」ではなく「YESかNOか」で分ける: n % 2 == 0 という式は、正しい(真)なら 1、間違い(偽)なら 0 という値になります。それをそのまま switch に放り込んでいます。
  • 負の数の問題を根本から解決: この方法なら、余りが 1 だろうが -1 だろうが、「0と等しくない」という事実(偽=0)として扱われるので、case -1 を書かなくて済むスマートな解法になります。

if else文の時も真か偽かの判定なのでこのマニアックコードと同じでマイナスの奇数もカバーしてしまってるんですね

まさにその通りです!

if (n % 2) というのは、コンピュータ内部では 「n % 2 の結果は 0 以外(真)か?」 という判定をしています。なので、結果が 1 でも -1 でも「0じゃないから真だね!」と判断して奇数側へ動きます。

ifの()のなかとswitchの()の中の判別の仕方がちがう?

ifの()の中とswitchの()の中の判別の仕方がちがう?ifは真偽で0か1。switchは式の値そのものが出てくるでしょ?

おっしゃる通り!その違いこそが、今回の「負の数問題」の核心です。

ここが決定的な違いです
  1. if の ( ):
    中身を評価して、最終的に「0(偽)か、それ以外(真)か」の2択に凝縮します。だから -1 も 1 も同じ「真」のグループになります。
  2. switch の ( ):
    中身の計算結果、つまり「値そのもの」をそのままケースに運びます。だから 1 と -1 は「別の行き先」として区別されてしまいます。
マニアックコードの種明かし

先ほどの switch(n % 2 == 0) がなぜ負の数をカバーできたかというと、( ) の中に == という比較演算子を入れたからです。

  • C言語では、== や > などの比較をした瞬間に、結果が強制的に 「0 か 1」 に変換されます。
  • つまり、switch に届く前に、自ら if と同じように「真偽値(0か1)」に加工してしまったわけです。
結論
  • if 自動的に「0か、それ以外か」に丸めてくれる(おまかせモード)。
  • switch 値をそのまま正直にケースにぶつける(こだわりモード)。

この「丸め処理」が自動で行われるかどうかが、負の奇数を扱うときの結果の差になったんですね。

演習3-13

62ページの3-18をswitch文を用いて書き換えるのが問題です。あの月を入れたら季節を表示するやつですね!なんかこれはできそうな気がします!フォールスルーが頭の中に浮かんできてます!

#include<stdio.h>

int main(void){
    int month;
    puts("季節が知りたい月を整数(1~12)で入力してください");
    printf("月:"); scanf("%d",&month);

    switch (month)
    {
    case 3:
    case 4:
    case 5:
        printf("%d月は春です\n",month);
        break;
    
    case 6:
    case 7:
    case 8:
        printf("%d月は夏です\n",month);
        break;

    case 9:
    case 10:
    case 11:
        printf("%d月は秋です\n",month);
        break;

    case 12:
    case 1:
    case 2:
        printf("%d月は冬です\n",month);
        break;
    
    default:
        puts("1~12を入力してください");
        break;
    }

    return 0;
}

できました!

素晴らしい!完璧なコードです。

これこそがフォールスルーの正しい使い方ですね。

良かったポイント

  1. 圧倒的な読みやすさ: if文の「>= 3 && <= 5」のような記号の羅列よりも、「3, 4, 5なら春」と直感的に分かります。
  2. 冬の処理: 12, 1, 2 を並べることで、年をまたぐ季節も見事に表現できています。
  3. default の活用: 範囲外の数値(13以上や0以下)をしっかり「1〜12を入力してください」と弾けているので、プログラムとしての完成度が高いです。

わー!一回で合格できてうれしいです!

学びのまとめ

今回の演習を通して、switch文の「本質」をすべて体験できましたね。

  • 効率性: 条件を一個ずつ評価するより、特定の値へジャンプするのが得意。
  • デザイン: フォールスルーを使えば、複数の選択肢を一つの結果に「合流」させられる。
  • 厳密さ: 変数が途中で変わったり(pとq)、負の数が混ざったりすると、工夫が必要になる。

次はいよいよ第4章に入ります