本文へスキップ
BecomeCoder

C言語コース · 第13章 まとめ・実践 · レッスン49

連結リストを組み立てる ― 構造体とmallocの総合演習

ブラウザで完結

導入

第7章の構造体と、第8章の mallocfree を組み合わせると、実行中に必要な数だけノードを増やせる「連結リスト」を自分の手で組み立てられます。ここでは、追加(append)・表示(print)・先頭削除(delete)の3つの操作をひとつのプログラムにまとめて、動くところまで作ります。

説明

連結リストの1つ分の要素をノードと呼びます。ノードは「値」と「次のノードへのポインタ」を持つ構造体です。最後のノードの next は、もう次がないことを表す NULL にします。

flowchart LR
  head["head(先頭を指すポインタ)"] --> n1["Node<br/>value=10<br/>next→"]
  n1 --> n2["Node<br/>value=20<br/>next→"]
  n2 --> n3["Node<br/>value=30<br/>next→"]
  n3 --> n4["Node<br/>value=40<br/>next→"]
  n4 --> nul["NULL(終わりの印)"]

ノードを作る・末尾に追加する・全件表示する・先頭を削除する・すべて解放する、という5つの関数に処理を分けて実装します。appenddelete_first は「head そのもの(どのノードが先頭か)」を書き換える必要があるので、headアドレスNode **、ポインタのポインタ)を受け取ります。

#include <stdio.h>
#include <stdlib.h>

typedef struct Node {
    int value;
    struct Node *next;
} Node;

// 新しいノードをmallocで確保して作る
Node *create_node(int value) {
    Node *node = (Node *)malloc(sizeof(Node));
    node->value = value;
    node->next = NULL;
    return node;
}

// リストの末尾に値を追加する(headを書き換えることがあるので Node** で受け取る)
void append(Node **head, int value) {
    Node *new_node = create_node(value);
    if (*head == NULL) {
        *head = new_node;
        return;
    }
    Node *cur = *head;
    while (cur->next != NULL) {
        cur = cur->next;
    }
    cur->next = new_node;
}

// 先頭から辿って全件表示する
void print_list(Node *head) {
    Node *cur = head;
    while (cur != NULL) {
        printf("%d ", cur->value);
        cur = cur->next;
    }
    printf("\n");
}

// 先頭のノードを1つ削除する
void delete_first(Node **head) {
    if (*head == NULL) return;
    Node *old_head = *head;
    *head = old_head->next;
    free(old_head);
}

// 残っているノードをすべて解放する
void free_list(Node *head) {
    Node *cur = head;
    while (cur != NULL) {
        Node *next = cur->next;
        free(cur);
        cur = next;
    }
}

int main(void) {
    Node *head = NULL;

    append(&head, 10);
    append(&head, 20);
    append(&head, 30);
    append(&head, 40);

    printf("追加後: ");
    print_list(head);          // 10 20 30 40

    delete_first(&head);

    printf("先頭を削除後: ");
    print_list(head);          // 20 30 40

    free_list(head);           // 使い終わったら必ず全部解放する

    return 0;
}

いくつか押さえておきたい点があります。

  • append(&head, 10)&head は「head という変数そのものの場所」です。append の中で *head = new_node; と書くことで、呼び出し元の head を直接書き換えられます。もし Node *head のまま受け取ると、関数の中だけのコピーを書き換えることになり、呼び出し元には反映されません。
  • free_list で全部解放したあとの head は、指す先が解放済みのメモリになっています(ダングリングポインタ)。この後うっかり使わないよう、実務では解放後に head = NULL; としておくのが安全です。
  • 一度 free したノードを、リストからつなぎ直す前にもう一度 free してしまう(二重解放)や、free し忘れる(メモリリーク)は、C でよく起きるバグです。「1つ malloc したら、どこかで必ず1回だけ free する」を徹底しましょう。

やってみよう

上のコードを実行し、追加・削除でリストの中身がどう変わるか確かめましょう。append(&head, 50); をもう1行足したり、delete_first(&head); をもう1回呼んだりして、表示がどう変わるか試してください。

演習

1, 2, 3, 4, 5 を持つ5つのノードを連結リストに追加してください。次に delete_first を2回呼んで先頭の2件(12)を削除し、残ったリストを リスト: に続けて表示し、続けて残りの値の合計を 合計= に続けて表示してください(結果は リスト: 3 4 5 合計=12)。

ヒント1を見る

for (int i = 1; i <= 5; i++) append(&head, i); で5件追加し、delete_first(&head); を2回呼びます。

ヒント2を見る

合計は head から辿る while ループcur->value を足し込みます(int sum = 0; から始めて cur = cur->next で進める)。

実際に動かしてみよう

下のエディタにCを書いて「コンパイル & 実行」を押すと、ブラウザ内で本物のCコンパイラ(clang + dlmalloc)がその場でコンパイルして実行し、出力が表示されます。構造体・共用体・malloc/free・関数ポインタ・ファイル入出力などがそのまま動きます。本文の例を書き換えて試しながら進めましょう(初回だけコンパイラの読み込みに時間がかかります。以降はキャッシュされます。この回は標準入力(scanf)は使わず、値はコードに直接書きます)。

C/C++ — ブラウザ内で本物のコンパイラで実行

ブラウザ内で本物のC/C++コンパイラ(clang + libc++ / WebAssembly)を動かします。構造体・malloc/free・std::string・vector・クラス・テンプレート・STL などがそのまま実行できます(初回のみコンパイラの読み込みに時間がかかります。以降はブラウザにキャッシュされます)。
スクロールして表示された時点でも自動で読み込まれます。