
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);
#endifshowRecordDataは、点検データを表示する関数です。
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->statusrecordが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.c | initDatabase、addRecord、getRecordの実体がある |
| dataOutput.c | showRecordData、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.c | getRecordの戻り値を扱うため |
複数のファイルで同じ構造体型を使う場合は、ヘッダファイルに定義して共有します。
ただし、外部に見せたくない内部専用の構造体であれば、.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言語プログラムに応用できます。
大きなプログラムを書くときほど、ファイル分割、公開と隠蔽、関数による操作の流れを意識して設計していきましょう。
