DATE : 2006/09/21 (Thu)
(;^ω^)ある程度 C++ の勉強が進みましたので、さっそく勉強日記を付けていこうと思います。間違いがあった場合は、ちょこちょこと修正していきます。
C++ を勉強し始めたわけですが、Java を使っている身としてはやはり、クラスをどのように書くのか気になります。
とりあえず、次のように書かれた Java のクラスを C++ のクラスに書き直してみます。
(Java のクラス)
class Person { private String name; private int age; public Persion(String name, int age) { this.name = name; this.age = age; } public String getName() { return this.name; } public void setAge(int age) { this.age = age; } public int getAge() { return this.age; } }
name プロパティが読み出し専用で age プロパティが読み書き可能な Person クラスです。コンストラクタで両者を設定します。
このクラスを C++ で表現すると、次のようになります。
(C++ のクラス)
#include <string> using std::string; class Person { private : string name; int age; public : Person(const string& name, int age); string getName() const; void setAge(int age); int getAge() const; }; Person::Person(const string& name, int age) : name(name), age(age) { } string Person::getName() const { return this->name; } void Person::setAge(int age) { this->age = age; } int Person::getAge() const { return this->age; }
string を使っているので少々余計な文がありますが、クラスの定義だけを取り出すと次のようになります。
class Person { private : string name; int age; public : Person(const string& name, int age); string getName() const; void setAge(int age); int getAge() const; };
( ^ω^)セミコロンがクラス定義の最後に必要なところが、ちょっと C っぽいですね。
同じアクセス修飾子のフィールドやメソッドはまとめて書くところなどを除けば、それほど難しくはありません。
ちなみに、Java では「フィールド」「メソッド」と呼んでいる言葉は、C++ ではそれぞれ「メンバ変数」「メンバ関数」と呼ばれているようです。
( ^ω^)ここらへん、C の構造体の「メンバ」から派生したような言葉のような感じがしますね
なおに、メンバ関数の後ろについている「const」は、そのメンバ関数がメンバ変数を書き換えないことを示しています。このキーワードは、メンバ変数を書き換えないのなら書くように推奨されているようです。
ところで、C++ のクラスにはアクセス修飾子はありません。クラスの書かれたファイル外にクラスを公開する場合は、ヘッダファイルにクラス定義を記述します。
( ^ω^)Java と違って、C++ のクラスはファイルスコープが最大のスコープのようです。
クラスの実装部分は次の通りです。
Person::Person(const string& name, int age) : name(name), age(age) { } string Person::getName() const { return this->name; } void Person::setAge(int age) { this->age = age; } int Person::getAge() const { return this->age; }
クラス定義のところに実装を書くこともできるのですが、クラス定義をヘッダファイルに書く場合は、クラス定義とその実装とを分離した方がコンパイル時間が短くなったり、オブジェクトファイルのサイズの肥大化を防ぐことができるようです。というのも、ヘッダファイルに実装も書いてしまうと、実装を修正しただけでそのヘッダファイルを取り込んだソースファイルをコンパイルし直さなければなりません。また、実装がヘッダファイルにある場合、include 文によって実装までもが取り込み先のソースファイルに展開されてしまいます。そのため、クラス定義と実装は分離しておくことが推奨されているようです(あえて取り込み先に埋め込む場合や、今回のように同じファイルにあるのであれば分離する必要は特にないようです)
ちなみに、自分自身のオブジェクトを示す this キーワードは C++ にもあります。ただし、 C++ の場合は自分自身のオブジェクトのポインタなので、自分自身のオブジェクトにあるメンバにアクセスする場合は、アロー演算子(->)を使わなければなりません。(Java と同様、メンバにアクセスする場合の this キーワードは省略可能です)
ところで、次のコンストラクタの文は何でしょうか。
Person::Person(const string& name, int age) : name(name), age(age) { }
「:」以降の「name(name), age(age)」は、「初期化リスト」と言って、コンストラクタに渡された引数をメンバ変数に設定する役割があります。
これを Java 風に書くと、次のようになります。
Person::Person(const string& name, int age) { this->name = name; this->age = age; }
上の Java 風のやり方でも動作はするのですが、 C++ では初めに示したような「name(name), age(age)」というやり方が推奨されています。
というのも、Java 風の考えでいくと、メンバ変数の「string name」は string 型の参照を格納する変数に見えるのですが、 C++ では string 型のオブジェクトそのものを格納する変数を表します。つまり、 Java 風の書き方の場合、
- メンバ変数 name が初期化。
- メンバ変数 age が初期化。
- メンバ変数 name に引数 name のオブジェクトを代入。
- メンバ変数 age に引数 age の値を代入。
という形になります。これが初期化リストを使った場合は
- メンバ変数 name に引数 name のオブジェクトをコピー。
- メンバ変数 int に引数 age の値をコピー。
という順序で初期化が完了します。初期化リストを使うことで、効率よくメンバ変数を初期化できるわけですね。
それでは、コンストラクタの引数にある「const string& name」は何でしょうか。「&」の付いた型は参照型と呼ばれます。詳細はまた別の記事に書きますが、参照型には渡される変数の別名のような役割があります。つまり、この引数に渡される変数(やオブジェクト)を表す型になります。「string name」とした場合は値渡しとなって、コンストラクタの呼び出し元とコンストラクタ間でオブジェクトのコピーが行われますが、参照型の場合は行われず、そのまま扱えるイメージです。なお、const は、参照型として渡されたオブジェクトの内容を変えない(つまり、const の付けられたメンバ関数以外にはアクセスしない)という意味を表します。