C++ クラスとオブジェクト (5) - ストリーム演算子のオーバーロード、const、およびアドレス取得

目次

1.ストリーム出力

1. 単一の出力を実現する

2. 連続出力を実現

2. ストリーム入力

  要約:

3.constの変更

4.アドレスを取得する

.Get address および const get address 演算子のオーバーロード

5. [ ] 演算子のオーバーロード


1.ストリーム出力

1. 単一の出力を実現する

日付クラスを作成します。

class Date
{
public:
	Date(int year = 1, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
private:
	int _year;
	int _month;
	int _day;
};

以前は、日付クラスで日付を出力したい場合は、クラス内に出力用のメンバー関数を作成する必要がありました。

	void Print()
	{
		cout<< _year << "年" << _month << "月" << _day << "日" << endl;
	}

組み込み型の場合は、 cout<< を直接使用して値を出力できます。

ストリーム抽出 << をオーバーロードして、組み込み型のメンバー値を出力することもできます。まず、cout の起源を理解しましょう。

  • iostream は、入出力用のストリーム クラスの定義を含む C++ 標準ライブラリのヘッダー ファイルです。 2 つの基本クラス  と  は iostream ヘッダー ファイルで定義されており、それぞれ入力操作と出力操作に使用されます。 istreamostream

  • ostream は、 iostream ヘッダー ファイルで定義されたクラスであり、出力ストリームの基本クラスです。 ostream クラスは、出力操作のための基本的な関数とインターフェイスを提供します。たとえば、 << 演算子はデータをストリームに出力するために使用されます。

  • cout は、標準出力ストリーム オブジェクトである ostream クラスのオブジェクトです。 cout オブジェクトは << 演算子を使用して、標準出力デバイス (通常はコンソール) にデータを出力できます。

iostream はヘッダー ファイル、 ostream は iostream で定義された出力ストリームの基本クラス、 cout は です。 a> クラス a>  演算子を使用すると、データをコンソールに簡単に出力できます。  オブジェクトと ostream のオブジェクト。標準出力デバイスにデータを出力するために使用されます。 cout<<

 次に、クラスに << オーバーロードを実装します。

	void operator<<(ostream& out)
	{
		out << _year << "年" << _month << "月" << _day << "日" << endl;
	}

ostream& は、出力ストリーム オブジェクトへの参照を表す参照型です。 ostream& 参照型を使用すると、出力ストリーム オブジェクトを演算子オーバーロードに渡すことができます。

 演算子のオーバーロード << を使用する場合は、次の形式を使用する必要があります。

d1 << cout;//或者d1.operator<<(cout);

演算子オーバーロード << の最初のパラメーターは左側のオペランドであり、2 番目のパラメーターは右側のオペランドです。

この形式でも希望する結果を出力できますが、cout<

これを変更して、出力ストリーム オブジェクトへの参照を最初のパラメーターとして、日付クラス オブジェクトへの参照を 2 番目のパラメーターとして取得し、<< 演算子をグローバル関数としてオーバーロードできます。

void operator<<(ostream& out, const Date& d)
{
	out << d._year << "年" << d._month << "月" << d._day << "日" << endl;
}

同時に、グローバル << 演算子オーバーロードが日付クラス オブジェクト d のプライベート メンバー変数にアクセスできるようにするために、日付クラスにフレンド関数宣言を作成して、そのメンバーにアクセスできるようにします。オブジェクト。

friend void operator<<(ostream& out, const Date& d);

テストしてみましょう: 

class Date
{
	friend void operator<<(ostream& out, const Date& d);
public:
	Date(int year = 1, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void Print()
	{
		cout<< _year << "年" << _month << "月" << _day << "日" << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
void operator<<(ostream& out, const Date& d)
{
	out << d._year << "年" << d._month << "月" << d._day << "日" << endl;
}

void Test()
{
	Date a(2023, 11, 24);
	a.Print();
	cout << a;
}

int main()
{
	Test();
	return 0;
}

 演算子 << のオーバーロードが正常に実装されました。

 

2. 連続出力を実現

以下のような連続出力だったらどうでしょうか?

void Test2()
{
	Date a(2023, 11, 24);
	Date b(2023, 11, 25);
	cout << a << b << endl;
}

この時点で、コンパイラはエラーを報告します。​ 

 

 連続出力の形式をサポートするには、<< オーバーロードに戻り値を追加する必要があります。

ostream& operator<<(ostream& out, const Date& d)
{
	out << d._year << "年" << d._month << "月" << d._day << "日" << endl;
	return out;
}

同時に、フレンド関数の宣言も変更する必要があります。

friend ostream& operator<<(ostream& out, const Date& d);

 テストを受けてください:

class Date
{
	friend ostream& operator<<(ostream& out, const Date& d);
public:
	Date(int year = 1, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void Print()
	{
		cout<< _year << "年" << _month << "月" << _day << "日" << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
ostream& operator<<(ostream& out, const Date& d)
{
	out << d._year << "年" << d._month << "月" << d._day << "日" << endl;
	return out;
}
void Test2()
{
	Date a(2023, 11, 24);
	Date b(2023, 11, 25);
	cout << a << b << endl;
}
int main()
{
	Test2();
	return 0;
}

連続出力を正常に達成しました: 

2. ストリーム入力

iostreamistream 、 cin は、C++ での入力に使用される関連クラスとオブジェクトです。

  • iostream は、入出力用のストリーム クラスの定義を含む C++ 標準ライブラリのヘッダー ファイルです。 2 つの基本クラス  と  は iostream ヘッダー ファイルで定義されており、それぞれ入力操作と出力操作に使用されます。 istreamostream

  • istream は、 iostream ヘッダー ファイルで定義されたクラスであり、入力ストリームの基本クラスです。 istream クラスは、入力操作のための基本的な関数とインターフェイスを提供します。たとえば、 >> 演算子はストリームからデータを読み取るために使用されます。

  • cin は、標準入力ストリーム オブジェクトである istream クラスのオブジェクトです。 cin オブジェクトは、 >> 演算子を使用して標準入力デバイス (通常はキーボード) からデータを読み取ることができます。

概要: iostream はヘッダー ファイル、istream は iostream で定義された入力ストリームの基本クラス、< /span>  演算子を使用すると、キーボードから簡単にデータを入力できます。  オブジェクトと  のオブジェクトであり、標準入力デバイスからデータを読み取るために使用されます。 cin はクラス istreamcin>>

 次に、ストリーム抽出>>演算子のオーバーロードを実装します。これはストリーム挿入<<の実装と同じです。

istream& operator>>(istream& in, Date& d)
{
	in >> d._year >> d._month >> d._day;
	return in;
}

 テストを受けてください

class Date
{
	friend ostream& operator<<(ostream& out, const Date& d);
	friend istream& operator>>(istream& in, Date& d);

public:
	Date(int year = 1, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}

	void Print()
	{
		cout<< _year << "年" << _month << "月" << _day << "日" << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
istream& operator>>(istream& in, Date& d)
{
	in >> d._year >> d._month >> d._day;
	return in;
}
void Test3()
{
	Date d;
	cin >> d;
	cout << d;
}
int main()
{
	Test3();
	return 0;
}

ストリーム抽出演算子のオーバーロードが正常に実装されました。​ 

要約:

クラスの宣言と定義がファイルに分割されている場合、通常、ストリーム抽出演算子とストリーム挿入演算子のオーバーロードをヘッダー ファイル内のインライン関数として配置します。これにより、リンク プロセスが不要になり、コンパイル プロセス中にアドレスを呼び出すことができるようになります。

inline ostream& operator<<(ostream& out, const Date& d)
{
	out << d._year << "年" << d._month << "月" << d._day << "日" << endl;
	return out;
}

inline istream& operator>>(istream& in, Date& d)
{
	in >> d._year >> d._month >> d._day;
	return in;
}
  • C++ では、メンバー関数がクラス内で直接定義されている場合、それはインライン関数とみなされます。インライン関数の定義と宣言はクラス定義内にあるため、コンパイラは関数呼び出しを通じて関数を実行する代わりに、関数のコードを呼び出し位置に挿入できます。
  • 通常、短い関数はインライン関数の呼び出しにかかるコストが小さく、関数呼び出しによる追加のオーバーヘッドを回避できるため、インライン関数として適しています。このような関数をクラス内で定義すると、その宣言と定義を簡単にまとめることができ、コードの可読性と保守性が向上します。
  • ただし、クラス内で定義されたメンバー関数をインライン関数として扱うかどうかは、最終的にはコンパイラーの決定であることに注意してください。コンパイラは、関数の複雑さ、呼び出し頻度などの要素に基づいて、関数をインライン展開するかどうかを決定する場合があります。
  • つまり、クラス内で定義された短い関数はインライン関数とみなすことができ、コードの実行効率と可読性を向上させることができます。ただし、最終的にはインライン展開するかどうかを決定するのはコンパイラーです。

3.constの変更

const 修飾された「メンバー関数」は、const メンバー関数と呼ばれます。const 修飾されたクラス メンバー関数は、実際にはメンバー関数内で暗黙的に this ポインターを変更し、メンバー関数内でクラスのメンバーを変更できないことを示します。

次のコードを見てみましょう。

class A {
public:
	void Print() 
    {
		cout << _a << endl;
	}
private:
	int _a = 1;
};
int main()
{
	A aa;
	aa.Print();
	return 0;
}

成功した出力: 

aaをconstで修飾しても大丈夫でしょうか?​ 

const A aa;

プログラムはコンパイル後にエラーを報告します。 

 

これは、許可増幅の問題によるものです。

 Print 関数のパラメータは A*this で、aa の型は const A* であるため、aa が Print 関数を呼び出すと権限の増幅が発生し、権限が変換される場合は this ポインタの前にある const で変更されます。 . これも禁止されています。this ポインタを変更することはできません。

今回は新しい間接的な方法があります。

  • 文法では const メンバー関数と呼ぶことが規定されており、const は *this を修飾するため、this の型は const A* 型になります。
  • メンバー関数が内部でメンバー変数を変更しない場合は、const を追加するのが最適です。const オブジェクトと通常のオブジェクトの両方を呼び出すことができます。

 現時点では、このファイルの Date.h ファイルにC++ クラスとオブジェクト (4)-Date クラス の実装を実装できます。記事 一部のメンバー関数が const で変更されました。

#include <iostream>
#include <assert.h>
using namespace std;

class Date {
public:
	Date(int year = 0, int month = 0, int day = 0);
	void Print();
	int GetMonthDay(int year, int month) const;

	bool operator==(const Date& d) const;
	bool operator!=(const Date& d) const;
	bool operator< (const Date& d) const;
	bool operator<=(const Date& d) const;
	bool operator> (const Date& d) const;
	bool operator>=(const Date& d) const;

	Date& operator+=(int day);
	Date operator+(int day) const;
	
	Date& operator-=(int day);

	// d1 - 100
	Date operator-(int day);

	// d1 - d2;
	int operator-(const Date& d) const;

	// ++d1
	Date& operator++();

	// d1++
	Date operator++(int);
	
	Date& operator--();

	Date operator--(int);

private:
	int _year;
	int _month;
	int _day;
};

4.アドレスを取得する

.Get address および const get address 演算子のオーバーロード

これら 2 つのデフォルトのメンバー関数は通常、再定義する必要はなく、コンパイラによってデフォルトで生成されます。

class Date
{
public :
    Date* operator&()
    {
        return this ; 
    }
    const Date* operator&()const
    {
        return this ;
    }
private :
    int _year ; // 年
    int _month ; // 月
    int _day ; // 日
};
これら 2 つの演算子は通常、オーバーロードする必要はありません。コンパイラによって生成されるデフォルトのアドレス取得オーバーロードを使用するだけです。特別な状況でのみ必要です。
オーバーロードするには、たとえば 他のユーザーが指定されたコンテンツを取得できるようにします。

5. [ ] 演算子のオーバーロード

class Array
{
public:
	int& operator[](int i)
	{
		assert(i < 10);

		return _a[i];
	}

	const int& operator[](int i) const
	{
		assert(i < 10);

		return _a[i];
	}
private:
	int _a[10];
	int _size;
};

void Func(const Array& aa)
{
	for (int i = 0; i < 10; ++i)
	{
		//aa[i]++;
		cout << aa[i] << " ";
	}
}

まず、Arrayクラスを見てみましょう:

  • Array クラスは、サイズ 10 のプライベート整数配列_aとプライベート整数変数_sizeを定義します。

  • Array クラスは [] 演算子をオーバーロードして、 Array クラスのオブジェクトを通常の配列と同じように使用できるようにします。

  • operator[]関数には 2 つのバージョンがあり、1 つは非定数バージョン、もう 1 つは定数バージョンです。非定数バージョンは変更可能な参照を返し、定数バージョンは変更不可能な定数参照を返します。

  • operator[] 関数では、assert 関数を使用して、インデックス i が 10 未満であることを確認します。配列が範囲外になるのを防ぎます。

次に、Func 関数を見てみましょう

  • Func 関数は、Array クラスの定数参照をパラメータとして受け入れます。パラメーターは定数参照であるため、関数内のパラメーターの値を変更することはできません。

  • Func 関数にはループがあり、ループ変数iは 0 から 9 まで移動します。ループ本体では、aa[i]++ が最初にコメント化されていますが、これは aa が定数参照であり、その値を変更できないためです。次に、cout を使用して aa[i] の値を出力し、スペースを出力します。

たとえば、 クラスのオブジェクトを作成し、 配列を 0 から 9 に初期化するとします。 、 を呼び出すと、 がコンソールに表示されます。 Arraya_aFunc(a)0 1 2 3 4 5 6 7 8 9

おすすめ

転載: blog.csdn.net/m0_73800602/article/details/134588898