C++/クラスの定義や継承

出典: フリー教科書『ウィキブックス(Wikibooks)』
< C++
ナビゲーションに移動 検索に移動

オブジェクト指向[編集]

オブジェクト指向とクラス[編集]

オブジェクト指向は、 データと手続きを一体化して記述することで、 プログラムの内容をわかりやすくするというプログラミングの手法です。

クラスはオブジェクト指向の中核となる概念で、 様々な言語でクラスを定義することが出来ます。 C++以外ではJava, Python, Rubyなどがオブジェクト指向言語として有名です。

C++のクラス定義は、 他の言語と比べても非常に汎用的で出来ることが多く、 それだけにわかりにくい面もあります。 単純にオブジェクト指向に興味があるのなら、 PythonやRubyのオブジェクト指向の方が簡単かも知れないので、 そちらも検討してみるとよいでしょう。

オブジェクト指向とプログラムの大きさ[編集]

オブジェクト指向は特に大きなプログラムを作成する場合によく用いられています。 逆に、それほど大きくないプログラムを書く時には、 オブジェクト指向はあまり役に立ちません。 具体的にどれくらいの大きさのプログラムが大きいと思うかは、 書き手の好みにもよります。

オープンソースからの具体例では、 webプラウザのMozilla Firefox、 メールソフトのThunderbird、、 オフィスソフトのOpenOfficeは、 C++で書かれています。 一方、巨大プログラムと呼ばれてもよい大きさのプログラムでも、 Cで書かれているプログラムは存在します。 例えば、 OSのLinux、 ウィンドウシステムのX Window Systemは、 Cで書かれています。 (これらのソフトウェアにCが採用された理由には、 プロジェクト立ち上げ当時のC++の普及度や安定度、 あるいは対象となるソフトウェアの扱う領域や目的等の要因があると思われ、 今まさに同様のプロジェクトをはじめた場合にもCが選ばれるかは分かりません)

オブジェクト指向とGtk[編集]

オブジェクト指向が用いられる例として、 GUIのウィジェットがあります。 ウィジェットは、 ウィンドウやボタンなど、 グラフィックを作成するために用いられるオブジェクトです。 これらは共通の操作として、最大化や最小化などを持っています。 これらの操作に対応する関数をウィジェットごとに作成するのは大変な作業なので、 多くの場合これらはオブジェクト自体に操作を与えるプログラム手法がとられます。

例えば、Gtkでは、 Containerクラスとそれを継承したクラスに属するウィジェットには、

gtk_container_add(GtkContainer, GtkWidget)

という関数を用いてウィジェットを入れ込む作業が行われます。 このように、多くの似通ったデータを扱う際には、 オブジェクト指向プログラミングがよく用いられます。 ただし、Gtk+はCを用いてウィジェットを作成しているので、 ここでのオブジェクト指向は変則的です。

実際には、 上の例ではオブジェクト指向でいう継承という考えが用いられています。 継承とは、 似通った性質をもつクラスを複数作成する時に、 それらの差分だけを新たに書き加え、 共通の部分は既存のものを流用するという考え方です。

GtkWindowクラスは、 GtkContainerクラスを継承しているので、 GtkWindowクラスに対してもgtk_container_addを用いることが出来ます。 これは、GtkWindowだけでなく、GtkContainerを継承した全てのクラスの特徴です。

実際に継承を用いる時には、 どの部分をどのクラスで導入するかが問題になりますが、 ある程度試行錯誤を繰り返すしかなさそうです。

C++でも、クラスの継承を用いることが出来ます。 これについては後述します。

クラスの基本[編集]

クラス(class)とは、データとその操作手順であるメソッドをまとめたオブジェクトの雛型を定義したものである。 [1]

クラスの宣言[編集]

C++でクラスを宣言するには、classキーワードを用いて次のように記述する。。

class クラス名{
	//非公開関数と変数
public:
	//公開関数と変数
}オブジェクトリスト;

クラス名でクラスの名前を指定する。

クラスは多くの点で構造体と似ている。 クラスはメンバ変数の他にメンバ関数を含めることができるが、 実は構造体もメンバ関数を含むことができる。 [2]

class内の関数と変数のことをメンバ(member)と呼ぶ。 メンバにはオブジェクトの外からアクセスできない非公開のものと、 アクセスできる公開のものがある。 classのメンバはデフォルトで非公開である。

オブジェクトリストを指定すると、 その名前のオブジェクトを作成する。 複数のオブジェクトを作成する場合は、 「,(コンマ)で区切る。 オブジェクトリストの指定は任意である。

メンバ関数が大きい場合、 クラスの宣言の外にその定義を記述することもできる。 その場合、メンバ関数の定義は、 関数名の前に「クラス名::」を付ける。 「::(コロン2文字)」のことをスコープ解決演算子(scope resoluthion operator)と呼ぶ。 次のように記述する。

戻り値の型 クラス名::関数名(仮引数の並び)
{
	関数の本体
}
  • 例(メンバ関数の定義をクラスの宣言の中に書く)
class aaa {
	int a;
public:
	int getA() const {return a;}
	int setA(int a){return this->a = a;}
};
  • 例(メンバ関数の定義をクラスの宣言の外に書く)
class aaa {
	int a;
public:
	int getA() const;
	int setA(int a);
};
 
int aaa::getA() const
{
	return a;
}
 
int aaa::setA(int a)
{
	return this->a = a;
}

上の例では、 getA,setAというメンバ関数と、 aというintのデータを持ったクラスを定義しています。 classのメンバはデフォルトでです。 getAはaの中身を得るためのメソッドで、 setAはaを設定するためのメソッドです。 これらはaがprivateであることから必要になります。 setAでは、thisが用いられていますが、 これは与えられたクラスの性質を持つオブジェクトへのポインタを表しています。 ポインタなので、それが表すデータにアクセスする時には->を用います。 これは(*this).aと書いても同じ意味になります。

オブジェクトの作成[編集]

オブジェクトの作成は次のように記述する。

クラス名 オブジェクト名;

クラス名でクラスの名前を指定する。 オブジェクト名で作成するオブジェクトの名前を指定する。 複数のオブジェクトを作成する場合は、 「,(コンマ)で区切る。

  • 例(ob1, ob2というaaaクラスのオブジェクトを作成する。)
aaa ob1, ob2;

メンバへのアクセス[編集]

メンバにアクセスするには、ドット(.)演算子を用いて次のように記述する。

オブジェクト名.メンバ名
  • 例(ob1のsetAメンバ関数を呼び出す。)
ob1.setA(1234);

オブジェクトのコピー[編集]

クラスの詳細[編集]

カプセル化[編集]

カプセル化とは、オブジェクト指向のクラスにおいて、 オブジェクトが与える手法以外の手法で、 オブジェクトの持つデータに影響を与えてはいけないとする考え方です。 これはオブジェクト自身が自分のデータを扱う方法を全て提供すべきで、 それ以外の方法に頼ることはクラスの設計の問題であるとする考え方にもとづいています。

Javaなどの言語でも、 カプセル化がサポートされています。 Pythonなどの言語では、 カプセル化は(あまり)サポートされていません。

C++では、 public, private, protectedの3つのキーワードがアクセス制御として与えられています。 privateは、同じクラスに属するメソッドからしか値が呼び出すことができなくなります。 publicは、プログラムの他の部分からも呼び出せるようになります。 protectedは、クラスを継承した先から用いることが出来るようになります。 しかし、プログラムの他の部分から呼び出すことは出来ません。

//カプセル化の例
#include <iostream>
using namespace std;

class aaa {
	int a;
public:
	int getA() const {return a;}
	int setA(int a){return this->a = a;}
};

int main()
{
	aaa ob1;
	//ob1.a=1234;//この行はコンパイルエラーになる。
	ob1.setA(1234);
	cout<<"ob1のaの値は"<<ob1.getA()<<endl;
}

上の例では、クラスaaaのaメンバ変数が非公開になっているため、 オブジェクトの外からアクセスするには、 公開になっているgetA関数及びsetA関数を使用しなければならない。

ポリモーフィズム[編集]

ポリモーフィズムは、 オブジェクト指向の考え方の一つで、 同一の名前で複数の異なる動作を作成し使う。

ポリモーフィズムは、 C++では関数のオーバーロードで実現される。 関数のオーバーロードとは、 複数の関数を同じ名前で作成し使うことである。 関数のオーバーロードを行うには、 単に同じ名前の関数を複数定義するだけである。 ただし、引数の型か個数が異ならなければ、 関数のオーバーロードはできない。 戻り値の型だけが異なっていてもできない。

//ポリモーフィズムの例
#include <iostream>
using namespace std;

int add(int a, int b);
int add(int a, int b, int c);

int main()
{
	cout<<"1+2は"<<add(1,2)<<endl;
	cout<<"1+2+3は"<<add(1,2,3)<<endl;
}

int add(int a, int b)
{
	return a+b;
}

int add(int a, int b, int c)
{
	return a+b+c;
}

上の例では、2つの引数を足して返すadd関数と、 3つの引数を足して返すadd関数を、 関数オーバーロードして定義している。

継承[編集]

継承とは、オブジェクト指向の考え方の一つで、 あるクラスの性質を受け継いだ別のクラスを定義することである。 継承によって、汎用的なクラスから特化したクラスを作ることができる。

あるクラスを別のクラスが継承するとき、 継承されるクラスを基本クラス(base class)と呼び、 継承するクラスを派生クラス(derived class)と呼ぶ。

基本クラスでは、共通の性質を定義し、 派生クラスでは、その共通の性質を受け継いだうえで、 個別の性質を定義する。 クラスの継承の仕方は次のとおりである。

class 派生クラス名 : アクセス指定子 基本クラス名
{
	//...
};

派生クラス名で派生クラスの名前をしてする。 アクセス指定子ではpublic, private, protectedのいずれかを指定する。 基本クラス名で基本クラスの名前を指定する。

//継承の例
#include <iostream>
using namespace std;

class aaa {
	int a;
public:
	int getA() const {return a;}
	int setA(int a){return this->a = a;}
};

class bbb : public aaa{
	int b;
public:
	int getB() const {return b;}
	int setB(int b){return this->b = b;}
};

int main()
{
	bbb ob1;
	ob1.setA(1234);
	ob1.setB(5678);
	cout<<"ob1のaの値は"<<ob1.getA()<<endl;
	cout<<"ob1のbの値は"<<ob1.getB()<<endl;
}

上の例ではaaaを継承して新たにbbbを定義している。 aaaのメンバ変数aにアクセスするgetA関数及びsetA関数が、 bbbでも使用できるようになっている。

コンストラクタ関数とデストラクタ関数[編集]

C++のクラスには、 初期化と終了処理を自動的に行う、 コンストラクタ関数とデストラクタ関数という仕組みがある。

コンストラクタ関数(constructor function)は、 オブジェクトが作成されると呼び出され、 主に初期化処理を記述する。 コンストラクタ関数をクラスに追加するには、 クラスの宣言内で次のように記述する。

クラス名(仮引数のリスト)
{
	//実行する処理
}

コンストラクタ関数の名前は、そのクラス名と同一である。 また、コンストラクタ関数は、 戻り値を返さないので戻り値の型は指定しない。 コンストラクタ関数には引数を指定することができる。

デストラクタ関数(destructor function)は、 オブジェクトが解放されると呼び出され、 主に終了処理を記述します。 デストラクタ関数をクラスに追加するには、 クラスの宣言内で次のように記述する。

~クラス名()
{
	//実行する処理
}

デストラクタ関数の名前は、そのクラス名と同一である。 クラス名の前に「~(チルダ)」を付ける。 デストラクタ関数は、 引数を取らず戻り値も返さないので、 引数も戻り値の型も指定しない。

//コンストラクタ関数とデストラクタ関数の例
#include <iostream>
using namespace std;

class aaa {
	int a;
public:
	aaa(){cout<<"コンストラクタ呼び出し。"<<endl; a=1234;}
	~aaa(){cout<<"デストラクタ呼び出し。"<<endl;}
	int getA() const {return a;}
	int setA(int a){return this->a = a;}
};

int main()
{
	aaa ob1;
	cout<<"ob1のaの値は"<<ob1.getA()<<endl;
}

フレンド関数[編集]

フレンド関数というのがあるのだが、メンバ関数と性質が近く、フレンド関数の存在の意義が疑われてる。 フレンド関数は、メンバ関数と宣言方法が、じゃっかん違う。

他のプログラム言語には、フレンド関数がないものもある。たとえばC#にはフレンド関数はない。

thisキーワード[編集]

newキーワードとdeleteキーワード[編集]

クラス・構造体・共用体の比較[編集]

クラスと構造体は非常によく似ています。 C++では構造体もメンバ関数、コンストラクタ関数、デストラクタ関数を 含むことができます。 クラスと構造体の違いは、 クラスはデフォルトでメンバが非公開であるのに対し、 構造体はデフォルトでメンバが公開であるだけです。

struct 型名{
	//公開関数と変数
private:
	//非公開関数と変数
}オブジェクトリスト;

一方、共用体にはいくつかの制限があります。

  • 継承したりされたりできない。
  • メンバにstaticを指定できない。
  • コンストラクタ・デストラクタがあるオブジェクトをメンバにできない。
  • コンストラクタ・デストラクタは持てる。
  • 仮想メンバ関数を持つことはできない。

脚注[編集]

  1. ^ クラスとは|class - 意味/定義 : IT用語辞典
  2. ^ 構造体と共用体

参考文献[編集]

  • 神林靖監修 Herbert Schildt著『独習C++ 第3版』トップスタジオ訳、翔泳社、2005年11月05日 第3版第10刷発行