C++入門/Classと型

柴田祐樹,東京都立大学,情報科学科
戻る

C++ では型により適用する演算を区別する.型と演算子の組み合わせを列挙しプログラムを構成できればよいのであるが,そのためには,予め用意されている int や double といった数値型だけでなく,設計者独自の型を定義する方法が必要である.

int と double には四則演算が定義されているが,文字列には普通四則演算は定義されない.このように,すべての型にすべての演算が定義されるわけではない.C++では四則演算以外にも多くの演算子が定義されている.例えば,代入演算子(=),比較演算子(==, <, >, <=, >=), 論理演算子(and, or, &&, ||)などがある.詳細は[Operators in C and C++, Wikipedia] など参照されたい.これらの中で,どの方にも定義されているのが代入演算子 = である.同じ型の変数の値は,別の方の変数へ代入することができる.このことを検証するのが次に示すコードである.新しい型は struct か class を新しい型名の前につけて宣言する.どちらでも良いのだが後にわかるようにここではかんたんに扱える struct を用いるとする.{ } の中には型の性質を記載するのだが,ここでは特に何も記載しないものとする.

struct C{
};

struct B{
};

int main(){

    C c1, c2;
    B b1;

    c1 = c2;

    b1 = c1; // 違う型は代入が不可能.
    c1 = 2; // 違う型は代入が不可能.

    return 0;
}

上記で定義された型 C と B は何も性質が定義されていないが,この状態でも代入演算子については動作が規定される.型 C の変数の値は,型 B の変数へ代入することができない.具体的に言えば,12行目は問題が無いが,14, 15 行目はコンパイルに失敗する.c1 の値は b1 に入らなし,数値2は少なくとも数値ではない c1 に入れることはできないからである.

この二つの型の間に様々な演算を定義したいところだが,型の示す具体的情報がなければ想像がしづらいと思うため,型による情報の表現方法を先に説明する.計算機においてこの表現に使えるものは,はじめから用意されている int や double などの数値型のみである.これらを組み合わせて新たな型の情報を表現しなければならない.これは先ほど説明した,struct の中身に変数を定義することで可能である.このように特に関係性の無かったデータ同士に構造をもたせることから,構造体と呼ばれるのだろう.以下に構造体を用いたプログラムの例を示す.

#include <iostream>
struct R2{
    double x;
    double y;
};

struct Monkey{
    int age;
    double weight;
};

int main(){

    int i=0;
    R2 a, b;
    Monkey m1;

    a.x = 1;
    a.y = 2;

    m1.age = 99;
    m1.weight = 120;

    b = a;
    std::cout << b.x << ", " << b.y << "\n";
    return 0;
}

R2 は2次元ベクトルを表現することを目的にしている.Monkey は名前の通り猿の表現を試みている.それぞれ二つの変数によりこの型の変数が表現されることを示している.R2 では型 double が二つである.このように struct の中に定義された変数をメンバ変数とよぶ.このメンバ変数は,18, 19行目に示すとおり,「.」を使い参照することができる.24行目のように代入を行えば,メンバ変数もすべて対応するものが代入されるようになる.ここで,変数はたかだか . を記載するだけであるなら,以下のように変数の命名規則を工夫すればよいと思われるかもしれない.

int main(){

    double a_x, a_y;

    a_x = 1;
    a_y = 2;

    return 0;
}

だが,の24行目の例でもすでにわかるとおり,まとめて取り扱うことでプログラムの行数を減らすことができ,さらに「_」は作成者が目で見て判断するものであるが,「.」はコンパイラが判断するため,変数とそのメンバ変数の関係性について,人の間違いが入り込む余地がなくなる.よって「.」を用いれる方が精確なプログラムを記載しやすくなる.

ここで,二次元ベクトルの表現には,この二つの要素で十分であろうが,猿の方は他にも毛の色など色々考えられ,きりがない.計算機で現実世界のものを数値型の組み合わせで表現することは,数理モデル化とも呼ばれるが,これは容易に行えるものではない.常によく考慮する必要がある.これ以降しばらくは定義がはっきりしている二次元ベクトルを扱っていく.ベクトルの計算は多くの分野で役に立つので題材として悪いものではないだろう.

まとめて扱えるという利点は,関数への引数に対しても同様に適用される.以下にR2の要素を二つ引数にとり,ベクトルの加算を実行して新しいR2の要素を生成する関数の例を示す.

#include <iostream>
struct R2{
    double x;
    double y;
};

R2 add(R2 a, R2 b){
    R2 r;
    r.x = a.x + b.x;
    r.y = a.y + b.y;
    return r;
}

int main(){

    R2 a, b;

    a.x = 1;
    a.y = 2;

    b.x = 1;
    b.y = 2;

    R2 c = add(a, b);

    std::cout << c.x << ", " << c.y << "\n"; // 2, 4 と表示されるはず.
    return 0;
}

加算を行うたびに,その要素をすべて列挙する以下,

R2 c;
c.x = a.x + b.x;
c.y = a.y + b.y;
        

に比べればだいぶ見通しがよいとわかるだろう.

さらに言えば,われわれは c = a + b のように記載したい.また,a や b の初期化を一行で済ませたいし,それが初期化を行っていると人目でわかるようにしたい.これを実現するのが以下のコードである.

#include <iostream>
struct R2{
    double x;
    double y;
};
R2 genR2(double x, double y){
    R2 r;
    r.x = x;
    r.y = y;
    return r;
}
R2 operator+(R2 a, R2 b){
    R2 r;
    r.x = a.x + b.x;
    r.y = a.y + b.y;
    return r;
}
int main(){

    R2 a, b;

    a = genR2(1, 2);
    b = genR2(2, 1);

    R2 c;

    c = operator+(a, b); // 以下に同値
    c = a + b;

    std::cout << c.x << ", " << c.y << "\n"; // 2, 4 と表示されるはず.
    return 0;
}

genR2 は add と同様に普通の関数であり,double 要素を二つ受け取って,R2 要素を生成している.つまり,double から R2 への変換関数である.ここではこれに加え,operator+ という名前の関数を定義している.この関数も + という変数名や関数名に普通使えない記号を含む関数名になっているが,機能としては普通と同じで,27行目のように呼び出すことができる.ただ,operator+ という関数には特別な省略記法が用意されていて,28行目のように記載することと27行目のように記載することが同値であると保証されている.

他にも用意されている.ベクトルの演算に必要な関数をいくらか用意したのが次の例である.

#include <iostream>
struct R2{
    double x;
    double y;
};
R2 genR2(double x, double y){
    R2 r;
    r.x = x;
    r.y = y;
    return r;
}
R2 operator+(R2 a, R2 b){
    R2 r;
    r.x = a.x + b.x;
    r.y = a.y + b.y;
    return r;
}
R2 operator-(R2 a, R2 b){
    R2 r;
    r.x = a.x - b.x;
    r.y = a.y - b.y;
    return r;
}
R2 operator*(double s, R2 b){
    R2 r;
    r.x = s*b.x;
    r.y = s*b.y;
    return r;
}
R2 operator*(R2 b, double s){
    R2 r;
    r.x = b.x*s;
    r.y = b.y*s;
    return r;
}
int main(){

    R2 a, b;

    a = genR2(1, 2);
    b = genR2(2, 1);

    R2 c, d, e, f;

    c = a + b;
    d = a - b;
    e = a * 2.1;
    f = 2.2 * a;

    return 0;
}

演習課題