構造体

ホームC#プログラミング応用講義 > 構造体

目次

構造体とは?

構造体というのは「クラス」と言葉が全然違うために、クラスとは全く関係のない「何かの構造か?」と考えがちです。
しかし、これはちょっと変わったクラスに他なりません。

そのちょっと変わったというのは「値型と参照型」という違いです。
クラスから実体(オブジェクト)を生成すると、それは参照型として認められます。

しかし、構造体から実体を生成すると、それは値型になります。
値型と参照型の区別は大きな困難となる、といいましたが、値型と参照型の区別は単なる変数だけにとどまりません。

確かに、構造体は値型のクラスとほぼ同じです。
しかし、なぜ値型を使うのでしょうか。

値型というのは参照型に比べて基本的に動作が軽くなります。ただし、それは実体が小さい場合です。
実体が大きくなれば、それを
a = b;
などと代入するごとに実体そのものがコピーされるため、メモリの大量消費につながります。

実体が小さければ、コピーするにもメモリを余り必要としません。
int 型などの単純な型に値型が採用されているのは、この使いやすさのためです。

しかし、実際は構造体は頻繁に利用されることはありません。クラスほど完全なものではなく、いろいろな制限が付いている点もその原因の一つです。
C# プログラミングにおいてこれは必ず構造体を利用すべきというときは皆無に等しく、クラスが一般的なアイテムです。

先ほども説明したように、構造体を乱用するとメモリを大量に消費しますから、私自身も実際のプロジェクトで構造体を使ったことはありません。
しかし、オブジェクト指向を理解する上で重要な項目であることには変わりありませんから、このページを飛ばして読むなんてことはしないように。

構造体とクラス

構造体の習得は値型と参照型の区別が付いていないと無理です。読んでも無駄なので、復習すべきでしょう。
とはいっても読み返していくのは大変なので、ここでもう1度その復習を済ませたいと思います。その復習は構造体の理解につながります。

その前に、構造体は「struct」キーワードで定義します。「class」キーワードをつけるはずの位置に「struct」を置くことで、クラスではなく構造体として宣言することになります。
struct というのは「structure(構造;建造物)」からとったのだと思いますが、構造体と訳さず、そのまま「ストラクチャー」を専門用語にしてもよかったのではないかという気がしてなりません。

それはともかく、構造体の定義方法がわかりましたから、ソースを見ても大丈夫でしょう。コメントもあり、長めですがきちんと見て理解してください。
using System;

struct Circle
{
    public double r;
    
    public double S()
    {
        return r * r * 3.14;
    }
}

class Class_Circle
{
    public double r;
    
    public double S()
    {
        return r * r * 3.14;
    }
}

class Test
{
    public static void Main(string[] args)
    {
        // --------------------
        // 構造体の例
        // --------------------
        Circle c = new Circle();
        c.r = 5;
        Console.WriteLine("半径" + c.r + "の円の面積は" + c.S() + "です"); // 78.5
        
        Circle c2 = c;
        Console.WriteLine("半径" + c2.r + "の円の面積は" + c2.S() + "です"); // 78.5
        
        c2.r = 3;
        // c と c2 は別の実体そのものが入っているから、答えが異なる
        Console.WriteLine("半径" + c.r + "の円の面積は" + c.S() + "です"); // 78.5
        Console.WriteLine("半径" + c2.r + "の円の面積は" + c2.S() + "です"); // 28.26
        
        
        // --------------------
        // クラスの例
        // --------------------
        Class_Circle cc = new Class_Circle();
        cc.r = 5;
        Console.WriteLine("半径" + cc.r + "の円の面積は" + cc.S() + "です"); // 78.5
        
        Class_Circle cc2 = cc;
        Console.WriteLine("半径" + cc2.r + "の円の面積は" + cc2.S() + "です"); // 78.5
        
        cc2.r = 3;
        // cc と cc2 は同じオブジェクトへの参照が入っているから、答えは同じ
        Console.WriteLine("半径" + cc.r + "の円の面積は" + cc.S() + "です"); // 28.26
        Console.WriteLine("半径" + cc2.r + "の円の面積は" + cc2.S() + "です"); // 28.26
    }
}
「Circle」は構造体、「Class_Circle」はそれと比較するためのクラスです。定義されたメンバは全く同じにしています。

Main() の中でも同じ操作をしていますが、表示結果は異なります。1行ずつ見ていくとダラダラするので、まとめてみてみましょう。
クラスと構造体

Main() に存在するフィールド(変数)は
c / c2 / cc / cc2
です。このうち前2つが構造体用、後ろ2つがクラス用です。

Circle c2 = c;
問題となるのはこのステートメントです。Circle 型の変数に c の実体を代入していますが、これは実際にその実体をコピーし、代入しています。

int a = 2;
int b = a;
このプログラムでは b には a の中身がコピーされ、すなわち 2 という値がコピーされ、コピーされたものが b に代入されることを意味します。

図示しているように、c と c2 の実体は別物です。コピーされたから、2つ存在するわけです。

Class_Circle cc2 = cc;
ところが、クラスの場合は cc2 を用意したものの、そこに入るのは実体へのアドレス(参照)です。変数 cc , cc2 から矢印が出ていますが、それが参照です。

この違いが表示結果に現れています。
構造体は別物ですから、c2 をいじっても c は変化しません。しかし、クラスは同じものですから cc2 をいじると cc は同じように変化します。(実際に変化するのは参照先のオブジェクトです)

構造体はこの違いがわかればもう終わったも同然です。あとは多少の制約を把握すれば良し。

制約

残念ながら、
構造体とは値型のクラスのことである
という説明では80点しかもらえません。少し付け加えて、
構造体とはいくつかの制限を持った値型のクラスのことである
とすれば減点対象は消えます。

その制限で理解しておかねばならないのが、「コンストラクタ」と「変数の初期化」です。
この2つに関しては大げさに言いましたが、大変簡単です。

まず「デフォルトコンストラクタの定義不可」です。
コンストラクタはクラス名と同じ名前で、値を返さず、クラスからオブジェクトを生成したときに呼び出されるメソッドでした。

しかし、これまでコンストラクタを定義しなかったのですが、
new Circle();
と、() 付きの、明らかなコンストラクタを呼び出しています。定義していないのにコンストラクタがある、このコンストラクタのことを「デフォルトコンストラクタ」といいます。

ここでのデフォルトコンストラクタとは、特に引数をとらないものです。クラスでは引数をとらないコンストラクタがあっても、デフォルトコンストラクタの出番がなくなって、自分で定義したものは普通に使えます。
しかし、構造体でコンストラクタを定義する際は必ず引数をとらなければなりません。

using System;

struct TRICK
{
    public string cast;
    
    public TRICK(string cast)
    {
        this.cast = cast;
    }
    
    public void Show()
    {
        Console.WriteLine("指定された出演者は" + cast + "です。");
    }
}

class Test
{
    public static void Main(string[] args)
    {
        string cast = "仲間由紀恵";
        TRICK tri = new TRICK(cast);
        tri.Show();
        
        TRICK tri2;
        tri2.cast = cast;
        tri2.Show();
    }
}
構造体名は TRICK ですから、コンストラクタ名は TRICK にし、string 型の引数を用意しています。ただこれだけのことです。

いや、これだけではありません。妙な変数 tri2 が存在しています。
これには実体が入っていないにもかかわらず、構造体のメンバにアクセスしています。

これは単にオブジェクトの初期化が行われないだけです。そのため、次のステートメントで直接代入しています。
クラスでは考えられないことですが、構造体ならではの「new なしの初期化」と覚えておけばよいでしょう。

次は変数の初期化です。構造体の中の変数、例えば上の例の
public string cast;
に対して、あらかじめ初期化しておくことは禁じられています。よって、以下のようにするとエラーが出ます。
public string cast = "仲間由紀恵";

構造体はクラスより単純なものです。制限があるというのは、応用性が利かないということです。
構造体の乱用は避けるべき、というのがお分かりだと思います。できるだけクラスを使うように、という意味ではありませんが、クラスを使えば無難であるというのは確かです。

構造体はともかく、値型と参照型の区別はこの構造体で完全マスターしてください。その区別のほうが C# プログラミングを総合的に考えると重要かもしれません。

[ ステップアップC# ] [ C#プログラミング応用講義 ]