ここまでたどり着いた方、あるいはいきなりここを見ている方、お疲れ様です。いよいよC++の本格的に便利な機能を紹介していきたいと思います。例としてベクトル演算クラスを構築するまでの流れを見せて行きたいと思います。
以下の例があります。
#include <stdio.h>
int main()
{
float ax,ay,az , bx,by,bz , cx,cy,cz;
ax = 2;
ay = 1;
az = -2;
bx = 1;
by = 1;
bz = 1;
cx = ax + bx;
cy = ay + by;
cz = az + bz;
printf("C = %f %f %f\n" , cx , cy , cz);
return 0;
}
ベクトルa , b , c間の演算をしたいわけですよ。今日のプログラムでは頻繁に行われる演算ですね。明らかに記述が面倒くさいです。配列でループを用いればと思われるかも知れませんが、3回の繰り返しにループの処理は重くなってしまいます。ここで、ひとまず変数をひとつのグループにまとめます。
C++で必要な機能をひとつのグループとしてみる場合、クラスと呼ばれる機能が用いれます。
#include <stdio.h>
class C3DVec{
private:
public:
float x , y , z;
};
int main()
{
C3DVec vA , vB , vC;
vA.x = 2;
vA.y = 1;
vA.z = -2;
vB.x = 1;
vB.y = 1;
vB.z = 1;
vC.x = vA.x + vB.x;
vC.y = vA.y + vB.y;
vC.z = vA.z + vB.z;
printf("C = %f %f %f\n" , vC.x , vC.y , vC.z);
return 0;
}
public:
クラスの要素をメンバと呼びます。publicは外部からアクセスできるメンバを意味します。
private:
privateは外部からアクセスできません。外部からアクセスする必要の無いものはできるだけprivateメンバとして確保しましょう。
クラスのメンバには上記のように . (ドット演算子?)でアクセスできます。用いる変数がグループ化されたので宣言では9個から3つへ減りましたね。しかし、実行部は相変わらずです。ではここでベクトルの足し算をする関数、ベクトルの初期化をする関数を作りましょう。
#include <stdio.h>
class C3DVec{
private:
public:
float x , y , z;
};
C3DVec vAdd(C3DVec v1 , C3DVec v2)
{
C3DVecvRet;
vRet.x = v1.x + v2.x;
vRet.y = v1.y + v2.y;
vRet.z = v1.z + v2.z;
return vRet;
}
C3DVec vSet(double x , double y , double z)
{
C3DVecvRet;
vRet.x = x;
vRet.y = y;
vRet.z = z;
return vRet;
}
int main()
{
C3DVec vA , vB , vC;
vA = vSet(2 , 1 , -2);
vB = vSet(1 , 1 , 1 );
vC = vAdd(vA , vB);
printf("C = %f %f %f\n" , vC.x , vC.y , vC.z);
return 0;
}
まあすっきりしてきましたよね。しかし、ここまではC言語の構造体という機能を用いれば実装可能な範囲です。
ここでよく考えれば、vAdd 、vSetはこのベクトルクラスに対してしか使わない関数であろうということです。そこでC++ではクラスの機能に関数を含めることができます。メンバ関数と呼ばれます。
メンバ関数はメンバ変数に自由にアクセスできます。
#include <stdio.h>
class C3DVec{
private:
public:
float x , y , z;
C3DVec vAdd(C3DVec v2)
{
C3DVecvRet;
vRet.x = x + v2.x;
vRet.y = y + v2.y;
vRet.z = z + v2.z;
return vRet;
}
void vSet(double ix , double iy , double iz)
{
x = ix;
y = iy;
z = iz;
}
};
int main()
{
C3DVec vA , vB , vC;
vA.vSet(2 , 1 , -2);
vB.vSet(1 , 1 , 1 );
vC = vA.vAdd(vB);
printf("C = %f %f %f\n" , vC.x , vC.y , vC.z);
return 0;
}
すっきりしましたし、なによりベクトル演算に必要な機能、変数がまとめられてわかりやすくなっています。vAddとvSetは3DVecオブジェクトからしか呼び出せず、ほかの目的に使うことはできなくなっています。
ここで、クラス型の変数をオブジェクトと呼びます。型のことをクラス、実際にメモリが確保され利用可能状態の変数をオブジェクトと呼ぶとだけ覚えておけばいいと思います。
さてC++の快進撃はまだまだ続きます。私たちの最終目標は
vC = vA + vB;
と書くことです。そこでC++では + という名前の関数をつくれます。しかし + はプログラム言語として数値加算の役割がすでにあるので関数名としては通常使えません。よってC++では以下のように書きます。
#include <stdio.h>
class C3DVec{
private:
public:
float x , y , z;
C3DVec operator+(C3DVec v2)
{
C3DVecvRet;
vRet.x = x + v2.x;
vRet.y = y + v2.y;
vRet.z = z + v2.z;
return vRet;
}
void vSet(double ix , double iy , double iz)
{
x = ix;
y = iy;
z = iz;
}
};
int main()
{
C3DVec vA , vB , vC;
vA.vSet(2 , 1 , -2);
vB.vSet(1 , 1 , 1 );
vC = vA.operator+(vB);
printf("C = %f %f %f\n" , vC.x , vC.y , vC.z);
return 0;
}
上記のようにoperatorキーワード 、 演算子 の形でかけます。そして、.operatorと関数呼び出し演算子である()を省略することができ、
int main()
{
C3DVec vA , vB , vC;
vA.vSet(2 , 1 , -2);
vB.vSet(1 , 1 , 1 );
vC = vA + vB;
printf("C = %f %f %f\n" , vC.x , vC.y , vC.z);
return 0;
}
このようにかけます。+演算子の機能をそのクラス間でのみ別の動作をさせることができます。これを演算子のオーバーロードといいまして非常によく使われます。便利ですから。
ちなみに演算子と呼ばれるものはすべてこのようにオーバーロードすることができます。- の減算演算子で、加算をさせることもできます。やりませんけどね。
受け取れる引数は演算ごとに大体固定です。+ 演算子なら引数は1つです。- * / = なども同様です。[]添え字演算子もオーバーロード可能です。もちろん==など比較演算子もいけますね。
関数呼び出し演算子()は特殊です。引数はいくつでも設定できます。まあ、関数ですからね。
などなど、詳しく知りたい方は以下の高度な内容をご覧ください。
しかし + と書いてもそれは関数を呼び出しているのだから重いのでは?と思うかも知れませんが、このような簡単な関数はコンパイル時にコードに埋め込まれます。わざわざcallで呼び出されることはありません。苦労して書いてきましたが、コンパイルするときには一番最初に書いた文へと展開されてしまうのです。なので速度については元のコードと変わらないので気にすることはありません。
まあ、コンパイルにはやたらと時間を食いますがwww
以上でクラスの基礎、オブジェクト指向の基礎を一通りわかっていただけると思います。