プログラミング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; }
© Copyright 2024 ExpyDoc