構造体(こうぞうたい、: structure)はプログラミング言語におけるデータ型の一つで、1つもしくは複数の値をまとめて格納できる型。それぞれのメンバー(フィールド)に名前が付いている点、またメンバーの型が異なっていてもよい点が配列と異なる。レコードという名前の類似機能として実装されている言語もある。

C/C++C#などでstructとしてサポートされているほか、Visual Basic/VBAのユーザー定義型Type[1]や、PascalAdarecord型も構造体に相当する。

クラスベースオブジェクト指向言語では、抽象データ型としてのクラスが構造体の役割をも内包する。Cの文法を継承した言語ではstructキーワードを含むこともあるが、言語によってその役割や性質は異なる。

  • C++structは、アクセシビリティの初期値がpublicであることを除いて、classと同等の機能を果たす。メンバー変数だけでなくメンバー関数を持つこともでき、派生型を定義することもできる。
  • Javaでは、structキーワードは存在しない。すべてのユーザー定義型は参照型であり、構造体に相当するデータ構造はclassキーワードを使ってクラスとして実現する必要がある。Java 16ではイミュータブルな「レコードクラス」をサポートする[2][3]
  • Kotlinでは、データを保持するためだけのクラスとして、data class構文により「データクラス」を定義することができる[4]。データクラスはミュータブル[注釈 1]だが、通常のクラス同様に参照型であることには変わりなく、変数の代入は参照のみのコピーとなる。そのため、すべてのプロパティをコピーした新たなオブジェクトを生成するには、コンパイラの推論によって暗黙的に自動定義されたcopy()関数を使うなどする。サブクラスを定義することはできない。
  • C#では、構造体の定義にstructキーワードを使用する[5]classキーワードにより定義されるクラス[6]は「参照型」である一方、構造体は軽量なオブジェクト型を定義するための「値型」であり、クラスと比較していくつかの制約がある。
  • VB.NETでは、構造体の定義にStructureキーワードを使用する[7]。姉妹言語であるC#の構造体と類似の機能である。
  • Swiftでは、構造体の定義にstructキーワードを使用する。C#と同様、classキーワードにより定義されるクラスは参照型である一方、構造体は値型である[8][9]。SwiftのクラスはObjective-Cのクラスに対応する概念であり相互運用できるが、構造体は相互運用することができない。

オブジェクト指向言語でないCなどでオブジェクト指向プログラミングを模倣するために構造体を使うこともある。

C言語の例

編集

下記は単純な例示のため、バッファオーバーフローや整数オーバーフローなどは考慮していないことに注意されたい。

#include <stdio.h>

/* PersonalDataを構造体として定義 */
struct PersonalData
{
  /* メンバー変数 (つまり構造体の要素) を名前と年齢とする */
  char Name[100];
  int Age;
};

/* 上で定義された構造体を使ってみる */
int main(void)
{
  struct PersonalData pd; /* 構造体変数の宣言 */
  struct PersonalData *ppd; /* 構造体へのポインタ */

  scanf("%s", pd.Name); /* 値を入力 */
  scanf("%d", &(pd.Age)); /* 値を入力 */

  ppd = &pd;
  ppd->Age++; /* ポインタの参照先のメンバーにアクセスするにはアロー演算子->を使う。*/

  printf("%s-%d\n", pd.Name, pd.Age);
  return 0;
}

下記は構造体へのポインタをユーザー定義のオブジェクト型のハンドルとして利用する例である。

/* MyObject.h */
/* 構造体の前方宣言 */
typedef struct MyObject MyObject;

extern MyObject* MyObject_create(void); /* コンストラクタの代替 */
extern void MyObject_destroy(MyObject* obj); /* デストラクタの代替 */
extern void MyObject_setPosition(MyObject* obj, double x, double y);
extern void MyObject_getPosition(const MyObject* obj, double* outX, double* outY);
/* MyObject.c */
#include <stdlib.h>
#include <assert.h>
#include "MyObject.h"

/* 構造体の定義 */
struct MyObject {
    double x, y;
};

MyObject* MyObject_create(void) {
    return calloc(1, sizeof(MyObject));
}
void MyObject_destroy(MyObject* obj) {
    free(obj);
}
void MyObject_setPosition(MyObject* obj, double x, double y) {
    assert(obj);
    obj->x = x;
    obj->y = y;
}
void MyObject_getPosition(const MyObject* obj, double* outX, double* outY) {
    assert(obj && outX && outY);
    *outX = obj->x;
    *outY = obj->y;
}

近代的なAPI設計では、このようにしてC互換のオブジェクト指向インターフェイスを定義することがよくある。例えばOpenCL[10]Vulkan[11]などで類似の手法が実際に利用されている。ヘッダーでは構造体の前方宣言だけをすることにより、構造体の具体的な詳細(定義)はAPIのユーザーからは隠蔽されており、不透明な型 (opaque type) として扱われる(カプセル化)。

この手法を使うことで、C互換のインターフェイスを維持しつつ、API関数の実装をCだけでなくC++や他の言語で記述することもできるようになる。また、オブジェクトの生成・破棄を含むあらゆる操作をAPI関数経由に限定し、オブジェクトハンドルを介した操作のみを提供することで、異なるABI間でも正しくオブジェクトをやりとりすることができるため、C/C++以外の他の言語向けバインディング(ラッパーライブラリ)を記述することも容易になる。

C#の例

編集

C#における構造体は、小規模なデータ構造の定義に適した値型 (value type) である[5]。C#におけるクラスはヒープ割り当てを必要とし、完全な継承機能をサポートする参照型 (reference type) である[6]一方、構造体はヒープ割り当てを必要としない軽量な値型であり、代わりに派生型を定義できないなど、制限されたクラスとして振る舞う。クラスと構造体の使い分けに関しては、ガイドラインが提示されている[12]

組み込みの軽量な型 (bool, char, int, double, etc.) もまた構造体である。

// int 型の数値リテラルも構造体 (System.Int32) のインスタンスであり、オブジェクトである。
string str = 123.ToString();

structキーワードを使用した、ユーザー定義の構造体の例を下に示す。プロパティやメソッド定義の一部にC# 6.0やC# 7.0で追加された糖衣構文が使用されているが、構造体自体はC# 1.0当初から存在する言語機能である。なお、C#の構造体は引数付きコンストラクタをユーザー定義することはできるが、引数のないコンストラクタ(デフォルトコンストラクタ)をユーザー定義することはできない。また、フィールドやプロパティを宣言時初期化することもできない。引数のないコンストラクタでは、各フィールドはその型の既定値で初期化される。C# 10.0以降の構造体は、引数のないコンストラクタをユーザー定義することもできるようになった[13]

using System;

public struct MutablePoint {
    /// <summary>カプセル化されたフィールド。</summary>
    private int _x, _y;
    /// <summary>X座標を取得・設定するプロパティ。</summary>
    public int X {
        get => _x;
        set => _x = value;
    }
    /// <summary>Y座標を取得・設定するプロパティ。</summary>
    public int Y {
        get => _y;
        set => _y = value;
    }
    /// <summary>原点からの距離を取得するプロパティ。</summary>
    public double Distance => Math.Sqrt((double)_x * (double)_x + (double)_y * (double)_y);
    /// <summary>引数付きコンストラクタ。</summary>
    public MutablePoint(int x, int y) {
        _x = x;
        _y = y;
    }
    /// <summary>System.ValueTypeのToString()メソッドをオーバーライド。</summary>
    public override string ToString() => $"({_x}, {_y})";
}

class Test {
    // フィールドで定義された構造体内の全てのフィールドはデフォルト値0で初期化されている。
    static MutablePoint s_point;
    static void Main() {
        Console.WriteLine(s_point.ToString()); // (0, 0) が表示される。
        // デフォルトコンストラクタ呼び出し。
        var zeroPoint = new MutablePoint();
        Console.WriteLine(zeroPoint.ToString()); // (0, 0) が表示される。
        // 引数付きコンストラクタ呼び出し。
        var point = new MutablePoint(5, 11);
        Console.WriteLine(TranslatePoint(point, 1, -9).ToString()); // (6, 2) が表示される。
        Console.WriteLine(point.ToString()); // (5, 11) が表示される。
        TranslatePoint(ref point, -2, 3);
        Console.WriteLine(point.ToString()); // (3, 14) が表示される。

        Console.WriteLine("Press any...");
        Console.ReadKey(true);
    }

    // 構造体型 (値型) は、仮引数に値のコピーが渡されるため、
    // メソッド内での仮引数の操作によって呼び出し元の実引数の値が変更されることはない。
    // 引数がrefパラメータならば、
    // あるいはMutablePoint型がstructではなくclassで宣言されていたならば、
    // 仮引数の状態の変更は実引数の状態に影響する。
    static MutablePoint TranslatePoint(MutablePoint p, int dx, int dy) {
        p.X += dx;
        p.Y += dy;
        return p;
    }
    static void TranslatePoint(ref MutablePoint p, int dx, int dy) {
        p.X += dx;
        p.Y += dy;
    }
}

C#のデータ型も参照のこと。

C#の構造体は属性によってメモリレイアウトを明示的に指定することができるため、C/C++の構造体との相互運用に便利である。Windows APIなどで定義されている構造体と互換性のある型をC#の構造体によってユーザー定義することで、P/Invokeに利用できる。

VB.NETF#といった.NET言語も、構文は異なるがC#と同様の構造体を備えている[7][14]

C++/CLIでは、value structまたはvalue classによって.NETの構造体型を定義することができる[15]

アライメント

編集

構造体のメンバーのメモリレイアウトは、必ずしも連続しているとは限らない。実行環境(プロセッサアーキテクチャ)に合わせてアクセス効率が最適になるよう、コンパイラによってバイト境界に応じた無名の詰め物(パディング)が挿入されることがある。このパディングはシリアライズや相互運用などで問題になることがあるため、フィールド属性やコンパイラ特有のディレクティブによってアライメントを明示的に調整できる言語や処理系も存在する[16][17]。ただし、アライメントされていないアドレスに構造体メンバーが配置された場合、メンバーアクセスがバスエラーを引き起こすケースもある。

脚注

編集

注釈

編集
  1. ^ データクラスのすべてのプロパティをvalで宣言することで、イミュータブルにすることもできる。

出典

編集

関連項目

編集