プログラミング言語 I 第 14 回 オブジェクト指向
埼玉大学工学部 電気電子システム工学科 伊藤 和人
Copyright © 2008 Kazuhito Ito
要求
四則演算を正しく扱う電卓が欲しい。
100 円ショップで売っている電卓
でも本当は 7 でなければならない
1 + 2 × 3 = 9 になる
1 + 2 × 3 = 7 になる電卓が欲しい
Copyright © 2008 Kazuhito Ito
問題の分析
間違いの原因は、加算と乗算の優先順位を 考慮していないこと
1 + 2 × 3
複数の演算子を比較し、優先順位に したがって演算を実行する
過去に入力した演算子を記憶する必要あり
Copyright © 2008 Kazuhito Ito
スタックを利用
スタック = 本を積み重ねたもの
後入れ、先出しのデータ管理方式
Last-In, First-Out (LIFO)
Copyright © 2008 Kazuhito Ito
スタックを用いた演算実行
計算例
数値のスタックを用意
1 + 2 × 3
1
1 2
数値スタック
直前演算子 ( なし ) +
1+
11 +
12
入力
1 1 2
+
1×
2+
1×
23
×
22 3
=
1 7
+
16
=
1 1 2
答え
Copyright © 2008 Kazuhito Ito
スタックの実現
配列によってスタックを表す
データを積む位置、取り出す位置
・・・ 常にスタックの頂上
スタックの頂上を表す変数 = スタックポインタ int stack[100];
int sp=99;
例 スタック本体
スタックポインタ
スタックは上位 ( 大きな添え字 ) から
下位 ( 小さな添え字 ) に向かってデータを積む
大域変数として用意
Copyright © 2008 Kazuhito Ito
スタックの操作
データをスタックに積む push 操作
スタックからデータを取り出す pop 操作
void push( int v ) { stack[sp -- ] = v;
}
int pop( void )
{ return stack[++sp];
}
sp 位置にデータを 入れて、 sp を減少
sp を増加し、
sp 位置の
データを読出し
Copyright © 2008 Kazuhito Ito
電卓プログラム例 1
int type, value;
char op, op1=' ', op2 =' ';
while( 1 ){
InputData( &type, &op, &value );
if( type == VALUE ) push( value );
else if( type == OPERATOR ){
if( op2 != ' ' ){ execute( op2 ); op2 = ' '; } if( op1 == ' ' ) op1 = op;
else if( op1 == '*' || op1 == '/' ){
execute( op1 ); op1 = op;
}else{ /* (op1 == '+' || op1 == ' - ') */
if( op == '*' || op == '/' ) op2 = op;
else{ execute( op1 ); op1 = op; } }else{ } /* type == EQUAL */
if( op2 != ' ' ){ execute( op2 ); op2 = ' '; } if( op1 != ' ' ){ execute( op1 ); op1 = ' '; } } }
優先順位に従い 先の演算を実行 演算子を
保留
優先順位に 従い演算を 保留
入力データ読み取り
数値、演算子、 ‘=’
Copyright © 2008 Kazuhito Ito
電卓プログラム例 1( 続き )
void execute( char op ){
int n1, n2;
n2 = pop();
n1 = pop();
switch( op ){
case '+': n1 = n1+n2; break;
case '
-': n1 = n1-n2; break;
case '*': n1 = n1*n2; break;
case '
/': n1 = n1/n2; break;
} push( n1 );
}
スタック先頭の 2 つの数値を取り出し、
演算結果をスタックに積む
n1, n2 の順序に注意
Copyright © 2008 Kazuhito Ito
電卓プログラムを拡張する
加算・減算と乗算・除算の 2 種類の優先順位
⇒ op1, op2 の 2 つの変数によって優先順位 を実現可能
⇒ ただし、面倒な場合分けが必要
例えば、べき乗 (x
y) を組み込む
優先順位は 3 種類
⇒ さらに場合分けが複雑に
演算子についてもスタックを利用することで
場合分けを簡単に
Copyright © 2008 Kazuhito Ito
演算子のスタックを利用
if( type == OPERATOR ){
while( ! empty() ){
if( op == '+' || op == '
-' ){
op1 = pop();
execute( op1 );
}else if( op == '*' || op == '/' ){
op1 = pop();
if( op1 == '*' || op1 == '/' || op1 == '^' ){
execute( op1 );
}else{
push( op1 );
break;
} }
} push( op );
}
演算子スタックが空でない スタック上演算の
優先順位は op 以上
演算 op1 の優先順位が 演算 op 以上なので
演算 op1 を実行
演算 op の優先順位が高い
ので op1 をスタックに戻す
演算 op をスタックに積む
Copyright © 2008 Kazuhito Ito
問題点
数値のスタックと演算子のスタック
2 つのスタックを区別する必要あり
スタックを引数に取る方法
専用の関数を用いる方法
push_data( value )
pop_data() push_op( op ) pop_op()
push( stk_data, value )
pop( stk_data ) push( stk_op, op ) pop( stk_op )
あくまでも関数 ( 手続き ) が主体 であることに原因がある
⇒ 型の問題あり
⇒ 効率が悪い
同じ型の スタックが
2
個ある 場合などCopyright © 2008 Kazuhito Ito
オブジェクト指向
数値スタック、演算子スタックがあり、
スタックに対して push 操作や pop 操作を行う
オブジェクト ( スタック ) が主体
data_stack.push( value )
data_stack.pop() op_stack.push( op ) op_stack.pop()
数値スタック 演算子スタック
この考え方をオブジェクト指向という
Copyright © 2008 Kazuhito Ito
クラス
オブジェクトの型を定義するもの
class CValueStack { public:
CValueStack();
virtual ~CValueStack();
void push( int v );
int pop( void );
int empty( void ){ return sp==99; } private:
int stack[100];
int sp;
};
データ
( 記憶領域とスタックポインタ ) メソッド
( スタック操作関数 ) コンストラクタ および デストラクタ
データと操作 ( メソッド ) が一体化されている
インライン記述
Copyright © 2008 Kazuhito Ito
クラス ( 続き )
各メンバ関数の定義
CValueStack::CValueStack() { sp = 99;
} CValueStack::~CValueStack() { }
void CValueStack::push( int v ) { stack[sp--] = v;
} int CValueStack::pop( void ) { return stack[++sp];
}
コンストラクタ
デストラクタ スタックポインタを初期化
Push 操作
Pop 操作
この場合何もしない
Copyright © 2008 Kazuhito Ito
インスタンスを作る
クラスからインスタンス ( オブジェクト ) を作る
CValueStack data_stack;
COpStack op_stack;
int n;
data_stack.push( 100 );
data_stack.push( -200 );
op_stack.push( '+' );
n = data_stack.pop();
Push 操作 Pop 操作
インスタンス作成
インスタンスに対して
Copyright © 2008 Kazuhito Ito
クラスを用いる利点
オブジェクト指向の考えを実現する
例 : スタックオブジェクトが存在し、スタックに 対して push や pop といった操作を指示する
操作だけを公開し、中身 ( 実装 ) は隠す
例 : スタックについて、利用者はデータの読み 書きは push(), pop() といった関数を使用する ことだけを知っていればよい。 push(), pop() が実際にはどのようなプログラムになっている か、知らなくてよい。
プログラムの再利用が容易になる
スタックを利用したいプログラムに、スタックの
クラスを単純にコピーすればよい
Copyright © 2008 Kazuhito Ito
オブジェクト指向の本当の意味
与えられた要求を実現するプログラムを開 発する際に、要求を分析し、プログラムの 構造を設計するための考え方
この作業を「モデリング」という 要求を分析し、本質を抽出
計算機プログラムとして表現できるように 単純化する
オブジェクトの振る舞い、オブジェクト間関係
( オブジェクト指向モデリング )
Copyright © 2008 Kazuhito Ito
モデリング例 : 数独
数独のルール
縦列 (9 マスの列が 9 つあります ) 、横列 (9 マス の列が 9 つあります ) のそれぞれに 1 から 9 まで の数字が 1 つずつ入ります
太い線で囲まれた 3 × 3 のブロック ( それぞれ 9 マスあるブロックが 9 つあります ) にも、 1 から 9 までの数字が 1 つずつ入ります
出典
:
朝日新聞オブジェクトを抽出
マス (9 × 9 個 ) 列 (9 個 ) 行 ( 横列 )(9 個 ) ブロック (9 個 )
要求
ボード (1 個 )
Copyright © 2008 Kazuhito Ito
クラスを整理
各オブジェクトを抽象化するクラスを作る
クラス間の関係を分析
マス CPlace
列 CColumn
行 CRow
ボックス CBox
ボード CBoard
CBoard
1 81CPlace
CColumn CRow
9
9
CBox
1 1 1 9
9 9
9
1 1 1
Copyright © 2008 Kazuhito Ito
クラスの役割分担 ( 責務 ) を分析
縦列に 1 から 9 までの数字が 1 つずつ入ります
横列に 1 から 9 までの数字が 1 つずつ入ります
ブロックに 1 から 9 までの数字が 1 つずつ入ります 9 個のマスの集め方が異なるだけで、責務は同じ 責務に注目して、クラス CGroup へ抽象化する
CBoard
1 81CPlace
CGroup
9 1
* 1
Copyright © 2008 Kazuhito Ito
クラスの定義 : CPlace
class CPlace { public:
CPlace();
virtual ~CPlace();
void FixValue( void );
int IsFixed( void );
int IsValueAllowd( int v ){ return aValue[v]; } void ExcludeValue( int v );
private:
int bFixed;
int aValue[10];
int nValue;
};
データ
メソッド コンストラクタ および デストラクタ
クラス CPlace ( マスを表す )
Copyright © 2008 Kazuhito Ito
クラスの定義 : CPlace
CPlace::CPlace() { int v;
for( v=1 ; v<=9 ; v++ ) aValue[v] = 1;
bFixed = 0;
}
int CPlace::IsFixed( void )
{ if( bFixed ) return nValue;
else return - 1;
}
void CPlace::ExcludeValue( int v ) { if( ! bFixed ) aValue[v] = 0;
}
初期化を行う コンストラクタ
オブジェクトが 作られるときに 1 度だけ実行する
マスの値が決定して いれば、その値
未定ならば- 1 を返す
マスの値が未定なら
値 v を除外する
Copyright © 2008 Kazuhito Ito
クラスの定義 : CPlace
void CPlace::Fix( void ) { int v, v0;
int nCount = 0;
for( v=1 ; v<=9 ; v++ ){
if( aValue[v] ){
nCount++; v0 = v;
if( nCount == 1 ){ } bFixed = 1;
for( v=0 ; v<9 ; v++ ) aValue[v] = 0;
aValue[v0] = 1;
nValue = v0;
} }
このマスに許される 値を数える
唯一可能な値に決定する
許される値が 1 つのみ ならば、その値に決定
マス ( オブジェクト ) に対して値を決定する
⇒ 値決定はマスの操作として定義する
Copyright © 2008 Kazuhito Ito
クラスの定義 : CGroup
class CGroup { public:
CGroup();
virtual ~CGroup();
void SetPlace( int k, CPlace *p );
void L1Infer( void );
void Fix( void );
private:
CPlace *place[9];
};
データ
⇒ グループを構成する 9 個のマス
メソッド コンストラクタ および デストラクタ
クラス CGroup
( 列、行、ボックスを表す )
グループを構成する
9
個のマスを登録するためのメソッド レベル1
の推論 値を決定できる マスを選ぶCopyright © 2008 Kazuhito Ito
クラスの定義 : CGroup
void CGroup::L1Infer() { int k, v;
for( v=1 ; v<=9 ; v++ ){
for( k=0 ; k<9 ; k++ ){
if( place[k] - >IsFixed() == v ) break;
} if( k<9 ){
for( k=0 ; k<9 ; k++ )
place[k] - >ExcludeValue( v );
} } }
レベル 1 の推論
他の 8 個のマスでは値 v を除外する
値 v に決定した
マスが存在する
Copyright © 2008 Kazuhito Ito
クラスを用いる利点
行、列、ボックスからグループへ抽象化
単に 9 個のマス間の関係のみを考慮
行、列、ボックスの区別は必要ない
CGroup では、 CPlace の実装を知らなくてよい
必ず CPlace のメソッド ( インターフェース ) を経由
CPlace 内の変数 bFixed 、配列 aValue などは意識 しない
クラスの役割分担 ( 責務 ) が明確になる
クラス単位でプログラムの再利用が容易になる
隠蔽 ( カプセル化 ) CPlace と CGroup の関係より
( 共通化 )
Copyright © 2008 Kazuhito Ito
クラスの定義 : CBoard
class CBoard { public:
CBoard();
virtual ~CBoard();
void Initialize( void );
void L1Infer( void );
/*
その他のメソッド(
省略) */
private:
CPlace place[9][9];
CGroup column[9];
CGroup row[9];
CGroup box[3][3];
};
データ
⇒ 81 個のマス 9 個の列
9 個の行
9 個のボックス メソッド
コンストラクタ および デストラクタ
クラス CBoard
Copyright © 2008 Kazuhito Ito
クラスの定義 : CBoard
void CBoard::Initialize() { int i,j,bx,by;
for( i=0 ; i<9 ; i++ )
for( j=0 ; j<9 ; j++ ){
column[i].SetPlace( j, &(place[i][j]) );
row[j].SetPlace( i, &(place[i][j]) );
for( bx=0 ; bx<3 ; bx++ ) } for( by=0 ; by<3 ; by++ )
for( i=0 ; i<3 ; i++ )
for( j=0 ; j<3 ; j++ )
box[bx][by].SetPlace( j*3+i,
&(place[bx*3+i][by*3+j]) );
}
列と行の
グループを設定 グループとマスの関係を 設定する
ボックスの
グループを設定
Copyright © 2008 Kazuhito Ito
レベル 2 の推論を組み込む
「ここ」に入りえない値を除外する
2 1
4
9
7 2 5 1 8
3 6
①ここが 5 に 決まった
②このボックス では値 5 は ここに入る
③このボックスでは
値 5 はこれらのマスには入らない
ボックスについてレベル 2 の推論をするための 機能が必要
グループとしてのボックスに、機能を追加する
Copyright © 2008 Kazuhito Ito
機能を追加したクラス CBox 追加
CBoard
1 81CPlace
CGroup
9 1
* 1
CBox
クラス CGroup の機能を引き継ぎ、さらに 機能を追加したクラス CBox を定義する
継承 一般
特殊
Copyright © 2008 Kazuhito Ito
クラスの定義 : CBox
class CBox : public CGroup { public:
CBox();
virtual ~CBox();
public:
void ExcludeColumn( int i, int v );
void ExcludeRow( int j, int v );
int OnlyColumn( int v );
int OnlyRow( int v );
};
レベル 2 の 推論用に 追加する メソッド
クラス CGroup を継承するクラス CBox を定義
継承を表す部分
CGroup の SetPlace, L1Infer なども利用可
Copyright © 2008 Kazuhito Ito
クラスの定義を修正
class CBoard { public:
CBoard();
virtual ~ CBoard();
void Initialize( void );
void L1Infer( void );
void L2Infer( void );
/*
その他のメソッド(
省略) */
private:
CPlace place[9][9];
CGroup column[9];
CGroup row[9];
CBox box[3][3];
}; ボックスをクラス CBox に変更 レベル 2 推論の
メソッドを追加
Copyright © 2008 Kazuhito Ito
クラス CBoard の関数 Initialize
void CBoard::Initialize() { int i,j,bx,by;
for( i=0 ; i<9 ; i++ )
for( j=0 ; j<9 ; j++ ){
column[i].SetPlace( j, &(place[i][j]) );
row[j].SetPlace( i, &(place[i][j]) );
for( bx=0 ; bx<3 ; bx++ ) } for( by=0 ; by<3 ; by++ )
for( i=0 ; i<3 ; i++ )
for( j=0 ; j<3 ; j++ )
box[bx][by].SetPlace( j*3+i,
&(place[bx*3+i][by*3+j]) );
}
列と行の
グループを設定
ボックスの
グループを設定 box には
関数 SetPlace が あり、利用可能
全く変更不要
Copyright © 2008 Kazuhito Ito
クラス CBoard の関数 L2Infer
void CBoard::L2Infer( void ) { int bx, by, bx0, by0;
int i, j, v;
for( v=1 ; v<=9 ; v++ ){
/*
行方向の推論*/
for( by=0 ; by<3 ; by++ )
for( bx=0 ; bx<3 ; bx++ ){
i = box[bx][by].OnlyRow( v );
if( i >= 0 )
for( bx0=0 ; bx0<3 ; bx0++ ){
if( bx0 == bx ) continue;
box[bx0][by].ExcludeRow( i, v );
} }
/*
列方向の推論*/
} }
レベル 2 の推論
( 省略 )
CBox のメソッドを 利用
ボックス
(bx,by)
では 値v
はi
行のみ可能 他のボックスではi
行に値v
は入らないCopyright © 2008 Kazuhito Ito
まとめ 1
オブジェクト指向を紹介
プログラム開発の際に、モデリング ( 要求分析、
プログラム構成設計 ) を行うための手法
モデリングを行うことで、バグを少なくし、
プログラム開発効率を向上する
要求とプログラムの動作条件の洗い出し
( 異常入力が与えられたときの振る舞いなど )
プログラム全体の構成を概観することで、
無駄な部分、無理のある部分を特定し、改善
する
Copyright © 2008 Kazuhito Ito
まとめ 2
オブジェクト指向プログラミング言語 (C++) を利用することで、モデルからプログラム への移行が容易になる
C 言語に比べて C++ 言語は便利な機能が あるが、本来はオブジェクト指向の考え方 と合わせて初めて真価を発揮する
だからといってしり込みすることなく、便利なと
ころはドシドシ利用すればいい
Copyright © 2008 Kazuhito Ito
プログラミング言語 I のまとめ
プログラミングによって、今まで世界中のど こにもないものを作り出すことが可能
プログラミングには、創造性、緻密さ、想像 力、知識が要求される
「習うより慣れろ」とにかくプログラムを作っ てみるのが習得の条件
プログラムが複雑になると予想したら、い きなりプログラムを書かずに、まずは分析 と設計 ( 場合によってはオブジェクト指向を 導入 )