C++/初心者むけ/クラス

< C++

おおまかな仕組み編集

オブジェクトとメンバ編集

C++ではオブジェクト指向をサポートするためにクラス(class)が導入されています。

「クラス」とは、ある変数Bが、別のデータAから派生したものであるとき、そのデータAと変数Bとをひとまとめにするものです。

たとえば、ある車が山田くんちの車だとして、データ名を「yamadacar」としましょう。車には、ナンバープレートがあります。そのナンバープレート番号(変数名:「bangou」)が文字列"1234"だったとしましょう。

このとき、yamadacar.bangouという表記により、yamadacarのナンバープレート番号を表現するのが、クラスをもちいた場合の記法です。(どうやって、「クラス」の機能を利用できる状態にするかは、あとの節で説明します。)

「yamadacar」と「bangou」の間にある「.」は、ドット記号「.」です。

この派生元のデータ「yamadacar」は、クラスのオブジェクト名というものです。

そして、派生先のデータ「bangou」は、クラスのメンバというものです。


さて、車が、3台あったとしましょう。

1台目の車(変数名:yamadacar)のナンバープレート番号が「1234」だとしましょう。
2台目の車(変数名:itoucar)のナンバープレート番号が「7722」だとしましょう。
3台目の車(変数名:nakanocar)のナンバープレート番号が「8181」だとしましょう。

このような場合、itoucar.bangouという表記で、2台目の車のナンバープレート番号「7722」の事を表現できます。

このように、あるデータをもとにして(例の場合は「車」)、別の変数が派生して存在するとき(例の場合は「ナンバープレート番号」)、クラスを使うと、コード内にある、階層性のあるデータと、そのデータから派生した変数とを、階層化できます。そして、このように、クラスをもちいて、データと変数を階層化することにより、コード内容を把握しやすくなります。

そして、「yamadacar」も「itoucar」も「nakanocar」も、すべて車なので、「kuruma」というクラス名に所属させます。

手順編集

※ 前節のように、「車」で説明するので、先に前節をお読みください。

手順として、まず、クラスとメンバとの関係を定義させるため、次のようにコードを書きます。

class kuruma{
   int bangou;
};

つまり、クラス名の定義時に いっしょにメンバが定義されます。

そして、次のコード

kuruma yamadacar;

のように、「クラス名 オブジェクト名」のような書式により、クラスと各オブジェクトとの関係を定義します。 このように、あらかじめ、kurumaというクラス名と内容(メンバ)を定義したあとで、kuruma yamadacar;と宣言することにより、yamadacarをクラスkurumaのオブジェクトとして定義できます。


オブジェクト定義のこのような定義の記法については、クラス名を、まるで新しい型のように解釈すると、あたかも変数の宣言と似ていることに、注目してください。このため書籍によっては、クラスについての説明で、「変数に対する型のようなもの」のような解説をしている書籍もあります。

ともかく、上記のコードのようにして、それぞれのオブジェクトをクラスに所属させる事ができます。

よって、まとめると、まず、

class kuruma{
   int bangou;
};

int main()
{
   kuruma yamadacar;
   
}

のように、記述する必要があります。


そして、kuruma yamadacar; 以降に、

   yamadacar.bangou = 1234;

と記述すれば、各オブジェクトのメンバに、具体的な数値を代入できます。(上記のコードの例の場合は値1234を代入している。)

まとめると、

class kuruma{
   int bangou;
};

int main()
{
   kuruma yamadacar;
   yamadacar.bangou = 1234;
}

のように、記述する必要があります。このように、オブジェクトの定義よりも先に、クラス名といっしょにメンバが定義されます。

つまり、クラス機能を利用するために行う定義・宣言の順序は、

1番目: クラス名の定義、および、クラスの構造としてメンバを定義。
2番目: 各オブジェクトの定義。(そのオブジェクトを、対応するクラスと関連づける方法で、オブジェクトの構造を定義する。)
3番目: 各オブジェクトのメンバへの、数値の代入など。

という順番になります。

メンバについて言うと、具体的な個別のデータとしてのメンバについては、メンバはオブジェクトから派生したものになりますが、しかし、メンバの定義の時では、メンバはクラスの構造として記述したものになります。

そして、オブジェクトを定義する際に、そのオブジェクトが、どのクラス名のクラスから派生されたのかを宣言することにより、オブジェクトの構造を定義します。

こうすることにより、もしもオブジェクトが何個も大量にあるときに、オブジェクトの定義の手間を、単にクラスに当てはめるだけでオブジェクトを定義できるので、オブジェクト定義の手間を簡略化できます。

コード例編集

次に示すコードは、実際に動作するコードです。

#include <iostream>
using namespace std;

class kuruma{
    public:
        int bangou;
        double nenryou;
};

int main()
{
    kuruma yamadacar;
    yamadacar.bangou = 1234;

    kuruma itoucar;
    itoucar.bangou = 7722;
    cout << "山田くんちの車の番号は" << yamadacar.bangou << endl;
    return 0;
}

上記のコードをコンパイルして実行すると、

山田くんちの車の番号は1234

と表示します。

「public」など未紹介のキーワードについては、のちの節で後述します。とりあえず、本書を読み進めてください。

なんのために、クラスを使うか?編集

実は、節「おおまかな仕組み」で説明した仕組みは、「構造体」の仕組みと、ほぼ同じです。


しかしクラスでは、さらに、クラス宣言部内で宣言されたメンバを、クラス宣言部の外からアクセスをできる公開設定にするか、それとも外部からのアクセスをできない非公開設定にするかを、決定できます。

メンバをアクセスできない設定に決定した場合、原則的に、クラス宣言の外部からは、そのメンバが存在しないものとして扱われるので、クラス宣言外部からは、そのクラスのそのメンバを参照する事ができません。

ただし、クラスのメンバには、変数をメンバとするものの他にも、関数をメンバとする事もできます あるクラスのメンバとなっている関数のことを「メンバ関数」といいます。メンバ関数の内容が書かれている記述場所は、クラスの宣言部の外部です。 このように、クラスの宣言の外部に、メンバ関数の内容は記述されていますが、しかし例外的に、メンバ関数は非公開設定されたメンバにアクセスすることができます。


では、なんのために、そんな外部からのアクセスのできない非公開メンバをわざわざ用意するのかというと、その理由は、クラス宣言内部で、外部からアクセスの可能な公開メンバのデータを決定するときに、ほかの外部アクセス不可能な非公開メンバのデータの内容にもとづいて、外部アクセス可能な公開メンバのデータを決定する場合があるからです。つまり、たとえメンバを外部アクセス不可能な非公開メンバとして決定していても、そのクラス宣言の内部およびメンバ関数内部だけなら、その外部アクセス不可能な非公開メンバでも参照することができます。


逆にいうと、もし、あなたが、こういう事(クラス宣言やメンバ関数の内部だけで非公開メンバを参照をしたい場合)をする必要のない処理をしたい場合には、わざわざクラスを宣言する必要は少なくて、構造体の宣言ですみます。

構造体は、C言語にも昔から存在している機能です。構造体のほうが、古いのです。

(※ 調査中: )おそらく、C言語の古いバージョンでは、構造体の要素の宣言では、このような外部アクセス可能なのか不可能なのかの設定は、できません。(なお、C++では、じつは、構造体でも公開か非公開かを、なんと設定できてしまう。また、プログラミング言語の規格は、数年ごとに変わるので、現代の規格のC言語が外部アクセス可能/不可能の設定ができるかどうかは、各個人が調べてください。)

おそらくC++の発明当初の開発者は、C++の規格を決定するさいに、構造体に外部アクセス可能/不可能の設定機能を追加するのではなく、構造体とは別の機能(今でいう「クラス」)をつくり、その「構造体とは別の機能」とやらに外部アクセス可能/不可能の機能を組み込むという方針を選んだのでしょう。


さて、クラス機能で、あるクラスのメンバが、外部アクセスのできる状態であることの宣言方法は「public」で宣言します。いっぽう、あるクラスのメンバが、外部アクセスのできない状態であることの宣言方法は「private」で宣言します。(この他にも「protected」という状態も宣言できるのだが、「継承」などの概念が必要になるので、説明を、あとの節に回し、この節では説明を省略する。)


なお、われわれのいう「外部アクセス可能/不可能の設定機能」のことを、プログラミング業界の専門用語で、「カプセル化」といいます。

つまり、カプセル化したい要素がない場合、クラス機能を使わずとも「構造体」機能でも済んでしまうでしょう。

C言語ではクラス機能を使えません。なので、もし、ある変数やデータの集合が、「構造体」機能でも記述できる事ならば、なるべく構造体としてコードを記述したほうが、そのコードを(C++だけではなく)C言語でも活用できるので、なるべく構造体で記述するのが良いでしょう。


複数個のメンバの場合編集

クラスのメンバは、複数個あっても、かまいません。

まず、前節までに作成したコードを再掲します。

class kuruma{
   int bangou;
};

メンバとは、上記のコードでは、「bangou」がメンバです。

では、複数個のメンバがある場合を考えます。

クラスのメンバとして、さらに、ある車の燃料の量(変数名「nenryou」)があったとしましょう。

そのような場合、コードは単に、

class kuruma{
   int bangou;
   double nenryou;
};

このように定義するだけです。

メンバ関数編集

クラスを定義するとき、メンバには、変数メンバの他にも、関数をメンバとすつ事もできます。

あるクラスのメンバとなる関数のことを関数メンバといいます。「関数メンバ」は、もちろん、関数であります。

では、「車」の例で、メンバ関数を学びましょう。まず、「車のナンバーは◯◯です。」(◯◯には、その車のナンバープレートの番号(円数名「bangou」)が入る。)と表示する関数を、クラスkurumaに追加しましょう。

class kuruma{
   int bangou;
   void kansuu();
};

上記のコードで、「kansuu()」がメンバ関数です。

そして、メンバ関数の内容を定義は、クラスの定義部の外で行うことになります。 メンバ関数の内容を定義するコードの記述方法は、下記のようになります。

void kuruma::kansuu()
{
   cout << "車のナンバーは" << bangou << "です" << endl;
}


さて、上記のコード中にもある演算子「::」は、スコープ演算子といいます。コロン「:」を2回、つづけて入力するだけです。(直接入力。)

メンバ関数の書式は「クラス名::メンバ関数名()」のようになります。


さて、上記のコードの例では、クラスは1つだけでした。しかし一般に、プログラムによっては、複数個のクラスを定義するプログラムもあります。

そして、クラスが複数個あるプログラムの場合、異なるクラスにおいて、同名のメンバを定義する事が、できます。

たとえば、あるプログラムで、クラスkurumaとクラスuntensyu(運転手)を定義して、両方のクラスとも、メンバとしてbangouを使うこともできます。

また、メンバが関数の場合(つまりメンバ関数では)、複数個の異なるクラスにある、同名のメンバ関数の内容を、まったく別々の内容にできます。


そのため、メンバ関数の内容を定義するさい、まず、どのクラスに所属するメンバ関数を定義しようとしてるかを、コードに記述する必要があります。

そのため、まず、クラス名を書いてから、直後にスコープ演算子「::」に書いて、そのうしろにメンバ関数名を書く必要があります。


命令とクラスと名前空間編集

C++では、「cout」や「cin」などの命令も、じつはクラスで分類されています。

たとえば命令「cout」は、じつは、正式には「std::cout」という命令です。つまり、coutはstdクラスに所属しています。 『C++/はじめに』では説明を省略していたusing namespace std;とは、「命令の冒頭の『std::』を省略しますよ」という意味の宣言だったのです。

「std」とは、標準入出力(standard input/output)を意味する略語です。

「cin」も同様に、じつは「std::cin」という命令です。つまり、cinはstdクラスに所属しています。

スコープ演算子「::」をつかうことから分かるように、stdクラスに含まれるメンバ関数として、「cout」や「cin」を扱っています。


書式は、

ジャンル名::組み込み関数名

というようになっています。

プログラミング言語にあらかじめ用意されている 組み込み命令 は、膨大にあるので、ジャンル名をくわえて分類する必要があり、使用時には本来、そのジャンル名をつける必要があるのです。なぜなら、そうしないと、もし、自分で新しく関数を定義しようと思ったときに、名前につけようと思ってた関数名が、すでに組み込み関数などで使われている可能性が高まってしまうからです。

このような工夫を、(使いたい名前が既に他の用途で使われてしまっているような事態をふせぐための工夫を)「名前の衝突をふせぐ」などと、言います。

また、プログラミングにかぎらず、名づけたいモノどうしの名前の衝突をふせぐために、(上記の例での「std」のように)冒頭につける分類名を「名前空間」と言いますが、ようするにジャンル名のようなものです。

一例として命令 cout の場合なら、クラス名「std」が名前空間も兼ねています。

coutやcinでは、わざわざusing namespace std;を省略してまで「std::」をつける意義は普通はないので、今のところは、命令のクラスによる分類については、あまり気にする必要はありません。

しかし今後、あなたのC++についての学習が進んで、stdクラス以外の、より高度な命令をあつかう場合には、クラスによる命令の分類を気にせざるを得ない必要も、出てくるでしょう。


コンストラクタとデストラクタ編集

コンストラクタ編集

オブジェクトの作成時に、自動的に、ある関数を呼び出すことができます。

オブジェクトの定義時に自動的に呼び出される関数のことをコンストラクタといいます。

クラスのメンバ関数を定義するときに、メンバ関数名を、クラス名と同じ名称にすると、そのメンバ名の内容が、コンストラクタの内容になります。

つまり、コード

class kuruma{
   int bangou;
   void kansuu();
   kuruma();
};

の、メンバ関数 kuruma() が、コンストラクタです。

コンストラクタにアクセスする場合は、メンバ関数へのアクセスと同様にスコープ演算子「::」を使うことになります。

たとえば、上記のクラスkurumaのコンストラクタを記述する場合なら、

kuruma::kuruma()

と記述することにより、クラスkurumaのコンストラクタを記述できます。

例として、クラスkurumaのコンストラクタの内容を、初期値としてメンバ変数に0を代入するとした場合、下記のコードのようになります。

kuruma::kuruma()
{
   bangou = 0;
}

コンストラクタも関数の一種なので、コンストラクタの内容の記述方法は、関数とほぼ同様の記述ですが、しかし、戻り値(冒頭のvoidなど)がない事に注目してください。

そして、オブジェクトが生成されたときにコンストラクタが呼び出されるわけですから、つまり、

kuruma yamadacar;

または

kuruma itoucar;

などによってyamadacarやitoucarを生成したときに、コンストラクタが自動的に呼び出されます。


「継承」とは編集

すでに存在するクラスをもとにして、新しい別のクラスを作成することが、クラスの継承です。

細かく言えば、「継承」(けいしょう、inheritance)とは、既に存在しているクラスについての定義文をもとにして、新しいクラスをつくる事を、クラスの「継承」といいます。


クラスBをもとにして、新しくクラスDをつくるとき、

class D : public B{

};

のように、宣言されます。ここ(「 class D : public B{ 」)でいう、 public とは、「継承を行う」というような意味の宣言をあらわす命令文だと思ってください。


あるクラスを別のクラスが継承するとき、継承される側(つまり、もとになる側の)クラスを基本クラス(きほんクラス、base class)または「基底クラス」(きていクラス)と呼び、 いっぽう、継承して新しく作られた側のクラスを派生クラス(はせいクラス、derived class)と呼びます。

さきほどのクラスBとクラスDとの例だと、クラスDが派生クラスです。いっぽう、クラスBは基本クラスです。


なお、英文法でのコロン記号「:」の役割は、コロン記号の前にある語句について、コロン記号のあとで内容を説明するという事を表す記号です。つまり、関係代名詞のような働きを、コロン記号「:」はしています。

さきほどのクラスの定義文の文例でも、コロン記号の前にある「class D」について、内容は「public B{ }」であると説明しているわけです。


とにかく、クラスの継承の仕方は、次のようになります。

class 派生クラス名 : public 基本クラス名
{

};


基本クラス側でprivateにアクセス指定されていたメンバは、派生クラスからはアクセスできません。また、privateをつけると、外部の一般の関数からも、アクセスできません。privateをつけると、main()関数からも、直接はアクセスできません。要するに、原則的に、private指定されたメンバには、外部からはアクセスできません。

private指定されたメンバに、main関数や他の一般の関数からアクセスしようとすると、エラーになります。

なお、クラスのアクセス指定を省略すると、private指定として扱われます。


基本クラス側でpublicにアクセス指定されていたメンバは、派生クラスからアクセスできます。なお、他のクラスやグローバルな関数からも、publicにアクセス指定されたメンバにはアクセスできます。

基本クラス側でprotectedというふうにアクセス指定すると、他のクラスやグローバルな関数からはアクセスできなくなりますが、しかし、派生クラスからはアクセスできます。

つまり、 「protected」とは何かというと、自分自身のクラス以外とのアクセスについては、派生クラスからのアクセスだけを許すためのアクセス指定こそが protected です。


なお、private, public, protected などをまとめて、アクセス指定子といいます。

※ 備考編集

よそのプログラミング言語では、基底クラスのことを「親クラス」といい、派生クラスのことを「子クラス」という場合もあります。

ほかにも、あるプログラミング言語では、基底クラスのことを「スーパークラス」といい、派生クラスのことを「サブクラス」という場合もあります。

継承に関するクラスの名前
派生もとのクラス 派生さきのクラス
基底クラス 派生クラス
親クラス 子クラス
スーパークラス サブクラス

しかし、C++言語では、「基底クラス」と「派生クラス」という言い方が使用されるのが一般的です。

多重継承編集

継承では、派生元のクラス(基底クラス)は、複数個であってもいい。

つまり、クラスDの派生元が、クラスBとクラスEというふうに、複数個の基底クラスがあるような継承も、可能である。

class D : public B, public class E{

};

上記のコードで、クラスBとクラスEが、基底クラスである。クラスDは、派生クラスである。

このように、複数個の派生元によって行われる継承のことを多重継承という。

多重継承のコードの記述では、複数個ある基底クラスはカンマ「,」で区切って、複数記述する。

  • 多重継承で、異なる基底クラスに、同名のメンバがあるとき
(※ 調査中)

スコープ演算子::で、「クラス名::メンバ名」の順序で記述することにより、区別します。


入れ子クラス編集

クラスの中に、別のクラスを入れることができる。このような手法を「入れ子クラス」(いれこ クラス)という。

(コード例)

#include <iostream>
using namespace std;

class kuruma{
    public:
        class utigawa{
            public:
                int bangou;
                double nenryou;
    };
};

int main()
{
    kuruma::utigawa yamadacar;
    yamadacar.bangou = 1234;

    kuruma::utigawa itoucar;
    itoucar.bangou = 7722;
    cout << "クラスの入れ子" << endl;
    cout << "山田くんちの車の番号は" << yamadacar.bangou << endl;
    return 0;
}

実行結果

クラスの入れ子
山田くんちの車の番号は1234


内側にあるほうのクラスのことを「内部クラス」とか「インナークラス」(inner class)という。上記のコードでは「utigawa」が内部クラスである。

いっぽう、外側にあるほうのクラスを「外部クラス」とか「アウタークラス」(outer class)などという。上記コードでは「kuruma」が外部クラスである。

「入れ子」(いれこ)のことを英語で nest ネストというので、もしネットで調べたいときは「nest class」(ネストクラス)などで調べれば良い。


  • 構造体の入れ子

なお、構造体でも同様に、ある構造体のなかに、別の構造体を入れることができる。

#include <iostream>
using namespace std;

struct kuruma{
    public:
      struct utigawa{
          public:
              int bangou;
              double nenryou;
     };
};

int main()
{
    kuruma::utigawa yamadacar;
    yamadacar.bangou = 1234;

    kuruma::utigawa itoucar;
    itoucar.bangou = 7722;
    cout << "構造体の入れ子" << endl;
    cout << "山田くんちの車の番号は" << yamadacar.bangou << endl;
    return 0;
}

実行結果

構造体の入れ子
山田くんちの車の番号は1234

「ゲッター」と「セッター」編集

無印C言語とは違い、C++のクラスや構造体には、関数を含めることができます。

このため、C++のクラスでは、メンバ変数を書き換えたりすることもできます。

一般に、C++でよくある、カプセル化を意図したプログラミング例として、メンバ変数はprivate宣言することにより、外部の関数が書き換えられようにして、メンバ関数をpublicで宣言することにより、 このメンバ関数でしか、書き換えできないようにするプログラム例が多くあります。


このように、メンバ関数だけをpublicにプログラムした場合に、ある関数について、その関数が、外部から読み込んだ変数をもとにしてメンバ変数を書き換える関数なら、その関数のことを「セッター」(setter)といいます。

また同様にメンバ関数だけがpublicの場合に、外部に数値などを出力するなどの目的の関数で、メンバ変数を読み込むための関数のことを「ゲッター」(getter)といいます。

C++では、けっして、C++の組み込み命令などで「setter」「getter」などの命令が用意されてるわけではありません。

C#やJavaでも、「ゲッター」や「セッター」という用語があり、上述の説明と同じような意味で使われます。

また、無印C言語には、そもそもメンバ関数の機能が無いので、一般に無印C言語には「ゲッター」や「セッター」の概念もありません。