【自主学習】ひたすらコードで覚えるC++［#7］

# はじめに
今回は7回目ということでクラスの機能を中心に学んでいきます。
後半はコードを書く際によく使用する機能を学びます。

# 使用する環境
環境はVisual Studio 2022を使用します。

# コンストラクタの定義
クラスからオブジェクトが作成されるときに自動的に呼び出される関数で、初期化処理を行います。

```diff_c
#include <iostream>
using namespace std;

//Carクラスの宣言
class Car {
	private:
		int num;
		double fuel;
	public:
		Car();
		void show();
};

//Carクラスメンバ関数の定義
Car::Car() {
	num = 0;
	fuel = 0.0;
	cout << "車を作成しました。\n";
}
void Car::show() {
	cout << "車のナンバーは" << num << "です。\n";
	cout << "燃費は" << fuel << "km/Lです。\n";

}

int main(void) {
	Car car1;
	car1.show();
	return 0;
}
```

# クラスの拡張（継承）
すでに作成したクラスをもとに新しいクラスを作成することができます。このように既存のクラスのメンバを受け継ぐことを継承といいます。

```diff_c
#include <iostream>
using namespace std;

//Carクラスの宣言
class Car {
	private:
		int num;
		double fuel;
	public:
		Car();
		void setCar(int n, double f);
		void show();
};

//RacingCarクラスの宣言
class RacingCar : public Car {
	private:
		int course;
	public:
		RacingCar();
		void setCourse(int c);
};

//Carクラスメンバ関数の定義
Car::Car() {
	num = 0;
	fuel = 0.0;
	cout << "車を作成しました。\n";
}

void Car::setCar(int n, double f) {
	num = n;
	fuel = f;
	cout << "ナンバーを" << num << "\n";
	cout << "燃費を" << fuel << "km/Lにしました。\n";
}

void Car::show() {
	cout << "車のナンバーは" << num << "です。\n";
	cout << "燃費は" << fuel << "km/Lです。\n";

}

//RacingCarクラスメンバ関数の定義
RacingCar::RacingCar() {
	course = 0;
	cout << "レーシングカーを作成しました。\n";
}

void RacingCar::setCourse(int c) {
	course = c;
	cout << "コース番号を" << course << "にしました。\n";
}

int main(void) {
	RacingCar rccar1;
	rccar1.setCar(1111, 19.1);
	rccar1.setCourse(3);
	return 0;
}
```

# メモリの確保および解放
オブジェクトがメモリを動的に確保する処理をおこなう際は、オブジェクトが破棄される前に確保したメモリを解放しなければなりません。オブジェクトが作成されるたびに利用できるメモリが減り、プログラムがうまく作動しなくなる可能性もあります。デストラクタは、オブジェクトが破棄される場合に自動的に呼び出されます。なお、メモリ解放などの終了処理が必要ない場合は、デストラクタを記述する必要はありません。

```diff_c
#include <iostream>
#include <string>
using namespace std;

// Carクラスの宣言
class Car {
    private:
    	int num;
    	double fuel;
    	char* pName;
    public:
    	Car(const char* pN, int n, double f);
    	~Car(); // デストラクタの宣言
    	void show();
};

// Carクラスメンバ関数の定義
Car::Car(const char* pN, int n, double f) {
	pName = new char[strlen(pN) + 1];
	strcpy_s(pName, strlen(pN) + 1, pN);
	num = n;
	fuel = f;
	cout << pName << "を作成しました。\n";
}

Car::~Car() {
	cout << pName << "を破棄します。\n";
	delete[] pName;
}

void Car::show() {
	cout << "車のナンバーは" << num << "です。\n";
	cout << "燃費は" << fuel << "km/Lです。\n";
	cout << "名前は" << pName << "です。\n";
}

// Carクラスの利用
int main() {
	Car car1("mycar", 1111, 23.3);
	car1.show();

	return 0;
}
```

## オブジェクトの初期化や代入について
先ほどのコードに関して
```diff_c
Car car1 = mycar;
```
mycarの値を使ってcar1を初期化
```diff_c
Car car2;
car2 = mycar;
```
mycarの値をcar2に代入する場合はmycarのメンバがcar1,car2の両方にコピーされます。
各オブジェクトを別のものとして扱いたい場合でもmycarのpNameとcar1,car2のpNameが同じメモリをさすようになってしまいます。

そこで、各メンバ用のメモリを確保するとともに、初期化と代入時に正しくメモリの確保と解放が行われるようにクラスを記述します。

```diff_c
#include <iostream>
#include <cstring> // strncpy_s, strcat_sを使うため
using namespace std;

// Carクラスの宣言
class Car {
private:
    int num;
    double fuel;
    char* pName;

public:
    Car(const char* pN, int n, double f);
    ~Car(); // デストラクタの宣言
    Car(const Car& c); // コピーコンストラクタの宣言
    Car& operator=(const Car& c); // 代入演算子のオーバーロード
};

// Carクラスメンバ関数の定義
Car::Car(const char* pN, int n, double f) {
    pName = new char[strlen(pN) + 1];
    strncpy_s(pName, strlen(pN) + 1, pN, strlen(pN));
    pName[strlen(pN)] = '\0'; // 終端文字の追加
    num = n;
    fuel = f;
    cout << pName << "を作成しました。\n";
}

Car::~Car() {
    cout << pName << "を破棄します。\n";
    delete[] pName;
}

// コピーコンストラクタ
Car::Car(const Car& c) {
    cout << c.pName << "で初期化します。\n";
    pName = new char[strlen(c.pName) + strlen("のコピー1") + 1];
    strncpy_s(pName, strlen(c.pName) + 1, c.pName, strlen(c.pName));
    strcat_s(pName, strlen(c.pName) + strlen("のコピー1") + 1, "のコピー1"); // 文字列連結
    num = c.num;
    fuel = c.fuel;
}

// 代入演算子のオーバーロード
Car& Car::operator =(const Car& c) {
    cout << pName << "に" << c.pName << "を代入します。\n";
    if (this != &c) { // 自己代入でないことを確認
        delete[] pName;
        pName = new char[strlen(c.pName) + strlen("のコピー2") + 1];
        strncpy_s(pName, strlen(c.pName) + 1, c.pName, strlen(c.pName));
        strcat_s(pName, strlen(c.pName) + strlen("のコピー2") + 1, "のコピー2"); // 文字列連結
        num = c.num;
        fuel = c.fuel;
    }
    return *this;
}

// Carクラスの利用
int main() {
    Car mycar("mycar", 1111, 23.3); 
    Car car1 = mycar;                
    Car car2("car2", 0, 0);          
    car2 = mycar;                    

    return 0;
}

```

このコードではコピーコンストラクタと代入演算子の定義を行うことで、それぞれのオブジェクトのメンバpNameが異なるメモリをさすようになります。



# 仮想関数の定義
基本クラスのメンバ関数を宣言する際にvirtualという指定をつけておくことで、新しく派生クラスで定義されたほうのメンバ関数が呼び出されます（オーバーライドが行われる）。オブジェクトの型に応じて適切なメンバ関数が呼び出されるという特徴があります。

```diff_c
#include <iostream>
using namespace std;

// Carクラスの宣言
class Car{
	protected:
		int num;
		double fuel;
	public:
		Car();
		void setCar(int n, double f);
		virtual void show();
};

//RacingCarクラスの宣言
class RacingCar : public Car {
	private:
		int course;
	public:
		RacingCar();
		void setCourse(int c);
		void show();
};

// Carクラスメンバ関数の定義
Car::Car() {
	num = 0;
	fuel = 0.0;
	cout << "車を作成しました。\n";
}

void Car::setCar(int n, double f){
	num = n;
	fuel = f;
	cout << "ナンバーを" << num << "\n";
	cout << "燃費を" << num << "km/ Lにしました。\n";
}

void Car::show() {
	cout << "車のナンバーは" << num << "です。\n";
	cout << "燃費は" << fuel << "km/Lです。\n";
}

// RacingCarクラスメンバ関数の定義
RacingCar::RacingCar() {
	course = 0;
	cout << "レーシングカーを作成しました。\n";
}

void RacingCar::setCourse(int c) {
	course = c;
	cout << "コース番号を" << course << "にしました。\n";
}

void RacingCar::show() {
	cout << "レーシングカーのナンバーは" << num << "です。\n";
	cout << "燃費は" << fuel << "km/Lです。\n";
	cout << "コース番号は" << course << "です。\n";
}

int main() {
	Car* pCars[2];

	Car car1;
	RacingCar rccar1;

	pCars[0] = &car1;
	pCars[0]->setCar(1111, 25.4);

	pCars[1] = &rccar1;
	pCars[1]->setCar(2222, 21.3);

	for (int i = 0; i < 2; i++) {
		pCars[i]->show();
	}

	return 0;
}

```

# 例外処理
エラーなどが起きた場合にある値を例外として送出する処理を行います。

```diff_c
#include <iostream>
#include <string>

using namespace std;

int main() {
    int num;
    cout << "1～３までの数を入力してください。\n";
    cin >> num;

    try {
        if (num < 1 || num > 3) {
            throw std::invalid_argument("1から3までの数値を入力してください。");
        }

        cout << num << "です。\n";
    }
    catch (const std::exception& e) {
        cerr << "エラー: " << e.what() << endl;
        return 1;
    }

    return 0;
}
```

# 出力幅の指定
標準ライブラリ```<iostream>```にはさまざまな機能があります。ここではwidth()関数を使うことで出力幅を指定できます。

```diff_c
#include <iostream>
#include <string>

using namespace std;

int main() {
	for (int i = 0; i <= 5; i++) {
		cout.width(5);
		cout << i;
	}
	cout << "\n";
}
```

# マニピュレータ
```\n```の代わりに```endl```を使って改行することができます。

```diff_c
#include <iostream>
#include <string>

using namespace std;

int main() {
	cout << "おはようございます" << endl;
	cout << "こんばんは" << endl;
}
```

# 参考文献

この記事は以下の情報を参考にして執筆しました。
[やさしいC++ 第4版 (「やさしい」シリーズ) ](https://www.amazon.co.jp/%E3%82%84%E3%81%95%E3%81%97%E3%81%84C-%E7%AC%AC4%E7%89%88-%E3%80%8C%E3%82%84%E3%81%95%E3%81%97%E3%81%84%E3%80%8D%E3%82%B7%E3%83%AA%E3%83%BC%E3%82%BA-%E9%AB%98%E6%A9%8B-%E9%BA%BB%E5%A5%88/dp/4797370998)