🖥️

【C言語入門の次】 第03.5回 配列リストの続き

に公開

https://youtu.be/D1j6clkVCeU

四国めたん
\textcolor{pink}{四国めたん: }教師役ですわ

ずんだもん
\textcolor{lime}{ずんだもん: }生徒役なのだ

\footnotesize \textcolor{pink}{四国めたん:} こんにちは。四国めたんです

\footnotesize \textcolor{lime}{ずんだもん:} ずんだもんなのだ。こんにちはなのだ

\footnotesize \textcolor{pink}{四国めたん:} 今回は 配列リスト の続きについてお話ししますわ

\footnotesize \textcolor{lime}{ずんだもん:} 前回は 配列 へのデータの挿入や削除についての話しだったのだ

\footnotesize \textcolor{pink}{四国めたん:} 今回は、 配列 のサイズ変更についてお話ししますわ

\footnotesize \textcolor{lime}{ずんだもん:} 配列 のサイズ変更?

\footnotesize \textcolor{pink}{四国めたん:} はい、 配列 にデータを追加する際、 配列 がいっぱいで追加できない場合の対処ですわ

\footnotesize \textcolor{lime}{ずんだもん:} なるほどなのだ

配列のサイズ変更

\footnotesize \textcolor{pink}{四国めたん:} 実は、 配列 のサイズを後から増やすことができればいいのですが、 配列 にはそのような機能は提供されていませんわ

\footnotesize \textcolor{lime}{ずんだもん:} ざんねんなのだ

\footnotesize \textcolor{pink}{四国めたん:} ですので、 配列 を宣言、初期化している場合は、 配列 のサイズを変更するのは難しいと思いますわ

\footnotesize \textcolor{lime}{ずんだもん:} 困ったのだ

\footnotesize \textcolor{pink}{四国めたん:} ただ、 配列 の代わりにmallocなどを使ってメモリ領域を取得していれば、サイズを増やすことができますわ

\footnotesize \textcolor{lime}{ずんだもん:} あぁ、たしかmallocで取得したメモリ領域は 配列 のように使えたのだ

  1. サイズの大きなメモリ領域を新たにmallocなどで取得
  2. 元のメモリ領域から新たなメモリ領域に、memcpy_sなどを使ってデータをコピー
  3. 元のメモリ領域を破棄
  4. ポインタを新たなメモリ領域に入れ替え

配列のサイズ変更

\footnotesize \textcolor{pink}{四国めたん:} まずはサイズの大きなメモリ領域、例えば2倍のサイズのメモリ領域をmallocで取得しますわ

\footnotesize \textcolor{lime}{ずんだもん:} うむ

\footnotesize \textcolor{pink}{四国めたん:} 次に元のメモリ領域から新しいメモリ領域に`memcpy_s'などで全てのデータをコピーしますわ

\footnotesize \textcolor{lime}{ずんだもん:} ふむふむ

\footnotesize \textcolor{pink}{四国めたん:} そして元のメモリ領域を破棄すると同時に、ポインタが新しいメモリ領域を指すようにしますわ

\footnotesize \textcolor{lime}{ずんだもん:} りょうかいなのだ

\footnotesize \textcolor{lime}{ずんだもん:} ところでmallocの代わりにreallocを使ってはいけないのか?

\footnotesize \textcolor{pink}{四国めたん:} reallocを使ってもOKですし、そのほうが簡単ですわね

\footnotesize \textcolor{pink}{四国めたん:} まぁ、今回はmallocを使ってみますわ

\footnotesize \textcolor{lime}{ずんだもん:} わかったのだ

\footnotesize \textcolor{pink}{四国めたん:} とりあえず、サイズが5の配列に5人分の学生のデータを初期値として割り当てた後、データを追加してみましょう

\footnotesize \textcolor{lime}{ずんだもん:} おねがいするのだ

\footnotesize \textcolor{pink}{四国めたん:} ついでにデータの挿入や削除などをおこなう関数等も実装しておきましょう

students.h
#pragma once
#ifndef _INC_STUDENTS
#define _INC_STUDENTS

#include <stdio.h>

#define NAME_SIZE (20)
#define FIRST_LIST_SIZE (5)

typedef struct {
  unsigned int id;
  char name[NAME_SIZE];
} student;

typedef struct {
  student* pstudents;
  size_t num;
  size_t size;
} student_list;

int init_student_list(student_list* plist);

void final_student_list(student_list* plist);

int insert(student_list* plist, size_t idx, unsigned int id, char name[]);

int delete(student_list* plist, size_t idx);

int push(student_list* plist, unsigned int id, char name[]);

int pop(student_list* plist);

#endif  // _INC_STUDENTS
students.c
#include "students.h"
#include <stdlib.h>
#include <string.h>
#include <memory.h>

int init_student_list(student_list* plist) {
  plist->pstudents = (student*)malloc(sizeof(student) * FIRST_LIST_SIZE);
  plist->num = 0;
  plist->size = FIRST_LIST_SIZE;
  return (plist->pstudents == NULL) ? -1 : 0;
}

void final_student_list(student_list* plist) {
  free(plist->pstudents);
  plist->pstudents = NULL;
  return;
}

int resize(student_list* plist) {
  size_t old_size = sizeof(student) * plist->size;
  size_t new_size = old_size * 2;
  student* pnew_list = (student*)malloc(new_size);
  if (pnew_list != NULL) {
    memset(pnew_list, 0, new_size);
    memcpy_s(pnew_list, new_size, plist->pstudents, old_size);
    free(plist->pstudents);
    plist->pstudents = pnew_list;
    plist->size = plist->size * 2;
  }
  return (pnew_list == NULL) ? -1 : 0;
}

int insert(student_list* plist, size_t idx, unsigned int id, char name[]) {
  int rc = 0;
  if (plist->num >= plist->size) {
    rc = resize(plist);
  }
  if (rc >= 0) {
    idx = (idx > plist->num) ? plist->num : idx;
    size_t dst_size = sizeof(student) * (plist->size - idx - 1);
    size_t src_size = sizeof(student) * (plist->num - idx);
    memmove_s((plist->pstudents + idx + 1), dst_size, (plist->pstudents + idx),
              src_size);
    (plist->pstudents + idx)->id = id;
    strcpy_s((plist->pstudents + idx)->name, NAME_SIZE, name);
    plist->num++;
  }
  return rc;
}

int delete(student_list* plist, size_t idx) {
  int rc = -1;
  if (idx < plist->num) {
    size_t dst_size = sizeof(student) * (plist->size - idx);
    size_t src_size = sizeof(student) * (plist->num - idx - 1);
    memmove_s((plist->pstudents + idx), dst_size, (plist->pstudents + idx + 1),
              src_size);
    (plist->pstudents + plist->num - 1)->id = 0;
    (plist->pstudents + plist->num - 1)->name[0] = 0;
    plist->num--;
    rc = 0;
  }
  return rc;
}

int push(student_list* plist, unsigned int id, char name[]) {
  int rc = insert(plist, plist->num, id, name);
  return rc;
}

int pop(student_list* plist) {
  int rc = delete (plist, plist->num - 1);
  return rc;
}
array_list.c
#include <stdio.h>
#include "students.h"

int main(int argc, char* argv[]) {
  student_list list;
  int rc = init_student_list(&list);
  if (rc == 0) {
    push(&list, 1, "佐藤紬");
    push(&list, 2, "鈴木陽翔");
    push(&list, 3, "高橋翠");
    push(&list, 4, "田中朝陽");
    push(&list, 5, "伊藤凛");
    insert(&list, 2, 6, "渡辺暖");
  }

  for (size_t i = 0; i < list.num; i++) {
    printf("学生番号: %d, 名前: %s\n", (list.pstudents + i)->id,
           (list.pstudents + i)->name);
  }

  final_student_list(&list);

  return 0;
}

\footnotesize \textcolor{pink}{四国めたん:} 今回は、配列リストや配列のサイズ、データの数を構造体student_listにまとめていますわ

\footnotesize \textcolor{lime}{ずんだもん:} ふむ

\footnotesize \textcolor{pink}{四国めたん:} そして、配列リストの操作にかかわる関数と合わせて、"students.h"ヘッダーファイルに宣言していますわ

\footnotesize \textcolor{lime}{ずんだもん:} りょうかいなのだ

\footnotesize \textcolor{pink}{四国めたん:} なお、各関数は"students.c"にまとめて定義していますわ

\footnotesize \textcolor{lime}{ずんだもん:} うむ

初期化関数

\footnotesize \textcolor{pink}{四国めたん:} まず、初期化の関数として、init_student_listを定義しますわ

\footnotesize \textcolor{lime}{ずんだもん:} 初期化関数はどのようなことをおこなうのだ?

\footnotesize \textcolor{pink}{四国めたん:} この関数で配列リスト"pstudents"にstudent構造体の配列を割り当てていますわ

\footnotesize \textcolor{lime}{ずんだもん:} うむ、初期のサイズを"FIRST_LIST_SIZE"として、malloc関数を用いて配列のメモリ領域を作成しているのだ

\footnotesize \textcolor{pink}{四国めたん:} その他にデータのサイズ"num"を0として初期化していますわ

\footnotesize \textcolor{lime}{ずんだもん:} りょうかいなのだ

終了関数

\footnotesize \textcolor{pink}{四国めたん:} 次に、終了処理の関数としてfinal_student_listを定義していますわ

\footnotesize \textcolor{lime}{ずんだもん:} うむ

\footnotesize \textcolor{pink}{四国めたん:} この関数で、配列リスト"pstudents"に割り当てられているメモリ領域をfree関数で破棄していますわ

\footnotesize \textcolor{lime}{ずんだもん:} 割り当てたメモリ領域を解放しないと大変なのだ

データ追加関数

\footnotesize \textcolor{pink}{四国めたん:} 次に、データを追加する関数insertを定義していますわ

\footnotesize \textcolor{lime}{ずんだもん:} 基本は前回のサンプルプログラムのinsertと概ね同じなのだ

\footnotesize \textcolor{pink}{四国めたん:} 追加でデータの数が配列のサイズと同じ場合にはresize関数で配列のサイズを変更していますわ

\footnotesize \textcolor{lime}{ずんだもん:} このresize関数が今回のキーなのだ

\footnotesize \textcolor{pink}{四国めたん:} なおresize関数はinsert関数からしか呼ばれないため"students.h"ファイルでの宣言はおこなっていませんわ

\footnotesize \textcolor{lime}{ずんだもん:} なるほどなのだ

\footnotesize \textcolor{pink}{四国めたん:} resize関数では、現在の配列のサイズの2倍のメモリ領域をmalloc関数で確保していますわ

\footnotesize \textcolor{lime}{ずんだもん:} うむ

\footnotesize \textcolor{pink}{四国めたん:} そして、新たなメモリ領域に元の配列のデータをmemcpy_sでコピーしますわ

\footnotesize \textcolor{lime}{ずんだもん:} りょうかいなのだ

\footnotesize \textcolor{pink}{四国めたん:} 最後に、元の配列のメモリ領域をfree関数で破棄し、"pstudents"に新たなメモリ領域を割り当てますわ

\footnotesize \textcolor{lime}{ずんだもん:} なるほどなのだ

\footnotesize \textcolor{pink}{四国めたん:} ちなみに、新たな配列のサイズは、元の配列のサイズの2倍にするのではなく、固定のサイズを増やすのでもOKですわ

\footnotesize \textcolor{lime}{ずんだもん:} このようにすれば、メモリの許す限り、いくらでもデータを追加することができるのだ

\footnotesize \textcolor{pink}{四国めたん:} ただデータをコピーする時間は必要ですわね

データ削除関数

\footnotesize \textcolor{pink}{四国めたん:} 次に、データを削除する関数deleteを定義していますわ

\footnotesize \textcolor{lime}{ずんだもん:} 基本的には、前回のサンプルプログラムのdeleteと同じなのだ

データの最後への追加 / 最後からの削除関数

\footnotesize \textcolor{pink}{四国めたん:} 最後は、データを配列の最後に追加する関数pushと、配列の最後から削除する関数popの定義ですわ

\footnotesize \textcolor{lime}{ずんだもん:} 別にinsert関数やdelete関数でも同じなのではないのか?

\footnotesize \textcolor{pink}{四国めたん:} はい、いずれもインデックスを配列の最後尾を指定してinsert関数やdelete関数を呼び出しているので、同じと云えば同じですわね

\footnotesize \textcolor{lime}{ずんだもん:} なぜ関数を追加しているのだ?

\footnotesize \textcolor{pink}{四国めたん:} push関数やpop関数は意外と使うことが多いので、今回は特別に追加していますわ

\footnotesize \textcolor{lime}{ずんだもん:} りょうかいなのだ

メイン関数

\footnotesize \textcolor{pink}{四国めたん:} メイン関数では、まずstudent_list構造体を宣言したあとに初期化関数init_student_listを呼び出していますわ

\footnotesize \textcolor{lime}{ずんだもん:} うむ

\footnotesize \textcolor{pink}{四国めたん:} 初期化が成功すれば、push関数で5人分のデータを追加していますわ

\footnotesize \textcolor{lime}{ずんだもん:} 初期化では5人分のメモリ領域を確保しているだけなので、これでデータは一杯なのだ

\footnotesize \textcolor{pink}{四国めたん:} はい、次に6人目のデータをインデックス2の部分に挿入していますわ

\footnotesize \textcolor{lime}{ずんだもん:} うむ、これでメモリ領域の拡張が必要になるのだ

\footnotesize \textcolor{pink}{四国めたん:} そして全てのデータを出力していますわ

\footnotesize \textcolor{lime}{ずんだもん:} ふむふむ

\footnotesize \textcolor{pink}{四国めたん:} 最後にfinal_student_listで終了処理をおこなっていますわね

\footnotesize \textcolor{lime}{ずんだもん:} 終了処理は必須なのだ

\footnotesize \textcolor{pink}{四国めたん:} それでは実行してみましょう

新たな学生の追加

\footnotesize \textcolor{lime}{ずんだもん:} しっかりと挿入したデータも表示されているのだ

まとめ

\footnotesize \textcolor{pink}{四国めたん:} お疲れさまでした

\footnotesize \textcolor{lime}{ずんだもん:} おつかれさまなのだ

\footnotesize \textcolor{pink}{四国めたん:} 以上で 配列リスト を終了しますわ

\footnotesize \textcolor{pink}{四国めたん:} 配列によるリストの操作は、意外と使う場面があったりしますわ

\footnotesize \textcolor{lime}{ずんだもん:} そうなのか?

\footnotesize \textcolor{pink}{四国めたん:} はい、インデックスの指定の間違いによるバグなども多いので、結構、慣れが必要ですわ

\footnotesize \textcolor{lime}{ずんだもん:} しっかりと使いこなすのだ

Discussion