スタック

先週書いた連結リストのコードをライブラリにして、スタックを実装してみた。

#include
#include

#include "linkedlist.h"

#define Stack Cell

Stack *Push(Stack *stack, int x);
int Pop(Stack **stack);

int main(void)
{
    Stack *stack;
    int n;

    stack = NULL;

    stack = Push(stack, 1);
    stack = Push(stack, 3);
    stack = Push(stack, 5);

    DisplayList(stack);

    n = Pop(&stack);
    printf("%d <- ", n);
    DisplayList(stack);
    FreeList(stack);

    return 0;
}

Stack *Push(Stack *stack, int x)
{
    Stack *list;
    list = Cons(x, stack);
    return list;
}

int Pop(Stack **stack)
{
    int x;
    x = Head(stack);
    return x;
}
typedef struct cell Cell;

Cell *NewCell(int val);
Cell *Cons(int val, Cell *list);
Cell *Insert(Cell *list, int pos, int val);
Cell *Delete(Cell *list, int pos);
int Head(Cell **list);
void DisplayList(Cell *list);
void FreeList(Cell *list);
#include

#include "linkedlist.h"

typedef struct cell {
    int value;
    struct cell *next;
} Cell;

Cell *NewCell(int val)
{
    Cell *data;

    data = (Cell *)malloc(sizeof(Cell));
    data->value = val;
    data->next = NULL;

    return data;
}

Cell *Cons(int val, Cell *list)
{
    Cell *data;

    data = NewCell(val);
    data->next = list;

    return data;
}

Cell *Insert(Cell *list, int pos, int val)
{
    Cell *cell1, *cell2;
    int i = 1;

    if (pos == 0) {
        list = Cons(val, list);
    } else {
        cell1 = list;
        while (i < pos) {
            cell1 = cell1->next;
            i++;
        }
        cell2 = NewCell(val);
        cell2->next = cell1->next;
        cell1->next = cell2;
    }

    return list;
}

Cell *Delete(Cell *list, int pos)
{
    Cell *cell1, *cell2;
    int i = 1;

    if (pos == 0) {
        cell2 = list;
        list = cell2->next;
        free(cell2);
    } else {
        cell1 = list;
        cell2 = cell1->next;
        while (i < pos) {
            cell1 = cell2;
            cell2 = cell1->next;
            i++;
        }
        cell1->next = cell2->next;
        free(cell2);
    }

     return list;
}

int Head(Cell **list)
{
    int val;

    val = (*list)->value;
    *list = Delete(*list, 0);

    return val;
}

void DisplayList(Cell *list)
{
    printf("%d", list->value);
    if (list->next == NULL) {
        printf("\n");
    } else {
        printf(", ");
        DisplayList(list->next);
    }
}

void FreeList(Cell *list)
{
    Cell *next = list->next;

    free(list);
    if (next != NULL) {
        FreeList(next);
    }
}

linkedlist.h で

typedef struct cell Cell;

とあるのは、構造体の中身を隠蔽して、その存在だけを公開する書き方。こうすることで、ライブラリを利用する側からは構造体を直接操作することはできなくなる(操作用の関数を使って操作する)。

実行結果:

takatoh@nightschool $ gcc -Wall main.c -c
takatoh@nightschool $ gcc -Wall linkedlist.c -c
takatoh@nightschool $ gcc -Wall main.o linkedlist.o -o stack
takatoh@nightschool $ ./stack
5, 3, 1
5 <- 3, 1

出力の1行目は値(整数)を3つ push したあとのスタックの状態。2行目の <- の左側は pop して取出した値で、右側がそのあとのスタックの状態。 期待通りに動いてるようだ。

連結リスト

連結リスト(linked list、リンクリスト)とは、構造体のメンバに「次の構造体へのポインタ」を持たせて、数珠つなぎにしたデータ構造。いちばん最後の構造体の持つポインタは NULL。ようするに Scheme のリストと同じ構造だと思って良さそう。

#include
#include

typedef struct cell {
    int value;
    struct cell *next;
} Cell;

Cell *NewCell(int val);
Cell *Cons(int val, Cell *list);
Cell *Insert(Cell *list, int pos, int val);
Cell *Delete(Cell *list, int pos);
int Head(Cell **list);
void DisplayList(Cell *list);
void FreeList(Cell *list);

int main(void)
{
    Cell *list;
    int i, head;

    list = NewCell(0);
    for (i = 1; i < 10; i++) {
        list = Cons(i, list);
    }
    puts("before:");
    DisplayList(list);
    list = Insert(list, 5, 10);
    puts("insert 10 at 5th position:");
    DisplayList(list);
    list = Delete(list, 0);
    puts("delete 0th cell:");
    DisplayList(list);
    head = Head(&list);
    printf("pick up head value (and delete the cell): %d\n", head);
    DisplayList(list);
    FreeList(list);

    return 0;
}

Cell *NewCell(int val)
{
    Cell *data;

    data = (Cell *)malloc(sizeof(Cell));
    data->value = val;
    data->next = NULL;

    return data;
}

Cell *Cons(int val, Cell *list)
{
    Cell *data;

    data = NewCell(val);
    data->next = list;

    return data;
}

Cell *Insert(Cell *list, int pos, int val)
{
    Cell *cell1, *cell2;
    int i = 1;

    if (pos == 0) {
        list = Cons(val, list);
    } else {
        cell1 = list;
        while (i < pos) {
            cell1 = cell1->next;
            i++;
        }
        cell2 = NewCell(val);
        cell2->next = cell1->next;
        cell1->next = cell2;
    }

    return list;
}

Cell *Delete(Cell *list, int pos)
{
    Cell *cell1, *cell2;
    int i = 1;

    if (pos == 0) {
        cell2 = list;
        list = cell2->next;
        free(cell2);
    } else {
        cell1 = list;
        cell2 = cell1->next;
        while (i < pos) {
            cell1 = cell2;
            cell2 = cell1->next;
            i++;
        }
        cell1->next = cell2->next;
        free(cell2);
    }

    return list;
}

int Head(Cell **list)
{
    int val;

    val = (*list)->value;
    *list = Delete(*list, 0);

    return val;
}

void DisplayList(Cell *list)
{
    printf("%d", list->value);
    if (list->next == NULL) {
        printf("\n");
    } else {
        printf(", ");
        DisplayList(list->next);
    }
}

void FreeList(Cell *list)
{
    Cell *next = list->next;

    free(list);
    if (next != NULL) {
        FreeList(next);
    }
}

Head() がちょっと難儀したので説明しておこう。
main() の中で list は連結リストの先頭へのポインタだ。Head() の呼び出しは Head(&list) というふうにしている。で、Head() の定義は、Head(Cell **list) となっていて **list はポインタへのポインタを表している。&list というのは list のアドレスを表しているので、この関数の引数は「連結リストの先頭のアドレスを保持する変数のアドレス」を渡していることになる。
なんでこういうことをしているかというと、Head() はリストの先頭の値を返すだけじゃなくて先頭のセルを削除する。ということはリストの先頭のアドレスが変わるってこと。ポインタの値を変更するには、こういうふうにポインタのポインタを渡して、関数内でアドレスを書き換えてやるわけだ。
ちなみに、Delete() もリストの先頭を削除するので、先頭へのポインタの値が変化するけど、こっちは素直に変更されたアドレスを返している(ほかに返す値がないから)。

実行結果:

takatoh@nightschool $ ./linkedlist
before:
9, 8, 7, 6, 5, 4, 3, 2, 1, 0
insert 10 at 5th position:
9, 8, 7, 6, 5, 10, 4, 3, 2, 1, 0
delete 0th cell:
8, 7, 6, 5, 10, 4, 3, 2, 1, 0
pick up head value (and delete the cell): 8
7, 6, 5, 10, 4, 3, 2, 1, 0

終了ステータス(EXIT_SUCCESSとEXIT_FAILURE)

main() 関数での return 文や、exit() 関数でプログラムを終了するときに渡す値は、終了ステータスといって、プログラムからシステム(OS)に返される。プログラムにもよるけど、通常は正常終了なら 0、何らかのエラーが起これば 1 を返すようだ。

stdlib.h には、そのためのマクロ、EXIT_SUCCESS と EXIT_FAILURE が定義されている。

ちょっと使ってみよう。次のプログラムは、名前をひとつパラメータにとってあいさつを返す。パラメータが無かったり多すぎる場合には、使い方を表示して終了する。その際、EXIT_SUCCESS または EXIT_FAILURE を終了ステータスとして返している。

#include
#include

void Usage(char prog_name[]);

int main(int argc, char *argv[])
{
    if (argc != 2) {
        Usage(argv[0]);
        exit(EXIT_FAILURE);
    }

    printf("Hello, %s!\n", argv[1]);

    return EXIT_SUCCESS;
}

void Usage(char prog_name[])
{
    printf("Usage: %s \n", prog_name);
}

実行結果。終了ステータスは、特殊な変数 $? に格納されているので、それも表示してみる。まずは正常な使い方の場合:

takatoh@nightschool $ ./greeting Andy
Hello, Andy!
takatoh@nightschool $ echo $?
0

つぎに、パラメータがない、異常終了の場合:

takatoh@nightschool $ ./greeting
Usage: ./greeting <name>
takatoh@nightschool $ echo $?
1

正常な場合には、終了ステータスとして 0 が、そうでない場合には 1 が返されているのがわかる。

関数の引数として関数ポインタを渡す

関数の引数に、関数ポインタを渡すことによって、高階関数のようなことができる。

次のプログラムは、整数の配列にフィルタをかけてその結果を表示する。Filter() 関数は奇数だけを残すために Odd()、偶数だけを残すために Even() をそれぞれ受け取ることで、ひとつの関数で 2 種類のフィルタとして動作している。

#include

#define MAX 10

int Odd(int val);
int Even(int val);
int Filter(int (*func)(int), int items[], int results[], int n);

int main(void)
{
    int items[MAX] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    int odds[MAX];
    int evens[MAX];
    int i, n;

    printf("before: ");
    for (i = 0; i < MAX; i++) {
        printf("%d ", items[i]);
    }
    printf("\n");
    n = Filter(Odd, items, odds, MAX);
    printf("odds: ");
    for (i = 0; i < n; i++) {
        printf("%d ", odds[i]);
    }
    printf("\n");
    n = Filter(Even, items, evens, MAX);
    printf("evens: ");
    for (i = 0; i < n; i++) {
        printf("%d ", evens[i]);
    }
    printf("\n");

    return 0;
}

int Odd(int val)
{
    if (val & 1) {
        return 1;
    } else {
        return 0;
    }
}

int Even(int val)
{
    if (!(val & 1)) {
        return 1;
    } else {
        return 0;
    }
}

int Filter(int (*func)(int), int items[], int results[], int n)
{
    int i, j = 0;

    for (i = 0; i < n; i++) {
        if ((*func)(items[i])) {
            results[j++] = items[i];
        }
    }

    return j;
} 
takatoh@nightschool $ gcc -Wall filter.c -o filter
takatoh@nightschool $ ./filter
before: 1 2 3 4 5 6 7 8 9 10 
odds:   1 3 5 7 9 
evens:  2 4 6 8 10

ところで、関数ポインタの型は引数名は書かなくてもいいんだな。つまり、

int (*func)(int val)

じゃなくて

int (*func)(int)

でいいということ。

staticな関数、変数

記憶クラス指定子のエントリで、関数内部の変数に static をつけるとその変数は関数を抜けても値を保持する、というようなことを書いた。

static にはもうひとつの使い方がある。
関数や、関数の外で宣言される変数に static をつけると、その関数や変数はファイルの中だけで有効になる。

これは、外部に公開したくない関数や変数をファイル内に隠蔽する効果がある。また、グローバル空間に公開されないので、関数や変数の名前が衝突することを避けることにもなる。

というわけで、ファイルの外部に公開しない関数や変数には static をつけておくのがいいようだ。

#include

#include "btree.h"

typedef struct tree {
    int value;
    struct tree *left;
    struct tree *right;
} Tree;

static Tree *Insert(Tree *root, int val);
static int Traverse(Tree *root, int ary[], int n);
static void FreeTree(Tree *root);

void BTreeSort(int ary[], const int n)
{
    int i;
    Tree *root = NULL;

    for (i = 0; i < n; i++) {
        root = Insert(root, ary[i]);
    }
    Traverse(root, ary, 0);
    FreeTree(root);
}

static Tree *Insert(Tree *root, int val)
{
    if (root == NULL) {
        root = (Tree *)malloc(sizeof(Tree));
        root->value = val;
        root->left = NULL;
        root->right = NULL;
    } else {
        if (val < root->value) {
            root->left = Insert(root->left, val);
        } else {
            root->right = Insert(root->right, val);
        }
    }

    return root;
}

static int Traverse(Tree *root, int ary[], int n)
{
    if (root->left != NULL) {
        n = Traverse(root->left, ary, n);
    }
    ary[n++] = root->value;
    if (root->right != NULL) {
        n = Traverse(root->right, ary, n);
    }

    return n;
}

static void FreeTree(Tree *root)
{
    if (root == NULL) {
        return;
    } else {
        FreeTree(root->left);
        FreeTree(root->right);
        free(root);
        return;
    }
}