🖥️

【C言語超入門】 第20回 構造体とポインタ

に公開

https://youtu.be/QLIb9v39aLM

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

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

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

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

\footnotesize \textcolor{pink}{四国めたん:} 今回もC言語のお勉強をしていきましょう

\footnotesize \textcolor{lime}{ずんだもん:} レッツゴーなのだ

\footnotesize \textcolor{pink}{四国めたん:} 前回は 配列ポインタ の関係についてお話ししましたわ

\footnotesize \textcolor{lime}{ずんだもん:} うむ、関数に使うときに注意が必要だったのだ

\footnotesize \textcolor{pink}{四国めたん:} はい、今回は 構造体ポインタ の関係についてお話ししますわ

\footnotesize \textcolor{lime}{ずんだもん:} よろしくなのだ

ポインタを使った構造体の要素へのアクセス

\footnotesize \textcolor{pink}{四国めたん:} まずは構造体の変数をポインタに代入することを考えますわ

\footnotesize \textcolor{pink}{四国めたん:} そしてポインタを使って構造体の要素にアクセスしてみましょう

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

#include <stdio.h>

#define WEIGHT (100)

void main() {
  enum color {
    RED,
    YELLOW,
    GREEN,
  };

  struct fruit {
    int weight;
    enum color fruit_color;
  };

  struct fruit apple = {WEIGHT, RED};
  struct fruit* papple = &apple;

  printf("リンゴの重さは%dグラムです。\n", (*papple).weight);
  if ((*papple).fruit_color == RED) {
    printf("リンゴの色は赤色です。\n");
  } else if ((*papple).fruit_color == YELLOW) {
    printf("リンゴの色は黄色です。\n");
  } else {
    printf("リンゴの色は緑色です。\n");
  }
}

ポインタを使った構造体

\footnotesize \textcolor{lime}{ずんだもん:} 以前の構造体を使ったプログラム例と同じなのだ

\footnotesize \textcolor{pink}{四国めたん:} はい、変わったのは構造体をポインタ型に代入して使った部分ですわ

\footnotesize \textcolor{lime}{ずんだもん:} 実際の構造体のポインタ型の宣言はどのような形式なのだ?

\footnotesize \textcolor{pink}{四国めたん:} 構造体のポインタ型の宣言はstruct 構造体名* ポインタ名;となりますわ

\footnotesize \textcolor{lime}{ずんだもん:} 構造体の要素にポインタからアクセスするためには、どのようにするのだ?

\footnotesize \textcolor{pink}{四国めたん:} 構造体の要素にポインタからアクセスするためには(*ポインタ名).要素名としますわ

\footnotesize \textcolor{lime}{ずんだもん:} つまり、ポインタ名の前に間接演算子*を付加して、括弧で括った後にピリオド.を介して要素にアクセスするということか

\footnotesize \textcolor{pink}{四国めたん:} そうですわ

\footnotesize \textcolor{pink}{四国めたん:} ここで重要なのはアスタリスクとポインタ名を括弧で括ることですわね

\footnotesize \textcolor{lime}{ずんだもん:} 括弧で括らないとどうなるのだ?

\footnotesize \textcolor{pink}{四国めたん:} これを忘れると*(ポインタ名.要素名)の意味になり、エラーとなりますわ

\footnotesize \textcolor{lime}{ずんだもん:} 注意するのだ

ポインタを使った要素へのアクセス(別の方法)

\footnotesize \textcolor{pink}{四国めたん:} (*ポインタ名).要素名による要素へのアクセスは間違いやすいうえに括弧などをつける分の手間が掛かりますわね

\footnotesize \textcolor{lime}{ずんだもん:} もっと簡単な方法はないのか?

\footnotesize \textcolor{pink}{四国めたん:} 実は、C言語ではポインタを使った要素へのアクセスに対して専用の アロー演算子 ->が用意されていますわ

\footnotesize \textcolor{lime}{ずんだもん:} アロー演算子

\footnotesize \textcolor{pink}{四国めたん:} はい、 アロー演算子 ->ですわ

\footnotesize \textcolor{pink}{四国めたん:} ドット演算子 .の代わりに使うことで間接演算子*や括弧を使わずに要素にアクセスできますわ

\footnotesize \textcolor{lime}{ずんだもん:} お~、便利なのだ

\footnotesize \textcolor{pink}{四国めたん:} とりあえず アロー演算子 ->を使ってプログラムを変更してみましょう

#include <stdio.h>

#define WEIGHT (100)

void main() {
  enum color {
    RED,
    YELLOW,
    GREEN,
  };

  struct fruit {
    int weight;
    enum color fruit_color;
  };

  struct fruit apple = {WEIGHT, RED};
  struct fruit* papple = &apple;

  printf("リンゴの重さは%dグラムです。\n", papple->weight);
  if (papple->fruit_color == RED) {
    printf("リンゴの色は赤色です。\n");
  } else if (papple->fruit_color == YELLOW) {
    printf("リンゴの色は黄色です。\n");
  } else {
    printf("リンゴの色は緑色です。\n");
  }
}

アロー演算子を使ったアクセス

\footnotesize \textcolor{lime}{ずんだもん:} 特に問題なく構造体の要素にアクセスできたのだ

関数に構造体を使おう

\footnotesize \textcolor{pink}{四国めたん:} 次に関数の引数に構造体を使ってみましょう

\footnotesize \textcolor{lime}{ずんだもん:} よろしくなのだ

\footnotesize \textcolor{pink}{四国めたん:} 関数呼び出し前の構造体のアドレスと関数内の構造体のアドレスを表示するプログラムですわ

\footnotesize \textcolor{lime}{ずんだもん:} なぜ、そんなプログラムにするのだ?

\footnotesize \textcolor{pink}{四国めたん:} 関数の引数に渡された構造体が、コピーかどうかの確認のためですわ

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

#include <stdio.h>

#define WEIGHT (100)

enum color {
  RED,
  YELLOW,
  GREEN,
};

struct fruit {
  int weight;
  enum color fruit_color;
};

void print_apple(struct fruit apple_copy) {
  printf("リンゴのアドレスは0x%pです。\n", &apple_copy);

  printf("リンゴの重さは%dグラムです。\n", apple_copy.weight);
  if (apple_copy.fruit_color == RED) {
    printf("リンゴの色は赤色です。\n");
  } else if (apple_copy.fruit_color == YELLOW) {
    printf("リンゴの色は黄色です。\n");
  } else {
    printf("リンゴの色は緑色です。\n");
  }
  return;
}

void main() {
  struct fruit apple = {WEIGHT, RED};

  printf("リンゴのアドレスは0x%pです。\n", &apple);
  print_apple(apple);
}

構造体のアドレス

\footnotesize \textcolor{lime}{ずんだもん:} 「リンゴのアドレスは0x000000EC864FF668です。」、「リンゴのアドレスは0x000000EC864FF640です。」と表示されたのだ

\footnotesize \textcolor{pink}{四国めたん:} はい、それぞれの構造体のアドレスは異なっていますわね

\footnotesize \textcolor{lime}{ずんだもん:} つまりコピーということか…

\footnotesize \textcolor{pink}{四国めたん:} はい、関数の引数に構造体を用いた場合には、構造体のコピーが渡されますわ

\footnotesize \textcolor{lime}{ずんだもん:} 関数の引数に配列を渡した場合には、配列の本体が渡されるのとは違うのだな

\footnotesize \textcolor{pink}{四国めたん:} しっかりと区別をして下さいね

列挙子や構造体を定義します

\footnotesize \textcolor{pink}{四国めたん:} それではプログラムの説明をしますわね

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

\footnotesize \textcolor{pink}{四国めたん:} まず、列挙子colorと構造体fruitをメイン関数の外で定義していますわ

\footnotesize \textcolor{lime}{ずんだもん:} メイン関数の外で定義できるのか?

\footnotesize \textcolor{pink}{四国めたん:} 可能ですわ

\footnotesize \textcolor{lime}{ずんだもん:} でも、なぜメイン関数の外で定義するのだ?

\footnotesize \textcolor{pink}{四国めたん:} 今回、列挙子と構造体は、メイン関数とprint_apple関数の両方で使っていますわ

\footnotesize \textcolor{lime}{ずんだもん:} たしかに…

\footnotesize \textcolor{pink}{四国めたん:} 今まではメイン関数内のみで定義していましたが、それではメイン関数内のみでしか利用できませんわ

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

\footnotesize \textcolor{pink}{四国めたん:} はい、関数内で定義した列挙子や構造体は、変数と同じく、関数内のみで有効となりますわ

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

\footnotesize \textcolor{pink}{四国めたん:} いっぽうで、関数の外で定義した列挙子や構造体は、定義以降の全ての関数内で使うことができますわ

\footnotesize \textcolor{lime}{ずんだもん:} なるほど、だからprint_apple関数より前、メイン関数の外で定義しているのか

\footnotesize \textcolor{pink}{四国めたん:} その通りですわ

関数の引数に構造体を使います

\footnotesize \textcolor{pink}{四国めたん:} プログラムの実行結果からわかる通り、関数の引数として渡される構造体は コピー ですわ

\footnotesize \textcolor{lime}{ずんだもん:} 元の構造体の変数appleのアドレスと、関数の引数のapple_copyのアドレスが異なっていたのだ

\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{lime}{ずんだもん:} ところで、関数の引数に渡した構造体の要素の変更を元の構造体の要素に反映したい場合にはどうするのだ?

\footnotesize \textcolor{pink}{四国めたん:} 関数の引数に構造体のポインタを使えばOKですわ

\footnotesize \textcolor{lime}{ずんだもん:} 構造体のポインタ?

\footnotesize \textcolor{pink}{四国めたん:} はい、実際の例を見てみましょう

#include <stdio.h>

#define WEIGHT (100)

enum color {
  RED,
  YELLOW,
  GREEN,
};

struct fruit {
  int weight;
  enum color fruit_color;
};

void print_apple(struct fruit* papple_copy) {
  printf("リンゴのアドレスは0x%pです。\n", papple_copy);

  printf("リンゴの重さは%dグラムです。\n", papple_copy->weight);
  if (papple_copy->fruit_color == RED) {
    printf("リンゴの色は赤色です。\n");
  } else if (papple_copy->fruit_color == YELLOW) {
    printf("リンゴの色は黄色です。\n");
  } else {
    printf("リンゴの色は緑色です。\n");
  }

  papple_copy->fruit_color = YELLOW;
  return;
}

void main() {
  struct fruit apple = {WEIGHT, RED};

  printf("リンゴのアドレスは0x%pです。\n", &apple);
  print_apple(&apple);

  if (apple.fruit_color == RED) {
    printf("リンゴの色は赤色です。\n");
  } else if (apple.fruit_color == YELLOW) {
    printf("リンゴの色は黄色です。\n");
  } else {
    printf("リンゴの色は緑色です。\n");
  }
}

ポインタを使った構造体のアドレス

\footnotesize \textcolor{lime}{ずんだもん:} 「リンゴのアドレスは0x0000002EB3F7FAF8です。」、「リンゴのアドレスは0x0000002EB3F7FAF8です。」と表示されたのだ

\footnotesize \textcolor{pink}{四国めたん:} はい、それぞれの構造体のアドレスは同じになっていますわね

\footnotesize \textcolor{pink}{四国めたん:} そしてprint_appleの最後で構造体の要素fruit_colorを"YELLOW"に変更した結果が反映されていますわね

\footnotesize \textcolor{lime}{ずんだもん:} たしかに「リンゴの色は黄色です。」と表示されたのだ

\footnotesize \textcolor{pink}{四国めたん:} ちなみに関数の引数に構造体のポインタを使えば、要素がコピーされませんわ

\footnotesize \textcolor{lime}{ずんだもん:} その分、メモリや時間などのリソースの節約になるということか…

\footnotesize \textcolor{pink}{四国めたん:} その通りですわ

\footnotesize \textcolor{lime}{ずんだもん:} ところで関数の引数に構造体のポインタを使う場合に、関数内で要素の値の変更を禁止したい場合にはどうするのだ?

\footnotesize \textcolor{pink}{四国めたん:} その場合にはconstキーワードを使いますわね

\footnotesize \textcolor{lime}{ずんだもん:} 具体的には?

\footnotesize \textcolor{pink}{四国めたん:} 関数print_appleの引数にconstキーワードを使ってみますわね

#include <stdio.h>

#define WEIGHT (100)

enum color {
  RED,
  YELLOW,
  GREEN,
};

struct fruit {
  int weight;
  enum color fruit_color;
};

void print_apple(const struct fruit* papple_copy) {
  printf("リンゴのアドレスは0x%pです。\n", papple_copy);

  printf("リンゴの重さは%dグラムです。\n", papple_copy->weight);
  if (papple_copy->fruit_color == RED) {
    printf("リンゴの色は赤色です。\n");
  } else if (papple_copy->fruit_color == YELLOW) {
    printf("リンゴの色は黄色です。\n");
  } else {
    printf("リンゴの色は緑色です。\n");
  }

  papple_copy->fruit_color = YELLOW;
  return;
}

void main() {
  struct fruit apple = {WEIGHT, RED};

  printf("リンゴのアドレスは0x%pです。\n", &apple);
  print_apple(&apple);

  if (apple.fruit_color == RED) {
    printf("リンゴの色は赤色です。\n");
  } else if (apple.fruit_color == YELLOW) {
    printf("リンゴの色は黄色です。\n");
  } else {
    printf("リンゴの色は緑色です。\n");
  }
}

constを使った場合のエラー

\footnotesize \textcolor{lime}{ずんだもん:} 「E0137 式は変更可能な左辺値である必要があります」と表示されたのだ

\footnotesize \textcolor{pink}{四国めたん:} はい、papple_copy->fruit_color = YELLOW;の部分で変更がエラーとなっていますわね

\footnotesize \textcolor{lime}{ずんだもん:} つまりconstキーワードを引数に使えば、関数内で値を変更できなくなるのか

関数の戻り値に構造体を使います

\footnotesize \textcolor{pink}{四国めたん:} 次に関数の戻り値に構造体を使用した場合のことを考えてみましょう

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

#include <stdio.h>

#define WEIGHT (100)

enum color {
  RED,
  YELLOW,
  GREEN,
};

struct fruit {
  int weight;
  enum color fruit_color;
};

struct fruit get_apple() {
  struct fruit apple = {WEIGHT, RED};
  printf("リンゴのアドレスは0x%pです。\n", &apple);
  return apple;
}

void main() {
  struct fruit apple = get_apple();

  printf("リンゴのアドレスは0x%pです。\n", &apple);

  printf("リンゴの重さは%dグラムです。\n", apple.weight);
  if (apple.fruit_color == RED) {
    printf("リンゴの色は赤色です。\n");
  } else if (apple.fruit_color == YELLOW) {
    printf("リンゴの色は黄色です。\n");
  } else {
    printf("リンゴの色は緑色です。\n");
  }
}

戻り値に構造体

\footnotesize \textcolor{pink}{四国めたん:} 最初はget_apple関数の内部で宣言した構造体のアドレスを表示していますわ

\footnotesize \textcolor{lime}{ずんだもん:} 「リンゴのアドレスは0x000000C304FCFB38です。」と表示されたのだ

\footnotesize \textcolor{pink}{四国めたん:} その次は関数の戻り値の構造体のアドレスを表示していますわね

\footnotesize \textcolor{lime}{ずんだもん:} 「リンゴのアドレスは0x000000C304FCFC58です。」と表示されたのだ

\footnotesize \textcolor{pink}{四国めたん:} それぞれのアドレスが異なっていることから、関数の戻り値の構造体はコピーだと確認できましたわ

\footnotesize \textcolor{lime}{ずんだもん:} たしかに…

\footnotesize \textcolor{pink}{四国めたん:} ただし、関数の内部で宣言した構造体のポインタを戻り値にすることはできませんわ

\footnotesize \textcolor{lime}{ずんだもん:} これは関数の内部で宣言した変数のポインタを戻り値にできないのと同じなのだ

\footnotesize \textcolor{pink}{四国めたん:} とりあえず確認してみましょう

#include <stdio.h>

#define WEIGHT (100)

enum color {
  RED,
  YELLOW,
  GREEN,
};

struct fruit {
  int weight;
  enum color fruit_color;
};

struct fruit*  get_apple() {
  struct fruit apple = {WEIGHT, RED};
  printf("リンゴのアドレスは0x%pグラムです。\n", &apple);
  return &apple;
}

void main() {
  struct fruit* papple = get_apple();

  printf("リンゴのアドレスは0x%pです。\n", papple);

  printf("リンゴの重さは%dです。\n", papple->weight);
  if (papple->fruit_color == RED) {
    printf("リンゴの色は赤色です。\n");
  } else if (papple->fruit_color == YELLOW) {
    printf("リンゴの色は黄色です。\n");
  } else {
    printf("リンゴの色は緑色です。\n");
  }
}

戻り値に構造体のポインタ

\footnotesize \textcolor{lime}{ずんだもん:} とりあえずは実行できたのだ

\footnotesize \textcolor{lime}{ずんだもん:} でも結果がなんかおかしいのだ

\footnotesize \textcolor{pink}{四国めたん:} はい、「C4172 ローカル変数またはテンポラリのアドレスを返します」というワーニングが出ていますわね

\footnotesize \textcolor{pink}{四国めたん:} これを避けるには、関数の呼び出し側で構造体の宣言を行って、ポインタを引数として渡すようにしますわ

まとめ

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

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

\footnotesize \textcolor{pink}{四国めたん:} 構造体とポインタ についての説明は以上ですわ

Discussion