構造体・クラス

プログラミング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 は間違い
– 後で,クラスやオブジェクトを追
加しても,影響が限定的である.