C言語の研究・C++編2
第2章 クラスの応用
前章を見る限りでは、クラスを使うメリットなど全く見えてこないだろう。
ここでは、クラスを使うメリットについてについて見て行こう。
1 派生クラス
前章の最終例題は「2人分の学生のデータ、学生番号、名前、4科目の得点の各データを構造体として与え、各学生の4科目の平均点を求めて、学生番号、名前、平均点を表示するプログラム」というものであった。
今ここで新たに、評価というメンバを追加して、「90点以上は'A'、80点以上は'B'、それ未満は'C'」とするメンバ関数を追加したい場合、どうすればいいのだろうか。
当然、seisekiクラスのメンバに評価を示すhyokaと、評価を求めるget_hyoka関数を追加する方法でもよいだろうが、それではあまりに能がない。
そこで、ここでは前の例題で作ったseisekiというクラスをほぼそのままの形で残しておいて、これを継承する新しいクラス、new_seisekiを派生させる方式を採用しよう。
それでは順番に見て行こう。まずヘッダーファイルを取り出して、これに次のように1行書き加えよう。
class seiseki {
public:
seiseki(int, char *,
int * ,double ave = 0.0); //コンストラクタ
double get_ave();
void print_out();
~seiseki(); //デストラクタ
friend
class new_seiseki; //new_seisekiクラスをフレンドとする
private:
int bango;
char name[10];
int ten[4];
double ave;
};
seiseki::seiseki(int bango, char *name, int *p, double ave)
{
int i;
seiseki::bango = bango;
strcpy(seiseki::name, name);
for(i=0; i < 4; i++) seiseki::ten[i] =
*(p+i);
seiseki::ave = get_ave();
printf("seisekiクラス%sのコンストラクタ\n",name);
//確認用
}
seiseki::~seiseki()
{
printf("seisekiクラス%sのデストラクタ\n",name);
//確認用
}
double seiseki::get_ave()
{
int i;
int ave = 0;
for(i=0; i < 4; i++) ave
+= *(ten +i);
return (double)ave/4.0;
}
void seiseki::print_out(void)
{
printf("%d %s
%5.2f\n",bango, name, ave);
}
ご覧のように赤字の部分だけ追加した。この説明についてはまた後ですることとしよう。
次にプログラムファイルでsesekiクラスを継承する新しいクラスnew_seisekiを宣言して、実行することとしよう。
とりあえず今は先にプログラムファイルを以下のように直して実行させてみよう。
実行結果を踏まえて説明に入ることとしよう。
#include <stdio.h>
#include <string.h>
#include "getave7.h"
class new_seiseki : public seiseki{ //派生クラスの宣言
public:
new_seiseki(int, char
*, int *,double ave=0, char hyoka = ' '); //派生クラスのコンストラクタ
char get_hyoka();
~new_seiseki(); //派生クラスのデストラクタ
void print_out();
private:
char hyoka; //新メンバの評価
};
//派生クラスのコンストラクタによる初期値設定
new_seiseki::new_seiseki(int bango, char *name, int *p, double
ave, char hyoka): seiseki(bango, name, p
,ave)
{
new_seiseki::hyoka =
get_hyoka();
printf("new_seisekiクラス%sのコンストラクタ\n",name);
//確認用
}
new_seiseki::~new_seiseki() //派生クラスのデストラクタ
{
printf("new_seisekiクラス%sのデストラクタ\n",name);
//確認用
}
char new_seiseki::get_hyoka() //評価を求める関数
{
char h;
if(ave >= 90.0) h = 'A';
else if(ave >= 80.0) h = 'B';
else h = 'C';
return h;
}
void new_seiseki::print_out(void) //表示する関数
{
printf("%d %s
%5.2f %c\n",bango, name, ave, hyoka);
}
void main()
{
int ten[2][4] ={
{97,95,98,99},
{99,100,100,100}
};
int *p1= ten[0];
int *p2 = ten[1];
new_seiseki akira (1001,"AKIRA",
p1); //オブジェクトakiraの作成
new_seiseki pell (1002,"Pell",p2 ); //オブジェクトpellの作成
printf("-----Result-----\n");
printf("番号 名前
得点 評価\n");
akira.print_out();
pell.print_out();
printf("----------------\n");
}
どうだろうか、コンストラクタ関数やデストラクタ関数に書いておいた確認用行がかなり表示されたことだろう。それでは説明に入ろう。
(1) クラスの派生(継承)
上記の例で変更点はヘッダーファイルは、赤字のfriend class・・・の1行、これは次項で説明するとして、プログラムファイルの方にちょっと目を向けてみよう。
今回追加した関数は評価を求める関数get_hyokaだけだ。これは戻り値として文字定数を指定している。
後は全部前のクラスをそのまま利用したという訳である。このnew_seisekiというクラスをseisekiクラスの派生クラスという。あるいは、new_seisekiクラスがseisekiクラスを継承したという。
この場合seisekiクラスは基本クラスと呼ばれる。
この例は基本クラスseisekiをほとんどそのまま利用することにした。だからnew_seisekiクラスで新たに追加するのは、評価を求める処理と、これらを表示する処理だけである。
それではクラスの派生方法を見てみよう。
class new_seiseki : public seiseki {
// 以下、メンバの定義をする
}
と宣言しているね。この派生クラスの直後につけるセミコロンはnew_seisekiというクラスがseisekiクラスから派生させたという意味を表している。
この派生クラスnew_seisekもコンストラクタ関数で初期値設定をすることとした。
そのために新たなメンバである評価・hyokaをプライベートなメンバとし、それを求める関数get_hyokaをこのクラス定義の中で宣言しておいた。
また、評価も含めて表示するのでprint_outという関数の中身をちょっと変えて新たに定義しメンバ関数とした。それとデストラクタ関数も忘れないように定義しておこう。
さて、コンストラクタ関数の定義方法を見てみよう。
new_seiseki::new_seiseki(int bango, char *name,
int *p, double ave, char hyoka):
seiseki(bango, name, p ,ave)
{
new_seiseki::hyoka =
get_hyoka();
printf("new_seisekiクラス%sのコンストラクタ\n",name);
//確認用
}
コンストラクタ関数の派生の方法もクラス定義の場合と同じだ。セミコロンの後に基本クラスのコンストラクタ関数を書いておくことだ。
派生クラスのコンストラクタ関数の引数は基本クラスの分も含めて、必要とするメンバは全部書いておかなければならないのがちょっと面倒くさい。
しかもこのメンバの並びには十分注意してほしい。
最初に基本クラスのメンバを基本クラスのコンストラクタ関数の引数順に並べて、新たなる引数を末尾に追加すること。
また、コンストラクタ関数のプロトタイプでdouble ave = 0.0と引数のデフォルト値を代入させているのは基本クラスのコンストラクタ関数がこうなっているからで、これを継承するためにはこうする必要がある。
しかし、番号、名前、得点配列への代入や平均点を求める処理については、基本クラスの各処理やメンバ関数get_aveがやってくれるので楽である。
これらのメンバを派生クラスのメンバ関数などで初期化する手間が省けた。これらを基本クラスに任せるためにわさわざセミコロンをつけて継承したたというわけだ。
だから、新たなるメンバである評価hyokaを求める処理としての関数get_aveを追加するだけでいい。
また派生クラスについては後からも出てくるので先を急ごう。
(2) フレンドクラス
上記の例でヘッダファイルを見てみよう。赤字で
friend class new_seiseki;
と書いているが一体これは何だろう。これを//で注釈文としてコンパイルしてもらうとよく分かると思うが、派生クラスのnew_seisekiから基本クラスのプライベートメンバである番号、名前、得点配列、平均点にアクセスしようとするとコンパイラちゃんはエラーを発することだろう。
エラーを発するのは当然だ、わさわざ他からのアクセスを避けるためprivateで宣言したからだ。
しかしこうなると派生クラスのnew_seisekiは平均点から評価を求めることも、これらを表示することさえ出来なくなってしまう。
そこで、new_seisekiというクラスにアクセスを許可するという意味で最初friendとしてプライベートメンバにアクセスを許可するクラス名をclass以下に指定しておく。
さしずめ、new_seisekiクラスはseisekiクラスのフレンドとでもいうことだろうか。
ただ、このfriendの使い方はC++コンパイラによって一定ではないので注意してほしい。
2 newとdelete演算子
派生・継承はさて置いて、C++の実行時メモリ確保の便利さをここで見ていこう。
サンプルとして今回はコンストラクタ関数で初期値を与えるかわりに、キーボードから学生の番号、名前、4科目の得点を入力してもらって、平均点と評価を表示するプログラムを考えてみよう。
構造体の代わりにクラスを使い、クラスのポインタを宣言する方法でやってみよう。
#include <stdio.h>
#include <string.h>
class akr_seiseki{ //クラスの宣言
public:
akr_seiseki(); //クラスのコンストラクタ〜初期値はなし
void get_data();
double get_ave();
char get_hyoka();
void print_out();
~akr_seiseki(); //デストラクタ
private:
int bango;
char name[10];
int ten[4];
double ave;
char hyoka;
};
akr_seiseki::akr_seiseki()
{
printf("akr_seisekiクラスのコンストラクタ\n");
//確認用
}
akr_seiseki::~akr_seiseki()
{
printf("akr_seisekiクラス%sのデストラクタ\n",name);
//確認用
}
void akr_seiseki::get_data() //データをキーボードから入力する
{
int i;
printf("番号を入力してください
:");
scanf("%d",&bango);
printf("名前を入力してください
:");
scanf("%s",name);
for(i=0; i < 4; i++){
printf("得点を入力してください(%d
/ 4) : ",i+1);
scanf("%d",&ten[i]);
}
ave = get_ave();
hyoka = get_hyoka();
}
double akr_seiseki::get_ave()
{
int i;
int ave = 0;
for(i=0; i < 4; i++) ave
+= ten[i];
return (double)ave/4.0;
}
void akr_seiseki::print_out(void)
{
printf("%d %s
%5.2f %c\n",bango, name, ave, hyoka);
}
char akr_seiseki::get_hyoka()
{
char h;
if(ave >= 90.0) h = 'A';
else if(ave >= 80.0) h = 'B';
else h = 'C';
return h;
}
void main()
{
akr_seiseki akira; //オブジェクトakiraの作成
akr_seiseki *p = &akira; //オブジェクトへのポインタ
p->get_data();
p->get_ave();
p->get_hyoka();
printf("番号 名前
得点 評価\n");
p->print_out();
}
いかがだろうか。何か懐かしい感じがする例題だった。
ここではまずakr_sesekiクラスのオブジェクトakiraを宣言し、これにポインタを設定して、コンストラクタに任せることなくこちらから各メンバ関数を指示して結果を表示させたわけである。
今度は、最初からクラスへのポインタを宣言し、実体であるオブジェクトは宣言・定義しないが、実行時にメモリ領域を確保する方法をとってみよう。
このためには以前習得した標準ライブラリ関数のmallocを使う必要がある。
これは次のように書ける。
#include <stdio.h>
#include <stdlib.h>
class akr_seiseki{ //クラスの宣言
public:
akr_seiseki(); //クラスのコンストラクタ
void get_data();
double get_ave();
char get_hyoka();
void print_out();
~akr_seiseki(); //デストラクタ
private:
int bango;
char name[10];
int ten[4];
double ave;
char hyoka;
};
akr_seiseki::akr_seiseki()
{
printf("akr_seisekiクラスのコンストラクタ\n");
//確認用
}
akr_seiseki::~akr_seiseki() //派生クラスのデストラクタ
{
printf("akr_seisekiクラス%sのデストラクタ\n",name);
//確認用
}
void akr_seiseki::get_data()
{
int i;
printf("番号を入力してください
:");
scanf("%d",&bango);
printf("名前を入力してください
:");
scanf("%s",name);
for(i=0; i < 4; i++){
printf("得点を入力してください(%d
/ 4) : ",i+1);
scanf("%d",&ten[i]);
}
ave = get_ave();
hyoka = get_hyoka();
}
double akr_seiseki::get_ave()
{
int i;
int ave = 0;
for(i=0; i < 4; i++) ave
+= ten[i];
return (double)ave/4.0;
}
void akr_seiseki::print_out(void)
{
printf("%d %s
%5.2f %c\n",bango, name, ave, hyoka);
}
char akr_seiseki::get_hyoka() //評価を求める関数
{
char h;
if(ave >= 90.0) h = 'A';
else if(ave >= 80.0) h = 'B';
else h = 'C';
return h;
}
void main()
{
akr_seiseki *p; //オブジェクトへのポインタ
p = (akr_seiseki
*)malloc(sizeof(akr_seiseki)); //オブジェクトの領域の確保
p->get_data();
p->get_ave();
p->get_hyoka();
printf("番号 名前
得点 評価\n");
p->print_out();
free(p); //領域の開放
}
malloc関数を使うために標準ライブラリstdlib.hをインクルードしておかねばならないのはご存知のとおりだ。
これを実行してもらうと、コンストラクタ関数とデストラクタ関数の両方ともにその確認用として挿入しているprintfの文が表示されていないのにお気づきになった方もおられると思う。
これはとりもなおさず、コンストラクタ関数とデストラクタ関数が全く実行されていないことを意味する。
次にC++独自の実行時メモリ領域確保をする関数・newと領域開放をする関数・deleteを見ていこう。
#include <stdio.h>
class akr_seiseki{ //クラスの宣言
public:
akr_seiseki(); //クラスのコンストラクタ
void get_data();
double get_ave();
char get_hyoka();
void print_out();
~akr_seiseki(); //デストラクタ
private:
int bango;
char name[10];
int ten[4];
double ave;
char hyoka;
};
akr_seiseki::akr_seiseki()
{
printf("akr_seisekiクラスのコンストラクタ\n");
//確認用
}
akr_seiseki::~akr_seiseki() //派生クラスのデストラクタ
{
printf("akr_seisekiクラス%sのデストラクタ\n",name);
//確認用
}
void akr_seiseki::get_data()
{
int i;
printf("番号を入力してください
:");
scanf("%d",&bango);
printf("名前を入力してください
:");
scanf("%s",name);
for(i=0; i < 4; i++){
printf("得点を入力してください(%d
/ 4) : ",i+1);
scanf("%d",&ten[i]);
}
ave = get_ave();
hyoka = get_hyoka();
}
double akr_seiseki::get_ave()
{
int i;
int ave = 0;
for(i=0; i < 4; i++) ave
+= ten[i];
return (double)ave/4.0;
}
void akr_seiseki::print_out(void)
{
printf("%d %s
%5.2f %c\n",bango, name, ave, hyoka);
}
char akr_seiseki::get_hyoka() //評価を求める関数
{
char h;
if(ave >= 90.0) h = 'A';
else if(ave >= 80.0) h = 'B';
else h = 'C';
return h;
}
void main()
{
akr_seiseki *p; //オブジェクトへのポインタ
p = new
akr_seiseki; //オブジェクトの領域の確保new
p->get_data();
p->get_ave();
p->get_hyoka();
printf("番号 名前
得点 評価\n");
p->print_out();
delete(p); //領域の開放delete
}
いかがだろうか、malloc関数のようにキャストさせる必要もないし、sizeof関数を使って領域の大きさを計算させる必要もない。簡単に領域を確保できた。
deleteの使い方はfreeと同様であるが、標準ライブラstdlib.hを取り込んでおく必要もない。
しかもびっくりしたのは、コンストラクタ関数とデストラクタ関数のそれぞれのprintfの文が実行されているということだ。
ということは、領域を確保すると同時にコンストラクタ関数で初期値設定することも可能ということを意味する。
それではここでnewとdelete演算子を使用して、コンストラクタ関数で先の例題を初期化してみよう。
今回はちょっと工夫して、成績結果は構造体として、その構造体の各メンバへのポインタをコンストラクタ関数への引数としよう。
#include <stdio.h>
#include <string.h>
class akr_seiseki{ //クラスの宣言
public:
akr_seiseki(int, char
*, int *); //クラスのコンストラクタ
double get_ave();
char get_hyoka();
void print_out();
~akr_seiseki(); //デストラクタ
private:
int bango;
char name[10];
int ten[4];
double ave;
char hyoka;
};
akr_seiseki::akr_seiseki(int a, char *b, int *c)
{
int i;
bango = a;
strcpy(name, b);
for(i=0; i < 4; i++) ten[i] = *(c+i);
ave = get_ave();
hyoka = get_hyoka();
printf("akr_seisekiクラスのコンストラクタ\n");
//確認用
}
akr_seiseki::~akr_seiseki() //派生クラスのデストラクタ
{
printf("akr_seisekiクラス%sのデストラクタ\n",name);
//確認用
}
double akr_seiseki::get_ave()
{
int i;
int ave = 0;
for(i=0; i < 4; i++) ave
+= ten[i];
return (double)ave/4.0;
}
void akr_seiseki::print_out(void)
{
printf("%d %s
%5.2f %c\n",bango, name, ave, hyoka);
}
char akr_seiseki::get_hyoka() //評価を求める関数
{
char h;
if(ave >= 90.0) h = 'A';
else if(ave >= 80.0) h = 'B';
else h = 'C';
return h;
}
void main()
{
typedef struct kekka{ //結果成績の構造体を宣言
int bango;
char name[10];
int ten[4];
} DATA;
DATA akira = { 1234, "Akira", 99,
88,77,66};
DATA *pk = &akira;
akr_seiseki *p; //オブジェクトへのポインタ
//オブジェクトの初期値の設定
p = new akr_seiseki(pk->bango, pk->name,
&(pk->ten[0]));
printf("\n-----Result-----\n");
printf("番号 名前
得点 評価\n");
p->print_out();
printf("----------------\n\n
");
delete(p); //領域の開放delete
}
どうだろうか、構造体とクラスの競演だった。
new演算子によって実行時領域確保した場合は、コンストラクタ関数やデストラクタ関数が見事に用いられていることが分かっていただけただろうか。
このように、newやdeleteはクラスのために存在すると言っても過言ではない。
3 多重継承
ここでまた派生・継承に戻ろう。あっちこっちへ飛んで申し訳ないが、new演算子を先に知ってほしかったからである。
さてここでは、C++では複数の基本クラスから新たにクラスを派生することができることを習得しておこう。
これはかなり強力なオブジェクト指向プログラミングの支援機能といえるだろう。
今回のサンプルは、computer_screenというクラスとmother_boardという2つのクラスが既に存在しているものと仮定して、これらのメンバ関数を継承するcomputerという新たなるクラスを派生させよう。
また今回は基本クラスのプライベートなメンバには直接アクセスすることなく、インターフェイス関数を通じてアクセスすることとしよう。したがってfriendクラスの指定は不要である。
それではちょっと長いがさっそく見てみよう。
#include <stdio.h>
#include <string.h>
//基本クラス
class computer_screen{
public:
computer_screen(char *, int, int ,int);
void show_screen();
~computer_screen();
private:
char type[32];
int colors;
int x_resolution;
int y_resolution;
};
//基本クラス
computer_screen::computer_screen(char *type, int colors,
int x_res, int y_res)
{
strcpy(computer_screen::type, type);
computer_screen::colors = colors;
computer_screen::x_resolution = x_res;
computer_screen::y_resolution = y_res;
}
void computer_screen::show_screen()
{
printf("スクリーンタイプ :
%s\n",type);
printf("カラー数 : %dbpp\n",
colors);
printf("解像度 : %d ×
%d\n",x_resolution, y_resolution);
}
computer_screen::~computer_screen()
{
}
class mother_board {
public:
mother_board(char *, int, int);
void show_mother_board();
~mother_board();
private:
char processor[10];
int speed;
int ram;
};
mother_board::mother_board(char *processor, int speed, int ram)
{
strcpy(mother_board::processor, processor);
mother_board::speed = speed;
mother_board::ram = ram;
}
void mother_board::show_mother_board()
{
printf("プロセッサ :
%s\n",processor);
printf("スピード : %dMHz\n",
speed);
printf("メモリ : %dMB\n", ram);
}
mother_board::~mother_board()
{
}
//2つの基本クラスからの派生クラス
class computer : public
computer_screen, public
mother_board{
public:
computer(char *, int, int, char *, int,
int, int, char *, int, int);
void show_computer();
~computer();
private:
char name[64];
int hard_disk;
int cd;
};
computer::computer(char *name, int hard_disk, int cd,
char *screen, int colors, int x_res, int y_res,
char *processor, int speed, int ram)
:computer_screen(screen, colors, x_res, y_res),
mother_board(processor, speed, ram)
{
strcpy(computer::name, name);
computer::hard_disk = hard_disk;
computer::cd = cd;
}
computer::~computer()
{
}
void computer::show_computer()
{
printf("名前 : %s\n", name);
printf("-----性能-----\n");
printf("ハードディスク :
%dGB\n", hard_disk);
printf("CD : %d倍速\n", cd);
show_mother_board();
show_screen();
}
void main()
{
computer *p; //オブジェクトへのポインタの設定
//領域の確保とコンストラクタ関数で初期値設定
p = new computer("Computer Pell", 6,
32,
"SVGA", 16,
800, 600, "PentiumU", 300 ,128);
p->show_computer();
delete(p); //領域の開放
}
さてどうだろうか。前回の派生と同様にクラスの宣言場所で継承するクラスをセミコロン:の後から書くが、基本クラスが複数の場合はカンマ,で区切る必要がある。
この場合、一番苦労したのが派生クラスcomputerのコンストラクタ関数で、継承するメンバの並びや型をそれぞれのクラス宣言と間違わないように、やはりセミコロン以下に書いて行き、基本クラス間はカンマでくくっておく必要がある。
クラス宣言内のプロトタイプはまだいいが、クラス宣言外でのコンストラクタ関数の実装はかなり面倒くさい。
なお、派生クラスのコンストラクタ関数の引数に自分のクラスのメンバを入れる場所は、基本クラスの並びさえ合っていれば最初でも最後でもいいが、main関数で初期値を与える時にその並びを間違わないようにしよう。
今回の例ではかなりメンバ数もint型のメンバも多く、どれがどの値かを間違わないようにするのに苦労した。
しかし、この複数の基本クラスからの派生は重要なアイテムだ。
4 継承の連鎖
先の例でcomputerクラスはcomputer_screenクラスとmother_boardクラスの2つのクラスを多重継承した。
さてここで、work_stationクラスというのを新たに生成し、このクラスがcomputerクラスを継承すると一体どうなるのだろうか。
この場合はcomputerクラスが基本クラスとなる。しかし、computerクラスはcomputer_screenクラスとmother_boardクラスの2つのクラスから派生したものである。
これを継承の連鎖と呼ぶ。ちょっと見てみよう。
#include <stdio.h>
#include <string.h>
//computerクラスの基本クラス
class computer_screen{
public:
computer_screen(char *, int, int ,int);
void show_screen();
~computer_screen();
private:
char type[32];
int colors;
int x_resolution;
int y_resolution;
};
//computerクラスの基本クラス
computer_screen::computer_screen(char *type, int colors,
int x_res, int y_res)
{
strcpy(computer_screen::type, type);
computer_screen::colors = colors;
computer_screen::x_resolution = x_res;
computer_screen::y_resolution = y_res;
}
void computer_screen::show_screen()
{
printf("スクリーンタイプ : %s\n",type);
printf("カラー数 : %dbpp\n", colors);
printf("解像度 : %d × %d\n",x_resolution,
y_resolution);
}
computer_screen::~computer_screen()
{
}
class mother_board {
public:
mother_board(char *, int, int);
void show_mother_board();
~mother_board();
private:
char processor[10];
int speed;
int ram;
};
mother_board::mother_board(char *processor, int speed, int ram)
{
strcpy(mother_board::processor, processor);
mother_board::speed = speed;
mother_board::ram = ram;
}
void mother_board::show_mother_board()
{
printf("プロセッサ : %s\n",processor);
printf("スピード : %dMHz\n", speed);
printf("メモリ : %dMB\n", ram);
}
mother_board::~mother_board()
{
}
//computerクラス
class computer : public computer_screen, public mother_board{
public:
computer(char *, int, int, char *, int,
int, int, char *, int, int);
void show_computer();
~computer();
private:
char name[64];
int hard_disk;
int cd;
};
computer::computer(char *name, int hard_disk, int cd,
char *screen, int colors, int x_res, int y_res,
char *processor, int speed, int ram)
:computer_screen(screen, colors, x_res, y_res),
mother_board(processor, speed, ram)
{
strcpy(computer::name, name);
computer::hard_disk = hard_disk;
computer::cd = cd;
}
computer::~computer()
{
}
void computer::show_computer()
{
printf("名前 : %s\n", name);
printf("-----性能-----\n");
printf("ハードディスク : %dGB\n", hard_disk);
printf("CD : %d倍速\n", cd);
show_mother_board();
show_screen();
}
//computerクラスからの派生したwork_stationクラス
class work_station : public computer{
public:
work_station(char *, char *, int, int, char *, int,
int, int, char *, int, int);
void show_work_station();
~work_station();
private:
char operating_system[64];
};
work_station::work_station(char *operating_system,
char *name, int hard_disk, int cd,
char *screen, int colors, int x_res, int y_res,
char *processor, int speed, int ram)
:computer(name, hard_disk, cd,
screen, colors, x_res, y_res,
processor, speed, ram)
{
strcpy(work_station::operating_system, operating_system);
}
work_station::~work_station()
{
}
void work_station::show_work_station()
{
show_computer();
show_mother_board();
show_screen();
printf("OS : %s\n", operating_system);
}
void main()
{
work_station *p; //オブジェクトへのポインタの設定
//領域の確保とコンストラクタ関数で初期値設定
p = new work_station("Windows98", "Computer
Pell", 6, 32,
"SVGA", 16, 800, 600, "PentiumU", 300
,128);
p->show_work_station();
delete(p); //領域の開放
}
いかがだろうか、最後の行にWindows98と表示されただろうか。
このようにクラスを派生して行くと使われる基本クラスが他の基本クラスから派生されたものであることも多々ある。
WindowsのMFCやJavaのクラスは階層状になっており、相当遡らなければ本当の基本クラス、というか基底クラスに到達しない場合も多い。
このようにして継承の連鎖が作られ、派生されたクラスのコンストラクタ関数が呼び出されると、基本クラスのコンストラクタ関数も連続して呼び出される訳である。
この章ではクラスの派生と継承について研究した。
C++には、ここで紹介した他にもC言語にはないさまざまな文法などがあるが、それについては、使用するC++コンパイラによって違いが生ずるため、プログラミングを実践して行く過程で独自に研究しながらマスターすることとしていただきたい。
あとは自分の好きなANSI C/C++コンパイラをフルに駆使して、有益なプログラム作成にいそしむことにしよう。
---あとがき---
このC言語の研究を作成してかなりの年月が流れた。また時間があれば作り直したいとも思っている。
私が自分のパソコンを初めて手にしたのはWindows3.1が出る直前からであり、あまり長いキャリアは無い。Windows95になって本格的にインターネットを始めた。そしてWindows98になり、今はLinuxをかじり始めている。
C言語との本格的な出会いはBorland社のTurbo Cからで、Visual C++に移行してからはWindowsのクラスライブラリには実際泣かされた。なんとか全面的にこれを理解しようとしたからである。だがこの考え方は基本的に無理があった。全部理解しようと思っても時間的に出来るはずがなかった。必要に応じて道具としてのクラスをいかにして探し適用するか、有り金をはたいて分厚い本を入手した時点でVisual C++によるWindowsプログラミングの方法が分かった。
このように初心者にとって、Windowsプログラムの場合はC/C++の習得度とは相当に話がかけ離れる。C/C++の習得のつもりが、いつしかクラスライブラリの理解へと方向変換してしまい、わけが分からなくなる。ついには、C言語が理解できているにもかかわらず「おれにはWindowsのC/C++は出来ない」と諦めてしまうおそれがある。これは避けてもらいたい。最初からケチって中途半端な入門書を買わず、4〜5万円出してクラスライブラリの辞典を買い揃えれていれば終っていたことだ。
Windowsのクラスなど理解する必要はないし、第一、これを理解しようとすること自体、時間の無駄だ。我々C/C++プログラマにはゆっくりWindowsを研究している時間などない。
どうしても習得したANSI C/C++でWindowsプログラムを作成したいと思う人には、Inprise社のBorland C++Builder(プロフェッショナル版)を推薦する。これなら普通のプログラムからデータベースまで簡単に効率的なプログラミングができる。ただ、Delphiと共通のPascal系クラスが多用されているので、純粋のC/C++とは程遠い感があることは事実だ。私も現在はC++Builder5でプログラムを作成している。「日曜プログラマ」にとってはありがたい開発環境である。
そうなると、C/C++習得を志す諸氏にとってはやはりLinuxがBestだと思う。Linuxに含まれているgccコンパイラを活用してFreeな環境でC/C++を勉強していくことがこれからは必要であると思う。ただUNIX系システム全体が2038年の時点でどうなるかという問題は残されている。まあ、まだ30年以上あるからその頃には時代もシステムも変っていることだろう。
また、将来、JAVAをはじめとする新しいプログラミング言語が次々と出現してくると思うが、正確な目で真実を把握できる洞察力を養うことが必須だ。
例えばJAVAは、私見で無礼を承知で言うと、C/C++からポインタを省いただけのC++の亜流のような気がする。もちろんこれは文法だけの指摘であり、Write once, Run anywhereの仮想マシンは素晴らしいと思う。(苦笑)
でも、やはりポインタや構造体、さらにはクラスを駆使できるC/C++こそが、現行では最大かつ最高のプログラミング言語であることは誰しもが認めているところではないだろうか。だからC/C++をとことんやってもらいたい。そうして、そこで養った目と知識を武器に、時代のニーズを真っ先に把握し、その変化に効率的に対応して行ってもらいたい、コンピュータのプロとして。
終わりに、今まで記述した拙い内容が将来のコンピュータシステム開発を背負って立とうとする皆様方のヒントや契機にでもなれば幸いであります。
Mashimo Manor