入門書でのおさらいを並行して進める5

今日は44ページのif文をやりたいともいます。早速例題3-1に取り組みます。

44ページ・if文

if文でうん十年前にはやってなかったこと

if (n % 5)
    puts("その数は5では割り切れません"); 

あれ??{}がない!!ifといえばif(条件式){};だったのに変わったのかな?当時から省略できたのかもしれないけどずっと下の分みたい{}でくくってました。省略していいとは習ってないな・・・。プログラムの本筋とは関係のない質問しかいつも出てこないな…まあ聞いてみるか。

if (n % 5)
    {
    puts("その数は5では割り切れません"); 
    }

if文の{}はつけなくていいの?

if文、昔は「if(条件式){};」の形だったんですが、今最近の入門書を息子から借りて勉強してるんだけど「if()条件式;」で{}がないの。これは昔からだった?

結論から言うと、{}(中カッコ)を省く書き方は、うん十年前も今も「全く同じルール」として存在しています。

実はC言語のルールでは、「if文の影響を受けるのは、直後の1文(セミコロンまで)だけ」と決まっているからです。比較してみるとわかりやすいです。

{} を書かない場合(直後の1文だけ実行)
if (score >= 80)
    printf("合格です!\n"); // ここまでがifの中身
printf("お疲れ様でした。");  // これはifに関係なく必ず実行される
{} を書く場合(複数の文をまとめたい時)
if (score >= 80) {
    printf("合格です!\n");
    printf("お祝いしましょう!\n"); // 複数行をifに入れたいなら{}が必須
}
なぜ入門書に「{}なし」が載っているのか?

最近の入門書で「{}なし」が紹介されているのは、おそらく「1行で済むならスッキリ書けるよ」という紹介のためだと思われます。

しかし、実務や現場では「{}は必ず書く」というルール(コーディング規約)が一般的です。
理由は、後から1行足したときに、{}を忘れて「条件に関係なく実行されてしまうバグ」が多発したからです。

おお!なるほど。じゃあ1行だけの時は{}なしで大丈夫だけどそのあとまだ条件付け足していくかもしれないから基本囲んだほうがバグが少なくなるということですね!

まさにその通りです!

「1行だけなら{}はいらない」というのは文法上のルールですが、後から処理を1行書き足した瞬間に、「{}がないせいで2行目がifの外に出てしまい、条件に関わらず常に実行されてしまう」というバグは、初心者からベテランまでが通る「C言語あるある」なんです。

ですので、今の入門書で{}がない書き方を見かけても、「自分は安全のために{}を書くぞ」というスタイルを貫くのは、プログラミングにおいて非常に賢明な判断です。

当時から{}なしでいいとは一切教わらなかったので、先生はバグが多くなるからわざと教えなかったのかもしれません。しかし1行なら省略できるという知識もあったほうがいいですね!

当時の先生は、生徒が「C言語の罠」にハマらないよう、あえて「{}は必須」として教えてくれたのでしょう。それはとても愛情のある教え方だと思います!

おっしゃる通り、「省略できる」という知識があれば、他人の書いたコードや入門書を読んだときに「あ、これでも動くんだな」と混乱せずに済みますね。

ちなみに、もう一つ「1行なら省略できる」に関連して、こんな書き方を見かけるかもしれません。

if (score >= 80) printf("合格!\n");

このように、条件式のすぐ横に1行で書いてしまうスタイルです。これも中身が1つだけなら有効です。

if文の条件式、()の中の数値が0なら実行しない。それ以外ならif文の{}の中の分を実行するということですね?これはC言語のルールでしょうか?

if文の条件式、()の中の数値が0なら実行しない。それ以外ならif文の{}の中の分を実行するということですね?これはC言語のルールでしょうか?

まさにその通り、C言語の根本的なルールです!

C言語には、もともと「真(True)」や「偽(False)」という専用のデータ型がありませんでした(後から追加されましたが、基本は今も数字です)。

そのため、コンピュータにとって一番わかりやすい形で決められています。

  • 0「偽(False)」とみなす
  • 0以外(1や-100など)「真(True)」とみなす

このルールを知っていると、30年前のベテラン勢がよく書いていたこんな「渋いコード」も理解できるようになります。

例えば:フラグの判定
int is_ready = 1; // 準備OK(0以外)

if (is_ready) {
    printf("準備完了!\n");
}

わざわざ if (is_ready == 1) と書かなくても、「中身が0じゃないから実行!」となるわけです。

逆のパターン(0のときだけ実行したい)

逆に「0のとき(偽のとき)」に実行したい場合は、「!」(否定)を使います。

if (!is_ready) {
    // is_readyが0のときだけ実行される
}

この「0かそれ以外か」という感覚は、この先学習するであろう「文字列の終わり判定」や「ポインタの空チェック」でもずっと出てくるC言語の背骨のようなルールです。

ちなみに3-1の例題で条件式の部分を解説書は(n % 5)でしたが私はつい(n % 5 != 0)としてしまいました。そんな回りくどいことしなくてよかったのですね。

それは決して「回りくどい」失敗ではなく、むしろプログラミングとしては非常に丁寧で分かりやすい書き方ですよ!

解説書の (n % 5) という書き方は、C言語の「0以外は真」という性質をフル活用した「玄人好みの短縮形」です。

  1. あなたの書いた (n % 5 != 0)
    • 意味:「nを5で割った余りが、0ではない場合」
    • 利点:パッと見て「何を判定したいのか」が誰にでも分かります。バグも起きにくいです。
  2. 解説書の (n % 5)
    • 意味:「nを5で割った余り(が0以外なら真)」
    • 利点:短く書けます。C言語に慣れた人には「余りがある=5の倍数ではない」と一瞬で伝わります。

うん十年前はメモリも少なく、コードをいかに短く書くかが重視される場面もありましたが、今は「読みやすさ(可読性)」がとても重視されます。

実は、最近のプログラマーの間でも、あえて省略せずに != 0 と書くべきだという意見は多いんです。なぜなら、その方が「論理(Yes/No)を判定している」という意図がハッキリするからです。

ですので、「つい書いてしまった」のではなく「より丁寧に書いた」と自信を持ってください!

return;の時の数字と逆な感じがしてこんがらがります

ところで「0は偽」「0以外は真」とのことでしたが、return 0;の時と何か逆な感じがしてこんがらがります。

「C言語初心者が必ず一度は混乱するポイント」です!おっしゃる通り、見た目は真逆に見えますよね。整理すると、実は「使っている土俵」が違うんです

if文の中(論理の世界)

ここでは、「その事象が成り立っているか?」を判定します。

  • 0 = No(偽)
  • 0以外 = Yes(真)
  • (イメージ:スイッチがOFF(0)か、ON(1)か)
return 0; (社会・OSとの約束の世界)

main関数の最後で return 0; と書くのは、プログラムを実行した「OS(外の世界)」に対して、「終了ステータス(結果報告)」を返しているからです。

  • 0 = 「異常なし(正常終了)」
  • 0以外 = 「何らかのエラー(異常終了)」
  • (イメージ:報告書で「問題の数 = 0件」なら安心、という感覚)
なぜ逆なの?

OSへの報告において「0が正常」とされる理由は、「エラーにはいろいろな種類があるから」です。

  • 正常なら「0」で完結。
  • エラーなら「1(メモリ不足)」「2(ファイルが見つからない)」のように、0以外の数字をエラーコードとして使い分けたいので、消去法で「0」が正常担当になったと言われています。

リターンの場合は0が返ってきたらOSは次の処理を実行、それ以外は実行しないじゃないですか?だからif文の時と逆っぽいなって思ってしまいました。

なるほど、おっしゃる意味がよくわかりました!

「関数が0を返してきたら成功だから、次の処理(メインの続きなど)に進める。でもエラー(0以外)が返ってきたら、そこで処理を止める」という、実務的な流れをイメージされているんですね。

その視点で見ると、確かに「0(成功)=進む」「0以外(失敗)=止まる」となるので、if文の「0(偽)=進まない」「0以外(真)=進む」というルールとは、感覚的に「真逆」になってしまいますよね。

なぜこんなにややこしいのか?

実は、C言語の世界には「2つの異なるルール」が同居しているからなんです。

  1. C言語の文法ルール(if文の心)
    • 0 は OFFそれ以外 は ON
    • これはコンピュータの電子回路に近い、理屈抜きの絶対的な決まりです。
  2. 関数の戻り値の慣習(プログラマーの約束事)
    • 関数を作るときに、「0を『異常なし』という意味にしよう」と人間が決めたルールです。
    • なぜなら、先ほどお話しした通り「エラーの種類(1, 2, 3…)」をたくさん作りたいからです。
混乱を解くための「書き方」のコツ

プログラマーもこの「逆転現象」で混乱しないように、関数の戻り値をチェックするときは、あえて「0以外か?」という書き方をすることが多いです。

// 何か処理をする関数が「異常(1)」を返したとすると…
int result = do_something(); 

if (result != 0) {
    printf("エラーが発生したので、ここで処理を止めます!\n");
    return 1; 
}

// 0(正常)のときだけ、ここにたどり着いて「次の処理」ができる
printf("成功したので、次の処理を実行します。\n");

こう書けば、if文のルール(0以外なら中身を実行する)に則りつつ、「エラーの時だけ{}の中に入って、エラー処理をする」という自然な流れになります。

「if文のカッコの中は、あくまでも数字の0か1かを見ているだけ」と割り切って、実際の「成功・失敗」の意味付けは == 0 や != 0 を使って言葉にするのが、うん十年ぶりの再開には一番優しいかもしれません。

47ページ・演習3-1の考察

最初に入力した数字があとに入力した数字の約数かどうかを調べるプログラム課題であったのでつくりました。解答がないので、あってるか見てください。

#include<stdio.h>
int main(void){
    int a, b;
    puts("整数2つを入力せよ。");
    printf("整数1:"); scanf("%d",&a);
    printf("整数2:"); scanf("%d",&b);

    if (a % b)
    {
        printf("%dは%dの約数ではありません。\n",b, a); 
    }else{
        printf("%dは%dの約数です。\n",b, a);
    }

    return 0;
    
}

コードを拝見しました!完璧に動作するプログラムですね。

うん十年ぶりとは思えないほど、scanfのアンパサンド(&)忘れや、セミコロンの打ち忘れもなく、正確に書けていて素晴らしいです。

特に注目すべきは、先ほどお話しした「0は偽、それ以外は真」のルールを、さっそく使いこなしている点です。

if (a % b)

この部分は、計算結果が:

  • 0以外(余りがある) → 「真」と判定 → if の中身(約数ではない)を実行
  • 0(余りがない) → 「偽」と判定 → else の方(約数である)を実行

というロジックになっています。C言語の性質を最大限に活かした、非常にスマートな書き方です。

さらなるレベルアップへのアドバイス

もし、もっと「誰が読んでも一瞬で意味がわかる」ように書くなら、以前おっしゃっていた「あえて回りくどく書く」方法も併記しておきます。

if (a % b == 0) {
    printf("%dは%dの約数です。\n", b, a);
} else {
    printf("%dは%dの約数ではありません。\n", b, a);
}

こうすると、if側が「成功(約数である)」、else側が「失敗」という配置になるので、人間の直感には少し近くなるかもしれません。でも、ご自身の書かれたコードも間違いなく「正解」です!

そうですよね!これは成功(?)側がelseに来るので少し変な感じがしました。というか最初中の分を逆に書いてしまいました。

まさにそこが、先ほどお話しした「C言語のルール」と「人間の感覚」のズレが形になった瞬間ですね!「余りが出る(0以外)」=「計算としては成功(真)」だけど、「意味としては不合格(約数ではない)」という逆転現象が起きているので、中の文を逆に書いてしまうのは非常に自然な反応です。

ご自身で「少し変な感じがした」と気づけたのは、プログラムの論理構造と、出力される言葉の整合性をしっかり意識できている証拠です。

これをより直感的に、「『余りがないこと(0であること)』を『成功(真)』として扱いたい」ときは、「!(否定)」を使ってこう書くこともあります

if (!(a % b)) {
    // 「余りがある」の反対、つまり「余りがない」ならここ!
    printf("%dは%dの約数です。\n", b, a);
} else {
    printf("%dは%dの約数ではありません。\n", b, a);
}

「!」は「~ではない」という意味なので、!(a % b) は「余りが0じゃない……ではない(=余りが0である)」という、ちょっとクイズのような読み方になります。

でも、一番スッキリするのは、やはり先ほどあなたが「回りくどいかな?」とおっしゃっていた if (a % b == 0) ですね。これなら「余りが0なら成功!」と一目で分かります。

52ページ・入れ子になったif文「else if(式)…」らへんの書き方とくに「{}」のつけ方

例題3-9をしていたんですが私は下のように3つの条件分岐のプログラムを書いたんですけどまた{}のつけ方がまたわからなくなってしまいました。

if (no == 0)
    {
        puts("その数は0です。"); 
    }else{
        if(no > 0){
            puts("その数は正の値です");
        }else{
            puts("その数は負です");
        }
    }

このように{}をelseにもifにもすべて付けました。しかし解説本はすべて省略して書いてないので複数行になる時はどうなのか気になりました。以下の分が解説本のプログラムです。

if (a == 0)
        puts("その数は0です。"); 
else if(no > 0)
        printf("その数は正の値です");
else
        printf("その数は負です\n");

ご自身の書かれたコード、構造としては完璧に正解です!

else の中にさらに if を入れる」という発想は非常に論理的です。まずは、その「{} の付け方」と「複数行のルール」についてスッキリ整理しましょう。

複数行になるときのルール

プログラミングにおいて「複数行」とは、「セミコロン(;)で終わる命令が2つ以上あるとき」を指します。

あなたのコードの else の中を見てみましょう。

else {
    if (no > 0) {
        puts("その数は正の値です");
    } else {
        puts("その数は負です");
    }
}

この中には if...else... という「大きな一つのカタマリ」(色が濃い部分)が入っていますが、実はC言語のルールでは、この if から else の終わりまでを「1つの文」とカウントします。

そのため、解説本のように else if と繋げる場合は、文法上は {} がなくても動いてしまうのです。

「else if」の正体

実はC言語には、厳密には「else if」という専用の命令はありません。
あなたの書いた「else のすぐ後に『一つの』 if のカタマリを置く」という形を、「中カッコと改行を省いて横に並べたもの」が、解説本の「{」が省略された else if の正体です。

  • あなたの書き方: 構造が丁寧に見える(入れ子構造)
  • 解説本の書き方: 3択以上の選択肢を並列に見せる(連鎖構造)

今回のように「正・負・ゼロ」の3択なら、解説本のように書いたほうがスッキリ見えますが、「すべてに {} をつける」というあなたのスタイルは、実務では「最も安全で推奨される書き方」です。

もし、1つの条件の中で2行以上の処理を書きたい場合は、以下のように必ず {} が必要になります。

if (no == 0) {
    puts("ゼロですね。");
    puts("もう一度入力しますか?"); // 2行以上なら {} は絶対!
}

解説本の「{} なし」は、ページ数の都合や見た目をスッキリさせるために多用されますが、慣れるまでは今のまま「迷ったら {} をつける」という方針で全く問題ありません。そのほうが、後でコードを書き足したときにバグが出ません。

じつはほかの言語でelseifみたいなもうくっついちゃったやつあるでしょ?あれと混同します

じつはほかの言語でelseifみたいなもうくっついちゃったやつあるでしょ?あれと混同します

わかります!PHPの elseif や Pythonの elif、VBの ElseIf など、「一つの単語」として存在している言語は多いですよね。

C言語を学んでいると、それらと混同して 「Cにも elseif という単語がある」 と勘違いしがちですが、実は違うんです。

C言語における else if の正体は、あくまでも:
else のあとに、たまたま1行だけの if 文が続いている状態」
に過ぎません。

C言語では「else の直後に1行だけ書くなら {} を省ける」というルールがあるため、たまたま else と if を横に並べて書けちゃったのが、今私たちがよく見る else if の見た目なんです。

C言語を書くときはこう考えると楽ですよ!
  • 「C言語に elseif という単語はない!」
  • else のあとに、新しい if 文を(1行ルールで)繋げているだけだ」

こう考えると、あなたの書いた {} を全部つけるスタイルは、まさに else の中身を丁寧に書いた形」 なので、実はC言語の構造を一番正しく理解している書き方と言えます。

ではelseとifの間の{をつけないといけない場合はあるのですか?

ではelseとifの間の{をつけないといけない場合はあるのですか?

C言語において、elseifの間に{(ブロック)を付ける必要はありません。else ifは繋げて書くのが一般的です。文法上、elseの直後が単一の命令なら中括弧は不要ですが、複数行の処理を実行する場合や、視認性・安全性を高める場合には{}を使用します。

  • 中括弧 { をつける必要がある(推奨される)ケース
    • else if の中(ブロック内)で、2行以上の命令を実行する場合
    • else if の中で変数定義を行う場合
    • 読み間違いを防ぎ、バグを減らしたい場合(コーディング規約で {} を必須としているプロジェクトも多い)

結論: 文法的に必須ではありませんが、予期せぬ動作を防ぐため、常に {} を付ける習慣をつけることが推奨されます。

まじで慣れてる人しかわからなくない?

まとめ:if文の中カッコ{ }を省略できるとき・できないとき

C言語には「ifやelseの直後にある『1つのユニット(文)』だけを対象にする」という基本ルールがあります。

省略できるとき

直後の処理が「1つ」だけのときです。

単純な1行のとき
if (a == 0)
    puts("ゼロです"); // 1つだけなのでOK
「else if」でつなぐとき(入れ子構造)

実はこれが一番のポイントです。C言語では「ifelse」のひとかたまりを全体で「1つの巨大なユニット」と数えます。そのため、elseの直後に別のif文が来ても、カッコなしでつなげられます。

if (a == 0)
    puts("ゼロ");
else if (a > 0)  // else と if の間の { } を省略している状態
    puts("正");
else
    puts("負");

省略できないとき(中カッコが必要なとき)

直後に処理を「2つ以上」並べたいときです。

1つの条件で複数の命令を実行したいとき
if (a == 0) {
    puts("ゼロです");
    puts("もう一度入力してください"); // 2行以上なら { } で囲って「1つの部屋」にする
}
elseの中で「if文以外の処理」も一緒にさせたいとき

ここが間違いやすいポイントです。入れ子のif文だけでなく、他の処理も並べるなら、else直後の{ }は必須です。

else {
    if (a > 0) {
        puts("正です");
    }
    puts("判定が終わりました"); // 「if」と「puts」の2つをelseに入れたいなら { } が必要
}

文法上は「1行なら省略できる」というルールがありますが、後からコードを1行書き足したときに「{ } がないせいで、追加した行がifの外に出てしまう」というバグはC言語の定番です。

「ルールとして知っておくのは大事だが、書くときは常に { } を付けるのが最も安全なスタイルです。

今日はここまで。明日は54ページから取り組みます。