0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ひたすらコードで覚えるC++【#7】

Last updated at Posted at 2024-11-12

はじめに

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

使用する環境

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

コンストラクタの定義

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

#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;
}

クラスの拡張(継承)

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

#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;
}

メモリの確保および解放

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

#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;
}

オブジェクトの初期化や代入について

先ほどのコードに関して

Car car1 = mycar;

mycarの値を使ってcar1を初期化

Car car2;
car2 = mycar;

mycarの値をcar2に代入する場合はmycarのメンバがcar1,car2の両方にコピーされます。
各オブジェクトを別のものとして扱いたい場合でもmycarのpNameとcar1,car2のpNameが同じメモリをさすようになってしまいます。

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

#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という指定をつけておくことで、新しく派生クラスで定義されたほうのメンバ関数が呼び出されます(オーバーライドが行われる)。オブジェクトの型に応じて適切なメンバ関数が呼び出されるという特徴があります。

#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;
}

例外処理

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

#include <iostream>
#include <string>

using namespace std;

int main() {
    int num;
    cout << "1~3までの数を入力してください。\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()関数を使うことで出力幅を指定できます。

#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を使って改行することができます。

#include <iostream>
#include <string>

using namespace std;

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

参考文献

この記事は以下の情報を参考にして執筆しました。
やさしいC++ 第4版 (「やさしい」シリーズ)

0
0
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?