• 検索結果がありません。

1-4 int a; std::cin >> a; std::cout << "a = " << a << std::endl; C++( 1-4 ) stdio.h iostream iostream.h C++ include.h 1-4 scanf() std::cin >>

N/A
N/A
Protected

Academic year: 2021

シェア "1-4 int a; std::cin >> a; std::cout << "a = " << a << std::endl; C++( 1-4 ) stdio.h iostream iostream.h C++ include.h 1-4 scanf() std::cin >>"

Copied!
32
0
0

読み込み中.... (全文を見る)

全文

(1)

1

C++によるオブジェクト 指向言語入門

1.1

C

との構文の違い

オブジェクト指向を学ぶ前に C++の構文について学ぶ。C++は C を拡張したものであるため、C と同じ構文を使用 することも出来るが C++独特の構文も存在する。以下の節ではそれらについて説明する。 1.1.1 標準入出力スト リーム Cでは画面への出力は printf()、キー入力は scanf() などでおこなったが 、C++ では「標準入出力ストリーム」を使 用する。例えば C における hello world は printf() を用いて

ソース 1-1

#include <stdio.h>

int main() {

printf( "hello world\n" ); return 0; } であったが 、C++では次の様になる。 ソース 1-2 #include <iostream> int main() {

std::cout << "hello world" << std::endl; return 0; } 同様に、キー入力について C では scanf() を用いて ソース 1-3 #include <stdio.h> int main() { int a; scanf( "%d", &a ); printf( "a = %d\n", a ); return 0; } としたが 、C++では

(2)

ソース 1-4 #include <iostream> int main() { int a; std::cin >> a;

std::cout << "a = " << a << std::endl; return 0; } となる。 ここで ソース 1-3 と ソース 1-4 を比較して説明する。まず始めに C++( ソース 1-4 ) では stdio.h の代わりに iostream をインクルード する。この時 iostream.h としていないことに着目すること。C++では include するライブラリのヘッダ に.h を付けないことになっている。

次に ソース 1-4 では scanf() の代わりに「std::cin」と 「>> 演算子」を使っていることに着目する。この std::cin の ことを「標準入力ストリーム (stream)」と呼び 、 >> 演算子と組み合わせて標準入力 ( std::cin ) から int 型変数 a の中 にデータを流れ込ませるということを意味している。

また ソース 1-4 では printf() の代わりに「std::cout」 と「<< 演算子」を使っている。これは標準出力ストリーム ( std::cout )の中に変数 a の内容を流れ込ませることを意味している。また std::endl は改行 ( end of line ) の意味である。

この様に C++では標準入出力ストリーム (std::cin, std::cout) とストリーム演算子 ( << 又は >> ) を組み合わせて画 面表示とキー入力を実現する。なお printf() や scanf() とは異なり型の認識は自動でおこなわれる。

問題 1-1

標準入出力ストリームを用いて任意の文字列を読み込み表示せよ (10 点)

問題 1-2

標準入出力ストリームを用いて数字 (double 型) を 2 つ読み込み、足し算、引き算、掛け算、割り算をおこ なえ。ただし std::cin, std::cout は各一度しか使用しないこと。(10 点) 1.1.2 ファイル入出力スト リーム ファイルからのデータの入出力は C では fopen()、fclose() などを用いたが 、C++では std::fstream ストリームを用い る。例えばファイル出力とファイル入力はそれぞれ ソース 1-5 #include <fstream> int main() { std::fstream fout; int a = 123;

fout.open( "data.txt", std::ios::out ); fout << a << std::endl;

fout.close();

return 0; }

(3)

ソース 1-6 #include <iostream> #include <fstream> int main() { std::fstream fout; int a;

fout.open( "data.txt", std::ios::in ); fout >> a ; fout.close(); std::cout << a << std::endl; return 0; } の様におこなう。 ソース 1-5 、 ソース 1-6 において着目する部分は「fstream」ヘッダをインクルードしていること、 open()の際の引数、及びストリーム演算子の方向である。ファイルにデータを出力する場合は open() の引数にファイル 名と「std::ios::out」を指定し 、「<< 演算子」を用いる。一方、ファイルからデータを入力する場合は open() の引数に ファイル名と「std::ios::in」を指定し 、「>> 演算子」を用いる。なお、標準入出力ストリームと同様に変数の型認識は 自動でおこなわれる。

問題 1-3

キーボード から入力した任意の文字列をファイル入出力ストリームを用いてファイルに出力せよ (10 点)

問題 1-4

ファイル入出力ストリームを用いてファイルから文字列を読み込み画面に表示せよ (10 点) 1.1.3 変数スコープ 厳密な C では変数を定義できる場所は関数の先頭部分のみと決められている。一方、C++では自由な場所で変数を定 義することが出来るが 、定義した変数には有効な範囲 (スコープ) があることに気を付けること。具体的には中カッコ内 で定義された変数はその中カッコ外で使用することは出来ない。例えば ソース 1-7 #include <iostream> int main() { int a; { std::cin >> a; } std::cout << a << std::endl; return 0;

(4)

} はコンパイルエラーにならないが 、次のソースではコンパイルエラーとなる。 ソース 1-8 #include <iostream> int main() { { int a; std::cin >> a; } std::cout << a << std::endl; return 0; } (補足) C++で変数 (および関数) を定義する際に、「名前空間」という近代的なプログラミング言語において必須の概 念を用いることもあるが 、今回の演習の範囲を越えるので割愛する。各自自習しておくこと。

問題 1-5

次のコード を実行させたときに起こる現象を調べ、なぜそうなったか考察せよ。 (10 点) ソース 1-9 #include <iostream> int main() { int a = 3; { int a; std::cin >> a; std::cout << a << std::endl; } std::cout << a << std::endl; return 0; }

(5)

1.1.4 newと delete

Cでは動的にメモリを確保するために malloc() と free() を使用したが、C++では「new、delete 演算子」を使用する。 例えば C で int 型のメモリを n 個確保するには ソース 1-10 #include <stdio.h> #include <string.h> int main() { int n, i; int *a; n = 4;

a = ( int* ) malloc( sizeof( int ) * n ); for( i = 0; i < n ; ++i ) scanf( "%d", a+i );

for( i = 0; i < n ; ++i ) printf( "[%d]=%d\n", i, a[i] ); free( a ); return 0; } としたが 、C++では new, delete 演算子を用いて ソース 1-11 #include <iostream> int main() { int n = 4;

int* a = new int[n];

for( int i = 0; i < n; ++i ) std::cin >> a[i];

for( int i = 0; i < n; ++i ) std::cout << "[" << i << "]=" << a[i] << std::endl; delete a;

return 0; }

とする。ここで a = new int[n] の部分で int 型のメモリを n 個確保して delete a でメモリを開放している。 ソース 1-11 から分かるように、new 演算子は malloc() 関数と異なり自動で確保するバイト数の計算をおこなうため、わざわざバイ ト数 (sizeof(int)*n バイト) をプログラマ側が指定する必要は無い。

(補足) C++では変数の型やバイト数などコンピューダの内部実装に関わる部分は極力排除するように出来ているが 、 JavaScriptや Ruby などのオブジェクト指向言語と比べると完全には排除しきれていない。

問題 1-6

int型変数 n に標準入力ストリームを用いて数字を入力し 、長さ n の double 型配列を new を用いて作り、

その配列に標準入力ストリームを用いて数字を代入し 、その和を表示するプログラムを作成せよ。delete も忘れずに呼 ぶこと (10 点)

(6)

1.1.5 std::vector 前節で説明した new を用いて配列を作成すると、delete 忘れによるメモリリークが生じる場合があるため、C++では ベクトル型クラスの「std::vector」クラスを配列の代わりに用いることが多い。例えば ソース 1-11 を std::vector クラ スを用いて書き直すと次の様になる。 ソース 1-12 #include <iostream> #include <vector> int main() { int n = 4; std::vector< int > a; a.resize( n );

for( int i = 0; i < n; ++i ) std::cin >> a[i];

for( int i = 0; i < n; ++i ) std::cout << "[" << i << "]=" << a[i] << std::endl;

return 0; }

ここで「vector」ヘッダを インクルードし 、「std::vector< int > 」と int 型を std::vector の引数として指定した後で a.resize( n )とベクトルサイズを変更していることに着目せよ。 このように std::vector クラスを用いて配列を作成すると、プログラマが意図的に delete を呼んでメモリを開放しなく ても配列の使用が終わる (変数スコープから外れる) と自動的に確保したメモリが開放されるためメモリリークを防ぐこ とが出来る。 (補足) std::vector クラスには「 イテレータ (iterator)」というオプジェクト指向言語では極めて重要な概念も関わって くるが 、今回の演習の範囲を越えるので割愛する。各自自習しておくこと。

問題 1-7

前節で作成したプログラムを std::vector クラスを用いて書き直せ ( 10 点 ) 1.1.6 std::stringによる文字列操作 Cでは文字列を char 型の配列で表現していたが 、バッファオーバフロー (BOF) によるセキュリティの問題が生じる ため C++では文字列クラスの「std::string」を用いて文字列を表すことが多い。例えば C で文字列を二つ読み込んでそ れを合成するには sprintf() を用いて ソース 1-13 #include <stdio.h> #include <string.h> int main() { char a[256]; char b[256]; char c[256]; scanf( "%s", a );

(7)

scanf( "%s", b ); sprintf( c, "%s%s", a, b ); printf( "%s\n", c ); return 0; } としていたが 、std::string クラスを用いると単に + 演算子を用いて ソース 1-14 #include <iostream> #include <string> int main() { std::string a; std::string b; std::string c; std::cin >> a; std::cin >> b; c = a + b; std::cout << c << std::endl; return 0; } とする。ここで「string」ヘッダをインクルードしていることに注意すること。 また 2 つの文字列が等しいか判定するには C では strcmp() を用いて ソース 1-15 #include <stdio.h> #include <string.h> int main() { char a[256]; char b[256]; scanf( "%s", a ); strcpy( b, "テスト" ); if( strcmp( a, b ) == 0 ) printf( "等しい\n" ); else printf( "等しくない\n" ); return 0; }

(8)

としたが 、std::string クラスを用いると単に「 == 演算子」を用いて ソース 1-16 #include <iostream> #include <string> int main() { std::string a; std::string b; std::cin >> a; b = "テスト"; if( a == b ) std::cout << "等しい\n"; else std::cout << "等しくない\n"; return 0; } だけで良い。

問題 1-8

int型変数 n に標準入力ストリームを用いて数字を入力し 、長さ n の std::string クラス配列を std::vector を 用いて作り、その配列に標準入力ストリームを用いて文字列を代入し 、それらの文字列を連結して表示するプログラム を作成せよ (10 点) 1.1.7 関数のオーバロード (多重定義) C言語では同じ名前の関数はひとつしか定義できなかったが 、C++では「引数の型が異なれば 」いくつでも同じ名前 の関数を定義できる。これを「関数のオーバロード (多重定義)」と言い、引数の型の違いによってコンパイラが自動的 に呼び出す関数を決定する。

問題 1-9

次のコード を実行させたときに起こる現象を調べ、なぜそうなったか考察せよ。 (10 点) ソース 1-17 #include <iostream>

double test( int a ){ return a + 1; }

double test( double a ){ return a - 1; }

int main() {

int a = 1; double b = 1;

std::cout << test( a ) << std::endl; std::cout << test( b ) << std::endl;

(9)

return 0; } 1.1.8 関数への変数の参照渡し Cでは関数への変数の渡し方として値渡しとポインタ渡ししかなかったが 、C++ではさらに参照渡しという方法が加 わる。参照渡しとは関数の引数において型の後に&を付けることによって、関数内での変数の変更を呼出元の変数に反 映させる方法である。例えば ソース 1-18 #include <iostream>

void test( int& z ){ z += 1; }

int main() { int a = 1; test( a ); std::cout << a << std::endl; return 0; } を実行すると変数 a が関数 test() 内で更新されて 2 が表示される。参照渡しを用いるとポインタ渡しと異なって変数の 前に*を付ける必要がないため分かりやすいコード になる。 (補足) 参照渡しの本当のメリットは関数呼び出し時のオーバヘッド の軽減と、コンパイル時に実体 (メモリ) を確保し ていないオブジェクトを関数に渡そうとするとコンパイル時にエラーが出るため、セグ メンテーションフォルトを未然 に防ぐことができることである。なお C++では原則としてポインタ渡しは使用しない。

問題 1-10

次のコード の test1()、test2()、test3() の引数の渡し方をそれぞれ何と呼ぶか。また以下のコード を実行 させたときに起こる現象を調べ、なぜそうなったか考察せよ。 (10 点) ソース 1-19 #include <iostream>

void test1( int a ){ a += 1; }

void test2( int* a ){ *a += 1; }

void test3( int& a ){ a += 1; }

int main() {

int a1 = 1, a2 = 1, a3 = 1;

test1( a1 ); test2( &a2 );

(10)

test3( a3 );

std::cout << a1 << " " << a2 << " " << a3 << std::endl;

return 0; }

(11)

1.2

クラスとオブジェクト (インスタンス)

この章では C++のクラス作成を通じてオブジェクト指向プログラミングについて学ぶ。オブジェクト指向プログラミ ングでは変数の型のことをクラス、実体化したクラスをオブジェクトまたはインスタンスと呼ぶ。例えば int a,b を考え ると、int が (整数型) クラス、a と b がオブジェクト (インスタンス) である。 1.2.1 クラスの定義方法 C++のクラスは C の構造体を拡張してメンバ関数を付け加えた形となっているので 、構造体の定義の方法から順を 追って説明していく。始めに C でテストの成績 (SCORE) という構造体を定義しよう。 ソース 1-20 #include <stdio.h> struct SCORE { int math; int english; }; int main() { struct SCORE sc; sc.math = 80; sc.english = 70;

printf( "math = %d english = %d\n", sc.math, sc.english );

return 0; } これをクラス化して C++で書き直すと次の様になる。 ソース 1-21 #include <iostream> class SCORE { public: int math; int english; }; int main() { SCORE sc;

(12)

sc.math = 80; sc.english = 70;

std::cout << "math = " << sc.math << " english = " << sc.english << std::endl;

return 0; }

ソース 1-20 と ソース 1-21 の違いは「struct」が「class」に変わったことと、「int math;」の前に「public:」というキー ワードが入ったことだけである。「public:」に関してはアクセス属性の節で説明するが 、SCORE クラス内で定義された 変数 math, english のことを「SCORE クラスの math,english メンバ変数」と呼ぶ。

さて C で math と english の平均値を計算するには関数 mean() を次のように定義して ソース 1-22 #include <stdio.h> struct SCORE { int math; int english; };

double mean( struct SCORE sc ) {

return ( sc.math + sc.english )/2.0; } int main() { struct SCORE sc; sc.math = 80; sc.english = 70;

printf( "math = %d english = %d\n", sc.math, sc.english ); printf( "mean = %lf\n", mean( sc ) );

return 0; } としていたが 、C++では関数 mean() をクラスの内部で定義して ソース 1-23 #include <iostream> class SCORE {

(13)

public: int math; int english;

double mean(){ return ( math + english )/2.0; } }; int main() { SCORE sc; sc.math = 80; sc.english = 70;

std::cout << "math = " << sc.math << " english = " << sc.english << std::endl << "mean = " << sc.mean() << std::endl;

return 0; }

とする。 ソース 1-22 と ソース 1-23 の違いは mean() の引数が無くなったことと、math や english の前の sc. が無くなっ てメンバ変数に直接アクセスしていることである。 ソース 1-23 のクラス定義内で定義した mean() のことを「SCORE クラスの maen() メンバ関数」と呼ぶ。また「sc.maen()」が メンバ関数を呼び出しているところである。このように、、 あるオブジェクトのメンバ関数にアクセスするには「オブジェクト名. メンバ関数名 ()」とする。 以上で示したように、クラスはメンバ変数とメンバ関数と呼ばれるクラス内部で定義された変数と関数から出来てい て、オブジェクト指向プログラミングでは複数のオブジェクトが互いにメンバ変数やメンバ関数にアクセスすることに よつて処理を進めていく。

問題 1-11

ソース 1-23 に math と english のうち大きい数字の方を返す int max() メンバ関数を追加せよ。

1.2.2 複数のオブジェクト (インスタンス) の生成 クラスを実体化したものがオブジェクト (インスタンス) であったので、当然一つのクラスから複数のオブジェクトを 生成する事が出来る.例えば SCORE クラスからオブジェクト tokai,oyama を作るには次のようにする。 ソース 1-24 #include <iostream> class SCORE { public: int math; int english;

double mean(){ return ( math + english )/2.0; } };

int main() {

(14)

SCORE tokai,oyama;

tokai.math = 10; tokai.english = 20;

oyama.math = 90; oyama.english = 80;

std::cout << "mean of tokai = " << tokai.mean() << std::endl << "mean of oyama = " << oyama.mean() << std::endl; return 0;

}

ここで tokai.mean() と oyama.mean() の戻り値が異なることに注目すること。tokai.mean() は関数内部で tokai オブジェ クトの math,english オブジェクトの平均を計算しているのに対して、oyama.mean() は関数内部で oyama オブジェクト の math,english オブジェクトの平均を計算している。

この様に、オブジェクト指向プログラミングではクラス定義は共通でもメンバ変数の値が異なるとオブジェクトの振 舞いが変化することに気を付けること。

問題 1-12

前節の問題で作成した max() メンバ関数が定義された SCORE クラスから tokai,oyama, 自分、の 3 つの オブジェクトを生成してそれぞれのオブジェクトの最大値を出力せよ。 1.2.3 コンスト ラクタとデスト ラクタ オブジェクトを生成する際に、メンバ変数の値をいちいち定義するのは面倒であるので何らかの関数に引数を渡して 一気に値を設定したい。この時に使用する特殊な関数が「コンストラクタ (construncor)」である。 コンストラクタはオブジェクトが生成される時に一度だけ呼ばれる特殊な関数であってオブジェクトの初期化のため にに使用される。例えば前節の SCORE クラスをコンストラクタを用いて書き直すと ソース 1-25 #include <iostream> class SCORE { public: int math; int english;

SCORE( int m, int e ){ math = m;

english = e; }

double mean(){ return ( math + english )/2.0; } };

int main() {

(15)

SCORE tokai( 10, 20 ), oyama( 90, 80 );

std::cout << "mean of tokai = " << tokai.mean() << std::endl << "mean of oyama = " << oyama.mean() << std::endl; return 0;

}

となり、main() 関数が非常にすっきりすることが分かる。ここで「SCORE( int m, int e )」がコンストラクタであり、 「クラス名と同じ関数名」「戻り値が無い」という特徴がある。 一方、オブジェクトが消滅するときに最後に呼び出される特殊関数を「デストラクタ (destruntor)」と呼び 、オブジェ クトを使い終わった後の後始末のために使用される。例えば ソース 1-26 #include <iostream> class SCORE { public: int math; int english;

SCORE( int m, int e ){ math = m; english = e; } ~SCORE() { std::cout << "消滅\n"; }

double mean(){ return ( math + english )/2.0; } };

int main() {

SCORE tokai( 10, 20 ), oyama( 90, 80 );

std::cout << "mean of tokai = " << tokai.mean() << std::endl << "mean of oyama = " << oyama.mean() << std::endl; return 0; } を実行すると main() 関数が終わってオブジェクト tokai,oyama が消滅する際に「消滅」という文字を表示する。ここで 「∼SCORE()」がデストラクタであり、「クラス名と同じ関数名の前にチルダを付ける」、「戻り値が無い」、「引数が無い」 という特徴がある。

問題 1-13

次のコード を実行して動作を観察し 、なぜそのような挙動になるか考察せよ ( 10 点 )

(16)

ソース 1-27 #include <iostream> #include <string> class FRUIT { public: std::string name; FRUIT( std::string n ) { name = n; std::cout << name << "生成\n"; }

~FRUIT(){ std::cout << name << "消滅\n"; } };

int main() {

FRUIT *fr = new FRUIT( "apple" ); delete fr;

fr = new FRUIT( "orange" ); delete fr; return 0; } 1.2.4 継承 (インヘリタンス) ある定義済みのクラスを基に、新しいメンバ変数やメンバ関数を追加・拡張して別のクラスをつくり出すことを「継 承 (インヘリタンス:inheritance)」と言う。基となったクラスの事を「基底クラス」「スーパークラス」などと呼び 、継承 したクラスの事を「派生クラス」「サブクラス」などと呼ぶ。例えば果物 (FRUIT) クラス ソース 1-28 #include <iostream> #include <string> class FRUIT { public: std::string name; FRUIT( std::string n ) { name = n;

(17)

}

void show_name() {

std::cout << name << std::endl; }

};

を基にしてりんご (APPLE) クラスとみかん (ORANGE) サブクラスを作るには次のようにする。 ソース 1-29

class APPLE : public FRUIT {

public:

APPLE() : FRUIT( "りんご" ){} };

class ORANGE : public FRUIT { public: ORANGE() : FRUIT( "みかん" ){} }; int main() { APPLE apple; ORANGE orange; apple.show_name(); orange.show_name(); return 0; }

ソース 1-29 においてはじめに注目するのは、APPLE や ORANG クラスの定義の後についている「 : public FRUIT 」 である。これはは「このクラスは FRUIT クラスを継承している」ということを意味している。つぎに注目するのはコン ストラクタのあとについている「 : FRUIT( ”名前” ) 」である。これは「継承したクラスのコンストラクタを呼び出す 前にスーパークラスのコンストラクタを呼び出す」ということを意味している。最後に注目するのは apple.show name() と orange.show name() である。このようにサブクラスはスーパークラスのメンバ変数やメンバ関数を文字どおり「継承」 して用いることが出来る。 もし派生クラスに新しいメンバ変数やメンバ関数を付け加えたい場合は次のように普通のクラスを作成するのと同様 に関数を定義すれば良い。 ソース 1-30

(18)

{ public: int size; APPLE() : FRUIT( "りんご" ){ size = 10; } void show_size() {

std::cout << size << " cm" << std::endl; } }; int main() { APPLE apple; apple.show_name(); apple.show_size(); return 0; } さらに派生クラスでは、つぎのようにスーパークラスのメンバ関数をオーバライド することもできる。 ソース 1-31

class APPLE : public FRUIT { public: int size; APPLE() : FRUIT( "りんご" ){ size = 10; } void show_name() {

std::cout << "名前は" << name << "です" << std::endl; }

};

int main() {

(19)

apple.show_name();

return 0; }

この例では、main() で apple.show name() を呼び出すと、スーパークラス (FRUIT) の show name() ではなくて、サブ クラス (APPLE) で定義された show name() が呼び出される。

問題 1-14

飲物 (DRINK) クラスは名前 (name) と値段 (yen) というメンバ変数とそれを表示する メンバ関数

(show name(),show yen())を持ち、名前と値段はコンストラクタでセットされる。飲物クラスを拡張して任意の飲物 のサブクラスを 2 つ作成してそれらの名前と値段を表示せよ (10 点) 1.2.5 アクセス属性とカプセル化 前節の ソース 1-29 で定義した APPLE クラスには、クラスの外から名前を勝手に書き変えることができるという問 題がある。例えば ソース 1-29 の main() 関数を ソース 1-32 int main() { APPLE apple; apple.name = "ぶど う"; apple.show_name(); return 0; } の様に書き換えられると「りんご 」が「ぶど う」に変わってしまう。 これまで C だけを習ってきた者にとってはこの現象は特に問題ないと思うかもしれないが 、大規模なソフトウェア開 発をおこなう際に、他のクラスや関数などからクラスの内部状態 (メンバ変数) を勝手に書き換えられてしまうことは誤 動作の原因となるため好ましくないとされる。 そこでオブジェクト指向プログラミングの世界では、他のクラスからクラスの内部状態を勝手に書き換えられないよ うにするため「カプセル化」と呼ばれる手法を用いることが多い。カプセル化をおこなうために、メンバ変数やメンバ関 数に対して「アクセス属性」を定義してどこまで他のクラスからクラス内部にアクセスすることが出来るかを設定する。

C++のアクセス属性には「public」「private」「protected」の 3 つがある。「public」は「他のクラスから自由にアクセ スしても良い」という意味であり、これまで例として挙げたクラスのメンバ変数は全て「public」指定をしていたため、 クラス外部から自由にメンバ変数を書き換えることが出来た。 一方「private」は「そのクラスの内部からのみアクセス可能」ということを意味している。例えば ソース 1-33 #include <iostream> #include <string> class FRUIT { private:

(20)

std::string name; public: FRUIT( std::string n ) { name = n; } }; int main() { FRUIT fruit( "ぶど う" ); fruit.name = "腐ったぶど う"; return 0; }

をコンパイルしようとしても、private 指定されている name メンバ変数を main() 関数から書き換えようとしているた めコンパイルエラーが出て書き換えることは出来ない。 最後の「protected」は「自分の内部と、自分を継承した派生クラスからのみアクセス可能」を意味している。例えば ソース 1-34 #include <iostream> #include <string> class FRUIT { protected: std::string name; public: FRUIT( std::string n ) { name = n; } };

class APPLE : public FRUIT { public: APPLE() : FRUIT( "りんご" ){} }; int main() { APPLE apple;

(21)

apple.name = "腐ったりんご "; return 0;

}

は main() 関数から protected 指定されている FRUIT クラスの name メンバ変数を書き換えようとしたためコンパイル エラーが出るが 、 ソース 1-35 #include <iostream> #include <string> class FRUIT { protected: std::string name; public: FRUIT( std::string n ) { name = n; } };

class APPLE : public FRUIT {

public:

APPLE() : FRUIT( "りんご" ){}

void change_name(){ name = "腐ったりんご "; } }; int main() { APPLE apple; apple.change_name(); return 0; }

とした場合は、FRUIT の派生クラスの APPLE 内で name メンバ変数を書き換えるためコンパイルエラーは生じない。 以上のようにしてオブジェクト指向プログラミングではメンバー変数やメンバー関数に適切なアクセス属性を与えて カプセル化を実現する。なお「 メンバ変数とメンバ関数は原則として private 属性にすること」。ちなみに何も指定しな い場合は自動的に private 属性になる。例えば

(22)

class FRUIT { std::string name; public: FRUIT( std::string n ) { name = n; } }; としたとき、name は private 属性になる。

問題 1-15

前節の問題で作成した DRINK クラスのメンバ変数を private にしたとき、main() 関数から名前や値段を

変えようとするとコンパイルエラーが起きることを確認せよ。また、メンバ変数の private 指定は外さずに 、main() 関 数から自由に名前や値段を変えたい場合はど うすれば良いか考察して実装せよ。

1.2.6 その他

今回は範囲を越えるので取り扱わないが 、オブジェクト指向には「仮想 (ヴァーチャル) 関数とポリモーフィズム」と いう極めて大事な概念がある。これについては各自で調べておくこと。

(23)

1.3

クラス図を用いた設計

1.3.1 has a 関係と is a 関係と関連

クラス間の関係は「has a(包含)」関係と「is a(継承)」関係と「関連」で表すことが出来る。

「has a」関係はクラス A がクラス B をメンバ変数として持っている (包含している) ことを意味している。つまり「 A has a B」ということである。例として次のようなソースコード を考えよう。 ソース 1-37 class TIRE { public: TIRE(){} }; class CAR {

TIRE front_right, front_left, rear_right, rear_left;

public: CAR(){} };

ソース 1-37 は車 (CAR) クラスが 4 つのタイヤ (TIRE) クラスをメンバ変数として持っている。

「is a」関係はクラス A がクラス B から継承されて出来ていることを意味している。つまり「A ia a B」ということ である。例として次のようなソースコード を考えよう。 ソース 1-38 class CAR { public: CAR(){} };

class TRUCK : public CAR {

public: TRUCK(){} };

ソース 1-38 はトラック (TRUCK) クラスが車 (CAR) クラスを継承して出来ている。

「関連」は「has a」でも「is a」でもないクラス間の関係を表す。具体的にはクラス A のメンバ関数の引数としてク ラス B を使用したり、クラス A の中の一時的なローカル変数としてクラス B を使用している場合などである。例として 次のようなソースコード を考えよう。

ソース 1-39

(24)

class CAR {

public:

CAR(){ std::cout << "車を借りた\n"; } ~CAR(){ std::cout << "車を返した\n"; } void drive(){ std::cout << "車に乗った\n"; } }; class HUMAN { public: HUMAN(){} void drive_car() { CAR car; car.drive(); } }; int main() { HUMAN human; human.drive_car(); return 0; }

ソース 1-39 は人 (HUMAN) が車 (CAR) に乗る (void drive car()) 時に一時的に車を借りてきて乗っていることを意味 している。

問題 1-16

ソース 1-39 を「has a」関係にせよ。すなわち人 (HUMAN) が自分の車 (CAR) を所有し 、void drive car() において自分の車に乗るように変更せよ。

(25)

1.3.2 クラス図の基本 オブジェクト指向プログラミングでは PAD 図やフローチャートではなく「クラス図」によって設計をおこなう。「has a」や「is a」など クラス間の関係はこのクラス図によってあらわされるが 、ひとつのクラスだけをクラス図で表したも のが次の図である。 図 1: クラス図の基本 図 1 において、「 クラス名」にはそのクラスの名前、「属性」にはメンバ変数名、「操作」にはメンバ関数名を記入する。 またメンバ変数やメンバ関数のアクセス属性をそれらの名前の前に記号で記す。「+」が public、「-」が private、「#」が protectedである。なお、メンバ変数のクラス名、メンバ関数の戻り値や引数、コンストラクタ、デストラクタなどは省 略される。例えば次の人 (HUMAN) クラス ソース 1-40 class HUMAN { std::string name; protected:

void change_name( std::string new_name ){ name = new_name; }

public: HUMAN(){}

void show_name(){ std::cout << name << std::endl; } };

をクラス図で表すと次の様になる。

(26)

1.3.3 has a(包含) のクラス図 「has a」関係はひしがた付きの直線でクラスをつないで表される。ひしがたは他のクラスを所有している方のクラス 側に付ける。例えば ソース 1-41 class PENCIL { public: PENCIL(){} }; class HUMAN { PENCIL pencil; public: HUMAN(){} }; は次のように表される。 図 3: has a 関係 1.3.4 is a(継承) のクラス図 「is a」関係は中抜き三角の矢印でクラスをつないで表される。矢印はスーパークラスの方に付ける。例えば ソース 1-42 class CAR { public: CAR(){} };

class TRUCK : public CAR {

(27)

TRUCK(){} }; は次のように表される。 図 4: is a 関係 1.3.5 関連のクラス図 関連は単純に矢印でクラスをつないで表される。矢印は使用するクラス側から使用されるクラス側の方向に付ける。例 えば ソース 1-43 class PENCIL { public: PENCIL(){} }; class HUMAN { public: HUMAN(){} void use_pencil() { PENCIL pencil; } }; は次のように表される。

(28)

図 5: 関連

1.4

設計の実際例

この章では、実際にどのようにしてオブジェクト指向プログラミングにおいて設計を行うかを簡単な例を用いて示す。 (1)仕様作成 「テレビ 」を作る。チャンネルを切り替えるとテレビの画面にテレビ局の名前が表示される。テレビ局は「ABC 局」 「DEF 局」の 2 局である。 実際に「テレビ 」のスイッチを入れる (プログラムを実行する) と表示するチャンネルを尋ねてきて、チャンネル番号 を入力すると画面にテレビ局の名前が表示されてまたチャンネルが尋ねられる。「off」と入力すると電源が切れる (プロ グラムが終了する)。 (2)機能分割 始めに「テレビ 」を機能 (役割) 別に分解する。 • 必要な機能 (役割) は「TV 本体」「画面」「チャンネル」の 3 つである。 • 「TV 本体」はチャンネルを変更する役割を持つ • 「画面」は表示の役割を持つ • 「チャンネル」は対応する TV 局の名前を知らせる役割を持つ。

• 「チャンネル」は「ABC 局のチャンネル」、「DEF 局のチャンネル」の 2 種類に分かれる (is a)。

• 「TV 本体」は「画面」、「ABC 局のチャンネル」及び「DEF 局のチャンネル」から出来ている (has a)。 (3)クラス設計

機能分割の結果から次の事が言える。

• 必要なクラスは「TV」(TV 本体)「SCREEN」(画面)「CHANNEL」(チャンネル) の 3 つとなることが分かる。さ らに「CHANNEL」は「ABC」(ABC 局)、「DEF」(DEF 局) の 2 クラスに派生する。

• 「TV」のメンバ変数は「SCREEN screen」「ABC abc」「DEF def」の 3 つであり全て private とする。メンバ関 数は「void change channel()」(チャンネルを変更する) のひとつであり public とする。チャンネル変更では off が 入力されるまでチャンネル入力と局名表示を繰り返す。

• 「SCREEN」のメンバ変数は無し 、メンバ関数は「void show text( std::string text )」(画面に文字列を表示) の ひとつであり public とする。

• 「CHANNEL」のメンバ変数は「std::string name」(名前)であり privateである。メンバ関数は「std::string get name()」 (名前取得) であり public である。

(29)

以上を表にまとめると次の様になる。

クラス名 基底クラス メンバ変数 メンバ関数 TV -screen +change channel()

-abc -def

SCREEN +show text() CHANNEL -name +get name() ABC CHANNEL DEF CHANNEL (4)クラス図 以上の結果をクラス図で表すと次の様になる 図 6: TV クラス図 (5)コーデ ィング 以上を C++でコーデ ィングしたものは次のようになる。 ソース 1-44 #include <iostream> #include <string> class SCREEN { public: SCREEN(){}

void show_text( std::string text ) {

std::cout << text << std::endl; }

(30)

//---class CHANNEL

{

std::string name;

public:

CHANNEL( std::string n ){ name = n; } std::string get_name(){ return name; } };

//---class ABC : public CHANNEL

{ public:

ABC() : CHANNEL( "ABC 局" ){} };

//---class DEF : public CHANNEL

{ public:

DEF() : CHANNEL( "DEF 局" ){} }; //---class TV { SCREEN screen; ABC abc; DEF def; public: TV(){ std::cout << "スイッチ on\n"; } ~TV(){ std::cout << "スイッチ off\n"; } void change_channel() { for(;;){ std::cout << "チャンネル入力 [ 0 or 1 or off ] : "; std::string input; std::cin >> input;

if( input == "0" ) screen.show_text( abc.get_name() ); if( input == "1" ) screen.show_text( def.get_name() ); if( input == "off" ) break;

} } };

(31)

//---int main() { TV tv; tv.change_channel(); return 0; } (6)実行 実際に実行すると次のようになる スイッチ on チャンネル入力 [ 0 or 1 or off ] : 0 ABC 局 チャンネル入力 [ 0 or 1 or off ] : 1 DEF 局 チャンネル入力 [ 0 or 1 or off ] : off スイッチ off

(32)

1.5

オブジェクト 指向プログラミング・レポート 課題 計 100 点

始めに手書きで良いので (1)∼(4) が出来たら持ってくること。OK をもらったら C++でコーディングして (1)∼(4) を PCで清書してから提出すること。 (1)好きなプログラムを書いてよい。作成するプログラムの仕様を書け。(10 点) (2) (1)の仕様から、どのように機能分割するか考えよ。ただし is a 関係と has a 関係の両方を必ず一回は使用すること。 (20点) (3) (2)の機能分割から、クラス名、基底クラス、メンバ変数とメンバ関数の表を作成せよ。(20 点) (4) (3)の表からクラス図を書け。(20 点) (5) (4)のクラス図を C++のコード に変換せよ。(20 点) (6)実行した様子を示せ。 (10 点)

図 2: 人 (HUMAN) クラスのクラス図

参照

関連したドキュメント

スライド5頁では

のようにすべきだと考えていますか。 やっと開通します。長野、太田地区方面  

[r]

In this paper, we characterize symmetric transversal designs STD λ [k, u]’s which have a semiregular automorphism group G on both points and blocks containing an elation group of

現行の HDTV デジタル放送では 4:2:0 が採用されていること、また、 Main 10 プロファイルおよ び Main プロファイルは Y′C′ B C′ R 4:2:0 のみをサポートしていることから、 Y′C′ B

定性分析のみ 1 検体あたり約 3~6 万円 定性及び定量分析 1 検体あたり約 4~10 万円

高さについてお伺いしたいのですけれども、4 ページ、5 ページ、6 ページのあたりの記 述ですが、まず 4 ページ、5

1 つの Cin に接続できるタイルの数は、 Cin − Cdrv 間 静電量の,計~によって決9されます。1つのCin に許される Cdrv への静電量は最”で 8 pF