Home English

Kuina-chan

くいなちゃんDec 15, 2017


6さいからのプログラミング」第9話では、プログラムをより解りやすくシンプルに書くために、型そのものを自分で作る方法について解説します。

構造体

第8話までの内容で、ほぼどんなプログラムでも作ることができますが、自分で型が作成できると、値を効率良く管理できて便利です。
例えば「構造体こうぞうたい」を使うと、複数の型を合わせて、1つの新しい型を作ることができます。 C言語で構造体を作るには、中身となる変数を「struct 型名{};」で囲むように書きます(struct.c)。
#include <string.h>
  
struct person
{
  char name[32];
  int age;
};
  
int main(void)
{
  struct person x;
  strcpy(x.name, "Kuina-chan");
  x.age = 6;
  return 0;
}
struct.c
この例では「struct person」という名前の構造体を作成し、中に文字列型の変数「name」とint型の変数「age」を持たせています。 この構造体「struct person」は型として扱え、11行目のようにstruct person型の変数xを作ることができます。
また作った変数xに対し「x.name」「x.age」と書くことで、構造体内の変数にアクセスできます。 構造体内の「.name」や「.age」のことを、その構造体の「メンバ」といいます。 メンバは普通の変数と同様に扱えます。

ポインタ渡し

C言語の構造体は、代入や関数の引数渡しのときに全メンバがコピーされるという仕様になっています。 例えば、struct person型の変数xとyがあったとき、「y=x;」と書くとxのメンバの値がすべてyにコピーされます。 これはたくさんの値をまとめて扱えるので便利ですが、関数の引数渡しのたびにコピーが起こるとプログラムの動作が遅くなります。 そこで引数には構造体そのものではなく、構造体のアドレスを渡すことがよく行われます(struct2.c)。
#include <stdio.h>
  
struct person
{
  char name[32];
  int age;
};
  
void f(struct person a)
{
  printf("%d\n", a.age);
}
  
void g(struct person* a)
{
  printf("%d\n", a->age);
}
  
int main(void)
{
  struct person x;
  x.age = 6;
  f(x);
  g(&x);
  return 0;
}
struct2.c
関数fは構造体の引数をそのまま渡す「値渡あたいわたし」、関数gはアドレスを渡す「ポインタわたし」になっています。 23行目の「f(x);」ではxの全メンバの値がコピーされるため効率的ではありません。 これに対し24行目の「g(&x);」ではxのアドレス、つまり数値1つ分だけが渡ります。
16行目の「a->age」は「(*a).age」と同じ意味です。 前回「a[2]」は「*(a+2)」と同じだと言いましたが、それと同様で直感的な代替表現となっています。 この「->」は「アロー演算子」と呼ばれ、ポインタのアドレス先の構造体のメンバを指すのに使われます。
ここで注意が必要なのは、関数fの場合は値がコピーされているので、関数f内で引数aの値を書き換えても元の構造体xには影響ありませんが、関数gの場合は構造体のアドレスが渡るため、関数g内で引数aのアドレス先の値を書き換えると元の構造体xも変更されることになります。 このことは構造体だけでなくあらゆる型に言えます。 例えば、2つの変数の値を入れ替える関数を作りたい場合には、それぞれの変数の値を書き換える必要がありますので、値渡しではなくポインタ渡しにする必要があります。

その他の言語の構造体

参考として、C言語以外も見ていくことにします。

C#

C#の構造体は、C言語に似ています(struct.cs)。
class HelloWorld
{
  struct person
  {
    public string name;
    public int age;
  }
  
  static void Main(string[] args)
  {
    person x;
    x.name = "Kuina-chan";
    x.age = 6;
  }
}
struct.cs
構造体のメンバに「public」を付けたり、構造体の閉じ括弧「}」の後に「;」が不要だったりといくつか差異はありますが、基本的にはC言語と同様です。

Java

Javaには構造体は存在せず、「class」という代替の機能を使って実現することになります。

PHP

PHPにも構造体は存在しません。 その代わりに第8話で紹介した連想配列を使って代用できます(struct.php)。
<?php
  $x = array();
  $x['name'] = 'Kuina-chan';
  $x['age'] = 6;
?>
struct.php

列挙型

さて、構造体以外で型を作る方法に「列挙型れっきょがた」があります。 これはいくつかの値の中から1つが選ばれるという型です。 例えば、「変数cは、red、green、blueのいずれかの値を持つ」というような型を作りたい場合に使用します。 これをC言語で書くと、enum.cのようになります。
enum color
{
  red,
  green,
  blue
};
  
int main(void)
{
  enum color c;
  c = red;
  if (c == red)
  {
    c = blue;
  }
  return 0;
}
enum.c
1行目でenum color型を作成し、10行目でenum color型の変数cを作り、11行目でこの変数cに値redを入れています。
12行目のように列挙型の値は、==演算子や!=演算子での比較もできます。 列挙型の値には0から順に番号が割り当てられており、メインメモリ上ではこの番号で扱われます。 例えばこの例ではメインメモリ上でredは0、greenは1、blueは2になります。 「(int)red」のようにキャストすると、この番号を数値として使用できます。 また4行目を「green=10,」と書くことで、この番号を明示的に指定することもできます。

定義の位置

構造体や列挙型を書く位置は、その変数が作られる位置よりも上でなければなりません。 関数の紹介のときにも説明しましたが、コンパイルがソースコードの上から下に向かって行われる関係上、使われる位置よりも上に定義が書かれていたほうが都合が良いためです。 もしも使われる位置よりも下で定義したい場合は、関数の場合と同様、上にプロトタイプ宣言を書きます(struct3.c)。
struct person;
  
int main(void)
{
  struct person x;
  x.age = 6;
  return 0;
}
  
struct person
{
  char name[32];
  int age;
};
struct3.c
1行目の「struct person;」が、プロトタイプ宣言です。 構造体や列挙型の「{~};」を「;」に置換したものを書きます。 これで10行目に定義された構造体を、それより上の5行目で参照することができます。
今回は、型を自分で作るための仕組みである「構造体」と「列挙型」について説明しました。 ここまで大きな重要機能を説明してきましたが、次回はこれまでに紹介しきれなかった知っておくべき細かな機能を補足します。
1513346838ja