C++ 基礎
首都大学東京 田川研究室
戻る

C++ クラスを用いたベクトル演算表記

ここまでたどり着いた方、あるいはいきなりここを見ている方、お疲れ様です。いよいよ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>

//以下、新しい変数型を定義します。
//3次元のベクトル型です。要素はx,y,zで3つ
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>

//以下、新しい変数型を定義します。
//3次元のベクトル型です。要素はx,y,zで3つ
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>

//関数vAdd , vSetもクラスに含めます。アクセスは当然publicで
class C3DVec{
    private:

    public:
    float x , y , z;

    C3DVec vAdd(C3DVec v2)
    {
        C3DVecvRet;
        //v1は自分の値を使えばよいので、引数はv2だけでよくなる。
        
        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)
    {
        //メンバ変数の名前とダブらないように引数をix,iy,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>

//関数vAdd , vSetもクラスに含めます。アクセスは当然publicで
class C3DVec{
    private:

    public:
    float x , y , z;

    C3DVec operator+(C3DVec v2)
    {
        C3DVecvRet;
        //v1は自分の値を使えばよいので、引数はv2だけでよくなる。
        
        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)
    {
        //メンバ変数の名前とダブらないように引数をix,iy,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;//.operator+(引数)が省略されている。

    printf("C = %f %f %f\n" , vC.x , vC.y , vC.z);

    return 0;
}
このようにかけます。+演算子の機能をそのクラス間でのみ別の動作をさせることができます。これを演算子のオーバーロードといいまして非常によく使われます。便利ですから。
ちなみに演算子と呼ばれるものはすべてこのようにオーバーロードすることができます。- の減算演算子で、加算をさせることもできます。やりませんけどね。
受け取れる引数は演算ごとに大体固定です。+ 演算子なら引数は1つです。- * / = なども同様です。[]添え字演算子もオーバーロード可能です。もちろん==など比較演算子もいけますね。
関数呼び出し演算子()は特殊です。引数はいくつでも設定できます。まあ、関数ですからね。

などなど、詳しく知りたい方は以下の高度な内容をご覧ください。
しかし + と書いてもそれは関数を呼び出しているのだから重いのでは?と思うかも知れませんが、このような簡単な関数はコンパイル時にコードに埋め込まれます。わざわざcallで呼び出されることはありません。苦労して書いてきましたが、コンパイルするときには一番最初に書いた文へと展開されてしまうのです。なので速度については元のコードと変わらないので気にすることはありません。
まあ、コンパイルにはやたらと時間を食いますがwww
以上でクラスの基礎、オブジェクト指向の基礎を一通りわかっていただけると思います。

高度な内容

先ほどのベクトル演算クラスは完璧ではありません。
#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;
    }

    C3DVec operator*(float sc)
    {
        C3DVecvRet;
        
        vRet.x = x * sc;
        vRet.y = y * sc;
        vRet.z = z * sc;

        return vRet;
    }

    //vSetもごらんのようにする
    void operator()(double ix , double iy , double iz)
    {
        //関数呼び出し演算子()の関数というのもおかしなものだが。記述が少し楽になる。
        x = x;
        y = y;
        z = z;
    }
};
//左にスカラー、右にベクトルを受け取る関数
C3DVec operator*(float sc , C3DVec vi)
{
    C3DVecvRet;
        
    vRet.x = vi.x * sc;
    vRet.y = vi.y * sc;
    vRet.z = vi.z * sc;

    return vRet;
}

int main()
{
    C3DVec vA , vB , vC;

    vA(2 , 1 , -2);//.operator()が省略されている
    vB(1 , 1 , 1 );


    vC = vA + vB * 2;
    vC = vA + 2  * vB;

    printf("C = %f %f %f\n" , vC.x , vC.y , vC.z);

    return 0;
}
上記は関数呼び出し演算子()をオーバーロードしました。クラス名に()をつけるだけで、値をセットする関数をよびだせます。

*について、クラスの外部、内部に二つの関数が定義されていますが、数値を前からかけるか後ろから掛けるかで自動で呼び出しを切り替えてくれます。これは関数のオーバーロードと呼ばれます。関数オーバーロードでは引数の型の違いにより呼び出す関数を分けてくれます。
クラスのメンバ関数では、自分の後ろ側に書かれたものしか引数としては受け取れないのでこのような表記をします。

関数オーバーロード、演算子オーバーロードを組み合わせればより柔軟なコードを書くことができます。