プログラミング・演習II

プログラミングII
第 8 回
コピーコンストラクタ
オーバーロード
クラスの合成・継承
連結リスト
バイナリーツリー
田向
前回の重要事項復習
1. new, delete によるメモリの動的確保.
2. 参照
コンストラクタの初期化
#include <iostream>
using namespace std;
class myclass { int x;
public:
// コンストラクタを2とおりにオーバーロードする
myclass() { x = 0; } // 初期化値を指定しない
myclass(int n) { x = n; } // 初期化値を指定する
int getx() { return x; }
};
int main(){
// 初期値を設定せずに宣言する
myclass o1[10];
// 初期値を設定して宣言する
myclass o2[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
myclass *p;
myclass ob(10);
// 1つの変数を初期化する
p = new myclass[10]; // ここでは初期値を使用できない
if(!p) { cout << "メモリ割り当てエラー\n"; return 1;}
int i;
for(i=0; i<10; i++) p[i] = ob; // すべての要素をobに初期化する
return 0;
}
コピ-コンストラクタ-
• コンストラクタはオブジェクト生成時に呼ば
れる.
– 初期化:領域確保
– 初期値の設定
• オブジェクトをコピーする時に呼ばれる時
– 代入
– 関数引数でのオブジェクト渡し
– 一時的な生成・削除
– コピーすると具合が悪いもの.
ポインター変数を持つ
動的に作られたオブジェクトを内に持つ
• コピーコンストラクタが考案された
– 自分と同じクラスの参照引数を持つ
コピーコンストラクタI
#include <iostream>
#include <cstdlib>
using namespace std;
class array {
int *p;
int size;
public:
array(int sz) { // コンストラクタ
p = new int[sz]; //動的に大きさが変わる
if(!p) exit(1);
size = sz;
cout << "「通常の」コンストラクタを使う\n";
}
~array() {delete [] p;}
array(const array &a); // コピーコンストラクタ
void put(int i, int j) { if(i>=0 && i<size) p[i] = j; }
int get(int i) { return p[i]; }
};
コピーコンストラクタII
array::array(const array &a) { int i;
size = a.size;
p = new int[a.size]; // コピー用のメモリを割り当てる
if(!p) exit(1);
for(i=0; i<a.size; i++) p[i] = a.p[i]; // 内容をコピー
する
cout << "コピーコンストラクタを使う\n";
}
int main(){ int i;
array num(10); // 「通常の」コンストラクタを呼び出す
for(i=0; i<10; i++) num.put(i, i); // 配列に値を格納する
// numの内容を表示する
for(i=9; i>=0; i--) cout << num.get(i);
cout << "\n";
// ほかの配列を作成し、numを使用して初期化する
array x = num; // コピーコンストラクタを呼び出す
for(i=0; i<10; i++) cout << x.get(i); // xを表示する
return 0;
}
練習 8-1.cpp
#include <iostream>
#include <cstring>
#include <cstdlib>
using namespace std;
class strtype {
char *p;
public:
strtype(char *s);
// コンストラクタ
ここを埋めよ // コピーコンストラクタ
~strtype() { delete [] p; } // デストラクタ
char *get() { return p; }
};
// 「通常」のコンストラクタ
strtype::strtype(char *s){
int l;
l = strlen(s)+1;
p = new char [l];
if(!p) { cout << "メモリ割り当てエラー\n"; exit(1);}
strcpy(p, s);
}
// コピーコンストラクタ
int main()
{
strtype a("Hello"), b("There");
show(a);
show(b);
strtype c = a;
c.show();
return 0;
}
関数オーバーロード
同じ関数名であるが,引数が違うもの
f(int a); f(float x); f(int a,int b);
デフォルト引数
void f(int a=0, int b=0);
f(); f(10); f(10,99);
曖昧さ:
float f(float x); double f(double x);
f(10) はどっち?
演算子のオーバーロード
• 演算子: C++文法で定めらている.
– 単項,2項演算子,
– 算術演算子,論理演算子など
• これらの演算子の機能を変更できる.
• 新演算子を付け加える事は不可.
• 新しい構文(文法)の付加も不可.
技術的な問題より,運用上の問題
C++では無くなってしまう.
基本構文
return-type class-name::operator #(arglist)
{
body
}
# オーバーロードされる演算子
. :: .* ? は オーバーロードできない.
演算子の優先度は変更できない
演算子の引数の個数は変えられない
+演算子オーバーロードの例
#include <iostream>
using namespace std;
class coord { int x, y; // 座標値
public:
coord() { x=0; y=0; }
coord(int i, int j) { x=i; y=j; }
void get_xy(int &i, int &j) { i=x; j=y; }
coord operator+(coord ob2);
};
coord coord::operator+(coord ob2){ coord temp;
temp.x = x + ob2.x; temp.y = y + ob2.y;
return temp; }
int main(){ coord o1(10, 10), o2(5, 3), o3; int x, y;
o3 = o1 + o2; // 2つのオブジェクト加算。
// O1のoperator+(o2)を呼ぶ
o3.get_xy(x, y);
cout << "(o1+o2) X: " << x << ", Y: " << y << "\n";
return 0;}
#include <iostream>
using namespace std;
class coord { int x, y; // 座標値
public:
coord() { x=0; y=0; }
coord(int i, int j) { x=i; y=j; }
void get_xy(int &i, int &j) { i=x; j=y; }
coord operator+(coord ob2);
coord operator-(coord ob2);
coord operator=(coord ob2); };
// +をcoordクラスに対してオーバーロードする
coord coord::operator+(coord ob2){ coord temp;
temp.x = x + ob2.x;
temp.y = y + ob2.y;
return temp;}
// -をcoordクラスに対してオーバーロードする
coord coord::operator-(coord ob2){ coord temp;
temp.x = x - ob2.x;
temp.y = y - ob2.y;
return temp;}
= 演算子のオーバーロード
// =をcoordクラスに対してオーバーロードする
coord& coord::operator=(coord ob2)
{
x = ob2.x;
y = ob2.y;
return *this; // 代入されたオブジェクトのアドレスを
返す
}
o3 = o1; // オブジェクトの代入
この場合はo3のアドレスが帰ってきても無用
o3=o2=o1 の連続代入の時,
o3=( o2=( o1 ) ) の時には
o3=( *this{o2の事} )
o3=( o2 ) となる
単項演算子
単項演算子は引数を持たない.
class coord { int x, y; // 座標値
public:
coord() { x=0; y=0; }
coord(int i, int j) { x=i; y=j; }
void get_xy(int &i, int &j) { i=x; j=y; }
coord operator-(coord ob2); // 2項負符号
coord operator-(); // 単項負符号};
// 二項-をcoordクラスに対してオーバーロードする
coord coord::operator-(coord ob2){ coord temp;
temp.x = x - ob2.x; temp.y = y - ob2.y;
return temp;
}
// 単項-をcoordクラスに対してオーバーロードする
coord coord::operator-(){
x = -x;
y = -y;
return *this;
}
[ ]のオーバーロード
type class-name::oerator [ ](int index) { … … }
------------------------------------------------------------------#include <iostream>
using namespace std;
const int SIZE = 5;
class array {
int a[SIZE];
public:
array() { int i; for(i=0; i<SIZE; i++) a[i] = i; }
int operator[ ](int i) { return a[i]; }
};
int main( ){ array ob; int i;
for(i=0; i<SIZE; i++) cout << ob[i] << " ";
return 0;
}
[ ]のオーバーロード
#include <iostream>
using namespace std;
const int SIZE = 5;
class array {
int a[SIZE];
public:
array() {int i; for(i=0; i<SIZE; i++) a[i] = i; }
int &operator[](int i) { return a[i]; }
};
int main(){ array ob; int i;
for(i=0; i<SIZE; i++) cout << ob[i] << " ";
cout << "\n";
// 配列の各要素に10を加算する
for(i=0; i<SIZE; i++)
ob[i] = ob[i]+10; // =の左辺に[]
for(i=0; i<SIZE; i++) cout << ob[i] << " ";
return 0;
}
練習 8-2.cpp
8-2.cpp内の3次元を表現するクラスに
対して,+演算を新たに定義せよ.余裕が
あるものは,減算,インクリメント等,思い
つく限りの演算を定義してみよ.
int main()
{
three_d o1(1, 1, 1), o2(2, 3, 4), o3;
int x, y, z;
o3 = o1 + o2;
o3.get(x, y, z);
cout << x <<" " << y <<" " << z;
return 0;
}
クラスの合成
• 合成によってクラスを再利用する.
– あるクラスのオブジェクトを新しいクラス
のメンバーとして宣言する.
– メンバーオブジェクトは引数を取ること
が出来ない.引数付のコンストラクタが
呼べない.
Class className{
// --memberClassName memberObjectName ;
// --Public:
className( arg-list); //constructor
// ---}
className::className(arg-list) : //ここで初期化
memberObjectName (arg-list2) {
//----- body ---}
クラスの継承
クラスの継承と合成とは異なる.
基本クラスから,下の記述で書かれる.
class className : public classBase {
//--public:
//--}
//node からANDを派生
class AND : public node{ //nodeと類似なもの
input i1, i2;
output o1;
Public:
…..
};
//AND,ORから halfAdder を合成
class halfAdder{
AND and1;
//部分として組み込まれる
OR or1 ;
Public:
…….
};
考えてみよう
• Node に 座標 coord を持たせよ.
• queue に積む要素をnode→ANDにせよ.
• 演算子をオーバーローディングしてみよ.
Queueは待ち行列に使われる.
Queue の要素として,event というclassを使
う.
eventはメンバー変数を動的に確保する.
コピーコンストラクタを持つ.
queueへ
Stack: last in first out
Queue: fast in first out
1)今までのstackの記述を改造.
領域はarrayで動的に確保する.
char stck[SIZE]; の部分を変更する.
1-2)演算子[ ]を作り,stack[i]を実行する.
解答例
#include <iostream>
using namespace std;
#define SIZE 10
class array { int *p; int size;
public:
array(){ p = new int[10]; if(!p) exit(1); size=10; }
array(int sz) { p=new int[sz]; if(!p) exit(1);size =
sz;}
~array() {delete [] p;}
array(const array &a); // コピーコンストラクタ
void put(int i, int j) {if(i>=0 && i<size) p[i] = j; }
int get(int i) { return p[i]; }
int &operator[](int i) { return p[i]; }
};
array::array(const array &a) { int i; size = a.size;
p = new int[a.size]; if(!p) exit(1);
for(i=0; i<a.size; i++) p[i] = a.p[i];
cout << "コピーコンストラクタを使う\n";}
class stack {
// char stck[SIZE];
array stck; // スタック領域を確保する
int tos;
// スタック先頭の索引
public:
stack( );
// コンストラクタ
void push(char ch); // スタックに文字をプッシュする
char pop();
// スタックから文字をポップする
int& operator[](int i) { return stck[i]; }
};
stack::stack():stck(10) // スタック(array)を初期化する
{ cout << "スタックを生成する\n"; tos = 0;}
void stack::push(char ch){
if(tos==SIZE) { cout << "スタックは一杯です\n";
return; }
stck[tos] = ch;
out<<"push@stck["<<tos<<"]="<<stck[tos]<<'\n';
tos++; }
char stack::pop(){
if(tos==0) { cout << "スタックは空です\n";
return 0; // スタックが空の場合はヌルを返す }
tos--;
cout<<"pop@stck["<<tos<<"]="<<stck[tos]<<'\n';
return stck[tos];}
// 文字を保存するqueueクラスを宣言する
class queue {
// char stck[SIZE];
array stck; // スタック領域を確保する
int tos;
// スタック先頭の索引
int tos2;
public:
queue();
// コンストラクタ
void push(char ch); // スタックに文字をプッシュする
char pop();
// スタックから文字をポップする
};
queue::queue():stck(10){ cout << "queueを生成する\n";
tos = 0; tos2= 0;}
// 文字をプッシュする
void queue::push(char ch){
if(tos==SIZE) { cout << "queueは一杯です\n"; return; }
stck[tos] = ch;
cout<<"push@queue["<<tos<<"]="<<stck[tos]<<'\n';
tos++;
}
char queue::pop(){
if(tos2==SIZE) { cout << "queueは空です\n";
return 0; // スタックが空の場合はヌルを返す }
cout<<"pop@queue["<<tos2<<"]="<<stck[tos2]<<'\n';
tos2++;
return stck[ tos2-1];
}
int main(){stack stack1;queue queue1;
stack1.push('a'); stack1.push('b');
stack1.push('c'); stack1.push('d');
stack1.push('e');
cout<<"\n -------------------------\n";
for(int i=0; i<10; i++) cout
<<(char)stack1[i]<<'('<<stack1[i]<<")";
cout<<"\n -------------------------\n";
cout << stack1.pop()<<'\n';
cout <<stack1.pop()<<'\n';
cout <<stack1.pop()<<'\n'
<<stack1.pop()<<'\n'<<stack1.pop()<<'\n';
cout <<"-----------------QUEUE -------------------\n";
queue1.push('a'); queue1.push('b');
queue1.push('c'); queue1.push('d');
queue1.push('e');
cout << queue1.pop()<<'\n';
cout <<queue1.pop()<<'\n';
cout <<queue1.pop()<<'\n'
<<queue1.pop()<<'\n'<<queue1.pop()<<'\n';
return 0;};
今のqueueの問題点
Queueのtos1,tos2の処理が不十分?
• popが正しく動かない.
– tos2がsize以上になるとおかしくなる.
– tos2は増加するばかりである
どう改造するか.
stck[size+1]→stck[0] のようにする.
2-2)queue に積む要素を複素数にして
演算子をオーバーローディングしてみよ.
class complex{
int real;
int imaginary;
public:
complex ( ){ real=0; imaginary=0;}
complex(int r, int i){ real = r; imaginary = i;}
int R(){return real;}
int I(){return imaginary;}
complex &operator+(complex& c)
{ real+=c.real; imaginary+=c.imaginary; return
*this;}
complex &operator-(complex& c)
{ real-=c.real; imaginary-=c.imaginary; return
*this;}
complex &operator*(complex& c)
{ int r=real*c.real-imaginary*c.imaginary;
int i=imaginary*c.real+real*c.imaginary;
real = r; imaginary = i; return *this;}
complex &operator/(complex& c)
{ int r=real*c.real-imaginary*c.imaginary;
int i=imaginary*c.real+real*c.imaginary;
real = r; imaginary = -i; return *this;}
};
arrayでの修正個所
class array {
complex *p;
int size;
public:
array(){ p = new complex[10]; if(!p) exit(1);
size=10; }
array(int sz) { p = new complex[sz]; if(!p) exit(1);
size = sz; cout << "「通常の」コンストラクタを使う\n"; }
~array() {delete [] p;}
array(const array &a);
void put(int i, complex j) { if(i>=0 && i<size) p[i] = j; }
complex get( int i){ return p[i]; }
int getSize(){return size;}
complex &operator[](int i){return p[i];}
};
Array:complexでの修正個所
class queue {
array stck;
int tos1; // キューの値を積んである先頭をさす(スタックは
// 空の位置を指す)
int tos2;
int size;
public:
queue();
void push(complex ch);
complex pop();
};
queue::queue():stck(100){ cout << "queueを生成する\n";
tos1= 0; tos2= 0; size = stck.getSize();}
void queue::push(complex ch) { ….. }
complex queue::pop(){ …..return stck[tos2-1]; }
Complex→coordinateにする.
同様な修正をする.→ Templateの考え
連結リスト:Linked List
Link
--------------_Next
--------------_prev
--------------linkedItem
リストの要素
Link
--------------_Next
--------------_prev
--------------linkedItem
Actual Item
class Link {
friend class List
Protected:
Link * _next;
Link * _prev;
Public:
Link() { _next = _prev = 0; }
virtual ~Link();
Link * next( ) { return _next;}
Link* prev( ) { return _prev; }
};
Link自体には,リストへの値の設定方法(割
り込み方)は無い.
FIFO(queue)もLIFO(stack)も値の設定方
法が異なる.
互いの結びつきを断ち切る方法のみを
デストラクタが知っている.
Link::~Link() {
if (_next) _next->prev = _prev;
if (_prev) _prev->next = _next;
_next = 0; _prev=0;
}
リスト・ヘッド
class List{
protected:
Link *_first;
Link *_last;
public:
List( ) { first = _last = 0; }
virtual ~List( );
Link *last( ){ return _last; }
Link *first( ){ return _first; }
List& append ( Link * );
List& prepend ( Link * );
List& remove ( Link *);
};
Append, prepend, remove  push,pop
Stackへの変更
class List{
public:
//
Link *last( ){ return _last; }
Link *first( ){ return _first; }
List& push(Link *l){
return append ( l ); }
Link* pop( ){
Link * l = _last;
remove (_last);
return l; }
Link* top( ) {return _last; }
};
2進木: binaryTreeNode
class BinaryTreeNode {
BinaryTreeNode *left ,*right;
int value;
public:
BinaryTreeNode(int newValue)
{left = right = 0; value = newValue; }
~BinaryTreeNode() {…} //???
};
2進木:binaryTreeNode使い方
ノードをキー(値)で並べる.
新しいノードをN,Cをツリー内の現在のノード
とする.
1.Cをツリーの根とする.
2.Cが空でないとき
– キー(c) < キー(N)ならCを左
部分木に設定.
– キー(c) > キー(N)ならCを右
部分木に設定.
– キー(c) = キー(N)ならエラー
3.位置CにNを挿入する.
binaryTreeNode
Class BinaryTree {
BinaryTreeNode * root;
Public:
BinaryTree ( ){ root = 0; }
~BinaryTree( );
int insert(int value); // 次回やります.
int lookup(int value);
};
int BinaryTree::lookup(int value){
BinaryTreeNode * cur = root;
while(cur){
if (cur->value == value) return 1;
else if ( cur->value <value)
cur = cur->left;
else
cur = cur ->right;
}
reuturn 0;
}