サイトマップ / C言語講座出入り口総目次目次:ヒープ領域双方向リスト(ヘッダ)||ソース1||ソース2

青い直線

双方向リスト(ソース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言語講座−それ自体コンパイルできる教材を使った講座です−

青い直線

サイトマップ / C言語講座出入り口総目次目次:ヒープ領域双方向リスト(ヘッダ)||ソース1||ソース2