前回の構造体の説明から、ポインタ渡しの説明を行った後、同様のプログラムをC++で書き換えることで、classやメソッドなどの説明を行う。
構造体でオブジェクト指向もどき
例えば、名前と電話番号の構造体で処理を記述する場合、 以下の様な記載を行うことで、データ設計者とデータ実装者で分けて 仕事ができることを説明。
struct Person {
char name[10] ;
int phone ;
} ;
void readPerson( struct Person* p ) {
// ポインタの参照で表記
scanf("%s%d" ,
(*p).name , &(*p).phone ) ;
}
void printPerson( struct Person* p ) {
// アロー演算子で表記
printf( "%s %d¥n" ,
p->name , p->phone ) ;
}
void main() {
struct Person table[ 10 ] ;
for( int i = 0 ; i < 10 ; i++ ) {
readPerson( &table[i] ) ;
printPerson( &table[i] ) ;
}
}
このプログラムの書き方では、mainの中を読むだけもで、 データ入力とデータ出力を行うことはある程度理解できる。 この時、データ構造の中身を知らなくてもプログラムが理解でき、 データ実装者はプログラムを記述できる。これをデータ構造の隠蔽化という。 一方、readPerson()や、printPerson()という関数の中身についても、 入力・出力の方法をどうするのか知らなくても、 関数名から動作は推測できプログラムも書ける。 これを手続きの隠蔽化という。
C++のクラスで表現
上記のプログラムをそのままC++に書き直すと以下のようになる。
class Person {
private: // クラス外からアクセスできない部分
char name[10] ; // メンバーの宣言
int phone ;
public: // クラス外から使える部分
void read() { // メソッドの宣言
// pのように対象のオブジェクトを明記する必要はない。
scanf( "%s%d" , name , &phone ) ;
}
void print() {
printf( "%s %d¥n" , name , phone ) ;
}
} ;
void main() {
Person table[ 10 ] ;
for( int i = 0 ; i < 10 ; i++ ) {
table[i].read() ; // メソッドの呼び出し
table[i].print() ; // オブジェクト.メソッド()
}
// 文法エラーの例
printf( "%d¥n" , table[0].phone ) ;
// phoneはprivateなので参照できない。
}
用語の解説:C++のプログラムでは、データ構造とデータの処理を、並行しながら記述する。 データ構造に対する処理は、メソッド(method)と呼ばれる。 データ構造とメソッドを同時に記載したものは、クラス(class)と呼ぶ。 そのclassに対し、具体的な値や記憶域が割り当てられたものをオブジェクト(object)と呼ぶ。
コンストラクタとデストラクタ
プログラムを記述する場合、そのデータ構造を使うにあたり、 初期値が代入を忘れその値を参照すると、予想外の動きの原因となってしまうことが多い。
そこでオブジェクト指向では、データ構造の初期化手続き(同様に処理が終わった後の事後手続き)を 明確に記載するための初期化のコンストラクタ(と事後処理はデストラクタ)の文法がある。
class Person {
private:
char name[10] ;
int phone ;
public:
Person( char s[] , int tel ) { // コンストラクタ
strcpy( name , s ) ;
phone = tel ;
}
void print() {
printf( "%s %d¥n" , name , phone ) ;
}
} ;
void main() {
// オブジェクト宣言とコンストラクタでの初期化
Person saitoh( "tsaitoh" , 272925 ) ;
saitoh.print() ;
}
コンストラクタを宣言する場合には、返り値無しのクラス名を関数名として記述する。 デストラクタを宣言する場合には、先頭に「~」をつけたクラス名で無引数で記述する。 簡単な例として、文字列をヒープ領域に保存する処理を示す。
// デストラクタが便利な例
class String {
private:
char* str ;
public:
// コンストラクタ
String( char*s ) {
// ヒープメモリ上に文字列を確保
str = (char*)malloc( strlen( s ) + 1 ) ;
strcpy( str , s ) ;
}
// デストラクタ
~String() {
free( str ) ; // freeしないとメモリーリークになる。
}
void print() {
printf( "%s" , str ) ;
}
} ;
void main() {
String s( "abcdefg" ) ;
s.print() ;
} // mainを抜ける段階でsは不要となる。
// ここで自動的にデストラクタ呼び出し~String()をしてくれる。