1
クラス Class
(1)抽象データ型とクラス
上級プログラミング 講義資料 成蹊大学理工学部 情報科学科
2
基本データ型 と クラス
基本データ型 [
int, double, char
など]データのみの存在であり、これらに対する操作は、そ れが宣言されたスコープ内において自由にできる。操 作はもともと用意された演算子(+,-,*,/,%)などを通じ て行う。
クラス [
string, ifstream
など]データ部分とそれを操作する関数をひとまとめにカプ セル化したもの。データ部分には、基本データ型や他 のクラスを用いることができる。
C++に既存のstringやifstreamについては、利用者は その構造を知らなくても利用できている。逆にクラス を設計する立場では、「利用者が中身を知らなくても 安全に使えるように」設計する。
3
クラスの必要性
学生
10
人の氏名と英語・数学の試験の得点 を整理したい。配列を利用すると・・・72 85 81 92 83
95 80 88 90 85 阿部 隆史
井上 裕樹 神田 美樹 佐藤 純子 田中 浩二
… … …
氏名 name[] 英語 e[] 数学 m[]
一人の学生に着目するために共通のインデックスを 使っているだけで、一人分としてまとまりがない
4
クラスの必要性
氏名と英語・数学の得点は、一人の学生が持 つデータとして整理すべき。
72 85 81 92
95 80 88 90 阿部 隆史
井上 裕樹 神田 美樹 佐藤 純子
…
配列と違って、異なるデータをひとつにまとめる 必要がある。
5
オブジェクト指向
◎
オブジェクト中心の「もの」の見方
◎
「もの」には、その「もの」自身を性格付け しているものがある。
属性:「もの」が持つデータ
振る舞い:「もの」が持つ機能
例:電気ポット
属性:容量、水量、湯温 振る舞い:お湯を沸かす
オブジェクトの設計=クラス
◎オブジェクト(対象物)=インスタンス
「対象」の機能(メンバ関数)を見極める。
「対象」の機能を実現するのに必要な
データ(データメンバ)を見極める。
最初は、あまり内部の詳細にとらわれず、表面的な性格 を大きく捉えるのがコツ。
クラスの利用者がクラス内部の詳細を知らなくても使える ように設計する。
7
オブジェクトの例
二次元平面上の点
点 B Y
O X
点 A
各点の固有のデータは、
名前・x座標・y座標 新しく点を作る
点を移動する
点の座標を表示する
属性 機能
機能
機能
クラスの定義の仕方
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";
}
クラスのメンバ関数と普通の関数の違い
例:円の面積を求める関数
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の面積
例:二次元平面の点を表すクラスを利用する
/*******************************
四角形の座標表示、移動関数
*******************************/
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) 実行結果
例:名前と年齢をデータメンバとし、このデータメンバにデータを入 力する関数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%
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にすることによって隠蔽したり 公開したりすることができる。
Person クラスの例
Person singer, writer;
singer
input print
writer
input print
中の構造(privateメンバ)は不明だが、使い方は 用意されたボタン(publicメンバ)で分かる
19
クラスの定義とオブジェクトの宣言
クラスPerson 名前
年齢
名前と年齢の取得 名前と年齢の表示
歌手 singer 名前
年齢
名前と年齢の取得 名前と年齢の表示
作家 writer 名前
年齢
名前と年齢の取得 名前と年齢の表示 クラス定義
オブジェクト宣言 をすると...….
設計図のみで実体はない
メモリ中に実体ができる データメンバ
メンバ関数
メンバの属性とアクセス権
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();
① データメンバは
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と虚数部imaginary public:
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;
} };
int main(void) {
Person a(“Ken”,21), b(“Ryo”,18);
// この生成の際にそれぞれのコンストラクタが実行され a.print();
b.print();
{ // 内部ブロックIB
Person a(“Aya”); // 新オブジェクトaの生成と、コンストラクタの実行 a.print();
b.print();
} // このブロック終端でaが消滅され、その際にデストラクタが実行される a.print();
b.print();
} // main関数の終了においてa,bが消滅されるが、その際にそれぞれ // デストラクタが実行される
29
実行結果
cserv1/home/usrX/us052999/advpro% example3
Object Ken has been generated. ← main関数ブロックでのaの生成時 Object Ryo has been generated. ← main関数ブロックでのbの生成時 Name : Ken
Age : 21 Name : Ryo Age : 18
Object Aya has been generated. ← 内部ブロックIBでのaの生成時 Name : Aya
Age : 20 Name : Ryo Age : 18
Object Aya is released... ← 内部ブロックIB終了時におけるaの消滅 Name : Ken
Age : 21 Name : Ryo Age : 18
Object Ryo is released... ← main関数ブロック終了におけるbの消滅時 Object Ken is released... ← main関数ブロック終了におけるaの消滅時
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);
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);
}
自分自身のデータメンバは引数にしな くても使用できる。
自分と他のオブジェクトを明確に区別する。
自分を中心に考えるのがコツ。
クラス引数の例
中点を作って返す、ということなのでコンストラクタ が活躍。