● 構造体とクラス

物理計算ではひとつの物体に複数の物理量が付随する。例えば粒子の 運動を扱うなら、粒子の質量 m 、位置 q 、運動量 p 、 加速度 a 、などの物理量がそれぞれの粒子に付随する。 この様な複数の量をまとめてひとつの大きな変数として扱う機構が 構造体であり、C++ではさらにクラスがある。

■ 構造体

構造体型はプログラマが定義できる変数の型の一種であり、その型の 内部に複数の型の変数を含めることができる。 例えば Particle 型という自作の型は次の様にして定義できる。
struct Particle
{
  double m, q, p, a;
};
この型の変数の作成は単に次の様にする。
Particle p1, p2;
この構造体変数 p1, p2 はそれぞれ内部に m, q, p, a の 要素をもち、これらの要素を本書ではpropertyと呼ぶことにする (C/C++言語ではmember変数と呼ぶのが普通である。)。 propertyの読み書きは次の様にする。
p1.m = 1.0;
p1.q += p1.p/p1.m * 0.001;
「構造体変数.property」である。 構造体変数で複数の量をまとめるとデータの管理が容易になる。

構造体変数は普通の型の1つの変数と同じように関数の間で取り引きされる。 構造体変数を引数として受け取る場合には、読み込み目的でも参照渡しで 受け取る方が効率が良い。読み込み目的の場合には次のように const をつけるべきである。

void func( const Particle& p );

■ Method

C言語の構造体は変数を束ねるに過ぎなかったが、C++では構造体変数自身が methodと呼ばれる機構でそのpropertyを操作する。 先の例の Particle 構造体を次の様に改造する。
struct Particle
{
  double m, q, p, a;
  void Evolve( double dt ){
    q += p/m*dt;
    p += a*dt;
  }
};
構造体の定義の中に関数が入っている。この関数を本書ではmethodと呼ぶ。 (C++言語ではmember関数と呼ぶのが普通である。)。 methodはpropertyを操作する。結果的に構造体の データと作業内容の定義がひとつに まとまる。構造体型の変数のmethodの呼び出しは次の様にする。
p1.Evolve( 0.001 );
「構造体変数.method(引数)」である。 構造体にmethodを備えることで、データとそれに関係する 処理がまとまり、あたかも変数が自立して自身の処理を 行うかのようにも見える。このような概念はオブジェクト指向と 呼ばれる最近のプログラミングスタイルの根幹をなす。

methodの名前の宣言と作業内容の定義を別にすることもできる。

struct Particle
{
  double m, q, p, a;
  void Evolve( double dt );
};

void Particle::Evolve( double dt )
{
  q += p/m*dt;
  p += m*a*dt;
}
このように「戻り値型 構造体名::method名(引数)」で どの構造体に所属するかを明記して作業内容を記述する。 プログラムをモジュール別に開発する際には構造体を そのpropertyとmethodの宣言部とmethodの定義部に分離して、 宣言部のみをheaderファイルに置いておく。


■ structとclass

C++では struct で設計される構造体とほぼ同様なものに class で設計されるクラスがある。共にmethodを定義することが できる型であり、これらにより生成される変数をobjectと呼ぶ。

classではobjectの独立性と自立性をより促進させるため、外部から objectのpropertyを直接操作することを原則的に禁じ (つまり p1.m=1.0 などが禁止される。)、特定の許可された methodを通じてのみpropertyを操作できるとする立場をとる。 先の Particle 構造体をclassとして再設計する。

class Particle
{
  double m, q, p, a;
public:
  void Evolve( double dt ){
    q += p/m*dt;
    p += a*dt;
  }
};
public: 文以降のproperty、methodは公的となり、 外部からobjectのそれらを読み書き実行することが許される。 また private: を指定するとそれ以降は私的となり、 外部からobjectのそれらを読み書き実行することが禁じられる。

class では public: 指定の前までの部分は私的となる。 この私的な領域のpropertyやmethodでも、同じobjectの methodや同じclassから生成された他のobjectのmethodから アクセスすることは許されるので、公的領域にあるmethodを仲介として 外部から私的領域のpropertyを操作することになる。

非効率的な作業に思えるが、データの独立性と機能を高めるためで あり、より巨大なプログラムを確実に構築するための近道なのである。 しかしながら非効率性は否定できない。それでも最近のC++コンパイラの 最適化の性能の向上により自動的にこの無駄な部分の多くは取り除かれる ものと期待されている。


■ constructorとdestructor

変数の初期化を忘れることがよくあるが、 クラスにはこの初期化作業を自動的に行ってくれる便利な機構がある。 Constructor(構築子)とはclassのobjectが作成される時に 自動的に呼び出され、その初期化作業を行う特別なmethodである。 クラスと同じ名前のmethodを作るとそれがconstructorとなる。

Destructor(破壊子)とはclassのobjectの寿命が尽きる時に 自動的に呼び出され、その解体作業を行う特別なmethodである。 classの名前の直前にティルダ ~ を冠した名前のmethodを 作るとそれが destructor となる。

constructor、destructor共に戻り値はあってはならず、destructorは 引数もあってはならない。なお、これらの定義はクラスの公的領域で行う。

class Particle
{
public:
  Particle( void ){ // do some initializations }
 ~Particle(){       // do some finalizations   }
};
constructorに引数を指定することもできる。 また引数の型の異なった複数のconstructorを用意することもできる。
  Particle( void      ){ q = 0.0; }
  Particle( double _q ){ q = _q;  }
こうすると、classのobjectの宣言時に
Particle p1;
Particle p2(1.0);
とすると、 p1 については上側のconstructorが起動し、 property q に 0.0 が設定され、 p2 については下側のconstructorが起動し、 property q に 1.0 が設定される。 この2つのconstructorは、次の様に1つにまとめられる。
  Particle( double _q=0 ){ q = _q;  }
引数内でのこの代入は、 p1 の宣言の様に引数が与えられていない 場合にdefault値として代入される。 p2 の宣言の様に 引数が与えられていれば代入されない。

■ 演算子の多重定義

整数や実数の変数に対する \verb|+|演算子の働きは足し算と決まってい るが、classやstructのobjectに対する \verb|+|演算子の働きはプログラマが 自由に定義できる。演算子に別な意味を持たせることを演算子の多重定義と呼 び、さまざまな便利な表現が可能になる。その実例をいくつか紹介する。

  • Vector型に対する+演算子の多重定義

    Vector型やComplex型など、内部に複数の要素をもつ数学的変数の加算 が数学通りに記述できれば便利である。Vector型を以下のように設計すると Vector a,b,c;に対して、c=a+b; と記述して加算が できるようになる。代入は元からできる。

    class Vector
    {
      double x, y;
    public:
      Vector( double _x=0, double _y=0 ){
        x = _x;   y = _y;
      }
      Vector operator + ( const Vector& v ){
        return( Vector( x+v.x, y+v.y ) );
      }
    };
    

    operator + としてmethodのように演算子を定義する。 a+b は a.operator+(b) と解釈され、object a のmethod operator+ に 引数として b のaliasが渡される。数学的な型へのこの種の多重定義の 応用はC++の教科書に詳しく説明がある。

  • [] 演算子の多重定義によるテーブルの設計

    [] も演算子である。普通は配列に使われるが、 classでより高機能の配列を設計する際に[] 演算子 の多重定義を使う。例として$\sin$関数のテーブルを管理するclassを 設計しよう。$-\pi$から$+\pi$までの区間内の$257$個の点での$\sin$の 値を記録することにする。$-\pi,\,0,\,+\pi$の点に対応するindexをそれぞれ $-128,\,0,\,+128$ としよう。以下のような設計になるであろう。

    class SinTable
    {
      const int N = 128;
      double table[2*N+1];    // array of the table
    public:
      //---- intializes this table
      inline SinTable( void ){
        for( int i=-N ; i<=N ; i++ ){
          table[i+N] = sin(M_PI/N*i);   // sets values to the table
        }
      }
      //---- returns value of the table at given index
      inline double operator []( int i ){
        return( table[i+N] );
      }
    } Sin;    // definition of an object of this table class
    

    設計したclassで直ちにobject Sin を生成している。 以後は Sin[i] とするだけでテーブルの値が得られる。 演算子 () も同様に多重定義することでSin(i) が使える。 演算子 () は2次元以上のテーブルにも応用できる。

    このようにテーブルを設計することの利点は、 テーブルの利用者がこれを初期化しなくて済むこと、 indexと配列変数のindexの対応が簡単でなくても、利用者はそれを 気にしなくて済むこと、などがある。

  • キャスト演算子の多重定義によるpropertyの簡易表現

    objectに (int) などのキャスト演算子が付けられた場合に 表される値をプログラマが指定することができる。キャスト演算子は compilerの暗黙の型変換の規則で自動的に付けられるので、これを利用して 種々の小細工ができる。例として先のSinTable class 内に次の キャスト演算子の多重定義を追加する。

      operator int (){ return( N ); }
    

    そのobject Sin を次のように用いるとこのキャスト演算子が適応される。

      int n=Sin;    double d=Sin;   for( int i=0 ; i<Sin ; i++ );
    
    この2つ目の例では (double)(int)Sin; と暗黙の型変換が行われている。



  • 次へ
  • 目次
    Copyright(C) by Naoki Watanabe. Oct 21st, 1995.
    渡辺尚貴 naoki@cms.phys.s.u-tokyo.ac.jp