プログラミングII 第 7 回 オブジェクトの配列 New, delete 参照 田向
単項演算子 * 2項演算子: x op y ; 単項演算子: op z ; ポインター 宣言 での * とは違う. 単項演算子 * 2項演算子: x op y ; 単項演算子: op z ; ポインター 宣言 での * とは違う. 変数宣言時では ポインター型の変数である事を宣言している. → 代入演算の時に,コンパイラが型チェックをするのに使用する. Int* p; 単項演算子 * は ポインタ変数 の値から アドレスを取り出して,値取り出しの処理(アドレス・レジスタにアドレスを書き込む)を行う. x = *p;
単項演算子 & 単項演算子 * と同じように,アドレス操作の 単項演算子 & がある. 単項演算子 & 単項演算子 * と同じように,アドレス操作の 単項演算子 & がある. 単項演算子 & は 変数,オブジェクトのアドレスを取り出す演算. myclass * mp ; myclass myObj ; mp = &myObj ; int x; int * p , q; p = q ; x = * p ;
オブジェクトのアドレス ポインタ変数 対象(オブジェクト,変数)の アドレスを値として持つ変数 オブジェクトの先頭アドレスを値として持つ 対象(オブジェクト,変数)の アドレスを値として持つ変数 オブジェクトの先頭アドレスを値として持つ コンパイラはオブジェクト名から先頭アドレスを取り出すことが出来る
オブジェクトのアドレス渡し ポインター変数で渡す オブジェクトが存在している場所を教える. クラス名 * ポインター変数名; で定義する. オブジェクトが存在している場所を教える. オブジェクトの先頭のアドレス オブジェクトはコピーしない どんなに大きくても先頭アドレスだけがコピーされる オブジェクトのアドレスは 単項演算子 & で取り出せる &で取り出した値はアドレスであるのでポインタ変数にしまう. アドレスにも型があるとエラーチェックができる
Objectのアドレス渡し #include <iostream> using namespace std; class samp { int i; public: samp(int n) { i = n; } void set_i(int n) { i = n; } int get_i() { return i; } }; void sqr_it(samp *o){ o->set_i(o->get_i() * o->get_i()); cout << "iのコピーの値は " << o->get_i(); cout << "\n"; } int main(){ samp a(10); sqr_it(&a); cout << "今、main()は変更された: "; cout << a.get_i(); return 0; }
オブジェクトの配列 #include <iostream> using namespace std; class samp { int a; public: void set_a(int n) { a = n; } int get_a() { return a; } }; int main(){ samp ob[4]; //普通に宣言できる int i; for(i=0; i<4; i++) ob[i].set_a(i); for(i=0; i<4; i++) cout << ob[i].get_a( ); cout << "\n"; return 0; }
初期値の設定も同時に出来る class samp { int a; public: samp(int n) { a = n; } #include <iostream> using namespace std; class samp { int a; public: samp(int n) { a = n; } int get_a() { return a; } }; int main(){ samp ob[4] = { -1, -2, -3, -4 }; int i; for(i=0; i<4; i++) cout << ob[i].get_a() << ' '; cout << "\n"; return 0; }
2次元配列も容易 5, 6, 7, 8 }; for(i=0; i<4; i++) { class samp { int a; #include <iostream> using namespace std; class samp { int a; public: samp(int n) { a = n; } int get_a() { return a; } }; int main(){ samp ob[4][2] = { 1, 2, 3, 4, 5, 6, 7, 8 }; int i; for(i=0; i<4; i++) { cout << ob[i][0].get_a() << ' '; cout << ob[i][1].get_a() << "\n"; } cout << "\n"; return 0; }
コンストラクタを使った初期化 samp(1, 2), samp(3, 4), samp(5, 6), samp(7, 8), #include <iostream> using namespace std; class samp { int a, b; public: samp(int n, int m) { a = n; b = m; } int get_a() { return a; } int get_b() { return b; } }; int main(){ samp ob[4][2] = { samp(1, 2), samp(3, 4), samp(5, 6), samp(7, 8), samp(9, 10), samp(11, 12), samp(13, 14), samp(15, 16) int i; for(i=0; i<4; i++) { cout << ob[i][0].get_a() << ' '; cout << ob[i][0].get_b() << "\n"; cout << ob[i][1].get_a() << ' '; cout << ob[i][1].get_b() << "\n"; } cout << "\n"; return 0; }
オブジェクトのポインタ p = ob; // 配列の開始アドレスを取得する int main(){ #include <iostream> using namespace std; class samp { int a, b; public: samp(int n, int m) { a = n; b = m; }; int get_a() { return a; }; int get_b() { return b; }; }; int main(){ samp ob[4] = { samp(1, 2), samp(3, 4), samp(5, 6), samp(7, 8) }; int i; samp * p; p = ob; // 配列の開始アドレスを取得する for(i=0; i<4; i++) { // p ->a = (*p).a cout << p -> get_a( ) << ' '; cout << p -> get_b( ) << "\n"; p + + ; // 次のオブジェクトに進む } cout << "\n"; return 0; }
練習 次のクラスを使用して,2行5列の2次元配列を作成し,配列内の各オブジェクトに任意の初期値を設定しなさい.次に,配列の内容をポインタを用いて表示しなさい. class a_type { double a, b; public: a_type(double x, double y){ a = x; b = y; } void show(){ cout << a << ‘ ‘ << b << “\n”; };
this pointer class inventory { char item [20]; double cost; #include <iostream> #include <cstring> using namespace std; class inventory { char item [20]; double cost; int on_hand; public: inventory(char * i, double c, int o) { strcpy(item, i); cost = c; on_hand = o; } void show(); }; void inventory::show(){ cout << item; cout << ": $" << cost; cout << " 在庫: " << on_hand << "\n"; } int main(){ inventory ob("レンチ", 4.95, 4); ob.show(); return 0; }
this pointer 明示して記述 class inventory { #include <iostream> #include <cstring> using namespace std; class inventory { char item[20]; double cost; int on_hand; public: inventory(char *i, double c, int o) { strcpy(this -> item, i); // thisポインタを this -> cost = c; // 使用してメンバに this -> on_hand = o; // アクセスする } void show(); }; void inventory::show(){ cout << this ->item; // thisポインタを使用してメンバにアクセスする cout << ": $" << this ->cost; cout <<" 在庫:"<< this ->on_hand<<"\n";} int main(){ inventory ob("レンチ", 4.95, 4); ob.show(); return 0; }
New ,delete 演算子 int main(){ int *p; p = new int; // 整数のメモリを確保・割り当てる #include <iostream> using namespace std; int main(){ int *p; p = new int; // 整数のメモリを確保・割り当てる if(!p) { cout << "メモリ割り当てエラー\n"; return 1; } *p = 1000; cout << "pが指している整数型は: " << *p << "\n"; delete p; // メモリを解放する return 0; } mallocにかわる. heap 領域に領域を確保する.
動的オブジェクトを割り当てる class samp { int i, j; public: #include <iostream> using namespace std; class samp { int i, j; public: void set_ij(int a, int b) { i=a; j=b; } int get_product() { return i*j; } }; int main(){ samp *p; p = new samp; // new で オブジェクトを割り当てる if(!p) { cout << "メモリ割り当てエラー\n"; return 1; } p->set_ij(4, 5); cout << "積は: " << p->get_product() << "\n"; return 0; }
動的オブジェクトを割り当てる class samp { int i, j; public: #include <iostream> using namespace std; class samp { int i, j; public: samp(int a, int b) { i=a; j=b; } int get_product() { return i*j; } }; int main(){ samp *p; // オブジェクトを割り当てて初期化する p = new samp(6, 5); if(!p) { cout << "メモリ割り当てエラー\n"; return 1; } cout<<"積は: "<< p->get_product()<<"\n"; delete p; return 0; }
配列のnew, delete p = new int [5]; int main(){ int *p; #include <iostream> using namespace std; int main(){ int *p; // 5つの整数用のメモリを割り当てる p = new int [5]; // 割り当てが成功したことを常に確認する if(!p) { cout << "メモリ割り当てエラー\n"; return 1; } int i; for(i=0; i<5; i++) p[i] = i; for(i=0; i<5; i++) { cout << "整数型p[" << i << "]は: "; cout << p[i] << "\n"; } delete [ ] p; // メモリを解放する return 0; }
オブジェクト配列でのnew,delete #include <iostream> using namespace std; class samp { int i, j; public: void set_ij(int a, int b) { i=a; j=b; } ~samp() { cout << "デストラクタ呼び出し\n"; } int get_product() { return i*j; } }; int main(){ samp *p; int i; p = new samp [10]; // オブジェクト配列を割り当てる if(!p) { cout << "メモリ割り当てエラー\n"; return 1; } for(i=0; i<10; i++) p[i].set_ij(i, i); for(i=0; i<10; i++) { cout << "積 [" << i << "] は: "; cout << p[i].get_product() << "\n"; } delete [] p; return 0; }
参照(reference)&変数 変数の別名(alias)として動作する 用途 関数の引数として参照を渡す. 関数から参照を返す. 独立した参照(非推奨) 効果 関数間でオブジェクトを渡す際,コピーが発生しないため,巨大なオブジェクトを取り扱うプログラムを高速処理可能 左辺で関数が呼べる コピーコンストラクタ,演算子のオーバーロードで利用される.
ポインタ仮引数を使用する場合 #include <iostream> using namespace std; void f(int *n); // ポインタ仮引数を宣言する int main(){ int i = 0; f(&i); // i のアドレスを取り出す&演算 cout << "iの新しい値: " << i << '\n'; return 0; } void f(int *pn){ *pn = 100; // nが指す引数に100を格納する *演算子: ポインタ変数 pn の値=オブジェクトiのアドレスの場所を取り出す. = 取り出したアドレスの処に100を置く.
参照仮引数を使用する場合 void f(int &n); // 参照仮引数を宣言する int main(){ int i = 0; #include <iostream> using namespace std; void f(int &n); // 参照仮引数を宣言する int main(){ int i = 0; f(i); cout << "iの新しい値: " << i << '\n'; return 0; } // f( )関数は参照仮引数 & を使用する void f(int &n) { // int &n = i; と同じ意味 // 次の文では * が必要ない *n=100 // n は i の別名である n = 100; // f()関数を呼び出すのに使用した引数nに100を格納する
参照の例-I void swapargs(int &x, int &y); int main(){ int i, j; #include <iostream> using namespace std; void swapargs(int &x, int &y); int main(){ int i, j; i = 10; j = 19; cout << "i: " << i << ", "; cout << "j: " << j << "\n"; swapargs(i, j); cout << "交換後: "; cout << "j: " << j << "\n"; return 0; } void swapargs(int &x, int &y){ int t; t = x; x = y; y = t; } x , y はi, j の別名なので, i, jの値が交換される.
参照の例-II class myclass { int who; public: myclass(int n) { who = n; #include <iostream> using namespace std; class myclass { int who; public: myclass(int n) { who = n; cout<<"コンストラクタ呼び出し"<<who<< "\n"; } ~myclass() { cout << "デストラクタ呼び出し " << who << "\n"; } int id( ) { return who; } }; void f(myclass &o){ // oを参照によって渡す // .演算子を使用していることに注意 cout << "受け取り " << o.id() << "\n"; } int main(){ myclass x(1); f(x); //fでXはコピーされない.fを抜ける時に //デストラクタ ~myclass は呼ばれない return 0; }
参照を返す関数の効能-I char &array::put(int i){// 配列に情報を格納する class array { #include <iostream> #include <cstdlib> using namespace std; class array { int size; char *p; public: array(int num); ~array() { delete [] p; } char &put(int i); char get(int i); }; array::array(int num){ p = new char [num]; size = num; } char &array::put(int i){// 配列に情報を格納する return p[i]; // p[i]への参照を返す
参照を返す関数の効能-II // Get something from the array. char array::get(int i){ return p[i]; // 文字を返す } int main(){ array a(10); //左辺で関数が呼べる.普通の関数では不可 // 戻り値がアドレスだから出来る. a.put(3) = ‘X’; a.put(2) = ‘R’; cout << a.get(3) << a.get(2); cout << "\n"; return 0;
独立参照 #include <iostream> using namespace std; int main() { int x; int &ref = x; // 独立参照を作成する x = 10; // この2つの文の ref = 10; // 機能は同等である ref = 100; // 100を2回出力する cout << x << ' ' << ref << "\n"; return 0; } refはxの別名である. 使用さえる事は少ない.
次のプロトタイプ宣言を利用して,orderで指定された桁数までnumの桁数を上げるmag()という関数を作成せよ. 練習 次のプロトタイプ宣言を利用して,orderで指定された桁数までnumの桁数を上げるmag()という関数を作成せよ. 例えば,num = 4, order = 2 のとき,関数 mag() が終了したとき num = 400 になるようにせよ. void mag(long &num, long order);
まとめ:オブジェクトは物 物objectの情報classは存在する. 抽象classから具体化classの流れ(派生). 物には内部構造(メンバー)がある. 物objectの中には物objectsがある. 物の中では物が同じように相互に関係している. Classという情報として存在している. 画像,音,物理的,数学的,情報的に存在するものは物: オブジェクトである. Windows, AV関連プログラムは物を扱う. 処理(プログラム)も物としてやり取りする. Applet (application + let(小さい)) 回路も物,HDL,LSI回路,仮想回路
WindowsもCRT画面内の物 myWindow-1 myWindow-3 myWindow-2 progII > myWindows::windows(…){…} Rootはwindow OSでCRT全体. すべてのwindowは,window OSにつながっている. Windos OSにメッセージを送って,それぞれのwindow オブジェクトに対して処理を行う.
Turbo C++によるアプリ例 フリーのIDEであるTurbo C++を利用したアプリの作成例を紹介する. Windows アプリの基本的部品である,Windowやボタン,画像格納領域等は全て開発環境内に準備されているクラスからオブジェクトを生成して利用する. 上記クラスの具体的な実装方法は知る必要が無い. 上記クラスの利用方法,すなわちメンバ変数やメンバ関数,継承関係などはヘルプやWEBで検索可能.