第十三回-01 テレビクラスを作ってみよう

さて、第十二回-03でクラスとは「新たな型である」そして 「クラスとは「もの」である」という考え方を紹介した。

さらに、クラスとは「属性や状態 (データメンバ)」と
属性や状態に対する処理 (メンバ関数)」の2つの要素から構成されると解説した。

理屈ばかりこねていてもしょうがないので、今日はとにかく実際にクラスを記述してみよう。

テレビクラスを書いてみよう

それでは、本ページでは前回紹介したテレビクラスを書いてみることにしよう。

テレビクラスとは以下のようなクラス図で書けるものであった。

テレビクラス
電源の状態
現在のチャンネル
現在の音量
電源ON/OFF
チャンネル変更
音量調節

これだけでは利用上機能が少し足りないので、以下のようにメンバ関数を追加してみることにしよう。

テレビクラス
電源の状態
現在のチャンネル
現在の音量
現在の電源状態取得
現在のチャンネル状態取得
現在の音量状態取得

電源ON/OFF
チャンネル変更
音量調節

現在の状態をコンソール表示

さて、それではいつもどおり「Win32 コンソールアプリケーション」の プロジェクトを作成して欲しい。
プロジェクト名は「TVProject」とした

作成すると、ソリューションエクスプローラーの階層構造表示は以下のようになる。
(Visual Studio 2019 では stdafx.cpp や stdafx.h は存在しないが、気にしなくとも良い)
TVProject.cpp がいつも作成している main 関数があるファイルである。



ここで、階層構造の「TVProject」上でマウスを右クリックし、 「追加→クラス」を選択して欲しい。



現われるクラスウィザードで、クラス名を と入力「.h ファイル」と「.cpp ファイル」の部分は自動入力される

このクラス名は本来は自分の自由に決めて良いものだが、記述が面倒な部分を後でコピー&ペーストする都合上、
Television という名前で綴り間違いがないように記述すること! (先頭の T だけ大文字。大文字、小文字も区別される!)

間違いがないと思ったら「完了」ボタンをクリック。



すると、ソリューションエクスプローラーの階層構造において、以下のようにファイルが増えていることがわかる。
(Visual Studio 2019 では stdafx.cpp や stdafx.h は存在しないが、気にしなくとも良い)



赤線の引かれた3つが重要なファイルである。これらのファイルの役割をまとめると以下の通り。

ファイル名 呼び方 備考
Television.h ヘッダファイル クラスの定義 (宣言) を書く Java、C# ではこの3ファイルは
1ファイルにまとめられている
Television.cpp cpp ファイル クラスの実装を書く
TVProject.cpp - main 関数があるファイル。クラスを呼び出して利用する

以下ではこの3ファイルを順に記述してプログラムを完成させてゆく。


ヘッダファイルを書こう

まずは、ヘッダファイルを記述してゆく。このファイルは冒頭でに書いたクラス図をそのまま記述したものと思って良い。

まず、階層構造で「Television.h」をダブルクリックすると以下のように Television クラスを作成するための枠組が現われる。
枠組は、Visual Studio のバージョンにより若干異なるが、違いを吸収するようにページで解説を行うので気にしなくとも良い。



ここで、既に記述済みの2つのメンバ関数は以下の意味がある重要なものである。

メンバ関数名 名前 説明
Television() コンストラクタ オブジェクトが生成されるときに自動的に呼ばれる。クラス名と同じ名前で戻り値を持たない特別な関数。
~Television() デストラクタ オブジェクトが破棄ときに自動的に呼ばれる。クラス名に ~ をつけた名前で戻り値を持たない特別な関数。

ここに、以下のようにデータメンバメンバ関数を自分で追加してゆく。



データメンバの3変数はいいだろうか。power は電源の状態、channel がチャンネルの状態、volume はボリュームの状態を表す変数である。
全て整数の int 型として実装することにする。

そして、後半にメンバ関数が並んでいることがわかる。

class Television(){ … }; の内部のみをコピー出来る形式で書くと以下のようになる。適切な位置に貼りつけることで、こちらの意図した内容となる。

	// データメンバ
	int power;
	int channel;
	int volume;

public:
	// コンストラクタ。オブジェクト生成時に自動で呼ばれる
	Television();
	// デストラクタ。オブジェクト消滅時に自動で呼ばれる
	~Television();

	// 状態取得用のメンバ関数
	int getPower();
	int getChannel();
	int getVolume();

	// 状態設定用のメンバ関数
	void setPower(int p);
	void setChannel(int c);
	void setVolume(int v);

	// 状態表示用のメンバ関数
	void printStatus();

以上で、クラスを記述する枠組みであるヘッダファイルが記述できた。


cpp ファイルを書こう

さて、ヘッダファイルで記述したクラスの定義に合わせて、メンバ関数の実装を行うのが cpp ファイルの役割である。
まずはソリューションエクスプローラーの Television.cpp をダブルクリックしてから中身を記述してゆこう。

ヘッダファイルと同様、コンストラクタ Television() とデストラクタ ~Television() については既に枠組みが記述されている。
ただし、Visual Studio のバージョンによって、この枠組みは異なる。
Visual Studio 2019 では「#include "Television.h"」の1行しか書かれていないが、違いを吸収するようにページを記述するので気にしなくとも良い。

記述すべき内容をあらかじめコピーできる形で提示すると以下のようになる。
Visual Studio 2019 の場合、「#include "Television.h"」の行の次に以下を丸ごと貼りつける、ということである。

#include <iostream>

// コンストラクタ。オブジェクト生成時に自動で呼ばれる
Television::Television()
{
	power = 0;   // 電源オフ
	channel = 1; // チャンネル1。首都圏では NHK
	volume = 0;  // ボリューム0
}

// デストラクタ。オブジェクト消滅時に自動で呼ばれる
Television::~Television()
{
}

// 値の取得用関数 (getter) 3つ
int Television::getPower(){
	return power;
}

int Television::getChannel(){
	return channel;
}

int Television::getVolume(){
	return volume;
}

// 値の設定用関数 (setter) 3つ
void Television::setPower(int p){
	power = p;
}

void Television::setChannel(int c){
	channel = c;
}

void Television::setVolume(int v){
	volume = v;
}

void Television::printStatus(){

	if(power == 1){
		std::cout << "電源はオンです。";
	}else{
		std::cout << "電源はオフです。\n";
		return; // オフの場合はここから先へ進まず終了させる
	}

	std::cout << "チャンネルは" << channel << "です。";

	std::cout << "ボリュームは" << volume << "です。\n";

}



まず、コンストラクタは、オブジェクト生成時に自動で呼ばれる関数であったから、 ここにデータメンバの初期化処理などを書く
上の例では、各変数の初期状態として「電源オフ (0)」、「チャンネル 1」、「ボリューム 0」で初期化している。
あと、先頭付近にいつも通り iostream の呼び出しも書いてある (後に使うため)。
デストラクタは今回は必要な処理はないので中身は記述していない。

コンストラクタとデストラクタの次には、 次に、ヘッダファイルで定義した値の取得用関数 (getter という) と値の設定用関数 (setter という) が記述されている。

値の取得用関数 (getter) は単にデータメンバの値を返しているだけ
値の設定用関数 (setter) は単に引数の値をデータメンバにセットしているだけである。
クラスを用いたプログラミングでは、このように小さな機能のメンバ関数を記述することが多い。

なお、ヘッダファイルでは「int getPower(); 」と書いていたものを cpp ファイルでは「int Television::getPower() 」と書かねばならない
(Television はクラス名)。これは手続き型言語である C をオブジェクト指向に無理矢理対応させたことからくる不便さであるが、
Java や C# ではそのような必要はなくなっている。

最後に、Television クラスの現在の状態を表示するメンバ関数である printStatus が記述されている。
このあたりはコンソール表示が中心なので皆さんがこれまで何度もやってきたところ。
power が 1 (オン) なら「電源はオンです。」、 power が 0 (オフ) なら「電源はオフです。」と表示される、などと読み取れるだろうか?


main 関数を書こう

最後に、Television クラスを呼び出す main 関数を記述してゆこう。

オブジェクト指向プログラミングでは main 関数はクラスの動作チェック用に使われることが多い。

冒頭の「Television tv;」は、これまで記述してきた Television クラスを型として利用し、変数 tv を宣言している

つまり、クラスは型である、ということを意味する。
なお、この変数 tv のことを、Television クラスのインスタンス、とも呼ぶ。

後は、電源、チャンネル、ボリュームのセットと、状態表示 (printStatus) を繰り返して動作チェックを行っている。
メンバ関数の呼び出しには、ドット演算子 ( . ) を用いて tv.printStatus() などとしていることにも注意。

Visual Studio 2019 では、「#include "stdafx.h"」は存在しない、「_tmain(int argc, _TCHAR* argv[]」ではなく「main()」である、などの違いがあるが気にしなくとも良い。



念のためコピーできる形式も挙げておく。貼り付ける場合は貼り付け場所にも注意すること!

まずは、記述した Television クラスを利用するための include 文を記述。

#include "Television.h"

そして、main 関数の中身がこちら。

	Television tv;  // Television クラスの変数 tv を宣言
					// クラスを「新しい型」として用いている
					// この時点でコンストラクタが呼ばれる

	tv.printStatus(); // 確認のために状態表示

	tv.setPower(1);    // 電源オン
	tv.setChannel(8);  // 8チャンネルにセット
	tv.setVolume(10);  // ボリュームを10に

	tv.printStatus();

	tv.setChannel(4);  // 4チャンネルにセット

	tv.printStatus();

実行結果は以下の通り。他にも、電源、チャンネル、ボリュームを変更して printStatus を実行して動作確認してみよう



本ページで学んだことはオブジェクト指向プログラミングにおいて基本的といえる事柄なので、
Java や C# を勉強している学生は、本ページのプログラムを Java や C# で書き直してみるのも良い勉強になるだろう。



←第十二回課題第十三回-02 何故クラスを使うのか?→

非情報系学生のための C/C++ 入門に戻る