C#でクラスを作ろう(1)/構造体とクラス

3月 17, 2020

このシリーズについて

C言語やC++言語などを学んではいるけどクラスをあまり作ったことが無い、という方を対象にしています。
このシリーズでは、C#でクラスを作るための基本的な構文を解説しています。C++やJavaなどと共通している概念も多いですが、サンプルコードは基本的にC#で解説します。ところどころ、C++特有の概念を解説することもあります。

どんなプログラミング言語でも大体、構造体やクラスといったものが登場します。これらは一体何なのか、使い分けはどうするのか、という基礎的なことを解説します。

スポンサーリンク

構造体やクラスはデータの集まり

構造体やクラスというのは、データの集まりです。例えば、人物の情報を表わすPersonというクラスをC#では以下のように定義します。

class Person
{
    public string Name;         //名前
    public DateTime Birthday;   //誕生日
    public string Address;      //住所
    public double Height;       //身長
    public double Weight;       //体重
}

プログラムの中で人物のデータを扱う必要があるとき、まさにその日本語の通り、「人物」を1つのものとして扱うようにします。それが、このPersonというクラスです。

プログラミングで重要なのは、我々が日本語として認識できる「モノ」をそのままプログラム上で1つの「モノ」として表現するということです。日本語の「人物」は、プログラム上では「Personというクラス」。

しかし、コンピューターというのは、「人物」なんていう複雑なデータをそのまま扱うことができません。コンピューターが直接扱えるのはせいぜい、数値や文字や日付くらいしかありません。

例えば画像データなども、

  • 座標(0, 0)の色は、赤255、緑0、青0
  • 座標(1, 0)の色は、赤224、緑16、青0
  • ・・・

という膨大な量の数値データの集まりです。他にも、動画データ、音声データ、インターネット接続情報、GPSが示す位置情報、3次元立体映像などなど、コンピューターが扱うどんな複雑なデータも、結局は数値や文字などのプリミティブなデータの集合にしかすぎません。

プリミティブ型

プリミティブ(primitive)とは、辞書によれば「原始の、初期の、根源的な、素朴な」などのようなものを表わす形容詞です。
プログラミング言語の世界でも、数値や文字などの「根源的な」データのことをプリミティブ型と呼びます。プログラミング言語というものを言語構造やコンパイラといった観点からもっと専門的に議論する場合は、何がプリミティブなのかそうでないのか厳密に議論が交わされることもありますが、そのあたりはガチ勢に任せておいて、とりあえずは「根源的な」データのことだと理解しておけばOKです。

コンピューターはプリミティブなデータしか扱うことができません。しかし我々がプログラムを書くとき、「名前を表わす文字列と誕生日を表わす日付値を画面に表示して、えーと…」って頭の中で考えていると大変です。

プログラムを書くという行為は、できるだけ頭の中の日本語とコンピューターへの指示の内容が一致しているほうがラクで効率がいいです。「人物の情報を画面に表示」っていう「やりたいこと」をそのままコンピューターへ指示できたほうがラクですよね?

だから、構造体やクラスというものを使って、プリミティブなデータ(数値や文字などの根源的なデータ)の集まりを、頭の中で考えやすい「人物」というものに変換するわけです。

何かプログラムを書こうと思ったとき、頭の中でいろいろな「モノ」が登場すると思います。それは動画データだったり音声データだったり、あるいはもっと複雑なモノだったりするかもしれません。そんな感じで頭の中に登場したモノは全て、1つの構造体やクラスになり得ます。

C#における構造体とクラスの違い

まず先に結論だけ書いておくと、自分で「データの集まり」を表現するものを定義したいと思ったときは、基本的にクラスを使うようにしましょう。

しかし、例えばグラフィックを扱うときに登場するSystem.Drawing.Point構造体のように、フレームワークが提供するものの中には、クラスではなく構造体で定義されているものもあります。

そのような場合、構造体とクラスの振る舞いの違いを知っておくことが大切です。構造体もクラスも「データの集まり」を表現できる点は同じなのですが、値型なのか参照型なのかという点で大きく違います。

Pointが構造体だった場合、以下のような振る舞いになります。

//Point構造体の例
struct Point
{
    double X;
    double Y;
}
  
void Main()
{
    Point pt1 = new Point(0, 0);
    Point pt2 = pt1; //pt1の値をpt2へコピーする
    pt2.X = 50;
    Console.WriteLine($"pt1の座標は({pt1.X}, {pt1.Y})");
    //出力結果は "pt1の座標は(0, 0)"
}

pt2 = pt1 のところでは、Pointは構造体なので、pt1の値がpt2へコピーされます。その後pt2.Xを50に変更しても、元のpt1は変化しません。pt2はpt1とは別物だからです。

一方、MyPointというクラスがあったときは、以下のような振る舞いになります。

//MyPointクラスの例
class MyPoint
{
    double X;
    double Y;
}
  
void Main()
{
    Point pt1 = new Point(0, 0);
    Point pt2 = pt1; //pt2はpt1への参照であるとする
    pt2.X = 50;
    Console.WriteLine($"pt1の座標は({pt1.X}, {pt1.Y})");
    //出力結果は "pt1の座標は(50, 0)"
}

pt2 = pt1のところで、「pt2はpt1への参照」であるとしています。その後pt2.Xを50に変更すると、「pt2」というオブジェクトはもともとpt1だったものへの参照なので、元のpt1.Xも50に変わります。

このようにC#の言語仕様では、struct(構造体)は値型、クラスは参照型、という決まりがあります。オブジェクトの振る舞いが値型(実体的)なのか参照型(参照的)なのかはとても重要です。詳しくはオブジェクトという考え方で解説しています。

この例ではあまり実感が沸かないかもしれませんが、オブジェクトがプログラムの中を縦横無尽に駆け巡る実際のプログラミングにおいては、オブジェクトがクラスとして参照的に振る舞うほうが便利です。つまり、自分でデータを定義するときは、構造体ではなくクラスとしたほうが後々ラクになります。

C++のクラス

C#などの現行のプログラミング言語では、言語仕様によって「構造体は値型、クラスは参照型」と決められています。

しかし、C++の場合は、構造体もクラスも全てが値型になっています。というより、値型とか参照型という概念が言語仕様には存在せず、オブジェクトを参照的に振る舞うようにするためにはポインタや「参照」というものを使わなくてはいけません。

歴史的に見て、C++のクラスというのは、値型か参照型かという区別の必要性によって登場したものではなく、メソッド(メンバ関数)の必要性によって構造体が拡張されたものだからというのが、その理由だと思います。メソッドを持つことができる構造体、それがつまりC++におけるクラスです。

プログラミング言語によって、このように構造体やクラスの振る舞いは微妙に違います。どんな言語を使う場合でも、そのあたりは確認するようにしましょう。そして、できるだけオブジェクトが参照的に振る舞うように使っていきましょう。

クラスの他の側面

というわけで、構造体のことはとりあえず置いといて、クラスというものについて今後どんどん注目していきたいと思います。

その最初のステップとして、クラスというのはプリミティブなデータの集まりであるということを解説しました。

しかし、クラスには、もっと別の強力な側面がたくさんあります。そのあたりは次回以降に解説します。

スポンサーリンク