この「C++入門」は、wxWidgesのコードを読むための必要最小限な説明をしています。
他の言語などの予備知識は必要としていません。
クラスとは、ある目的のためのデータ(変数)と関数を一つにまとめたものです。
#include <iostream>
class Class
{
//private:
std::string name;
public:
Class(std::string s)
{
name = s;
}
void Iam()
{
std::cout << "I am " << name << "." << std::endl;
}
};
int main()
{
Class jane("Jane");
Class mary("Mary");
jane.Iam();
mary.Iam();
Class *bob = new Class("Bob");
Class *tom = new Class("Tom");
bob->Iam();
tom->Iam();
delete bob;
delete tom;
return 0;
}
I am Jane.
I am Mary.
I am Bob.
I am Tom.
一つのクラスから、複数のインスタンスを作ることができます。それぞれのインスタンスは、それぞれが別の個性を持っています。
クラスのコンストラクタの役割は、おもに、メンバ変数の初期化ですが、コンストラクタは次のように書くこともできます。
#include <iostream>
class Class
{
std::string name;
public:
Class(std::string s) : name(s) {}
void Iam()
{
std::cout << "I am " << name << "." << std::endl;
}
};
int main()
{
Class jane("Jane");
Class mary("Mary");
jane.Iam();
mary.Iam();
Class *bob = new Class("Bob");
Class *tom = new Class("Tom");
bob->Iam();
tom->Iam();
delete bob;
delete tom;
return 0;
}
クラスの定義と、そのメンバ関数の実装は、分けて記述することができます。
#include <iostream>
class Class
{
std::string name;
public:
Class(std::string s);
void Iam();
};
Class::Class(std::string s) : name(s) {}
void Class::Iam()
{
std::cout << "I am " << name << "." << std::endl;
}
int main()
{
Class jane("Jane");
Class mary("Mary");
jane.Iam();
mary.Iam();
Class *bob = new Class("Bob");
Class *tom = new Class("Tom");
bob->Iam();
tom->Iam();
delete bob;
delete tom;
return 0;
}
さらに、実際のプログラミングでは、クラスの定義部分と実装部分を別のファイルに記述します。main関数の部分も別のファイルに記述します。そして、クラスの定義部分には .h という拡張子をつけます。この .h ファイルのことをヘッダーファイル、もしくはヘッダーと呼びます。
#include <iostream>
class Class
{
std::string name;
public:
Class(std::string s);
void Iam();
};
#include "class.h" // class.hを読み込んでいます
Class::Class(std::string s) : name(s) {}
void Class::Iam()
{
std::cout << "I am " << name << "." << std::endl;
}
#include "class.h" // class.hを読み込んでいます
int main()
{
Class jane("Jane");
Class mary("Mary");
jane.Iam();
mary.Iam();
Class *bob = new Class("Bob");
Class *tom = new Class("Tom");
bob->Iam();
tom->Iam();
delete bob;
delete tom;
return 0;
}
この3つのファイルをコンパイルするには次のようにします。g++ コマンドには、ヘッダファイルは指定しません。
g++ class.cpp main.cpp
なお、「wxWidgetsでGUIプログラミング」では、クラスの定義と実装は分けていますが、ファイルは分けず、一つのファイルに記述しています。ファイルを分割するとコードを読みにくくなるからです。しかし実際のプログラミング現場では、コード量が多いため、ファイルを分割したほうが管理しやすくなります。
クラスには「継承(inheritance)」という重要な仕組みがあります。
継承とは、あるクラスのすべてのメンバーを引き継いだ、新しいのクラスを作る仕組みです。元のなったクラスをスーパークラスと呼び、新しく作られたクラスをサブクラスと呼びます。サブクラスではスーパークラスのメンバーを定義せずに使えるだけでなく、新しいメンバーも追加できます。
#include <iostream>
class Animal
{
protected:
std::string name;
public:
Animal(std::string name)
{
this->name = name;
}
// Animal(std::string name) : name(name) {} とも記述できます。
};
class Dog : public Animal
{
public:
Dog(std::string name) : Animal(name)
{
}
void OnVoice()
{
std::cout << "ワンワン、私は" << name << "よ" << std::endl;
}
};
class Cat : public Animal
{
public:
Cat(std::string name) : Animal(name)
{
}
void OnVoice()
{
std::cout << "ニャ〜ン、私は" << name << "よ" << std::endl;
}
};
int main()
{
Dog pochi("ポチ");
Dog shiro("シロ");
pochi.OnVoice();
shiro.OnVoice();
Cat *mike = new Cat("ミケ");
Cat *kuro = new Cat("クロ");
mike->OnVoice();
kuro->OnVoice();
delete mike;
delete kuro;
return 0;
}
ワンワン、私はポチよ
ワンワン、私はシロよ
ニャ〜ン、私はミケよ
ニャ〜ン、私はクロよ
protected:
プロテクッドを指定したメンバーは、そのクラスを引き継いだサブクラスでも使えます。this->name = name;
メンバー変数の名前と引数の名前が同じ場合は、メンバー変数に「this->」をつけると区別することができます。class Dog : public Animal
あるクラスを継承する場合は、クラス宣言のあとにコロン : と public を記述して、そのあとに継承するクラス名を記述します。Cat(std::string name) : Animal(name)
サブクラスのコンストラクタからスーパークラスのコンストラクタを呼び出すには、サブクラスのコンストラクタのあとにコロン : を記述して、そのあとにスーパークラスのコンストラクタを記述します。void OnVoice()
サブクラスには、新しいメンバーを追加することができます。std::cout << "ワンワン、私は" << name << "よ" << std::endl;
サブクラスからはスーパークラスのプロテクテッドもしくはパブリックなメンバーを自分のクラスのメンバーのように使えます。前節の inheritance.cpp では、Dog クラスにも、Cat クラスにも OnVoice というメンバ関数があります。このメンバ関数を「オーバーライド(over ride)」と言う仕組みを使って、もう少し整理されたコードにすることができます。
#include <iostream>
class Animal
{
protected:
std::string name;
public:
Animal(std::string name)
{
this->name = name;
}
void OnVoice()
{
std::cout << "私は" << name << "よ" << std::endl;
}
};
class Dog : public Animal
{
public:
Dog(std::string name) : Animal(name)
{
}
void OnVoice()
{
std::cout << "ワンワン、";
Animal::OnVoice();
}
};
class Cat : public Animal
{
public:
Cat(std::string name) : Animal(name)
{
}
void OnVoice()
{
std::cout << "ニャ〜ン、";
Animal::OnVoice();
}
};
int main()
{
Dog pochi("ポチ");
Dog shiro("シロ");
pochi.OnVoice();
shiro.OnVoice();
Cat *mike = new Cat("ミケ");
Cat *kuro = new Cat("クロ");
mike->OnVoice();
kuro->OnVoice();
delete mike;
delete kuro;
return 0;
}
ワンワン、私はポチよ
ワンワン、私はシロよ
ニャ〜ン、私はミケよ
ニャ〜ン、私はクロよ
サブクラスにスーパクラスと同名の関数が存在する場合は、そのサブクラスでは、そのサブクラスの関数が優先的に呼び出されます。スーパークラスの同名の関数を呼び出したい場合は、
スーパークラス::関数(); という形で呼び出します。
このようにスーパークラスのメンバ関数を、サブクラスで書き換えることを関数の再定義もしくはオーバーライドと言います。
#include <iostream>
class Animal
{
protected:
std::string name;
public:
Animal(std::string name)
{
this->name = name;
}
// 次のコードを virtual void OnVoice() に変えてみてください
void OnVoice()
{
std::cout << "私は" << name << "よ" << std::endl;
}
};
class Dog : public Animal
{
public:
Dog(std::string name) : Animal(name) {}
void OnVoice()
{
std::cout << "ワンワン、";
Animal::OnVoice();
}
};
class Cat : public Animal
{
public:
Cat(std::string name) : Animal(name) {}
void OnVoice()
{
std::cout << "ニャ〜ン、";
Animal::OnVoice();
}
};
int main()
{
Animal *shiro = new Animal("ポチ");
Animal *pochi = new Dog("シロ");
Animal *mike = new Cat("ミケ");
shiro->OnVoice();
pochi->OnVoice();
mike ->OnVoice();
delete shiro;
delete pochi;
delete mike;
return 0;
}
クラスのインスタンスをポインターで扱う場合は、スパークラスのポインターにサブクラスのインスタンスを代入することができます。しかし、そのインスタンスのメンバーを呼び出すと、スーパークラスのメンバーが呼び出されます。
私はポチよ
私はシロよ
私はミケよ
次に、void OnVoice() を virtual void OnVoice() に変えてみてください。今度は、サブクラスのメンバーが呼び出されるようになります。
私はポチよ
ワンワン、私はシロよ
ニャ〜ン、私はミケよ
このように virtual キーワードのついたメンバ関数のことを「仮想関数」と言います。
サブクラスで再定義しなければならないことを前提としたメンバ関数を作ることもできます。このようなメンバ関数を「純粋仮想関数」と呼びます。そして、純粋仮想関数を含むクラスは「抽象クラス(abstract class)」と呼ばれます。抽象クラスからはインスタンスを作ることはできません。必ずサブクラスを作って、そこからインスタンスを作らなければなりません。
純粋仮想関数は、virtual void OnVoice() = 0; というように関数の定義の際に 実装部分である { と } を無くし、 = 0; と記述すことによって作ります。
#include <iostream>
class Animal
{
protected:
std::string name;
public:
Animal(std::string name)
{
this->name = name;
}
virtual void OnVoice() = 0;
};
class Dog : public Animal
{
public:
Dog(std::string name) : Animal(name) {}
void OnVoice()
{
std::cout << "ワンワン、私は" << name << "よ" << std::endl;
}
};
class Cat : public Animal
{
public:
Cat(std::string name) : Animal(name) {}
void OnVoice()
{
std::cout << "ニャ〜ん、私は" << name << "よ" << std::endl;
}
};
int main()
{
//Animal *shiro = new Animal("シロ"); これはエラーになります。
Dog *pochi = new Dog("ポチ");
Cat *mike = new Cat("ミケ");
pochi->OnVoice();
mike->OnVoice();
delete pochi;
delete mike;
return 0;
}
ワンワン、私はポチよ
ニャ〜ん、私はミケよ
抽象クラスは、抽象クラスで定義された純粋仮想関数が、 抽象クラスを継承したサブクラスに 必ず実装(再定義)されている事を保証(明確に)するために用いられます。
クラスには、インスタンスが消滅する(破棄される)ときに呼び出される
デストラクタ(destructor)というメンバ関数も作ることができます。
デストラクタは、~クラス名( ) {} と記述し、戻り値はなく、先頭にチルダ( ~ )をつけます。
#include <iostream>
class Base
{
public:
Base()
{
std::cout << "Hello" << std::endl;
}
~Base()
{
std::cout << "Bye" << std::endl;
}
};
class Sub : public Base
{
protected:
std::string name;
public:
Sub(std::string name) : name(name)
{
std::cout << "Hello " << name << std::endl;
}
~Sub()
{
std::cout << "Bye " << name << std::endl;
}
};
int main()
{
Sub *jane = new Sub("Jane");
delete jane; // この行を削除するとどうなるかみてください
Sub mary("Mary");
return 0;
}
Hello
Hello Jane
Bye Jane
Bye
Hello
Hello Mary
Bye Mary
Bye
インスタンスが作成される時には、スーパークラスのコンスタクタが呼び出され、次にサブクラスのコンストラクが呼びされます。
反対にインスタンが破棄される時には、サブクラスのデストラクタが呼び出され、次にスーパークラスのデストラクタが呼び出されます。
delete jane; の行を削除すると次のようになります。
Hello
Hello Jane
Hello
Hello Mary
Bye Mary
Bye
ポインターに代入したインスタンスは、自動的には破棄されません。必ず delete キーワードを使って、ポインターが指し示す実体を破棄するようにしましょう。
デストラクの重要な役割の一つは、クラス内のポインターを delete することですが、 destructor.cpp では、クラス内にポインターはありませんので、特に delete することは何もありません。