プログラミングII 第3回 構造体・クラス 田向 理解度チェック #include <iostream> 何処に誤りがあるか? using namespace std; class myclass { int i; public: } int main( ) { myclass ob; ob.i = 10; } 変数からオブジェクト • classで定義・宣言された 何か構造をもった物はクラス名で定 義される. • このクラス名で定義・宣言された変 数をオブジェクトという. – クラス定義とオブジェクト定義とは 異なる. – クラス名で指定された構造と同じ 構造をもった物.インスタンス,実 体とも言う. – 実際の値が置けるメモリ領域が 存在している. 分割ファイル • プログラムが大 → ファイルが大 →ファイルを分割:ファイル全体でプロ グラム(Project)となる. • ファイル内でグローバル,ローカル 変数? Project全体で? プログラムが巨大になり多人数で設計 ヘッダーファイルだけでは 全体が見えない で バグが増大 影響を限定的にする必要がある 変数・関数の定義領域:スコープ • 構造体,関数が沢山になる. – 同じ関数名で異なる機能,同じ 変数名で別の意味等混乱が生じ る • 定義されている部分は何処まで? global ,local , static 定義域: scope を組織的に制御す る必要がある -Cの復習-変数:メモリの使い方 • 変数→ アドレス: 変数の値 ( int x; ) • 配列→ 配列の最初のアドレス+添え字: 配列変数の値 int *x; ポインターを使ってアドレス計算 int y[10]; 配列を宣言 共に配列に必要な領域が必要 メモリ空間 名前表 int *y; 名前1 y= (int *) malloc(10*4) x 型 アドレス1 名前2 y[10] 型 アドレス2 •配列(同一の型の要 素): –0番目要素: 属性(int で区別) –1番目要素: x座標 –2番目要素: y座標 Cの構造体 • struct structname{ – Field-type1 field name1 – Field-type2 field name2 } variable name; 型情報も同時に持つ variable name 名前表 struct name 構造表 ベースアドレス fieldname1 filedname2 インスタンス表 (実体表) 構造体の例 • Struct bin{ char name[30]; int quantity; int cost; } printer_cabel_box; bin: 袋,箱 構造 部品の名前 箱の中に幾つ入れるか 部品1個の値段 箱として作った物の名前 構造体の名前 I 構造体名で変数を定義で きる struct bin terminal_cable_box; 構造体名を省略できる • struct { char name[30]; int quantity; int cost; } printer_cabel_box; 別の箱を定義する. 箱という構造の名前が 無い. 無名の何か 構造物は無名, 物には名前がある メモリ内の配置を定義している 構造体の名前 II • 変数名も省略で きる struct bin{ char name[30]; int quantity; int cost; }; struct bin terminal_cable_box; 何か構造がある雛 型に型名 をつける. その名前は bin その中身の構造は 以下のとお り その構造が 構造名binであるものと 同じである物. その名前は....である. ほとんどの物は構造を持つ. 多用される. 多種多様な使われ方がされ, この構造の概念が発展した. class: その何かをクラスと呼ぶ struct stack{ char name[30]; int count; int data[STACK_SIZE]; }; class stack{ char name[30]; int count; int data[STACK_SIZE]; }; これだけでは 何も変わらない class stack{ private: //非公開 char name[30]; int count; int data[STACK_SIZE]; public: //公開 ………………. ………………. }; structからclassへの拡張 • スコープ機能の強化: クラス名でスコープ! • データ構造にさらに関数も加える. – クラスに関係するだけに注目する. – 全ての物はどれかのクラスに繋 がっている. • メンバー変数,メンバー関数 – クラス内の物はどのクラスか区別 する必要がない. • メンバー関数のコードを含む. メンバー関数とスコープ演算子:: //xxx.h class stack1{ class stack2 char name[30]; { int count; int data[STACK_SIZE]; int count; int data[STACK_SIZE*2]; … // メンバー関数 void f1(int, int ); void f1(int, in); … ………………. }; } 外部から見えるところを纏めて ヘッダー ファイルに書く メンバー関数とスコープ演算子:: Stack1のf1の実部 #include “xxx.h” void stack1::f1(int x, int y){ count = data[x ] +data[ y]; count++; } Stack2のf1の実部 stack2 :: f1(int x, int y){ … count= a; } •Stackクラスのメンバー関数f1はメンバー変数count をf1の記述内部で自由にアクセスできる. •他のクラスのメンバーは見えない. →プログラムが作れない. → 一部,見れるようにする. Public: 見えないものは private: スコープのヘッダファイルを作成 // stack.h const int STACK_SIZE = 100; class stack{ private: int count; int data[STACK_SIZE]; public: void init(void); void push( const int item ); int pop ( void ); }; 特殊な関数:constructor class stack{ private: int count; int stck[STACK_SIZE]; stack2* data2; public: stack( ) ; //constructor ~ stack( ); //destructor void init(void); void push(char ch); char pop( ); }; クラス名と同じ名前を持ち 戻り値を持たない. オブジェクトが生成される たびに呼出される. する事: クラスが使用するメモリ領域 を確保する. 初期値を設定する. (関係付けの)リンクを張る. constructor と destructorの例 // スタックを初期化する stack::stack(){ cout << "スタックを生成する\n"; tos = 0; } // 文字をプッシュする void stack::push(char ch){ if(tos==SIZE) { cout << "スタックは一杯です\n"; return; } stck[tos] = ch; tos++; } // 文字をポップする char stack::pop(){ if(tos==0) { cout << "スタックは空です\n"; return 0; // スタックが空の場合はヌルを返す } tos--; return stck[tos]; } int main(){ // 自動的に初期化される2つのスタックを作成する stack s1, s2; int i; s1.push('a'); s2.push('x'); s1.push('b'); s2.push('y'); s1.push('c'); s2.push('z'); for(i=0; i<3; i++) cout << “s1をポップする: ” << s1.pop() << "\n"; for(i=0; i<3; i++) cout << “s2をポップする: ” << s2.pop() << "\n"; return 0; } Constructorが成功のカギ • このアイデアが無いと,C++はアイ デア倒れで終わった. • メモリ領域の制御を明示的に行う. • デバッグがしやすい. • 実際にクラスとコンストラクターを作 成する. オブジェクトの使い方 • 今までは,作り方; class objA{ private宣言部 – 変数,関数 public: public宣言部 – 変数,関数 } • その使い方は? objA obj1; • 普通の変数と同じように扱える – 同じように計算が出来る事 • 値の代入や取り出し – Obj1.a=A; A=Obj1.a; – Obj1=obj2; – Obj1=obj2*obj3; など • メンバー関数はメンバー変数 (private/publicに関係なく)に自由 にアクセスできる オブジェクト代入の例I #include <iostream> using namespace std; class myclass { int a, b; public: void set(int i, int j) { a = i; b = j; } void show() { cout << a << ' ' << b <<"\n"; } }; int main(){ myclass o1, o2; o1.set(10, 4); // o1をo2に代入する o2 = o1; o1.show(); o2.show(); return 0; } オブジェクト代入の例II O2.a=O1.a; O2.b=O1.b; を実行する. • O2=O1はこの文を生成するのか? = オペレータを呼び出すはず • O1とO2は同じ値になるけれど,別のオブ ジェクト このあと,O1の値を変えてもO2の値は変 らない • コンパイラーが影で色々な演算を生成し ている – 最低限の記述を簡潔に書けば良い – オブジェクトの中を考えずに扱うことが できる このプログラムには誤りがある #include <iostream> using namespace std; class myclass { int a, b; public: void set(int i, int j) { a = i; b = j; } void show() { cout << a << ' ' << b << "\n"; }}; class yourclass { int a, b; public: void set(int i, int j) { a = i; b = j; } void show() { cout << a << ' ' << b << "\n"; }}; int main(){ myclass o1; yourclass o2; o1.set(10, 4); o2 = o1; o1.show(); return 0;} o2.show(); 間違いの理由 • 同じ型のオブジェクトでなければ代 入できない. – 違う場合にはどう処置していいか 情報が無い • どの代入演算子(=)を呼べばよいは 判らない – 型(クラス名)チェックで避けるし かない • 間違って違うものを代入することは 無い. • 型チェックはプログラムを書く上で 強力なサポーター Objectの代入I #include <iostream> using namespace std; #define SIZE 10 // 文字を格納するstackクラスを宣言する class stack { char stck[SIZE]; // スタック領域を確保する int tos; // スタックの先頭の索引 public: stack(); // コンストラクタ void push(char ch); char pop(); }; stack::stack(){ cout << "スタックを生成する\n"; tos = 0; } void stack::push(char ch){ if(tos==SIZE) { cout << "スタックは一杯です\n"; return; } stck[tos] = ch; tos++; } Objectの代入II char stack::pop(){ if(tos==0) { cout << "スタックは空です\n"; return 0; } tos--; return stck[tos]; } int main(){ stack s1, s2; int i; s1.push('a'); s1.push('b'); s1.push('c'); s2 = s1; // これで、s1とs2は同じになる for(i=0; i<3; i++) cout << "s1をポップする: " << s1.pop() << "\n"; for(i=0; i<3; i++) cout << "s2をポップする: " << s2.pop() << "\n"; return 0;} コンパイラーがしている事 • 自動的に代入文を実行? • S1のバイナリコードをそのままS2に コピーする. s1 ベースアドレス f I E L D N A M E 1 f I E L D N E M E 2 s2 ベースアドレス f I E L D N A M E 1 f I E L D N E M E 2 オブジェクト:まとめ • 手続き型記述で書きやすさ,Cの資 産を継承 • class: 配列,構造体を強化 • new: ポインター変数とalloc()を強化 • Scope”::”スコープ機能,隠蔽を強化 デバッグ機能が強化できる. – 関数の引数のチェックを厳しくす ることが出来る • あと,役に立つもの,便利なものは 何でも試みる. – 演算子のオーバーローディング – クラスの継承:派生(inheritance) – テンプレート このプログラムにはエラーがあるI #include <iostream> #include <cstring> #include <cstdlib> using namespace std; class strtype { char *p; int len; public: strtype(char *ptr); ~strtype(); void show(); }; strtype::strtype(char *ptr){ len = strlen(ptr); p = (char *) malloc(len+1); if(!p) { cout << "メモリ割り当てエラー\n"; exit(1); } strcpy(p, ptr); } strtype::~strtype(){ cout << "pを解放する \n"; free(p); } このプログラムにはエラー..II void strtype::show(){ cout << p << " - 長さ: " << len; cout << "\n";} int main(){ strtype s1("This is a test."), s2("I like C++."); s1.show(); s2.show(); s2 = s1; s1.show(); s2.show(); return 0; } このプログラムにはエラー..III • 結果: • システムエラーとなる. – char* P が問題. – ポインター値の代入が問題 s2.p = s1.p p の値はmalloc()で与えられた アドレス値.s1 とs2 で違う場所 Mainの実行が終わって,メモリ領 域を開放するとき,同じアドレス なので,s1,s2で2度開放されるた め,システム・エラーとなる. クラス・オブジェクトの大きな利点 • オブジェクトの型を常に考えるくせ が出来てくる. – 先ずオブジェクトを設計する. – 計算・演算が良いか悪いかと同 じように正しくデータを扱っている かどうか. – 今までは,データの区別が無 かったので,間違いが多かった. • 10s,10min,100hourの区別が無い • myMoney =yourMoney は間違い – 後で,クラスやオブジェクトを追 加しても,影響が限定的である.
© Copyright 2024 ExpyDoc