この章では、C++に導入されたクラスについて説明をしていきます
クラスの基礎
C++におけるクラスとは、データ(属性)と関数(メソッド)をひとまとめにした設計図です
これは、オブジェクト指向プログラミングの基本的な概念で、オブジェクトと呼ばれる実体の型を定義します
オブジェクト指向の基本的な考え方
オブジェクト指向とは、プログラムの要素をモノ(オブジェクト)として捉え、それらの相互作用によってシステムを構築する考え⽅のことです
各オブジェクトはデータ(属性)と動作(メソッド)を持ち、現実世界の事物や概念をモデル化します
・データ(属性)
オブジェクトが持つ特性や状態を表す情報です
C++ではデータメンバと呼ばれ、プログラム上では変数として表現されます
※フィールドと呼ばれることもあります
・動作(メソッド)
オブジェクトが持つ機能や振る舞いを定義します
データに対する処理手順を記述し、外部からの指示に応じてどのような動作をするかを規定します
C++ではメンバ関数として表現されます
※メソッドと呼ばれることもあります
例えば、「車」というオブジェクトを考えると、「発信する」「停止する」「曲がる」などが動作(メソッド)に相当し、「メーカー」「排気量」「走行距離」などがデータ(属性)に相当します
| データ(属性) | 動作(メソッド) |
|---|---|
| メーカー | 発信する |
| 排気量 | 停⽌する |
| ⾛⾏距離 | 曲がる |
さらに、オブジェクト指向には、システムの柔軟性、再利用性、保守性を高めるための三大要素があります
- 継承
既存のクラス(親クラスまたは基底クラス)の持つフィールド(データメンバ)やメソッド(メンバ関数)を、新しいクラス(子クラスまたは派生クラス)に引き継がせる仕組みです
これにより、コードの再利用を促進し、クラス間の関連性を明確にできます - 多態性
同じ名前のメソッド呼び出しに対して、オブジェクトの型や状態に応じて異なる処理を実行させる仕組みです
これにより、柔軟で拡張性の高いプログラムを設計できます - カプセル化
クラス内でデータ(属性)とそれに関連する動作(メソッド)をまとめ、外部からの直接的なアクセスを制限する仕組みです
これにより、オブジェクトの内部構造を隠蔽し、データの保護とクラスの独立性を高めることができます
クラスの定義
クラスは、classというキーワードで宣言します
クラスの中には、データメンバとメンバ関数を定義できます
データメンバはクラスの属性を表し、メンバ関数はクラスのメソッドを表します
class クラス名 {
// データメンバ:クラスのデータを定義する
型 変数名;
// メンバ関数:クラスの処理を定義する
戻り値型 関数名 (引数);
};メンバ関数は、宣言のみを記述し、関数の定義はクラス定義の外に記述します
戻り値型 クラス名::関数名(引数){
// 関数内の処理
return 戻り値;
}クラスのメンバ関数であることを表すため、スコープ演算子(::)を使い、関数名の前にクラス名を記述します
また、メンバ関数の宣言および定義をクラス内に記述することもできます
class クラス名 {
// データメンバ:クラスのデータを定義する
型 変数名;
// メンバ関数:クラスの処理を定義する
戻り値型 関数名 (引数){
// 関数内の処理
return 戻り値;
}
};この方法で書かれた関数はインライン関数と呼ばれ、コンパイル時にインライン展開されます
メンバ関数は、単体で使うことはできません
常に関連付けられたクラスのオブジェクトと組み合わせて使う関数です
オブジェクトの生成とメンバへのアクセス
クラスのデータメンバ、メンバ関数を使うためには、まず、クラスからオブジェクト(インスタンス)を生成します
オブジェクトはクラスというモデルをもとに作られた実体のことです
クラスのオブジェクトを⽣成する⽅法は以下の通りです
クラス名 オブジェクト名;クラスのメンバへアクセスするには、オブジェクト名とメンバ名の間にドット(.)を入れます
オブジェクト名.変数名;
オブジェクト名.関数名(引数);以下に、クラスの宣言とメンバにアクセスするサンプルプログラムを示します
※アクセス指定子については、次の章であらためて説明します
// 矩形クラスの宣言
class Rectangle {
int width, height; // データメンバ(属性)
public: // アクセス指定子
void set_values (int,int); // メンバ関数(メソッド)の宣言
int area (void); // メンバ関数(メソッド)の宣言
} rect; // オブジェクト名
// メンバ関数の定義
void Rectangle::set_values (int x, int y) {
width = x;
height = y;
}
int Rectangle::area (void) {
return width * height;
}
// クラスの使用
int main () {
rect.set_values (3,4); // メンバ関数の呼び出し
std::cout << "Area: " << rect.area() << std::endl; // メンバ関数の呼び出し
return 0;
}上記のサンプルのように、classでクラスを定義したあと、オブジェクト名を指定することもできます
// 車クラスの宣言
class Car {
public: // アクセス指定子
std::string brand; // データメンバ(属性)
std::string model; // データメンバ(属性)
int year; // データメンバ(属性)
void show(){
std::cout << brand << " " << model << " " << year << std::endl;
}
};
// クラスの使用
int main () {
// オブジェクトの作成
Car carObj1;
Car carObj2;
// データメンバに値を代入
carObj1.brand = "Toyota";
carObj1.model = "Corolla";
carObj1.year = 2020;
carObj2.brand = "Honda";
carObj2.model = "Civic";
carObj2.year = 2019;
// データメンバの値を出力
carObj1.show();
carObj2.show();
return 0;
}コンストラクタとデストラクタ
コンストラクタ
初期化を行うための特殊なメンバ関数をコンストラクタといいます
コンストラクタはオブジェクトを作成(インスタンス化)するときに必ず呼び出されるため、データメンバの初期化の処理を自動化できるというメリットがあります
コンストラクタは戻り値のない関数で、クラス名と同じ名前でメンバ関数を記述することで定義できます
また、さまざまな方法で初期化を実現するため、オーバーロードされたコンストラクタを複数定義できます
- 引数なしのコンストラクタ
引数なしのコンストラクタは、オブジェクトが作成される際に自動的に呼び出され、特別な初期化を行わない場合や、デフォルト値で初期化する場合に使用します
クラス内に明示的に定義しない場合、コンパイラが暗黙的に生成することがあります
※ただし、他のコンストラクタを定義した場合は生成されません
以下に、引数なしのサンプルプログラムを示します
class MyClass {
public:
// 引数なしのコンストラクタ
MyClass() {
// コンストラクタの処理(初期化など)
std::cout << "引数なしのコンストラクタが呼ばれました。" << std::endl;
}
void printMessage() const {
std::cout << "Hello from MyClass!" << std::endl;
}
};
int main() {
std::cout << "start" << std::endl;
MyClass obj1; // 引数なしでオブジェクトを作成。デフォルトコンストラクタが呼ばれる。
std::cout << "call printMessage()" << std::endl;
obj1.printMessage();
return 0;
}実行結果
start
引数なしのコンストラクタが呼ばれました。
call printMessage()
Hello from MyClass!上記の例では、MyClass() が引数なしのコンストラクタです
オブジェクト obj1 が作成される際に、このコンストラクタ内の処理(ここではメッセージの出力)が実行されます
- 引数ありのコンストラクタ
引数ありのコンストラクタは、オブジェクトの作成時に初期値を渡したい場合に使用します
複数の引数を持つことも可能です
以下に引数が複数あるときのサンプルプログラムを示します
#include <iostream>
// コンストラクタとデストラクタを持つクラスの宣言
class Person {
private: // アクセス指定子
std::string name; // データメンバ(属性)
int age; // データメンバ(属性)
public: // アクセス指定子
Person(std::string, int); // コンストラクタの宣言
void show(); // メンバ関数(メソッド)の宣言
};
// コンストラクタの定義
Person::Person(std::string n, int a) {
name = n;
age = a;
}
// メンバ関数の定義
void Person::show() {
std::cout << "名前: " << name << std::endl;
std::cout << "年齢: " << age << std::endl;
}
// クラスの使用
int main () {
// オブジェクトの作成
Person personObj("Alice", 20);
// メンバ関数の呼び出し
personObj.show();
return 0;
}実行結果
名前: Alice
年齢: 20この例ではPerson(std::string n,int a)が引数をとるコンストラクタです
オブジェクトpersonObjの作成時に、名前と年齢がコンストラクタに渡され、メンバ変数nameとageが初期化されます
一方、以下のようにメンバ初期化子リストを使用して記述することもできます
// コンストラクタの定義
Person::Person(std::string n, int a) : name(n),age(a){
}メンバ初期化子リストは、コロン(:)に続いて記述されます
初期化するメンバ変数名とその初期値を メンバ変数名(初期値) の形式で記述します
複数のメンバ変数を初期化する場合は、カンマ(,)で区切ります
コンストラクタの本体 {} が実行される前に、メンバ変数の初期化が行われます
デストラクタ
デストラクタはオブジェクトが破棄されるときに呼び出される特殊なメンバ関数で、確保したメモリなどのリソースの解放を行うために使用します
デストラクタは、クラス名の前にチルダ(~)をつけて定義します
デストラクタは値を返すことも、引数を受け取ることもできません
クラス内に明示的に定義しない場合、コンパイラが暗黙的に生成します
以下にデストラクタを定義したサンプルプログラムを示します
#include <iostream>
// コンストラクタとデストラクタを持つクラスの宣言
class Person {
private: // アクセス指定子
std::string name; // データメンバ(属性)
int age; // データメンバ(属性)
public: // アクセス指定子
Person(std::string, int); // コンストラクタの宣言
~Person() ; // デストラクタの宣言
void show(); // メンバ関数(メソッド)の宣言
};
// コンストラクタの定義
Person::Person(std::string n, int a) : name(n),age(a){
std::cout << "コンストラクタが呼ばれました" << std::endl;
}
// デストラクタの定義
Person::~Person() {
std::cout << "デストラクタが呼ばれました" << std::endl;
}
// メンバ関数の定義
void Person::show() {
std::cout << "名前: " << name << std::endl;
std::cout << "年齢: " << age << std::endl;
}
// クラスの使用
int main () {
// オブジェクトの作成
std::cout << "オブジェクトの作成" << std::endl;
Person personObj("Alice", 20);
// メンバ関数の呼び出し
personObj.show();
// プログラムの終了時にデストラクタが呼ばれる
std::cout << "プログラムの終了" << std::endl;
return 0;
}実行結果
オブジェクトの作成
コンストラクタが呼ばれました
名前: Alice
年齢: 20
プログラムの終了
デストラクタが呼ばれましたこの例では、 ~Person();でデストラクタを定義しています
コメント