2011年1月20日木曜日

「独習C++」を読む

独習C++
著者 :  Herbert Schildt
訳:   株式会社トップスタジオ
監修:  神林靖
出版社: 翔泳社
発行年: 2002


おなじみ独習シリーズ。著者はC言語の権威であるハーバート・シルト先生。
C言語が扱えることを前提としてC++を解説しています。Cがわかる人には極めて有用。

同じように独習C++について書かれたサイトさん リンク





1 C++の概要

◆1.1 オブジェクト指向プログラミングとは
▽カプセル化
カプセル化とは、プログラムコードと、プログラムコードが扱うデータを一体化し、外部の干渉や御用から両者を保護するためのしくみである。
オブジェクト指向言語では、プログラムコードとデータを組合わせて自己完結型のブラックボックスを作成することができる。これをオブジェクトと呼ぶ。
従って、オブジェクトとはカプセル化をサポートするための工夫だと云える。

▽ポリモーフィズム
ポリモーフィズムとは、1つの名前を2つまたはそれ以上の関連する目的に使;用できるようにする性質のことである。
オブジェクト指向プログラミングでは、ポリモーフィズムを使うことによって、1つの名前で複数の動作の汎用クラスを指定することができる。
例 えば、Cの場合絶対値を求めるためには整数型ならばabs()、長整数型ならばlabs()、浮動小数点数ならばfabs()とを使い分ける必要がある が、C++の場合はひとつを指定するだけで引数に対応した処理を施すことができる。これは関数のオーバーロードと呼ばれるポリモーフィズムの概念のひとつ である。
具体的には、同じ名前で違う引数の型もしくは同じ名前で違う引数の数の関数を複数宣言する。すると、関数を使用する際にコンパイラが引数から適切な関数を自動的に選択する。プログラマは宣言を終えた後は引数の型や数を気にすることなく関数を使用することができる。

▽継承
継承とは、一つのオブジェクトが他のオブジェクトの特色を獲得するプロセスのことである。
例えば、「家」は「建物」というクラスを継承した上で独自の特色を定義したものであり、「建物」はさらに汎用的なクラスである「人工物」を継承した上で独自の特色を定義したものだと云える。
このように、継承を行うことにより、各オブジェクト共通の特色をすべて明示的に定義する必要がなくなる。


◆1.2 2つのバージョンのC++
▽ヘッダスタイルの変更
Cでは#includeの後になどファイル名を括弧で括って指定したが、新しいC++では必ずしもファイル名を指定する必要はなく、#includeのような形となる。
現在のコンパイラはファイル名を指定する古い形式も使用可能ではあるものの、新しい形式を推奨している。

▽名前空間
新しいスタイルのヘッダをインクルードすると、その内容がstd名前空間に含まれる。
名前空間とは、単に宣言された領域のことであり、識別子の名前を局所化することで名前の競合を防ぐことができる。
using namespace std;という文を使用することで可視状態にすることができる。
この文がコンパイルされた後は、古いヘッダスタイルと新しいヘッダスタイルの相違はなくなる。


◆1.3 C++のコンソール入出力
C++は,Cの全ての要素を含むため、Cとまったく同じ記述でも実行することができる。しかし、C++を活用するためには、C++スタイルのプログラムを作成しなくてはならない。
C++スタイルの記述として代表的なものがコンソール入出力である。C++のコンソール入出力は以下のように記述される。

出力 cout << 100.99; 入力 cin >> i;


◆1.5 クラス
クラスとは、オブジェクトを作成するために使われる仕組みのことである。
クラス宣言内で宣言された関数と変数はそのクラスのメンバと呼ぶ。publicを付けない限り非公開である。
クラスの各オブジェクトは、そのクラス内で宣言されたすべての変数の独自のコピーを持っている。
クラスの宣言から実行までの流れを以下に記す。

#include
using namespace std;

class myclass { //クラス宣言は型を宣言しているだけであり、中身は無い。
//非公開
int a;
public:
//公開
void set_a(int num); //myclassのメンバであるset_aとget_aは、aにアクセスすることができる。
int get_a();
};

void myclass::set_a(int num) //メンバ関数の定義
{
a = num;
}

int myclass::get_a()
{
return a;
}

int main()
{
myclass ob1, ob2; //クラスmyclassを2つ宣言している。ob1、ob2はmyclassのコピーだと云える。

ob1.set_a(10); //ob1.a = 10 ではエラー。非公開関数にはアクセスできない。
ob2.set_a(99);

cout << ob1.get_a() << "\n"; cout << ob2.get_a() << "\n"; return 0; } ◆1.6 CとC++の相違点 ①関数で仮引数を受け取らない場合、プロトタイプの関数仮引数のリストにvoidを記述する必要が無い。 ②C++では、すべてのj関数のプロトタイプを記述しなくてはならない。 ③C++では、関数が戻り値を返すものとして宣言した場合、必ず返さなければならない。 ④ローカル変数をどこでも宣言できる。宣言がブロックの先頭である必要が無い。 ⑤bool型のデータが使用できる。bool型には、trueかfalseのどちらかしか入らない。 2 クラスの概要 ◆2.1 コンストラクタとデストラクタ関数 C++では、クラス宣言にコンストラクタ関数とデストラクタ関数を含めることができる。 コンストラクタはクラスと同じ名前を持ち、戻り値を持たない。引数を渡すことは可能である。 デストラクタは、クラス名にチルダ(~)を付けたものであり、戻り値を持たない。 コンストラクタはそのクラスのオブジェクトが作成される度に呼び出され、デストラクタはオブジェクトが破棄されるときに呼び出される。 一般的には、コンストラクタでメモリの確保と初期化、デストラクタでメモリの開放を行う。 また、コンストラクタでタイマーをスタートさせ、デストラクタでストップし表示させることにより、オブジェクトの実行時間を見るなどの使用も可能である。 ◆2.3 継承 継承される側のクラスを基本クラス、継承する側のクラスを派生クラスと呼ぶ。 派生クラスは基本クラスの全ての公開メンバを含む。 従って、派生クラスの関数内で基本クラスの関数を呼び出すことができる。しかし、非公開の関数、変数を扱うことはできない。 以下に、フルーツという基本クラスと、アップルおよびオレンジという派生クラスを定義した例を記す。 #include
#include
using namespace std;

enum yn {no, yes};
enum color {red, yellow, green, orange};
void out(enum yn x);
char *c[] = {
"red", "yellow", "green", "orange"};

// 汎用fruitクラス
class fruit {

// この基本クラスでは,すべての要素が公開
public:
enum yn annual;
enum yn perennial;
enum yn tree;
enum yn tropical;
enum color clr;
char name[40];
};

// Appleクラスを派生する
class Apple : public fruit {
enum yn cooking;
enum yn crunchy;
enum yn eating;
public:
void seta(char *n, enum color c, enum yn ck, enum yn crchy,
enum yn e);
void show();
};

// Orangeクラスを派生する
class Orange : public fruit {
enum yn juice;
enum yn sour;
enum yn eating;
public:
void seto(char *n, enum color c, enum yn j, enum yn sr,
enum yn e);
void show();
};

void Apple::seta(char *n, enum color c, enum yn ck,
enum yn crchy, enum yn e)
{
strcpy(name, n);
annual = no;
perennial = yes;
tree = yes;
tropical = no;
clr = c;
cooking = ck;
crunchy = crchy;
eating = e;
}

void Orange::seto(char *n, enum color c, enum yn j,
enum yn sr, enum yn e)
{
strcpy(name, n);
annual = no;
perennial = yes;
tree = yes;
tropical = yes;
clr = c;
juice = j;
sour = sr;
eating = e;
}

void Apple::show()
{
cout << name << " apple is: " << "\n"; cout << "Annual: "; out(annual); cout << "Perennial: "; out(perennial); cout << "Tree: "; out(tree); cout << "Tropical: "; out(tropical); cout << "Color: " << c[clr] << "\n"; cout << "Good for cooking: "; out(cooking); cout << "Crunchy: "; out(crunchy); cout << "Good for eating: "; out(eating); cout << "\n"; } void Orange::show() { cout << name << " orange is: " << "\n"; cout << "Annual: "; out(annual); cout << "Perennial: "; out(perennial); cout << "Tree: "; out(tree); cout << "Tropical: "; out(tropical); cout << "Color: " << c[clr] << "\n"; cout << "Good for juice: "; out(juice); cout << "Sour: "; out(sour); cout << "Good for eating: "; out(eating); cout << "\n"; } void out(enum yn x) { if(x==no) cout << "no\n"; else cout << "yes\n"; } int main() { Apple a1, a2; Orange o1, o2; a1.seta("Red Delicious", red, no, yes, yes); a2.seta("Jonathan", red, yes, no, yes); o1.seto("Navel", orange, no, no, yes); o2.seto("Valencia", orange, yes, yes, no); a1.show(); a2.show(); o1.show(); o2.show(); return 0; }


◆2.5 クラス、構造体、共用体の関連
クラスと構造体は非常によく似た働きをする。違いはクラスのメンバがデフォルトで非公開であるのに対し、構造体のメンバはデフォルトで公開であるということだけである。
クラスと共用体はこれとは違い、共用体を用いることにより、全てのデータが同じメモリ位置を共有するクラス型をつくることができる。


◆2.6 インライン関数
C++では、実際に呼び出す変わりに各呼び出しごとにインラインで展開するインライン関数を定義することができる。
インライン関数は、関数定義の前にinline指定子を付けることで呼び出される。

inline int even(int x)
{
retirm !(x%2);
}

インライン関数の利点は、関数の呼び出しと終了に伴うオーバーヘッドが発生しないことである。
また、仮引数付きマクロではなくinlineを使うことの利点は、より構造的な方法で短い関数を展開できること、コンパイラがインライン関数を徹底して最適化を行うことができるという2点である。


◆2.7 自動インライン化
メンバ関数の定義が短い場合は、クラス定義の中にその定義を含めることができる。この場合、関数は可能であれば自動的にインライン化される。


◆3.1 オブジェクトの代入
二つのオブジェクトのクラスが同じであれば、代入することができる。
同じように宣言していても、違うクラスのオブジェクトを代入することはできない。
代入されたオブジェクトはコピーであり、アドレスへのリンクではない。
クラスにポインタ変数が含まれる場合、代入先のポインタ変数は代入元のポインタ変数と同じ場所を指し示すこととなり、本来指すべきスタックが開放されないことがある。そのため、文字列を扱う場合、コンストラクタでメモリを確保する場合は特に注意が必要である。


◆3.2 関数へのオブジェクトの引き渡し
オ ブジェクトを変数と同じように関数に送ることができる。関数の引数には同じクラスを宣言する。関数内で宣言されたオブジェクトは引数のオブジェクトのコ ピーとなる。このコピーを作成するときにコンストラクタは呼び出されないが、破棄するときにはデストラクタが呼び出される。


◆3.3 関数からのオブジェクトの返し
関 数にオブジェクトを渡すことができるのと同じように、関数からオブジェクトを返すことができる。そのためには、メインで返すクラスの型を用意し、 returnで返す。このとき戻り値には返すオブジェクトのコピーが作成され、引き渡し後破棄されるが、破棄されるときにデストラクタが実行され、メモリ の開放が実行されるプログラムでは予期せぬエラーが発生する場合がある。これを解消するには、コピーコンストラクタを使う方法がある。


◆3.4 フレンド関数
フレンド関数の用途は3つある。
一つは演算子のオーバーロードにかかわるもの、二つは入出力関数の作成に関係するもの、三つめは2つ以上の別々のクラスの非公開メンバにアクセスするためのものである。


◆4.1 オブジェクトの配列
オブジェクトは変数であるため、配列にすることが可能である。オブジェクトの配列を宣言する構文は、他の型の変数の配列を宣言する際に使用する構文とまったく同じである。オブジェクト配列へのアクセス方法も、他の型の変数配列と同じである。多次元配列も可能。


◆4.2 オブジェクトのポインタ
オブジェクトにはポインタを使用してアクセスすることができる。オブジェクトポインタを使用する場合は、ドット演算子の代わりにアロー演算子を使用する。
オ ブジェクトポインタを使用したポインタ演算は、他のデータ型のポインタ演算と同様に、そのオブジェクト型に関して行われる。たとえば、オブジェクトポイン タをインクリメントするとそのポインタは次のオブジェクトを指すようになり、デクリメントすると前のオブジェクトを指すようになる。


◆4.3 thisポインタ
thisポインタは全てのメンバ変数の呼び出し時に自動的に渡されるポインタであり、その呼び出しを行ったオブジェクトを指す。thisポインタが渡されるのはメンバ関数だけであり、フレンド関数などには渡されない。
具体的には、あるオブジェクトのコンストラクタ関数内で cost = c; と記述されたものは、 this->cost = c; と同じ意味である。


◆4.4 newとdeleteの使用
C++においてメモリを割り当てるときには、new演算子とdelete演算子を使用する。記述は以下の通り。
p = new type;
delete p;
このとき変数pはポインタであり、typeはメモリを割り当てるオブジェクトの型指定子である。

newとdeleteを利用することにより、動的に割り当てたオブジェクトに初期値を与えることができる。また、配列を動的に割り当てることも可能である。
動的に割り当てたオブジェクトに初期値を与えるには、次のように記述する。
p = new type(initial-value);
1次元配列を動的に割り当てるには、次のように記述する。これを初期化することはできない。
p = new type[size];
動的に割り当てた配列を削除するには、次のように記述する。
delete[] p;


◆4.6 参照
参照とは、あらゆる点で変数の別名として動作する暗黙的なポインタのことである。
参照の用途は3通りある。一つが参照を関数に渡すことができること、二つが関数から参照を返すことができること、三つが独立した参照を作成することができることである。要はCの弱点である関数への引渡しをポインタでごっそりやっちゃうアレである。


◆4.7 オブジェクト参照の引き渡し
オブジェクトを関数へ送る際も、同じようにアドレスを送ることが可能である。
この場合、受け取る関数の引数に&を付けて記述する。


◆4.8 参照の返し
関数から参照を返すことで、配列の境界チェックをすることができる。これにより、安全配列を作成することができる。


◆4.9 独立参照と制限
独立参照とは、単に他の変数の別名である参照変数のことである。
独立参照はときどき使われるが、ほとんどのプログラマは独立参照を使用するとプログラムがわかりにくくなると考えている。大抵の場合は独立参照を使用しないほうがよい。



5 関数のオーバーロード


C++においてクラスの次に重要といえるのが、関数のオーバーロードである。
C++では、複数の関数に同じ名前を使用することができる。ただし、それぞれの引数の型か引数の数が異なっていなければならない。2つ以上の関数で同じ名前を使用するとき、それらはオーバーロードされていると表現する。


◆5.1 コンストラクタのオーバーロード
クラスのコンストラクタはオーバーロードすることができる。デストラクタはオーバーロードできない。
コンストラクタをオーバーロードする目的は以下の3つである。
①柔軟性を得る
②配列をサポートする
③コピーコンストラクタを作成する

0 コメント:

コメントを投稿