構造体を使いこなす

こんにちは、めのんです!

前回まで配列とポインタのことを中心に解説してきました(文字列操作もその一環です)。
今回はようやくそこから離れて構造体を解説することにします。
ポインタは出てきますけどね。

複数のデータをまとめて扱う

配列でも複数のデータをまとめて扱うことはできます。
ただ、できれば個々のデータに名前を付けたいですし、異なる型のデータも扱いたいですね。

月並みな例ですが、ここでは個人の名前と年齢、身長、体重をまとめて扱うことを考えましょう。
名前は文字列ですし、年齢は整数、身長と体重は浮動小数点数で扱うことにします。

まずはPHPで考えましょう。
一番安直なのは配列を使う方法ですね。

<?php
$person = [
  'name' => '青池 康夫',
  'age' => 20,
  'height' => 170.0,
  'weight' => 60.0 
];

名前は「日本人名前自動生成機」さんで自動生成させていただいたものなので深い意味はありません。
ほかのデータも適当です。

上のように配列を使えばPHPでは簡単に型が異なる複数のデータをひとまとめにできます。
ただ、この方法だと名前、年齢、身長、体重を持つ型を定義したわけではありませんので、同じ種類のデータをいくつも使う場合は支障が出るかもしれません。
DBを使うならDBでテーブルを用意すればいいんでしょうけど、ここではあくまでもPHPだけで完結させることを考えます。

ほかにはpublicなプロパティを並べたクラスを作る方法もあります。

<?php
class Persion
{
  public string $name;
  public int $age;
  public float $height;
  public float $weight;
}

PHPでは配列を使う方が多いんでしょうか?
人にもよるしプロジェクトにもよるのかもしれませんね。

Cではこういう場合は「構造体」というものを使います。
大まかなイメージは先ほどのクラスとよく似ています。

構造体の定義

先ほども書きましたが、Cの構造体はPHPのクラスとよく似ています。
といっても、クラスのようにメソッドは使えませんので、あくまでも扱えるのはデータだけです。
もちろん継承やトレイトなんかは使えませんし、アクセス修飾もできないので全部publicです。

では早速、先ほどの名前、年齢、身長、体重をまとめた構造体を定義してみましょう。

struct person
{
  char name[32];
  int age;
  double height;
  double weight;
};

見た目はPHPのクラスとそっくりですよね。
構造体の定義の最後にはセミコロンが必ず必要なので、そこだけは注意が必要です。

この構造体の定義を順に解説していきますね。

タグ

まずはstruct personの部分です。

personは構造体の名前になる部分で「タグ」といいます。
PHPではclass Personと書けば以後Personだけでこのクラスを指定できましたが、Cの場合はstruct personと書いてやっと型名になります。

struct person aoike_yasuo;  // 構造体型のオブジェクトの宣言では型名をstruct personのようにする。

タグを省略することもできるのですが、直接型名を指定することができなくなってしまうので注意が必要です。

struct  // タグを省略
{
  int a;
} x;

上の例ではタグ名を省略した構造体型のxを宣言しています。
xを通じてaにアクセスすることはできますが、struct ○○という型名を直接表現する方法はありません。

また、タグを省略した構造体は、まったく内容が同じだったとしても定義ごとに別の型になりますので、その点も注意が必要です。

メンバ

構造体の内容についてはとくに説明は必要ないと思いますが、一応用語の説明だけしておきますね。

PHPではクラスが持つデータをプロパティと呼びましたが、Cでは「メンバ」と呼びます。
人によっては「フィールド」と呼ぶこともありますが、規格上はメンバです。

nameに関しては32要素の配列として定義しています。
日本の法律では名前の長さに規制はないので落語の寿限無みたいな名前も理屈上はあり得るんですけど、問題の本質ではないので今は深く考えないことにしましょう。

ほかのメンバはとくに問題ないですよね。

構造体型のオブジェクトに対する初期化子

構造体型のオブジェクトを宣言する際には初期化子を与えることができます。
基本は配列の初期化子と同じで、各メンバの初期値を順に並べます。

struct person aoike_yasuo = 
{
  "青池 康夫",
  20,
  170.0,
  60.0
};

メンバは全部書く必要はなくて、省略した場合はゼロで初期化されます。
でも、初期化子を書く場合は必ずひとつは初期値を書かなければなりません。
初期化子自体を省略してしまうと初期値無しになってしまうので注意してくださいね。

構造体の初期化子でも配列と同じように要素指示子を使うことができます。
先ほどの初期化子を要素指示子を使って書き直すと次のようになります。

struct person aoike_yasuo = 
{
  .name = "青池 康夫",
  .age = 20,
  .height = 170.0,
  .weight = 60.0
};

要素指示子は定義したときのメンバの順でなくてもかまいません。
たとえばこんな風にバラバラの順に書くこともできますし、省略することもできます。

struct person aoike_yasuo = 
{
  .weight = 60.0
  .name = "青池 康夫",
  .height = 170.0,
  // ageは省略されているので0になる。
};

構造体の定義を書ける場所

構造体の定義は関数の中にも外にも書くことができますし、関数の返却値の型や仮引数の型で直接定義することもできます。
また、オブジェクトの宣言でも、式の中でも定義することができます。

名前空間

PHPにも名前空間はありますが、Cの名前空間はあんな高級なものではありません。
まったく同じ名前であっても別物として扱うことができる文脈のことだと思ってください。

Cの名前空間には次の4つがあります。

  • ラベル名
  • タグ
  • メンバ
  • その他

ラベル名はgoto文の分岐先なのですが、これについてはまた別の機会に解説します。
タグとメンバは先ほど解説しましたね。
その他というのは、関数やオブジェクトなど上の3つ以外のすべてです。

Cの名前空間にはこの4種類があり、名前空間が異なれば同じ名前を使っても問題ありません。
たとえばこんな風にです。

struct sample 
{
  int a;
  int b;
};

struct sample sample { 1, 2 };

この例ではsampleという同じ名前をタグとオブジェクト名に使っています。
まぎらわしいのでこういう使い方には注意が必要ですが、Cの仕様上はとくに問題ありません。

構造体のメンバへのアクセス

構造体のオブジェクトを定義したら、今度はそのオブジェクトを使って構造体の各メンバにアクセスしないといけませんね。
まずはコードをご覧ください。

struct person
{
  char name[32];
  int age;
  double height;
  double weight;
};

struct person aoike_yasuo;
aoike_yasuo.age = 20;

構造体型オブジェクト経由でのメンバアクセス

上のサンプルコードの20行目ではstruct person型のオブジェクトaoike_yasuoを使ってメンバageにアクセスしています。
この場合は「ドット演算子」を使います。
ドット演算子は「.」(ピリオド)で表します。

この場合であれば、aoike_yasuo.ageをあたかもひとつのオブジェクト名であるかのように扱うことができます。

ポインタ経由でのメンバアクセス

構造体型へのポインタというのももちろん宣言することができます。
ポインタを使ったメンバへのアクセスは、素直に書くと次のようになります。

struct person aoike_yasuo;
struct person *p = &aoike_yasuo;
(*p).height = 170.0;

ここでもし*p.heightと書いてしまうと*(p.height)の意味になってしまいます。
ですので先に(*p)としてから.heightを続けています。

この書き方でも問題はないのですが、ちょっと面倒ですね。
そこで、もう少し簡単な書き方が用意されています。

struct person aoike_yasuo;
struct person *p = &aoike_yasuo;
p->height = 170.0;

この書き方はPHPでもおなじみですね。
PHPではクラスのメソッドやプロパティにアクセスするために「->」を使いますが、Cではポインタ経由で構造体のメンバにアクセスするために「->」を使います。
Cでは「->」のことを「矢印演算子」といいます。

まだ構造体に関しては解説しないといけないことが少し残っているんですけど、今回はここまでにしておきます。

最後にひとつだけ補足しておきます。
配列と構造体を総称して、Cでは「集成体」と読んでいます。
ときどき耳にする用語ですのでぜひ覚えておいてください。


それでは今回の解説は以上となります。
次回は割付け記憶域期間についてお話ししようと思っていますが、ちょっと間が空くかもしれません。
その間もなるべく別の話題を投稿しますのでよろしくお願いします。