C言語基礎|8章のまとめ

「マクロ・ソート・列挙体・再帰・入出力――“道具箱”が一気に増える、第8章総復習!」

第8章は“C言語で作れること”が急に広がる章

第8章は、いろいろなミニプログラムを題材にしながら、C言語の実戦力をぐっと上げる章でした。

  • コードを短く強くする:関数形式マクロ、コンマ演算子
  • データを扱う力:ソート、列挙体
  • 考え方の幅:再帰
  • 入出力の基礎体力:getchar、EOF、文字コード、エスケープシーケンス

ここでは「何を学んだか」を表で整理しつつ、章末のサンプルも 別のシンプルな例に差し替え、表示メッセージも 別の日本語にしてまとめ直しますね。

第8章で学んだこと一覧(全体の見取り図)

第8章のトピックと要点

テーマひとことでできるようになること
関数形式マクロ引数つきで展開されるマクロ型をまたいだ短い記述、処理の共通化
マクロの副作用引数が複数回評価される危険a++ などを安全に扱う意識がつく
コンマ演算子a と b を順に評価し、値は b複数処理を「1つの式」として書ける
ソート(バブルソート)隣同士を比べて入れ替える配列の並べ替えの基本が分かる
列挙体限られた値に名前を付ける選択肢を安全・読みやすく表現できる
名前空間enumタグと変数名は別枠同じ綴りでも区別できる理由が分かる
再帰同じ関数を自分の中から呼ぶ分割統治や木構造の入口を理解できる
文字入出力getchar と EOF1文字ずつ処理するループが書ける
文字コード文字は整数'0' と 0 の違いが腹落ちする
エスケープ見えない文字を表す\n や " を正しく扱える

表の説明
章全体は「小技」ではなく、実装の幅を増やすための“道具セット”です。どれも後の章や実務で何度も登場します。

関数形式マクロ:置換ではなく展開で“式”を作る

オブジェクト形式マクロは単純置換、関数形式マクロは 引数つきで展開されます。

マクロの種類の違い

種類書き方の例何が起きる?
オブジェクト形式マクロdefine PI 3.14159PI が 3.14159 に置換される
関数形式マクロdefine max2(a,b) ...max2(式,式) が定義通りに展開される
引数なし関数形式マクロdefine alert() ...alert() が展開される(呼び出し風)

表の説明
関数形式マクロは「呼び出しっぽく書けるけど関数ではない」のがポイントです。コンパイル前の段階で展開されます。

命令の書式:define

  • 書式(オブジェクト形式)
    #define マクロ名 置換テキスト
  • 書式(関数形式)
    #define マクロ名(仮引数...) 置換テキスト

何をする命令?
define はプリプロセッサへの指示で、ソースコードを翻訳(コンパイル)する前に、指定した規則で置換・展開します。

副作用:マクロは“引数が何回評価されるか”に注意

関数形式マクロの落とし穴は、引数が展開後に 複数回出てくると、そのぶん評価されることです。

副作用が起きる典型例(イメージ)

定義:sqr(x) は (x) * (x)
呼出:sqr(a++)
展開: (a++) * (a++)
結果:a が2回増える

図の説明
関数なら引数は一度だけ評価して仮引数に渡しますが、マクロは展開された式として評価されるため、同じ引数が複数回出るとそのまま複数回動きます。

コンマ演算子:複数の処理を“1つの式”としてまとめる

コンマ演算子 a, b は a を評価して捨て、b の値を結果にする演算子です。

コンマ演算子のルール

評価順式全体の値
a, ba → bb の評価結果

表の説明
if の本体などで「式1個」を求められる場所に、複数の処理を置きたいときに役立ちます。マクロと相性がいいです。

ソート:バブルソートは“比較と交換”の繰り返し

ソートは、データを昇順・降順に並べ替えること。バブルソートは隣同士を比べて入れ替える素朴な方法です。

バブルソートの1パスのイメージ(昇順)

後ろから見ていき、逆なら交換
…を繰り返すと、小さい値が前へ寄っていく

図の説明
本文の例では「末尾から先頭へ走査」して、最小値が先頭に寄っていく説明でした。走査方向は実装により変えられますが、考え方は同じです。

列挙体:限られた値に“名前”を付ける

列挙体は「選択肢の集合」を作るのが得意です。数値そのものではなく 名前で表現できて読みやすくなります。

列挙体の基本要素

用語意味
列挙体タグenum RGB列挙型の識別のためのタグ
列挙定数Red, Green, Blue個々の値(中身は整数)
型名enum RGBこれが型名(RGB だけでは型名にならない)

表の説明
列挙体タグ RGB は「タグ」、型として使うときは enum RGB と書きます。ここが最初の注意点です。

名前空間:enumタグ名と変数名は“別枠”

C言語では、識別子は用途ごとに別の名前空間に所属します。
そのため、列挙体タグと変数名が同じ綴りでも区別される場合があります。

名前空間のイメージ

enum のタグ名  と  変数名  は別の箱に入っている
同じ綴りでも衝突しないことがある

図の説明
現実のコードでは混乱しやすいので、同名にしないのが無難ですが、「区別される理由」を知っておくと読み解きが楽になります。

再帰:同じ関数を呼び出して問題を小さくする

再帰は「自分と同じ関数を呼ぶ」ことで、問題を小さく分けながら解きます。
ただし、終了条件がないと無限に呼び続けるので、必ず 止まる条件が必要です。

再帰のチェックポイント

観点何を見る?
終了条件どこで止まる?
分割1回の呼び出しで問題が小さくなる?
戻り戻りながら答えを組み立てられる?

表の説明
階乗は再帰の入門にちょうどよい例ですが、実務では木構造・探索・分割統治で本領を発揮します。

文字入出力:getchar と EOF、そして文字コード

getchar は1文字読み込み、読み込めないと EOF を返します。
さらに C言語では、文字は整数(文字コード)として扱われます。

getchar と EOF の要点

項目内容
getchar の戻り値int(EOF を扱うため)
EOF入力終了やエラーを表す特別な値(多くは負)
典型ループwhile ((ch = getchar()) != EOF)

表の説明
char ではなく int で受けるのは、EOF と区別するためです。ここは超重要です。

数字文字の性質:'0' からの差で 0〜9 にできる

数字文字 '0'〜'9' は 1ずつ増える関係が保証されるので、ch - '0' で 0〜9 に変換できます。

変換のイメージ

'0' - '0' = 0
'5' - '0' = 5
'9' - '0' = 9

図の説明
文字コードの値そのもの(例:48)を直接使わずに済むので、環境が変わっても動きやすい書き方になります。

エスケープシーケンス:引用符や改行を安全に書く

単一引用符や二重引用符は、文字列や文字定数の中でルールが変わります。

引用符とエスケープの要点

目的使う表記
文字列の中で " を書く"
文字定数の中で ' を書く'
改行\n
逆斜線そのもの\

表の説明
一番よくやるミスは「文字列の中の " をそのまま書く」ことです。" を習慣にすると安心です。

サンプルプログラム

まとめ例1:列挙体で「メニュー選択」を表示する

#include <stdio.h>

enum Menu { Coffee, Tea, Water };

int main(void)
{
    int sel;

    printf("0~2を入力してください:");
    scanf("%d", &sel);

    printf("選んだ飲み物は ");
    switch (sel) {
        case Coffee: printf("コーヒー"); break;
        case Tea:    printf("お茶");     break;
        case Water:  printf("水");       break;
        default:     printf("未対応");   break;
    }
    printf(" です。\n");

    return 0;
}

この例で確認できること

  • enum Menu の列挙定数は 0,1,2 になる
  • switch の case ラベルに列挙定数を使うと読みやすい
  • default を入れると不正入力にも対応しやすい

登場する命令の役割

  • enum:列挙型を宣言するためのキーワード
  • switch / case / break:値に応じて分岐する構文
  • printf / scanf:入出力(標準出力・標準入力)

まとめ例2:マクロ+コンマ演算子+getchar+EOF+数字処理を一気に使う

数字の合計を出しつつ、改行が来たら区切り線を出すプログラムです。

プロジェクト名:chap8-15-2 ソースファイル名:chap8-15-2.c

#include <stdio.h>

// 区切り線を出す(引数なし関数形式マクロ)
#define separator() (puts("-----"))

// 文字cを表示して改行(コンマ演算子で1つの式にする)
#define putchar_ln(c) (putchar(c), putchar('\n'))

int main(void)
{
    int ch;
    int sum = 0;  // 入力中の数字の合計

    puts("文字を入力してください(終了は Ctrl+D または Ctrl+Z)。");

    while ((ch = getchar()) != EOF) {
        if (ch >= '0' && ch <= '9')
            sum += ch - '0';

        if (ch == '\n') {
            separator();
        } else {
            putchar_ln(ch);
        }
    }

    printf("入力された数字の合計は%dです。\n", sum);

    return 0;
}

まとめ例2で使った要素

要素どこで使った?何がうれしい?
引数なし関数形式マクロseparator()呼び出しっぽく書けて読みやすい
コンマ演算子putchar_ln(c)複数処理を1つの式にまとめられる
getchar と EOFwhile ループ1文字ずつ読み、終端で止まれる
数字文字判定ch >= '0' && ch <= '9'文字→数値変換の前に安全確認
ch - '0'sum += ...可搬性を保った変換

表の説明
この1本で「第8章の要点が何度も出てくる」構成になっています。短いのに学びが濃いです。