サイトマップ / C言語講座>出入り口>総目次>目次:ヒープ領域>双方向リスト(ヘッダ)||ソース1||ソース2
/* 今日は、ヘッダファイルと分割コンパイルについて学びます。標準ライブラリ関数を使うには、関数リファレンスを見て、その関数に必要なヘッダファイルをインクルードします。
ヘッダファイルには、データ型の宣言( 例:typedef unsigned size_t; )、マクロ( 例:#define BUFSIZE 1024 )、関数のプロトタイプ宣言( 例:int scanf(const char *, ...); )、などが書かれています。プログラムを作る時、型の宣言、マクロ、プロトタイプ宣言などを、自作のヘッダファイルに記述することができます。
それをインクルードするには、下記のように " " で囲みます。ヘッダファイルは、ソースファイルと同じディレクトリ ( フォルダ ) に入れて下さい。
#include "myheader.h"
話は変わって、Cでは分割コンパイルが可能です。プロジェクトが大きくなってくると、複数の人が参加します。複数の人が一つのファイルを編集することはできないので、当然分割コンパイルになります。ここで、話を分かりやすくするために、例え話をします。
夏祭りで、シチューを500人分作るとします。当然、共同作業になります。夏祭りが近づいてきたら、打ち合わせをします。打ち合わせで、役割分担を決めます。シチューを作る役割になった人は、人参はどのくらいの大きさに切るか、タマネギはどの向きからどのくらいの大きさに切るか、決めておく必要もあります。
役割分担に当たるものが、分割コンパイルです。関連のある役割を受け持つ人同志で、ヘッダファイルを共有します。そのことにより、どういうものを作れば良いかがわかります。また、規格外のものを作ろうとすれば、コンパイラが警告を発したり、エラーになります。ヘッダファイルはパブリックなものです。
分割コンパイルにおいて、他のファイルの変数を参照したい場合、extern を付ければ良いことは、以前に取り上げました。
一方、同じファイルの複数の関数で、共有したい変数もあります。この場合も { } の外で宣言します。そうすると、たまたま名前が一致したりして、他の人が自分のファイルの中で extern を付けて、その変数を書き変えてしまう危険性があります。それを防ぐため、その変数を宣言する時に static を付けることを勧めます。
静的変数はコンパイル時にメモリが割り当てられ、0に初期化され、メモリのサイズが変化しません( 静的 )。静的変数が { } の中で static を付けて宣言されれば、その { } の中でのみ有効です。
静的変数はプログラムを終えるまで存在し続け、次に、その { } の中に戻って来た時、またその変数を読み書きできます。
静的変数が { } の外で宣言された時は、その宣言がある行から、そのファイルの終わりまで、有効です。
自動変数も { } の中で宣言されますが、自動変数は { } から抜けると消滅します。 */
/* 今日のソースプログラムでは双方向リストを使って、データの追加、削除、一覧表示を行います。また、データはファイルに保存し、次回にプログラムを実行する時、ファイルからデータを読み込みます。
双方向リストは、構造体を使って実現します。構造体は必要になった時に、malloc( ) でヒープ領域に確保します。
この構造体のメンバには、一つ前の構造体を指すポインタと、一つ後ろの構造体を指すポインタを含みます。これらのポインタをたどることにより、双方向へ移動することができます。
また、ポインタをつなぎ変えることにより、任意の位置に新しいデータを挿入したり、削除できます。
双方向リストは柔軟なデータ構造をしています。今回の双方向リストは下記の構造をしています。ヘッダファイル "Myhead.h" で定義されています。
#define MAX_ITEM_NAME 40 typedef struct item item; // item という構造体に対して // item というデータ型を宣言 struct item { char name[MAX_ITEM_NAME + 1]; // アイテム名 unsigned long prise; // 値段 }; typedef struct shop shop; // shop という構造体に対して // shop というデータ型を宣言 struct shop { item item; shop *previous; shop *next; };
item というデータ型の構造体を含む、shop というデータ型の構造体で、双方向リストを実現しています。 */
/* 今回のソースプログラムは、メインルーチンのあるこのファイルと、ファイル関係の関数をまとめたFile.c をコンパイルして下さい。ヘッダファイル "Myhead.h" も同じディレクトリ ( フォルダ ) に置いて下さい。
プログラムの流れについて簡単に説明します。最初に同じディレクトリ ( フォルダ ) で、"item.txt" という名前のファイルを読み込みモードで開きます。開くのに成功したら、データを双方向リストに読み込みます。もし開くのに失敗したら ( このプログラの1回目の実行では、ファイルがないので失敗します ) 、プログラムの終了時に同じ名前のファイルを書き込みモードで開き、プログラム実行時に入力されたデータを保存します。
次いで、下記の表示が現れます。アルファベットを入力すると、それを担当している関数へ制御が移ります。
'n' 次のアイテム 'p' 前のアイテム 's' アイテム一覧 'a' アイテム追加 'r' アイテム削除 'e' 終了 コマンドを入力して下さい
今回の自作の関数の名前と、何をする関数かを下記に簡単に示します。
"Mem7.c" の関数 shop *AddItem(shop *present); // アイテムの追加 shop *RemoveItem(shop *present); // アイテムの削除 void FreeAll(shop *head); // malloc( ) で確保したメモリの終了時における解放 void ShowAllItem(shop *head); // アイテムの一覧表示 void ShowPresentItem(shop *present); // 現在対象になっているアイテムの表示 void main(void); // コマンドの処理 File.c の関数 shop *ReadData(shop * head, FILE *fp); // データの読み込み void *WriteItem(FILE *fp, shop *head); // データの書き込み
後は、ソースプログラムの中のコメントを読んで下さい。 */
#include <stdio.h> #include <stdlib.h> #include <string.h> #include "Myhead.h" /* "Myhead.h" は同じディレクトリ ( フォルダ ) に置く */ /* presentの指すアイテムの後ろへアイテムを追加 */ shop *AddItem(shop *present) { shop *p, *new; char name[MAX_ITEM_NAME + 1]; unsigned long prise; p = present->next; /* データを挿入するために p に保存 */ /* 新しいアイテムのためのメモリを確保 */ if (( new = (shop *)malloc(sizeof(shop))) == NULL) { fprintf(stderr, "メモリ不足です。\n"); exit (2); /* メモリ確保に失敗したらプログラム終了 */ } printf("アイテム名を入力して下さい\t"); scanf("%s", name); printf("値段を入力して下さい\t"); scanf("%lu", &prise); strcpy((*new).item.name, name); /* 入力データのコピー */ (*new).item.prise = prise; new->previous = present; /* presentの後ろに */ present->next = new; /* newがくるように */ new->next = p; /* ポインタのつなぎ変え */ if (p != NULL) /* p がリストの途中を指していたら */ p->previous = new; /* p の前に new が入る */ return (new); /* 挿入したアイテムへのポインタを返す */ } /* presentの指すアイテムの削除 */ shop *RemoveItem(shop *present) { shop *next, *previous; previous = present->previous; /* ポインタの保存 */ next = present->next; /* ポインタの保存 */ if (previous != NULL) previous->next = next; /* ポインタのつなぎ変え */ if (next != NULL) next ->previous = previous; /* ポインタのつなぎ変え */ free(present); /* メモリの解放 */ return(next); /* 次のアイテムへのポインタを返す */ } /* メモリの解放 ( プログラム終了時の処理 ) */ void FreeAll(shop *head) { shop *p; while (head != NULL) { p = head->next; /* 次のアイテムへのポインタを保存してから */ free(head); /* メモリを解放する */ head = p; /* 次のループに備える */ } } /* 全てのアイテムを表示 */ void ShowAllItem(shop *head) { head = head->next; /* head にはゴミが入っている */ while (head != NULL) { printf("アイテム:%s\t値段:%lu\n", (*head).item.name, (*head).item.prise); head = head->next; } printf("\n"); } /* 現在のアイテムを表示 */ void ShowPresentItem(shop *present) { if (present != NULL) /* データが存在したら */ printf("対象のアイテム:%s\t値段:%lu\n", /* 表示する */ (*present).item.name, (*present).item.prise); } void main(void) { FILE *fp, *nfp; char *filename = "item.txt"; shop *head, *present; int c; if ((head = (shop *)calloc(1, sizeof(shop))) == NULL) { fprintf(stderr, "メモリが足りません\n"); exit(2); } head->previous = NULL; if ((fp = fopen(filename, "r")) == NULL) { fprintf(stderr, "ファイル: %s は存在しません!\n", filename); present = head; /* ファイルが存在しない時の処理 */ present->next = NULL; } else /* ファイルが存在したらデータを読み込む */ /* ポインタの付け替えは ReadData( ) がやってくれる */ present = ReadData(head, fp); for ( ; ; ) { ShowPresentItem(present); printf("'n' 次のアイテム\n"); printf("'p' 前のアイテム\n"); printf("'s' アイテム一覧\n"); printf("'a' アイテム追加\n"); printf("'r' アイテム削除\n"); printf("'e' 終了\n"); fflush(stdin); printf("\nコマンドを入力して下さい\t"); c = getchar( ); switch (c) { case 'n': /* 次のアイテムへ移動 */ if (present->next != NULL) present = present->next; else printf("アイテムがありません\n"); break; case 'p': /* 前のアイテムへ移動 */ if (present->previous != NULL) present = present->previous; else printf("アイテムがありません\n"); break; case 'a': /* アイテムの追加 */ present = AddItem(present); break; case 'r': /* アイテムの削除 */ present = RemoveItem(present); break; case 's': /* 全アイテムの表示 */ ShowAllItem(head); break; case 'e': /* 終了 */ /* ファイルが存在しなければ */ if ((nfp = freopen(filename, "w", fp)) == NULL) { fprintf(stderr, " ファイル: %sを新規作成します。\n", filename); /* 改めて書き込みモードで開く */ if ((nfp = fopen(filename, "w")) == NULL) { fprintf(stderr, "ファイル作成に失敗しました。\n"); exit(2); } } WriteItem(nfp, head); /* ファイルへデータの書き込み */ fclose(nfp) ; FreeAll(head); /* メモリの解放 */ exit(0); /* シェルへ */ default: /* 一致する文字がなければ */ break; /* 何もしない */ } } } |
/* (C) 2000- YFプロ. All Rights Reserved. */ 提供:C言語講座−それ自体コンパイルできる教材を使った講座です−