6日でできる 新C言語入門|構造体・static・externを使った分割設計

データは構造体でまとめ、内部状態はstaticで守り、必要な情報だけexternで共有する。
分割設計を学ぶと、C言語プログラムは大きくなっても読みやすく保守しやすくなります。

C言語で小さなプログラムを書くときは、main.cだけにすべての処理を書いても動かせます。けれども、機能が増えてくると、1つのファイルにすべてを書き続けるのはだんだん難しくなります。

データを管理する処理、画面に表示する処理、エラー状態を扱う処理、プログラム全体の流れを決める処理が同じファイルに混ざると、どこを修正すればよいのか分かりにくくなります。また、ある処理を変更したときに、別の処理へ思わぬ影響が出ることもあります。

そこで大切になるのが、ソースコードの分割設計です。

分割設計では、役割ごとにヘッダファイルとソースファイルを分けます。たとえば、データ管理は専用のファイルにまとめ、表示処理は別のファイルにまとめ、main.cでは全体の流れだけを分かりやすく書く、という考え方です。

さらに、C言語らしい設計として、構造体、static、externを組み合わせると、より実践的な分割ができます。

構造体を使えば、関連するデータを1つの型としてまとめられます。
staticを使えば、外部から直接触らせたくない変数や関数をファイル内に隠せます。
externを使えば、別のファイルで定義された変数を必要な場所から参照できます。

この記事では、設備点検データを管理する小さなデータベースを題材にして、構造体・static・externを使った分割設計を解説します。複数ファイル構成の考え方、ヘッダファイルとソースファイルの役割、staticによる隠蔽、externによる共有、列挙型によるエラー管理まで、順番に見ていきましょう。

ソースコードを分割する目的

ソースコード分割の目的は、単にファイル数を増やすことではありません。

大切なのは、役割ごとに処理を整理することです。

目的内容
見通しを良くする1つのファイルが長くなりすぎるのを防ぐ
修正しやすくする変更する場所を見つけやすくする
影響範囲を小さくするある機能の変更が他の機能へ広がりにくくなる
再利用しやすくするデータ管理や表示処理を別のプログラムでも使いやすくする
役割を明確にするmain.c、管理処理、出力処理の担当を分けられる

小さな学習用プログラムでは、分割しなくても動きます。
しかし、実用的なプログラムでは、機能が増えたときに整理されているかどうかがとても重要です。

たとえば、データ管理の処理をbookDatabase.cへ集めておけば、登録や検索の処理を修正したいときに見る場所が明確になります。表示処理をdataOutput.cへ集めておけば、出力形式を変えたいときに管理しやすくなります。

今回のファイル構成

今回の演習では、次のようにファイルを分けます。

ファイル主な役割
main.cプログラム全体の流れを担当する
bookDatabase.hデータ管理に必要な構造体、定数、列挙型、関数宣言を公開する
bookDatabase.cデータベース本体と登録・検索処理を実装する
dataOutput.h表示処理とエラー表示処理の関数宣言を公開する
dataOutput.cデータ表示とエラー表示を実装する

main.cは、データを登録して、結果を表示する流れを担当します。

bookDatabase.cは、点検記録の保存、初期化、追加、検索を担当します。

dataOutput.cは、登録された点検記録の表示や、エラー状態の表示を担当します。

このように、データ管理と出力処理を分けることで、プログラム全体の役割が整理されます。

図:複数ファイルに役割を分ける設計

この図から分かること

この図から分かるのは、分割設計ではファイルごとに担当を分けることが大切だということです。

main.cは全体の流れだけを持ち、bookDatabase.cはデータ管理、dataOutput.cは表示処理を担当します。ヘッダファイルには、他のファイルから使ってよい構造体や関数宣言をまとめます。役割を分けることで、処理の場所が分かりやすくなります。

ヘッダファイルとソースファイルの役割

C言語では、ヘッダファイルとソースファイルを分けて使うことがよくあります。

種類役割
ヘッダファイル他のファイルから使うための宣言を書く
ソースファイル実際の処理内容を書く

ヘッダファイルには、構造体の定義、定数、列挙型、関数プロトタイプなどを書きます。

ソースファイルには、関数の本体や、内部で使う変数を書きます。

今回のbookDatabase.hには、点検記録を表す構造体や、登録件数の上限、関数プロトタイプを置きます。
bookDatabase.cには、実際の配列や登録処理、検索処理を置きます。

このように、公開する情報と内部で使う情報を分けることが、分割設計の大きなポイントです。

構造体・static・externの役割

今回のプログラムでは、構造体、static、externがそれぞれ重要な役割を持ちます。

仕組み役割
構造体1件分のデータをまとめる
staticファイル外から直接見せたくない変数を隠す
extern別ファイルにある変数を参照する
enumエラー状態などの意味を名前で表す

構造体は、点検ID、設備名、状態などを1つのデータとしてまとめます。

staticは、データベース本体の配列や登録件数を、bookDatabase.cの中だけで使えるようにします。これにより、他のファイルから直接書き換えられることを防ぎます。

externは、エラー状態のように複数ファイルで参照したい変数を共有するために使います。

enumは、エラー状態を数値だけで扱うのではなく、名前付きの状態として表すために使います。

サンプルプログラムの全体像

今回のプログラムでは、設備点検データを登録し、登録結果とエラー状態を表示します。

登録するデータには、ID、設備名、点検状態があります。
同じIDを二重に登録しようとした場合は、エラー状態になります。

処理の流れは次の通りです。

手順内容
1データベースを初期化する
2複数の点検データを登録する
3登録のたびにエラー状態を表示する
4指定したIDのデータを検索する
5検索結果を表示する

今回の構成では、main.cは流れを担当します。
データの保存や検索はbookDatabase.cが担当します。
表示はdataOutput.cが担当します。

main.cで全体の流れを書く

main.cでは、点検データを用意して、データベースの初期化、登録、表示を行います。

プロジェクト/ファイル名: Lesson69_1/main.c

#include <stdio.h>
#include "bookDatabase.h"
#include "dataOutput.h"

int main(void) {
    int i;

    char names[][NAME_LENGTH] = {
        "火災報知器",
        "非常灯",
        "避難誘導灯",
        "消火ポンプ"
    };

    char statuses[][STATUS_LENGTH] = {
        "正常",
        "点検済み",
        "要確認",
        "正常"
    };

    int ids[] = { 201, 202, 202, 203 };

    // データベースを初期化する
    initDatabase();

    // 点検データを登録する
    for (i = 0; i < 4; i++) {
        addRecord(ids[i], names[i], statuses[i]);

        printf("登録:%d %s (%s)\n", ids[i], names[i], statuses[i]);

        // 直前の処理結果を表示する
        showError();
    }

    // 登録済みのデータを検索して表示する
    for (i = 0; i < 3; i++) {
        showRecordData(getRecord(i + 201));
    }

    return 0;
}

このファイルでは、bookDatabase.hとdataOutput.hを読み込んでいます。

bookDatabase.hを読み込むことで、initDatabase、addRecord、getRecordを使えるようになります。

dataOutput.hを読み込むことで、showRecordDataとshowErrorを使えるようになります。

main.cでは、データベースの中身を直接操作していません。
登録にはaddRecord、検索にはgetRecord、表示にはshowRecordDataを使っています。

これは、データ管理の内部構造をmain.cから隠す設計です。
main.cは、何をするかという流れに集中できます。

bookDatabase.hで公開する情報をまとめる

bookDatabase.hには、他のファイルから使うために必要な情報をまとめます。

プロジェクト/ファイル名: Lesson69_1/bookDatabase.h

#ifndef _BOOK_DATABASE_H_
#define _BOOK_DATABASE_H_

#define MAX_RECORD 10
#define NAME_LENGTH 50
#define STATUS_LENGTH 30

typedef struct {
    int id;
    char name[NAME_LENGTH];
    char status[STATUS_LENGTH];
} InspectionRecord;

enum DATABASE_ERROR {
    DATABASE_OK,
    DATABASE_ERROR
};

void initDatabase(void);
int addRecord(int, const char*, const char*);
InspectionRecord* getRecord(int);

extern int DatabaseError;

#endif

このヘッダファイルでは、点検記録を表すInspectionRecord構造体を定義しています。

typedef struct {
    int id;
    char name[NAME_LENGTH];
    char status[STATUS_LENGTH];
} InspectionRecord;

InspectionRecordには、id、name、statusの3つのメンバがあります。

メンバ内容
id点検データを識別するID
name設備名
status点検状態

また、最大登録数や文字列長も定数として定義しています。

#define MAX_RECORD 10
#define NAME_LENGTH 50
#define STATUS_LENGTH 30

定数を使うことで、配列サイズの意味が分かりやすくなります。

さらに、エラー状態を表す列挙型も定義しています。

enum DATABASE_ERROR {
    DATABASE_OK,
    DATABASE_ERROR
};

DATABASE_OKは正常、DATABASE_ERRORはエラーを表します。
数値のまま扱うよりも、意味が読み取りやすくなります。

ヘッダガードで二重読み込みを防ぐ

bookDatabase.hには、次のようなヘッダガードがあります。

#ifndef _BOOK_DATABASE_H_
#define _BOOK_DATABASE_H_

/* ヘッダの内容 */

#endif

ヘッダガードは、同じヘッダファイルが複数回読み込まれても、内容が重複して定義されないようにする仕組みです。

複数ファイル構成では、同じヘッダがいろいろな場所から読み込まれることがあります。
そのため、ヘッダファイルにはヘッダガードを書く習慣をつけておくと安全です。

bookDatabase.cでデータ管理を実装する

bookDatabase.cには、点検データの配列、登録件数、初期化、登録、検索の処理を書きます。

プロジェクト/ファイル名: Lesson69_1/bookDatabase.c

#include "bookDatabase.h"
#include <string.h>

static int num_of_records = 0;
static InspectionRecord record_database[MAX_RECORD];

int DatabaseError = DATABASE_OK;

void initDatabase(void) {
    int i;

    for (i = 0; i < MAX_RECORD; i++) {
        record_database[i].id = -1;
        strcpy(record_database[i].name, "");
        strcpy(record_database[i].status, "");
    }

    DatabaseError = DATABASE_OK;
    num_of_records = 0;
}

int addRecord(int id, const char* name, const char* status) {
    if (getRecord(id) == NULL && num_of_records < MAX_RECORD) {
        record_database[num_of_records].id = id;

        strncpy(record_database[num_of_records].name, name, NAME_LENGTH - 1);
        record_database[num_of_records].name[NAME_LENGTH - 1] = '\0';

        strncpy(record_database[num_of_records].status, status, STATUS_LENGTH - 1);
        record_database[num_of_records].status[STATUS_LENGTH - 1] = '\0';

        num_of_records++;
        DatabaseError = DATABASE_OK;

        return 1;
    }

    DatabaseError = DATABASE_ERROR;

    return 0;
}

InspectionRecord* getRecord(int id) {
    int i;

    for (i = 0; i < num_of_records; i++) {
        if (record_database[i].id == id) {
            return &record_database[i];
        }
    }

    return NULL;
}

このファイルで特に重要なのは、次の2つの変数です。

static int num_of_records = 0;
static InspectionRecord record_database[MAX_RECORD];

num_of_recordsは、現在登録されている件数です。
record_databaseは、点検データを保存する配列です。

どちらにもstaticが付いています。

staticで内部データを隠す

bookDatabase.cの中にあるrecord_databaseとnum_of_recordsにはstaticを付けています。

static int num_of_records = 0;
static InspectionRecord record_database[MAX_RECORD];

関数の外側で宣言した変数にstaticを付けると、その変数は同じソースファイル内だけで使えるようになります。

つまり、record_databaseはbookDatabase.cの中では使えますが、main.cやdataOutput.cから直接触ることはできません。

これはとても重要な設計です。

変数staticの効果
record_databaseデータベース本体を外部から直接変更できないようにする
num_of_records登録件数を外部から直接変更できないようにする

もしrecord_databaseを外部から自由に変更できるようにしてしまうと、登録件数と実際のデータがずれたり、同じIDが勝手に追加されたりする可能性があります。

staticで隠しておけば、データの追加や検索はaddRecordやgetRecordを通して行う設計にできます。

図:staticでデータベース内部を隠す

この図から分かること

この図から分かるのは、staticを使うことで、データベース内部の配列や件数を外部から隠せるということです。

main.cはrecord_databaseを直接操作せず、addRecordやgetRecordなどの公開関数を通してデータを扱います。このようにすると、データの管理ルールをbookDatabase.cに集められるため、安全で保守しやすい設計になります。

addRecordで登録ルールをまとめる

addRecordは、点検データを追加する関数です。

int addRecord(int id, const char* name, const char* status)

この関数では、次の条件を確認しています。

条件内容
getRecord(id) == NULL同じIDのデータがまだ登録されていない
num_of_records < MAX_RECORD登録件数が上限に達していない

この2つの条件を満たす場合だけ、record_databaseに新しいデータを追加します。

if (getRecord(id) == NULL && num_of_records < MAX_RECORD) {
    ...
}

登録に成功したらDatabaseErrorをDATABASE_OKにします。

DatabaseError = DATABASE_OK;

登録できなかった場合は、DatabaseErrorをDATABASE_ERRORにします。

DatabaseError = DATABASE_ERROR;

このように、データ登録のルールをaddRecordにまとめることで、main.c側では細かい判定を書かずに済みます。

getRecordでID検索を行う

getRecordは、IDを指定してデータを検索する関数です。

InspectionRecord* getRecord(int id)

戻り値はInspectionRecord*です。
つまり、見つかった場合は構造体データのアドレスを返します。
見つからなかった場合はNULLを返します。

return &record_database[i];

該当するIDが見つかったときは、record_database内の要素のアドレスを返します。

return NULL;

見つからない場合はNULLを返します。

この設計により、呼び出し側は戻り値がNULLかどうかで、データが存在するかを判断できます。

dataOutput.hで表示関数を宣言する

dataOutput.hには、表示に関する関数宣言をまとめます。

プロジェクト/ファイル名: Lesson69_1/dataOutput.h

#ifndef _DATA_OUTPUT_H_
#define _DATA_OUTPUT_H_

#include "bookDatabase.h"

void showRecordData(InspectionRecord*);
void showError(void);

#endif

showRecordDataは、点検データを表示する関数です。
showErrorは、直前のデータベース処理の状態を表示する関数です。

dataOutput.hではInspectionRecord型を使うため、bookDatabase.hを読み込んでいます。

#include "bookDatabase.h"

これにより、showRecordDataの引数としてInspectionRecord*を使えるようになります。

dataOutput.cで表示処理を実装する

dataOutput.cには、点検データの表示処理とエラー表示処理を書きます。

プロジェクト/ファイル名: Lesson69_1/dataOutput.c

#include "dataOutput.h"
#include <stdio.h>

extern int DatabaseError;

void showRecordData(InspectionRecord* record) {
    if (record != NULL) {
        printf("点検ID:%d 設備名:%s 状態:%s\n",
            record->id, record->name, record->status);
    } else {
        printf("点検データが登録されていません。\n");
    }
}

void showError(void) {
    switch (DatabaseError) {
        case DATABASE_OK:
            printf("OK!\n");
            break;

        case DATABASE_ERROR:
            printf("ERROR!\n");
            break;
    }
}

showRecordDataは、InspectionRecord*を受け取ります。

void showRecordData(InspectionRecord* record)

recordがNULLでなければ、構造体ポインタとしてメンバを表示します。

record->id
record->name
record->status

recordがNULLの場合は、データが登録されていないことを表示します。

showErrorでは、DatabaseErrorの値を見て、OKまたはERRORを表示します。

externで別ファイルの変数を参照する

dataOutput.cでは、次のようにDatabaseErrorをextern宣言しています。

extern int DatabaseError;

DatabaseErrorの実体はbookDatabase.cにあります。

int DatabaseError = DATABASE_OK;

externは、変数の実体が別の場所にあることを示し、このファイルから参照できるようにするために使います。

書き方意味
int DatabaseError = DATABASE_OK;変数の実体を定義する
extern int DatabaseError;別の場所にある変数を参照する

DatabaseErrorは、bookDatabase.cの処理結果を表す共有状態です。
addRecordが成功したか失敗したかを、dataOutput.cのshowErrorで表示するためにexternを使っています。

ただし、externで共有する変数は、使いすぎに注意が必要です。
多くのファイルから自由に変更できるようにすると、どこで値が変わったのか追いにくくなります。

今回のように、エラー状態を管理する目的で範囲を絞って使うと分かりやすくなります。

図:externでエラー状態を共有する流れ

この図から分かること

この図から分かるのは、externを使うことで、別ファイルで定義された変数を参照できるということです。

DatabaseErrorの実体はbookDatabase.cにあります。dataOutput.cではextern宣言を使って、その変数を参照しています。これにより、登録処理の結果を表示処理側で確認できます。ただし、共有変数は増やしすぎず、用途を明確にして使うことが大切です。

実行結果を確認する

このプログラムでは、4件のデータ登録を試みます。

ただし、ID 202が2回出てくるため、2回目のID 202の登録はエラーになります。

実行結果例

登録:201 火災報知器 (正常)
OK!
登録:202 非常灯 (点検済み)
OK!
登録:202 避難誘導灯 (要確認)
ERROR!
登録:203 消火ポンプ (正常)
OK!
点検ID:201 設備名:火災報知器 状態:正常
点検ID:202 設備名:非常灯 状態:点検済み
点検ID:203 設備名:消火ポンプ 状態:正常

1件目のID 201は登録成功です。
2件目のID 202も登録成功です。
3件目もID 202なので、すでに登録済みのIDと重複します。そのためERRORになります。
4件目のID 203は登録成功です。

その後、ID 201、202、203のデータを検索して表示しています。

ビルドするときのポイント

複数ファイル構成では、main.cだけをコンパイルすればよいわけではありません。

今回のプログラムでは、次の.cファイルを同じプロジェクトに追加してビルドする必要があります。

ファイル必要な理由
main.cプログラムの開始地点がある
bookDatabase.cinitDatabase、addRecord、getRecordの実体がある
dataOutput.cshowRecordData、showErrorの実体がある

ヘッダファイルは宣言を提供しますが、関数の本体は.cファイルにあります。
そのため、bookDatabase.cやdataOutput.cがプロジェクトに含まれていないと、リンク時に関数が見つからないエラーになります。

Visual Studioで作成する場合は、Lesson69_1プロジェクトにmain.c、bookDatabase.c、dataOutput.cを追加し、bookDatabase.hとdataOutput.hも同じプロジェクト内で参照できるようにしておきます。

Visual Studioでのビルド

1.「SDLチェック」を「いいえ」にします。
2.メニューの「ビルド」から「ソリューションのビルド」を実行します。

staticとexternの使い分け

staticとexternは、どちらも複数ファイル構成で重要ですが、役割は反対に近いです。

指定子役割今回の例
staticファイル内だけで使えるようにするrecord_database、num_of_records
extern別ファイルの変数を参照するDatabaseError

staticは、見せたくないものを隠すために使います。
externは、必要なものを別ファイルから参照するために使います。

今回の設計では、データベース本体であるrecord_databaseはstaticで隠しています。
一方、エラー状態であるDatabaseErrorは、showErrorで表示するためにexternで参照しています。

このように、公開するものと隠すものを分けることが、C言語の分割設計ではとても重要です。

構造体をヘッダファイルに置く理由

InspectionRecord構造体は、bookDatabase.hに定義しています。

これは、main.cやdataOutput.cでもInspectionRecord型を知る必要があるためです。

ファイルInspectionRecordが必要な理由
bookDatabase.cデータベース配列を作るため
dataOutput.c受け取った点検データを表示するため
main.cgetRecordの戻り値を扱うため

複数のファイルで同じ構造体型を使う場合は、ヘッダファイルに定義して共有します。

ただし、外部に見せたくない内部専用の構造体であれば、.cファイルの中だけに定義することもあります。

今回のように、表示処理でも構造体のメンバを使う場合は、ヘッダファイルに構造体を置くと扱いやすくなります。

列挙型でエラー状態を読みやすくする

エラー状態を単なる数値で扱うと、0や1が何を意味するのか分かりにくくなります。

そこで、enumを使って名前を付けます。

enum DATABASE_ERROR {
    DATABASE_OK,
    DATABASE_ERROR
};

これにより、DatabaseErrorに設定する値の意味が分かりやすくなります。

DatabaseError = DATABASE_OK;
DatabaseError = DATABASE_ERROR;
意味
DATABASE_OK正常
DATABASE_ERRORエラー

数値ではなく名前で状態を表すことで、プログラムの読みやすさが上がります。

showErrorでも、switch文で状態ごとの表示を分けています。

switch (DatabaseError) {
    case DATABASE_OK:
        printf("OK!\n");
        break;

    case DATABASE_ERROR:
        printf("ERROR!\n");
        break;
}

strncpyを使うときの注意点

今回のaddRecordでは、文字列コピーにstrncpyを使っています。

strncpy(record_database[num_of_records].name, name, NAME_LENGTH - 1);
record_database[num_of_records].name[NAME_LENGTH - 1] = '\0';

strncpyは、指定した文字数までコピーできるため、配列のサイズを意識したコピーに使えます。

ただし、コピー元の文字列が長い場合、終端文字が自動で入らないことがあります。
そのため、最後の要素に終端文字を入れています。

record_database[num_of_records].name[NAME_LENGTH - 1] = '\0';

statusも同じように、最後に終端文字を入れています。

このように、固定長のchar配列に文字列をコピーするときは、配列の範囲外へ書き込まないように注意が必要です。

getRecordがポインタを返す意味

getRecordは、見つかったデータのアドレスを返します。

InspectionRecord* getRecord(int id)

戻り値がInspectionRecord*なので、呼び出し側は構造体ポインタとして受け取ります。

showRecordDataでは、そのポインタを使ってメンバを表示しています。

record->id
record->name
record->status

構造体そのものをコピーして返すのではなく、配列内にあるデータのアドレスを返しているため、効率よくデータを扱えます。

ただし、ポインタを返す設計では、返した先のデータが有効であることが大切です。
今回のrecord_databaseはstaticな配列なので、関数が終わってもデータは残っています。そのため、getRecordから配列要素のアドレスを返しても利用できます。

分割設計で意識したいルール

複数ファイル構成では、次のようなルールを意識すると整理しやすくなります。

ルール内容
公開する情報はヘッダファイルに置く他ファイルから使う構造体や関数宣言を書く
実装は.cファイルに置く実際の処理内容を書く
内部データはstaticで隠す直接触らせたくない変数を守る
共有が必要な変数だけexternで参照する共有範囲を広げすぎない
main.cは全体の流れに集中する細かい処理は専用ファイルへ任せる
関数を通してデータを操作するデータ管理のルールを1カ所に集める

特に、内部データを直接触らせないことは大切です。

record_databaseをmain.cから直接変更できるようにしてしまうと、登録件数との整合性が崩れる可能性があります。addRecordを通して登録する設計にすれば、重複チェックや件数チェックを必ず通せます。

ファイルごとの責任を整理する

今回の設計を、責任という観点で整理すると次のようになります。

ファイル責任
main.cデータ登録と表示の流れを制御する
bookDatabase.hデータ管理機能の使い方を公開する
bookDatabase.cデータの保存、初期化、追加、検索を担当する
dataOutput.h表示機能の使い方を公開する
dataOutput.c点検データとエラー状態の表示を担当する

責任が分かれていると、修正するときの見通しがよくなります。

たとえば、登録できる最大件数を変えたい場合は、bookDatabase.hのMAX_RECORDを確認します。
登録ルールを変えたい場合は、bookDatabase.cのaddRecordを確認します。
表示形式を変えたい場合は、dataOutput.cのshowRecordDataを確認します。

このように、どこを見ればよいかが分かりやすくなります。

分割設計をさらに発展させるなら

今回のプログラムは、構造体、static、extern、enumを使った分割設計の基本形です。

ここからさらに発展させるなら、次のような改良が考えられます。

発展例内容
エラーの種類を増やす重複エラー、件数上限エラーなどを分ける
削除機能を追加するIDを指定してデータを削除する
更新機能を追加する既存データの状態を変更する
ファイル保存を追加する点検データをテキストやバイナリで保存する
表示形式を切り替える一覧表示、詳細表示などを分ける
static関数を使う内部専用の補助関数を隠す

たとえば、重複エラーと登録上限エラーを区別したい場合は、enumに状態を追加できます。

enum DATABASE_ERROR {
    DATABASE_OK,
    DATABASE_DUPLICATE_ID,
    DATABASE_FULL
};

このようにすると、エラー表示もより具体的にできます。

実用的なC言語設計へ進むために

構造体・static・externを組み合わせると、C言語のプログラムはかなり実用的な形に近づきます。

構造体は、関連するデータを1件分のまとまりとして表現します。
staticは、外部から直接触らせたくない内部データを守ります。
externは、必要な共有状態を別ファイルから参照できるようにします。

分割設計では、どの情報を公開し、どの情報を隠すのかを考えることが大切です。

すべてを外部から見えるようにすると、便利なようで管理が難しくなります。反対に、内部データを適切に隠し、関数を通して操作する設計にすると、安全で読みやすいプログラムになります。

今回のLesson69_1では、データ管理、表示処理、全体制御を分けながら、staticとexternの使い方を確認しました。
この考え方は、在庫管理、会員管理、成績管理、設定データ管理など、さまざまなC言語プログラムに応用できます。

大きなプログラムを書くときほど、ファイル分割、公開と隠蔽、関数による操作の流れを意識して設計していきましょう。