(1)抽象データ型とクラス - SEIKEI › advpro › docs › class1-AbstractionDataTy...1...

Preview:

Citation preview

1

クラス Class(1)抽象データ型とクラス

上級プログラミング 講義資料

成蹊大学理工学部 情報科学科

2

基本データ型とクラス基本データ型 [int, double, char など]

データのみの存在であり、これらに対する操作は、それが宣言されたスコープ内において自由にできる。操作はもともと用意された演算子(+,-,*,/,%)などを通じて行う。

クラス [string, ifstreamなど]

データ部分とそれを操作する関数をひとまとめにカプセル化したもの。データ部分には、基本データ型や他のクラスを用いることができる。

C++に既存のstringやifstreamについては、利用者はその構造を知らなくても利用できている。逆にクラスを設計する立場では、「利用者が中身を知らなくても安全に使えるように」設計する。

3

クラスの必要性 学生10人の氏名と英語・数学の試験の得点

を整理したい。配列を利用すると・・・

7285819283

9580889085

阿部 隆史

井上 裕樹

神田 美樹

佐藤 純子

田中 浩二

… … …氏名 name[] 英語 e[] 数学 m[]

一人の学生に着目するために共通のインデックスを使っているだけで、一人分としてまとまりがない

4

クラスの必要性 氏名と英語・数学の得点は、一人の学生が持

つデータとして整理すべき。

72

85

81

92

95

80

88

90

阿部 隆史

井上 裕樹

神田 美樹

佐藤 純子

…配列と違って、異なるデータをひとつにまとめる必要がある。

5

オブジェクト指向

◎オブジェクト中心の「もの」の見方

◎「もの」には、その「もの」自身を性格付けしているものがある。

属性:「もの」が持つデータ

振る舞い:「もの」が持つ機能

例:電気ポット属性:容量、水量、湯温振る舞い:お湯を沸かす

6

オブジェクトの設計=クラス

◎オブジェクト(対象物)=インスタンス 「対象」の機能(メンバ関数)を見極める。

「対象」の機能を実現するのに必要なデータ(データメンバ)を見極める。

最初は、あまり内部の詳細にとらわれず、表面的な性格を大きく捉えるのがコツ。

クラスの利用者がクラス内部の詳細を知らなくても使えるように設計する。

7

オブジェクトの例

二次元平面上の点

点B

Y

XO

点A

各点の固有のデータは、名前・x座標・y座標

新しく点を作る

点を移動する

点の座標を表示する

属性

機能

機能機能

8

クラスの定義の仕方

class クラス名{private: // 省略してもpublicまでのメンバはすべてprivateになる

public:

};

メンバの宣言(非公開部)主にデータメンバ

メンバの宣言(公開部)主にメンバ関数の定義

メンバ関数の定義は、上記の中ではプロトタイプ宣言のみにし、クラスの定義のあとに、クラススコープ演算子::を用いて、関数定義を行うこともできる。

上のようにメンバ関数定義をクラス定義内で行うと、その関数はデフォルトでインライン関数となる。

9

例:二次元平面の点を表すクラスを定義する。#include <iostream>#include <string>#include <iomanip>using namespace std;class Point { // 以下はクラスPointの定義

private: // このprivateは省略可string nm; // 点の名前int x, y; // x座標とy座標

public:Point(); // 点を作るvoid set(string, int, int); // 点のデータを設定するvoid move(int, int); // 点を移動するvoid print() const; // 点の座標を表示する

};

Point::Point(){ nm="noname"; x=0; y=0; }void Point::set(string n0, int x0, int y0){ nm=n0; x=x0; y=y0; }void Point::move(int dx, int dy){ x+=dx; y+=dy; }void Point::print() const{

cout <<"(" <<nm <<", " <<setw(2) <<x <<", " <<setw(2) <<y <<")¥n";

}

10

クラスのメンバ関数と普通の関数の違い 例:円の面積を求める関数

double circle_area(double r){return r*r*M_PI;

} // 普通の関数

class Circle{private:

double r;public:

double area();};double Circle::area(){ return r*r*M_PI; }

仮引数は、その関数に必要なデータを渡す

ためのもの

メンバ関数は、そのオブジェクト(クラス)のデータメンバを引数として受け取らなくても使用するこ

とができる。

11

オブジェクト(インスタンス)の宣言

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

例: Circle A, B; // 円Aと円Bの宣言

メンバ関数の呼び出し

オブジェクト名.メンバ関数名();

例: double sa=A.area(); // 円Aの面積double sb=B.area(); // 円Bの面積

12

例:二次元平面の点を表すクラスを利用する

/*******************************四角形の座標表示、移動関数

*******************************/void move(Point rect[], int sz, int dx, int dy){

for(int i=0; i<sz; i++)rect[i].move(dx, dy);

}

void print(Point rect[], int sz){

cout <<"rectangular ----¥n";for(int i=0; i<sz; i++){

cout << "¥t";rect[i].print();

}}

【続く】

プログラム例1(続き)

13

例:二次元平面の点を表すクラスを利用する

/*************************mainプログラム四角形を作って移動する

*************************/int main(){

const int sz=4;Point r[sz];print(r, sz);r[0].set("A", 0, 0);r[1].set("B",20, 0);r[2].set("C",20,10);r[3].set("D", 0,10);print(r, sz);move(r, sz, 10, 5);print(r, sz);move(r, sz, 10, 5);print(r, sz);return 0;

}

プログラム例1(続き)

rectangular ----(noname, 0, 0)(noname, 0, 0)(noname, 0, 0)(noname, 0, 0)

rectangular ----(A, 0, 0)(B, 20, 0)(C, 20, 10)(D, 0, 10)

rectangular ----(A, 10, 5)(B, 30, 5)(C, 30, 15)(D, 10, 15)

rectangular ----(A, 20, 10)(B, 40, 10)(C, 40, 20)(D, 20, 20)

実行結果

14

例:名前と年齢をデータメンバとし、このデータメンバにデータを入力する関数inputとデータを出力する関数printをメンバ関数とするクラスPersonの定義

class Person{ // 以下はクラスPersonの定義private:string name; // 名前int age; // 年齢

public:void input(); // 名前と年齢の入力(プロトタイプのみ)void print(); // 名前と年齢の出力(プロトタイプのみ)

};void Person::input() // 入力用メンバ関数の定義{

cout << "名前と年齢は? ";cin >> name >> age;

}void Person::print() // 出力用メンバ関数の定義{

cout << "名前:" << name << " 年齢:" << age << endl;}

15

int main(){

Person singer, writer; // オブジェクトの宣言singer.input(); // メンバ関数の呼び出しwriter.input();singer.print();writer.print();return 0;

}

実行例comsv1% example1名前と年齢は? Hayashi 19名前と年齢は? Kawai 20名前:Hayashi 年齢:19名前:Kawai 年齢:20

comsv1%

16

class Person{string name; // 名前int age; // 年齢

public:void input(){ // 名前と年齢の入力

cout << “名前と年齢は? ”;

cin >> name >> age;}void print(){ // 名前と年齢の表示

cout << "名前:" << name << " 年齢:" << age << endl;}

};

クラス定義のメンバ関数をプロトタイプ宣言だけでなく、関数の定義も記述する例

メンバ関数を上のように定義すると、それらはインライン関数となる。

インライン関数にすると、実行ファイルサイズは大きくなるが実行速度は速くなる。関数の内容がよほど簡単な場合以外はメンバ関数はクラス定義の外部で記述する方が良い。

17

カプセル化(encapsulation)

データとそれらに対する操作を1つの構造にカプセル化

クラスはオブジェクト指向プログラミングにおいて重要な概念

データ3データ2データ1

操作1

操作2

クラスの中以外の場所で宣言されるint型やdouble型のような基本データ型の変数は、クラスのオブジェクトと比較するとデータのみの構造であり、publicと同様の扱いであるため、その変数のスコープ内ではどこでも自由にアクセスできるようになっている。従って情報隠蔽の立場では非常に弱い状態にあると言える。

カプセル化(encapsulation)

操作1

操作2

メンバをprivateあるいはpublicにすることによって隠蔽したり公開したりすることができる。

18

Personクラスの例

Person singer, writer;

singer

input

print

writer

input

print

中の構造(privateメンバ)は不明だが、使い方は用意されたボタン(publicメンバ)で分かる

19

クラスの定義とオブジェクトの宣言

クラスPerson名前年齢名前と年齢の取得名前と年齢の表示

歌手 singer名前年齢名前と年齢の取得名前と年齢の表示

作家 writer名前年齢名前と年齢の取得名前と年齢の表示

クラス定義

オブジェクト宣言をすると...….

設計図のみで実体はない

メモリ中に実体ができる

データメンバ

メンバ関数

20

メンバの属性とアクセス権privateとpublicの違い

privateメンバ

name age

publicメンバ

input() print()

Personクラスのオブジェクトsinger

Personクラスの他のオブジェクトのメンバ関数

search()

Personとは別のクラスや関数の中

func()やmain()などアクセス不可

singer.input() singer.print()

これらの呼び出しはどこからでも可

21

int main() // このmainではコンパイルしてもエラー{

Person singer, writer; // オブジェクトの宣言

singer.input(); // メンバ関数の呼び出しwriter.input();singer.print();writer.print();

singer.print();return 0;

}

singer.age++;

privateメンバへの不正アクセスの例

正しくは、Personクラスのpublicメンバ関数に年齢ageをインクリメントする関数を準備し、それをこのmain関数の中から呼び出すようにすればよい。

void Person::happyBirthday(){ age++; }

singer.age++; // main関数からはsinger.ageに// 直接アクセスできない

singer.happyBirthday();

22

①データメンバは private にする(方が良い)。

②データメンバに直接アクセスできるメンバ関数をpublicで準備する。

③データメンバは必要最小限を準備し、それらの加工により得られる情報のためにメンバ関数を準備する。

クラスを定義する場合のポイント(1)

23

クラスを定義する場合のポイント(2)

そのクラスのオブジェクトの使われ方から先に考える

例:複素数のクラスComplexを作るとして、その使われ方は・・・

int main(){

Complex x,y,z;x.input(); // 複素数xの入力y.input(); // 複素数yの入力z= x.add(y); // xにyを加えた複素数をzに代入z.print(); // zを表示cout << endl;return 0;

}

24

クラス定義の例class Complex {

double real, imaginary; // 実数部realと虚数部imaginarypublic:

void input();void print();Complex add(Complex);

};void Complex::input(){

cout << "実数部と虚数部を入力せよ-->";cin >> real >> imaginary;

}void Complex::print(){

cout << real;if(imaginary>0) cout << "+";cout << imaginary << “i”; // a+biまたはa-biの形式で表示

}Complex Complex::add(Complex k){

Complex sum;sum.real=real+k.real;sum.imaginary=imaginary+k.imaginary;return sum;

}

25

コンストラクタとデストラクタ コンストラクタ(constructor)

– クラス名と同名のメンバ関数– オブジェクト生成時に自動実行される関数。– オブジェクトのデータメンバの初期化が主な目的。

(データメンバ変数は、宣言時に初期化できないから)。– リターン型を持たない。– 多重定義すれば、色々な方法でオブジェクトを初期化可能。– 明示的に呼び出すと、新しいオブジェクトを生成する。

デストラクタ(destructor)– クラス名の前に~(tilde:チルダ)を付けた名前のメンバ関数– オブジェクト消滅時に自動実行される関数。– リターン型を持たない。– 多重定義はできない。

26

コンストラクタの例(1)

class Complex {double real, imaginary; // 実数部realと虚数部imaginary

public:Complex(double a=0, double b=0){ // デフォルト引数で

// 実数部虚数部ともに0にする準備real=a; // 初期値を与えられたらそれを使ってデータメンバを初期化imaginary=b;

}// ・・・ 中略 ・・・

Complex add(Complex k){return Complex(real+k.real, imaginary+k.imaginary);

} // コンストラクタでリターン値用のオブジェクトを構成することができる};

int main(){

Complex x(4,7), y(2), z; // yの虚数部はデフォルト引数により0,// zも同様に0+0iとなる

// ・・・ 後略 ・・・

27

コンストラクタとデストラクタの例(2)

#include <iostream>#include <string>class Person {

string name;int age;

public:Person(string tmp=“”, int y=20){ // コンストラクタ

name=tmp; age=y;cout << "Object " << name << " has been generated.¥n";

}~Person(){ // デストラクタ

cout << "Object " << name << " is released...¥n";}void print(){

cout << "Name : " << name << endl;cout << "Age : " << age << endl;

}};

28

int main(void){

Person a(“Ken”,21), b(“Ryo”,18);// この生成の際にそれぞれのコンストラクタが実行され

a.print();b.print();

{ // 内部ブロックIBPerson a(“Aya”); // 新オブジェクトaの生成と、コンストラクタの実行

a.print();b.print();

} // このブロック終端でaが消滅され、その際にデストラクタが実行される

a.print();b.print();

} // main関数の終了においてa,bが消滅されるが、その際にそれぞれ// デストラクタが実行される

29

実行結果cserv1/home/usrX/us052999/advpro% example3Object Ken has been generated. ← main関数ブロックでのaの生成時Object Ryo has been generated. ← main関数ブロックでのbの生成時

Name : KenAge : 21Name : RyoAge : 18Object Aya has been generated. ← 内部ブロックIBでのaの生成時

Name : Aya Age : 20Name : RyoAge : 18Object Aya is released... ← 内部ブロックIB終了時におけるaの消滅

Name : KenAge : 21Name : RyoAge : 18Object Ryo is released... ← main関数ブロック終了におけるbの消滅時Object Ken is released... ← main関数ブロック終了におけるaの消滅時

30

constメンバ関数ユーザ定義のクラスAtypeがあるとする。このとき、

const Atype x;x.func();

のように呼び出されるAtypeのメンバ関数func()はconstメンバ関数でなければならない。すなわち、

リターン型 func(引数1, ・・・) const { ・・・・・ }

のように記述しなければならない。

例: double func() const { return data*10; }データメンバdataを10倍して返すだけなので、funcはconstメンバ関数と明示した方が良い。なぜならばデータメンバを変更することがないことをコンパイラにもはっきり示すことができるからである。

constメンバ関数にしておけば、constオブジェクトからだけでなく、非constオブジェクトからも呼び出すこともできる。しかし逆はできない(C++の仕様)。

31

メンバ関数の作成方法

1. メンバ関数の利用場面を注意深く見る。

2. 関数の引数とリターン値に注目してプロトタイプを決める。

3. 引数省略の関数呼び出しには、デフォルト引数の利用を考慮する。

4. そのオブジェクト自身のデータメンバは、メンバ関数の引数にしなくても、関数本体で使用できる。

5. データメンバおよび受け取る引数から結果を求める処理の流れを決定する。

32

1.メンバ関数の利用場面を注意深く見る

int main()

{

Point P(10, 0), Q, M;

P.print(); cout << endl;

Q.set(0, 5.5);

Q.move(0, 4.5);

double d=P.distance(Q);

cout << "PQ=" << d << endl;

M=P.middle(Q);

cout << "PM=" << P.distance(M) << " QM=" << Q.distance(M) << endl;

}

33

2.引数と返値に注目してプロトタイプを決定

int main()

{

Point P(10, 0), Q, M;

P.print(); cout << endl;

Q.set(0, 5.5);

Q.move(0, 4.5);

double d=P.distance(Q);

cout << "PQ=" << d << endl;

M=P.middle(Q);

cout << "PM=" << P.distance(M) << " QM=" << Q.distance(M) << endl;

}

オブジェクト宣言時の引数はコンストラクタに渡す。double型を2つ。プロトタイプ Point(double, double);

コンストラクタ呼び出し時に引数なしの場合あり。その時は各々0とみなす。プロトタイプ Point(double=0, double=0);

引数なし、返値なし。プロトタイプ void print();

引数double型2つ、返値なし。プロトタイプ void set(double, double);引数double型2つ、返値なし。プロトタイプ void move(double, double);

引数Point型1つ、返値double型。プロトタイプ double distance(Point);

引数Point型1つ、返値Point型。プロトタイプ Point middle(Point);

34

Pointクラスの定義

class Point{

private:

double x, y; // x座標とy座標

public:

Point(double=0, double=0); // コンストラクタ

void print();

void set(double, double);

void move(double, double);

double distance(Point);

Point middle(Point);

};

35

Pointクラスのメンバ関数の定義

Point::Point(double a, double b){ x=a; y=b; }

void Point::print(){cout << "(" << x << "," << y << ")";

}void Point::set(double a, double b){ x=a; y=b; }void Point::move(double a, double b){ x+=a; y+=b; }double Point::distance(Point G){return sqrt((x-G.x)*(x-G.x)+(y-G.y)*(y-G.y));

}

Point Point::middle(Point G){return Point((x+G.x)/2, (y+G.y)/2);

}

自分自身のデータメンバは引数にしなくても使用できる。

自分と他のオブジェクトを明確に区別する。自分を中心に考えるのがコツ。

クラス引数の例

中点を作って返す、ということなのでコンストラクタが活躍。

Recommended