この型の変数の作成は単に次の様にする。struct Particle { double m, q, p, a; };
この構造体変数 p1, p2 はそれぞれ内部に m, q, p, a の 要素をもち、これらの要素を本書ではpropertyと呼ぶことにする (C/C++言語ではmember変数と呼ぶのが普通である。)。 propertyの読み書きは次の様にする。Particle p1, p2;
「構造体変数.property」である。 構造体変数で複数の量をまとめるとデータの管理が容易になる。p1.m = 1.0; p1.q += p1.p/p1.m * 0.001;
構造体変数は普通の型の1つの変数と同じように関数の間で取り引きされる。 構造体変数を引数として受け取る場合には、読み込み目的でも参照渡しで 受け取る方が効率が良い。読み込み目的の場合には次のように const をつけるべきである。
void func( const Particle& p );
構造体の定義の中に関数が入っている。この関数を本書ではmethodと呼ぶ。 (C++言語ではmember関数と呼ぶのが普通である。)。 methodはpropertyを操作する。結果的に構造体の データと作業内容の定義がひとつに まとまる。構造体型の変数のmethodの呼び出しは次の様にする。struct Particle { double m, q, p, a; void Evolve( double dt ){ q += p/m*dt; p += a*dt; } };
「構造体変数.method(引数)」である。 構造体にmethodを備えることで、データとそれに関係する 処理がまとまり、あたかも変数が自立して自身の処理を 行うかのようにも見える。このような概念はオブジェクト指向と 呼ばれる最近のプログラミングスタイルの根幹をなす。p1.Evolve( 0.001 );
methodの名前の宣言と作業内容の定義を別にすることもできる。
このように「戻り値型 構造体名::method名(引数)」で どの構造体に所属するかを明記して作業内容を記述する。 プログラムをモジュール別に開発する際には構造体を そのpropertyとmethodの宣言部とmethodの定義部に分離して、 宣言部のみをheaderファイルに置いておく。struct Particle { double m, q, p, a; void Evolve( double dt ); }; void Particle::Evolve( double dt ) { q += p/m*dt; p += m*a*dt; }
classではobjectの独立性と自立性をより促進させるため、外部から objectのpropertyを直接操作することを原則的に禁じ (つまり p1.m=1.0 などが禁止される。)、特定の許可された methodを通じてのみpropertyを操作できるとする立場をとる。 先の Particle 構造体をclassとして再設計する。
public: 文以降のproperty、methodは公的となり、 外部からobjectのそれらを読み書き実行することが許される。 また private: を指定するとそれ以降は私的となり、 外部からobjectのそれらを読み書き実行することが禁じられる。class Particle { double m, q, p, a; public: void Evolve( double dt ){ q += p/m*dt; p += a*dt; } };
class では public: 指定の前までの部分は私的となる。 この私的な領域のpropertyやmethodでも、同じobjectの methodや同じclassから生成された他のobjectのmethodから アクセスすることは許されるので、公的領域にあるmethodを仲介として 外部から私的領域のpropertyを操作することになる。
非効率的な作業に思えるが、データの独立性と機能を高めるためで あり、より巨大なプログラムを確実に構築するための近道なのである。 しかしながら非効率性は否定できない。それでも最近のC++コンパイラの 最適化の性能の向上により自動的にこの無駄な部分の多くは取り除かれる ものと期待されている。
Destructor(破壊子)とはclassのobjectの寿命が尽きる時に 自動的に呼び出され、その解体作業を行う特別なmethodである。 classの名前の直前にティルダ ~ を冠した名前のmethodを 作るとそれが destructor となる。
constructor、destructor共に戻り値はあってはならず、destructorは 引数もあってはならない。なお、これらの定義はクラスの公的領域で行う。
constructorに引数を指定することもできる。 また引数の型の異なった複数のconstructorを用意することもできる。class Particle { public: Particle( void ){ // do some initializations } ~Particle(){ // do some finalizations } };
こうすると、classのobjectの宣言時にParticle( void ){ q = 0.0; } Particle( double _q ){ q = _q; }
とすると、 p1 については上側のconstructorが起動し、 property q に 0.0 が設定され、 p2 については下側のconstructorが起動し、 property q に 1.0 が設定される。 この2つのconstructorは、次の様に1つにまとめられる。Particle p1; Particle p2(1.0);
引数内でのこの代入は、 p1 の宣言の様に引数が与えられていない 場合にdefault値として代入される。 p2 の宣言の様に 引数が与えられていれば代入されない。Particle( double _q=0 ){ q = _q; }
整数や実数の変数に対する \verb|+|演算子の働きは足し算と決まってい るが、classやstructのobjectに対する \verb|+|演算子の働きはプログラマが 自由に定義できる。演算子に別な意味を持たせることを演算子の多重定義と呼 び、さまざまな便利な表現が可能になる。その実例をいくつか紹介する。
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の対応が簡単でなくても、利用者はそれを 気にしなくて済むこと、などがある。
objectに (int) などのキャスト演算子が付けられた場合に 表される値をプログラマが指定することができる。キャスト演算子は compilerの暗黙の型変換の規則で自動的に付けられるので、これを利用して 種々の小細工ができる。例として先のSinTable class 内に次の キャスト演算子の多重定義を追加する。
operator int (){ return( N ); }
そのobject Sin を次のように用いるとこのキャスト演算子が適応される。
この2つ目の例では (double)(int)Sin; と暗黙の型変換が行われている。int n=Sin; double d=Sin; for( int i=0 ; i<Sin ; i++ );