プログラミング言語でのクラスについて紹介します。
プログラミング言語での「クラス」は、構造体のように構造化された変数を持つとともに、それらを操作する処理を含めたデータ型です。「抽象データ」とも呼ばれます。
こちらの記事で紹介した構造体ですが、いちいちメンバ変数に値を代入していくのは大変ですので、3つの座標を同時に設定できると便利です。また、3つの数値の和を頻繁に利用するため、座標の和を取り出す処理を記述しておきたいと考えました。
プログラミング言語にもよりますが、構造体では、内部に構造体を操作するための関数を実装できます。
struct MyPosition {
int x;
int y;
int z;
void SetValue(int px, int py, int pz) {
x = px;
y = py;
z = pz;
}
int Total() {
return x + y + z;
}
};
int plot(MyPosition pos);
int main()
{
MyPosition pos1, pos2, pos3;
pos1.SetValue(3, 5, 3);
pos2.SetValue(2, 6, 1);
pos3.SetValue(7, 1, 4);
plot(pos1);
plot(pos2);
plot(pos3);
char buffer[10];
fgets(buffer, 10, stdin);
return 0;
}
int plot(MyPosition pos) {
printf("(%d, %d, %d) %d\r\n", pos.x, pos.y, pos.z, pos.Total());
return 0;
}
下記の構造体の宣言で、構造体に2つの関数を実装しています。1つ目がSetValue関数です。この関数を呼び出し、3つの引数を与えることで、x,y,zの値を同時に設定できます。もう一つの関数がTotal関数です。この関数を呼び出すと、x,y,zの和を取得できます。
SetValue, Total 関数を構造体の外に記述して処理することもできますが、コードの記述ミスで、関係のない変数にアクセスしてしまう可能性があるため、リスクを減らすことができます。また、MyPositionに関する操作のロジックは、MyPosition内に記述して閉じた状態になることのメリットもあります。
struct MyPosition {
int x;
int y;
int z;
void SetValue(int px, int py, int pz) {
x = px;
y = py;
z = pz;
}
int Total() {
return x + y + z;
}
};
上記のコードで分かりやすく記述できるようになりましたが、ここで、Total関数の処理に時間がかかる例を考えます。もし、Total関数の処理に長い時間がかかる場合、Total関数の値が必要になるたびにTotal関数を呼び出して計算するのでは処理時間がかかってしまいます。x,y,z変数の値が変更されたタイミングで計算を実行して結果を変数に格納しておけば、その都度計算をする必要がなくなります。
そこで、構造体のコードを下記に変更します。SetValue関数で構造体の座標値が設定された際に、合計値の計算を実行し結果をtotal変数に保存しておきます。このコードであれば、座標の合計値が必要な場合は、total変数の値を参照すればよく、計算は不要になります。
しかし、このコードを利用した場合、x, y, zの値は必ずSetValue()関数を利用する必要があります。x, y, zのメンバ変数に値を直接代入した場合は、totalの更新がされず、合計値が合わなくなってしまいます。また、totalの値は外部から変更可能なため、うっかりtotalに値を代入しないよう注意する必要があります。
struct MyPositionNew {
int x;
int y;
int z;
int total;
void SetValue(int px, int py, int pz) {
x = px;
y = py;
z = pz;
total = x + y + z;
}
};
上記の問題点があることから、以下の仕組みがあれば、より安全なコードが記述できると考えられます。
これらの機能を追加したものがクラスになります。
上記の問題点を解決するため、クラスを利用したコードが下記になります。
class MyPosition {
private:
int ix;
int iy;
int iz;
int itotal;
public:
void SetValue(int px, int py, int pz) {
ix = px;
iy = py;
iz = pz;
itotal = ix + iy + iz;
}
int GetX() {
return ix;
}
int GetY() {
return iy;
}
int GetZ() {
return iz;
}
int GetTotal() {
return itotal;
}
};
int plot(MyPosition pos);
int main()
{
MyPosition pos1;
MyPosition pos2;
MyPosition pos3;
pos1.SetValue(4, 9, 2);
pos2.SetValue(6, 1, 3);
pos3.SetValue(9, 2, 5);
plot(pos1);
plot(pos2);
plot(pos3);
char buffer[10];
fgets(buffer, 10, stdin);
return 0;
}
int plot(MyPosition pos) {
printf("(%d, %d, %d) %d\r\n", pos.GetX(), pos.GetY(), pos.GetZ(), pos.GetTotal());
return 0;
}
クラスの定義が下記コードになります。
座標の値を格納するメンバ変数 ix, iy, izと3つの座標の合計を格納する itotal変数がクラスのメンバ変数として用意されていますが、これらの変数はprivate
セクションに記載されています。private
セクションに記述されているメンバ変数はクラスの外部からの参照や代入はできません。
ix, iy, izの値を設定するには、SetValueメソッド(関数)を利用します。SetValueメソッドでは、メソッドに与えられた引数を ix, iy, iz に設定するとともに、itotal変数に3座標の合計値を設定します。
各座標値の取得は、それぞれの値ごとにメソッド(関数)が用意されており、GetX, GetY, GetZ, GetTotal メソッドを利用します。関数を利用して値を取得するため、読み取り専用となり、外部から直接ix, iy, iz, itotalの値を書き換えることはできなくなっています。
class MyPosition {
private:
int ix;de
int iy;
int iz;
int itotal;
public:
void SetValue(int px, int py, int pz) {
ix = px;
iy = py;
iz = pz;
itotal = ix + iy + iz;
}
int GetX() {
return ix;
}
int GetY() {
return iy;
}
int GetZ() {
return iz;
}
int GetTotal() {
return itotal;
}
};
クラスを利用することで
が実現できました。なお、「構造体内のメンバ変数を設定する際に、ロジックを実行できるとよい」の機能はさらに進んだオブジェクト指向言語である,
C#,Java,Delphiといった言語の「プロパティ」機能で実現できます。C++は標準では「プロパティ」の実装はサポートされていません。
先のコードでクラスを利用できましたが、先のプログラムでは、プログラムの開始後の下記コードでクラスのメモリ領域が確保されます。
MyPosition pos1;
MyPosition pos2;
MyPosition pos3;
確保されたメモリが解放されるのは、pos1, pos2, pos3 のスコープが失われたタイミングになるため、プログラムの終了時になります。
小規模なプログラムでは問題にならないのですが、大きなプログラムやクラスの処理で大量のメモリを使う場合、クラスの利用を開始したいタイミングでメモリを確保し、処理が終了したタイミングで明示的にメモリを開放する必要があります。
この処理を実現するために、クラスの実体を宣言するのではなく、クラスのポインタを宣言し必要になったタイミングで、メモリの確保と開放を実行します。C++の場合はnew delete 演算子を利用します。
なお、現代的なオブジェクト指向言語である、C#, Javaなどでは、ポインタの概念がなくなり、クラスの実体の型でのオブジェクトの宣言に対してnewが必要な仕様になっています。(静的クラスの場合は不要ですが、通常のクラスでは必ず必要です。)
class MyPosition {
private:
int ix;
int iy;
int iz;
int itotal;
public:
void SetValue(int px, int py, int pz) {
ix = px;
iy = py;
iz = pz;
itotal = ix + iy + iz;
}
int GetX() {
return ix;
}
int GetY() {
return iy;
}
int GetZ() {
return iz;
}
int GetTotal() {
return itotal;
}
};
int plot(MyPosition* pos);
int main()
{
MyPosition* pos1;
MyPosition* pos2;
MyPosition* pos3;
pos1 = new MyPosition();
pos2 = new MyPosition();
pos3 = new MyPosition();
pos1->SetValue(4, 8, 2);
pos2->SetValue(6, 2, 0);
pos3->SetValue(8, 5, 3);
plot(pos1);
delete pos1;
plot(pos2);
delete pos2;
plot(pos3);
delete pos3;
char buffer[10];
fgets(buffer, 10, stdin);
return 0;
}
int plot(MyPosition* pos) {
printf("(%d, %d, %d) %d\r\n", pos->GetX(), pos->GetY(), pos->GetZ(), pos->GetTotal());
return 0;
}
MyPositionクラスのオブジェクトをポインタとして宣言しています。ポインタの"*"を型名の後に記述しています。
MyPosition* pos1;
MyPosition* pos2;
MyPosition* pos3;
ポインタで宣言しているため、ポインタの指し示す実体は空(null)の状態です。new 演算子を利用して MyPositionクラスのメモリを確保してクラスを作成します。
pos1 = new MyPosition();
pos2 = new MyPosition();
pos3 = new MyPosition();
SetValueメソッドを呼び出して、値を設定します。C++特有の記述ですが、クラスのポインタのメソッドを呼び出す場合は"."ではなく"->"演算子を利用します。意味は"."と同様にクラスのメソッドを呼び出す処理になります。
pos1->SetValue(4, 8, 2);
pos2->SetValue(6, 2, 0);
pos3->SetValue(8, 5, 3);
plot関数を呼び出し、クラスオブジェクトに設定された値を画面に表示します。画面表示が完了すれば、クラスオブジェクトは不要のため、delete 演算子を利用してクラスのメモリを開放します。
plot(pos1);
delete pos1;
plot(pos2);
delete pos2;
plot(pos3);
delete pos3;
plot関数ではクラスのメンバ変数の値を画面に表示します。こちらもクラスのポインタのメソッドの呼び出しのため"->"演算子を利用します。
int plot(MyPosition* pos) {
printf("(%d, %d, %d) %d\r\n", pos->GetX(), pos->GetY(), pos->GetZ(), pos->GetTotal());
return 0;
}