はじめに
以下の前回の記事では、構造体をレコード形式で管理して、配列とrealloc を使った可変長リストで扱う方法について書きました。
クラウド開発環境PaizaCloudクラウドIDEでHello World(C言語ポインタ 発展編)
いったん前回で一段落させようと思ったのですが、もう一段階進めたほうがいいかなと思ったので、今回は「延長編」としました。
今回は一歩進めて、リンクリスト(連結リスト) で“つながるデータ構造”を使ってみます。
配列ではデータの追加・削除のたびに要素を動かす必要がありますが、 リンクリストでは 前後のポインタをつなぎ変えるだけ でデータ構成を変えられます。
今回のサンプルコード:双方向リンクリストで挿入と削除
今回のサンプルコードです。
※注意事項としてですが、初学者の方には難易度が高いかもしれないです。
構造体の定義方法や各メンバへのアクセス方法、関数でのデータ受け渡しやアドレス操作などです。その場合は別途C言語の基礎を習得してから見ていただくか、補足的な内容も書いたので何となく流れだけでも見ていただければと思います。
あと、前回と前々回も参考にしていただければと思います。
クラウド開発環境PaizaCloudクラウドIDEでHello World(C言語ポインタ 導入編)
クラウド開発環境PaizaCloudクラウドIDEでHello World(C言語ポインタ 発展編)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// リンクリストのレコード構造体
// (会員データのような内容です)
typedef struct Node {
int id; // ID
char name[50]; // 名前
int age; // 年齢
struct Node *prev; // 前のデータへのポインタ
struct Node *next; // 次のデータへのポインタ
} Node;
// ノードを作成
Node* create_node(int id, const char *name, int age) {
Node *n = (Node*)malloc(sizeof(Node));
n->id = id;
strcpy(n->name, name);
n->age = age;
n->prev = n->next = NULL;
return n;
}
// リストを順に表示
void print_list(Node *head) {
Node *p = head;
while (p != NULL) {
printf("[%d: %s (%d)] ", p->id, p->name, p->age);
if (p->next) printf("-> ");
p = p->next;
}
printf("\n");
}
// idを指定して削除
Node* delete_node(Node *head, int id) {
Node *p = head;
while (p != NULL) {
if (p->id == id) {
if (p->prev) p->prev->next = p->next;
if (p->next) p->next->prev = p->prev;
if (p == head) head = p->next;
free(p);
break;
}
p = p->next;
}
return head;
}
// idの後ろに新ノードを挿入
void insert_after(Node *head, int after_id, Node *new_node) {
Node *p = head;
while (p != NULL) {
if (p->id == after_id) {
new_node->next = p->next;
new_node->prev = p;
if (p->next) p->next->prev = new_node;
p->next = new_node;
return;
}
p = p->next;
}
}
int main(void) {
// 初期データ作成 (1, 3, 5)
Node *head = NULL;
Node *current = NULL;
// データ配列(初期登録用)
struct { int id; const char *name; int age; } init_data[] = {
{1, "Taro", 18},
{3, "Hanako", 19},
{5, "Jiro", 20}
};
// データ配列のデータから初期状態のリンクリストを生成する
for (int i = 0; i < 3; i++) {
Node *new_node = create_node(init_data[i].id, init_data[i].name, init_data[i].age);
if (head == NULL) {
// 最初のノードをheadに設定
head = new_node;
current = head;
} else {
// それ以降はcurrentにつなげて更新
current->next = new_node;
new_node->prev = current;
current = new_node;
}
}
printf("初期状態: ");
print_list(head);
// id=4をid=3の後ろに挿入
Node *new_node = create_node(4, "Mika", 21);
insert_after(head, 3, new_node);
printf("id=4を挿入後: ");
print_list(head);
// id=5を削除
head = delete_node(head, 5);
printf("id=5を削除後: ");
print_list(head);
// メモリ解放
Node *p = head;
while (p) {
Node *next = p->next;
free(p);
p = next;
}
return 0;
}
実行例
初期状態: [1: Taro (18)] -> [3: Hanako (19)] -> [5: Jiro (20)]
id=4を挿入後: [1: Taro (18)] -> [3: Hanako (19)] -> [4: Mika (21)] -> [5: Jiro (20)]
id=5を削除後: [1: Taro (18)] -> [3: Hanako (19)] -> [4: Mika (21)]
ここから今回のサンプルプログラムの解説、補足です。
💡構造体 Node についての補足
リンクリストの説明では、「ノード(Node)」 という言葉がよく使われます。
ノードとは「データが入った箱」であり、他のノードとのつながりを持つ要素です。
今回のコードでは次のように定義しています:
typedef struct Node {
int id;
char name[50];
int age;
struct Node *prev; // 前のノードを指すポインタ
struct Node *next; // 次のノードを指すポインタ
} Node;
ここで重要なのは、
- Node という「型(構造体)」を自分で定義している
- その型へのポインタ(struct Node *)を prev と next に持たせている
という点です。
つまり、
「1つのノード(Node)が、前と次のノードをポインタで“指している”」
という構造になっています。
このように自分自身と同じ型を指すポインタを持つことで、ノード同士をつなげてリスト全体を構築できるわけです。
もし、「だからそのポインタって具体的には何なんだ?」と思われたら以下も見てください。
クラウド開発環境PaizaCloudクラウドIDEでHello World(C言語ポインタ 導入編)
💡 構造体のアクセス方法:「.」と「->」の違い
C言語では、構造体のメンバ(中のデータ)にアクセスする方法が2種類あります。
| 宣言の仕方 | アクセス演算子 | 使い方の例 | 意味 |
|---|---|---|---|
| 構造体そのものを宣言した場合 |
. ドット |
student.id |
直接アクセス |
| 構造体のポインタを宣言した場合 |
-> アロー |
student_ptr->id |
ポインタを通してアクセス |
たとえば次のようなコードです:
Node n;
n.id = 1; // 実体に直接アクセス
Node *p = &n;
p->id = 2; // ポインタ経由でアクセス
ここで p->id は実は (*p).id の省略形です。
つまり「ポインタが指す先の構造体のidを使う」という意味になります。
🧩 初学者向けポイント
-> は見慣れないかもしれませんが、
「ポインタを通して中身を触る」だけの記号とも言えます。
慣れると「次のノードへ進む(p = p->next;)」などが自然に読めるようになります。
配列とリンクリストの違い
| 項目 | 配列 | リンクリスト |
|---|---|---|
| メモリ配置 | 連続 | バラバラ(ポインタで接続) |
| 要素の挿入・削除 | 全要素の移動が必要 | 前後ポインタをつなぎ変えるだけ |
| アクセス速度 | 高速(添字でO(1)) | 順にたどる(O(n)) |
| メモリ確保 | まとめて確保 | 必要に応じてmalloc |
| 柔軟性 | サイズ固定 | 可変長で動的 |
サンプルプログラムの初期状態でのリンクリストの例

データ3件分のノードが生成されてつながっている状態です。会員データの内容以外に、nextとprevで保持している前後のノードのポインタを保持しています。前後にデータがない場合は、NULL という値が設定されています。
💡 関数に分割している意味(モジュール化)
プログラムを関数に分けることを モジュール化(modularization) といいます。
これは「ひとつの大きな処理を、小さく分けて整理する」ための基本的な考え方です。
リンクリストのように操作が多いプログラムでは、
関数を使ってそれぞれの処理を独立させておくと、次のようなメリットがあります。
- 🔹 コードの見通しがよくなる
- 🔹 同じ処理を何度も使いまわせる
- 🔹 修正や拡張がしやすくなる
- 🔹 不具合が起きたときに原因を特定しやすい
📘 今回の関数構成と役割
| 関数名 | 役割の説明 |
|---|---|
create_node() |
新しいノード(レコード)を作成して初期化する |
print_list() |
リスト全体を先頭から順に表示する |
insert_after() |
指定したidのノードの「後ろ」に新しいノードを挿入する |
delete_node() |
指定したidのノードを削除し、前後をつなぎ直す |
main() |
全体の流れを制御(初期データ生成、挿入、削除、表示、解放) |
🧩 初学者向けポイント
「関数=ひとつの仕事をする部品」と考えると分かりやすいです。
小さな部品に分けておけば、あとで関数単位で再利用したり、テストしたりも簡単になります。
ここからまとめに入ります。
まとめその1
🔹 配列は連続領域で効率的だが、挿入・削除が重い
🔹 リンクリストは柔軟で、ポインタ操作による構造変化が簡単
🔹 データ構造を理解するには、ポインタで「つながる」感覚を持つのが大事
📘 今回の例のように、レコード構造をポインタでつなげていく発想は
データベースやファイルシステムなどの基礎にも通じています。
まとめその2
🔹 -> はポインタを通してアクセスする演算子
🔹 関数分割はひとつの大きな処理を分けて整理する方法
最後に
ここまで読めたあなたはエールを送りたいです。
C言語のポインタとリンクリストは一見すると難しく見えますが、仕組みがわかると一気に世界が広がります。
実際には、C++、C#、Java、Pythonなどの言語には、リンクリスト等のデータ構造を扱うための標準ライブラリが用意されています。そのため実際にはこのような細かいプログラミングをすることは少ないと思います。ですが、その標準ライブラリの有難さも理解出来ると思います。
ぜひ引き続き幅広く学習、技術の習得を続けてください。
わたくし、おっさんも続けます。
ありがとうございます。
