C++ のクラスとオブジェクト (6)

1. コンストラクターについての話

1.1. コンストラクター本体の割り当て

オブジェクトを作成するとき、コンパイラはコンストラクターを呼び出して、オブジェクト内の各メンバー変数に適切な初期値を与えます。

class Date
{
    
    
public:
	Date(int year, int month, int day)//构造函数
	{
    
    
		_year = year;
		_month = month;
		_day = day;
	}

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

上記のコンストラクタが呼び出された後、オブジェクトにはすでに初期値が存在しますが、それはオブジェクト内のメンバ変数の初期化とは言えず、コンストラクタ本体内のステートメントは初期化ではなく初期値の代入とのみ呼ばれます。初期化は 1 回しか初期化できず、コンストラクター本体は複数回割り当てることができるためです

1.2. 初期化リスト

初期化リスト:コロンで始まり、データ メンバーのカンマ区切りのリストが続き、各「メンバー変数」の後に括弧で囲まれた初期値または式が続きます。

class Date
{
    
    
public:
	Date(int year, int month, int day)//初始化列表
		:_year(year)
		,_month(month)
		,_day(day)
	{
    
    }

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

【知らせ】

  1. 各メンバー変数は、初期化リストに1 回だけ出現できます(初期化は 1 回だけ初期化できます)。
  2. クラスには次のメンバーが含まれており、初期化のために初期化リストに配置する必要があります。
  • 参照メンバー変数
  • const メンバー変数
  • カスタム タイプのメンバー タイプ (クラスにはデフォルトのコンストラクターがありません)
class A
{
    
    
public:
A(int a)
:_a(a)
{
    
    }
private:
int _a;
};
class B
{
    
    
public:
B(int a, int ref)
:_aobj(a)
,_ref(ref)
,_n(10)
{
    
    }
private:
A _aobj;  // 没有默认构造函数
int& _ref;  // 引用
const int _n; // const
};
  1. 初期化リストの初期化を使用するようにしてください。初期化リストを使用するかどうかに関係なく、カスタム型のメンバー変数の場合は、最初に初期化リストを使用して初期化する必要があります。
class Time
{
    
    
public:
	Time(int hour = 0)
		:_hour(hour)
	{
    
    
		cout << "Time()" << endl;
	}
private:
	int _hour;
};
class Date
{
    
    
public:
	Date(int day)
	{
    
    }
private:
	int _day;
	Time _t;
};
int main()
{
    
    
	Date d(1);
}
  1. クラス内でメンバー変数が宣言される順序は、初期化リスト内の順序に関係なく、初期化リスト内で初期化される順序になります。
class A
{
    
    
public:
	A(int a)
		:_a1(a)
		, _a2(_a1)
	{
    
    }

	void Print() {
    
    
		cout << _a1 << " " << _a2 << endl;
	}
private:
	int _a2;
	int _a1;
};
int main() {
    
    
	A aa(1);
	aa.Print();
}

//A.输出1  1
//B.程序崩溃
//C.编译不通过
//D.输出1  随机值 right

1.3. 明示的なキーワード

コンストラクターはオブジェクトを構築して初期化するだけでなく、1 つのパラメーターの型変換の機能や、最初のパラメーターを除くデフォルト値を持つコンストラクターもあります。

// 单参数的构造函数
class A
{
    
    
public:
	explicit A(int a)
		:_a1(a)
	{
    
    
		cout << "A(int a)" << endl;
	}

	//explicit A(int a1, int a2)
	A(int a1, int a2)
		:_a1(a1)
		, _a2(a2)
	{
    
    }

	A(const A& aa)
		:_a1(aa._a1)
	{
    
    
		cout << "A(const A& aa)" << endl;
	}

private:
	int _a2;
	int _a1;
};

int main()
{
    
    
	// 单参数构造函数 C++98
	A aa1(1);  // 构造函数
	//A aa2 = 1; // 隐式类型转换   构造+拷贝+优化->构造
	//const A& ref = 10;


	// 多参数构造函数 C++11
	A aa2(1, 1);
	A aa3 = {
    
     2, 2 };
	const A &ref = {
    
     2, 2 };

	int i = 1;
	double d = i; // 隐式类型转换

	return 0;
}
class Date
{
    
    
public:
	// 1. 单参构造函数,没有使用explicit修饰,具有类型转换作用
// explicit修饰构造函数,禁止类型转换---explicit去掉之后,代码可以通过编译
	explicit Date(int year)
	//Date(int year)
		:_year(year)
	{
    
    }
	/*
	// 2. 虽然有多个参数,但是创建对象时后两个参数可以不传递,没有使用explicit修饰,具
	有类型转换作用
	// explicit修饰构造函数,禁止类型转换
	explicit Date(int year, int month = 1, int day = 1)
	: _year(year)
	, _month(month)
	, _day(day)
	{}
	*/
	Date& operator=(const Date& d)
	{
    
    
		if (this != &d)
		{
    
    
			_year = d._year;
			_month = d._month;
			_day = d._day;
		}
		return *this;
	}
private:
	int _year;
	int _month;
	int _day;
};
void Test()
{
    
    
	Date d1(2022);
	// 用一个整形变量给日期类型对象赋值
	// 实际编译器背后会用2023构造一个无名对象,最后用无名对象给d1对象进行赋值
	d1 = 2023;
	// 将1屏蔽掉,2放开时则编译失败,因为explicit修饰构造函数,禁止了单参构造函数类型转
	//换的作用
}

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


ここに画像の説明を挿入

上記のコードは可読性があまり良くありません。コンストラクターを明示的に変更すると、コンストラクターの暗黙的な変換が禁止されます。

2. 静的メンバー

2.1. コンセプト

static として宣言されたクラス メンバーはクラスの static メンバーと呼ばれ、static で変更されたメンバー変数はstatic メンバー変数、 static で変更されたメンバー関数はstatic メンバー関数と呼ばれます静的メンバー変数はクラスの外部で初期化する必要があります。

インタビューの質問: クラスを実装し、プログラム内で作成されるクラス オブジェクトの数を計算します。

【方法1】

//方法1:全局变量统计
int count = 0;
using  std::cout;
using std::endl;
class Date
{
    
    
public:
	Date(int year=0)
		:_year(year)
	{
    
    
		++count;
	}
	Date(const Date& d)
		:_year(d._year)
	{
    
    
		++count;
	}

private:
	int _year;
};

int main()
{
    
    
	Date d1;
	Date d2(2023);
	Date d3 = d1;
	cout << count << endl;
	return 0;
}

グローバル変数を定義することの欠点:

  1. グローバル変数は変数名の汚染や名前の重複を引き起こしやすい
  2. コンストラクター呼び出しを作成およびコピーするときに count の値を変更するだけでなく、他の場所でも count の値を変更すると、結果が不正確になる可能性があります。

【方法2】

//方法2:static成员变量进行统计
class Date
{
    
    
public:
	Date(int year = 0)
		:_year(year)
	{
    
    
		++_count;
	}
	Date(const Date& d)
		:_year(d._year)
	{
    
    
		++_count;
	}

	static int GetCount()
	{
    
    
		return _count;
	}

private:
	int _year;
	static int _count;
};
int Date::_count = 0;

int main()
{
    
    
	Date d1;
	Date d2(2023);
	Date d3 = d1;
	cout << Date::GetCount() << endl;
	return 0;
}

静的メンバー変数を使用する場合の注意点:

  1. 初期化はグローバルで行う必要があります
  2. クラス外のプライベートメンバー変数にアクセスできない問題を解決するには、_count の値を取得する関数を実装する必要があります。
  3. _count の値を取得しないようにするには、クラスを定義し、関数を静的型に設定する必要があります。クラスの外では、クラス名を通じて関数に直接アクセスできます。

2.2. 特徴

  1. 静的メンバーはすべてのクラス オブジェクトで共有され、特定のオブジェクトに属さず、静的領域に格納されます。
  2. 静的メンバー変数はクラスの外部で定義する必要があり、定義時に static キーワードは追加されず、クラス内でのみ宣言されます。
  3. クラスの静的メンバーには、class name::static memberまたはobject.static memberを使用してアクセスできます。
  4. 静的メンバー関数には非表示の this ポインターがないため、非静的メンバーにアクセスできません。
  5. 静的メンバーはクラスのメンバーでもあり、public、protected、および private のアクセス修飾子によって制限されます。

3.フレンド機能

問題: ここで、operator<< をオーバーロードしようとしましたが、operator<< をメンバー関数にオーバーロードする方法がないことがわかりました。cout の出力ストリーム オブジェクトと暗黙的な this ポインターが最初のパラメーターの位置をプリエンプトしているためです。this ポインターのデフォルトは、左側のオペランドである最初のパラメーターです。ただし、実際の使用では、cout が通常使用される最初の仮パラメータ オブジェクトである必要があります。したがって、operator<< をグローバル関数にオーバーロードします。ただし、クラス外のメンバーにアクセスする方法がなくなるため、問題を解決するには友人が必要です。オペレーター>>同じ理由です。

class Date
{
    
    
public:
	Date(int year, int month, int day)
		: _year(year)
		, _month(month)
		, _day(day)
	{
    
    }
	// d1 << cout; -> d1.operator<<(&d1, cout); 不符合常规调用
// 因为成员函数第一个参数一定是隐藏的this,所以d1必须放在<<的左侧
	ostream& operator<<(ostream& _cout)
	{
    
    
		_cout << _year << "-" << _month << "-" << _day << endl;
		return _cout;
	}
private:
	int _year;
	int _month;
	int _day;
};

friends関数はクラスのメンバに直接アクセスできる関数で、クラスの外で定義されクラスに属さない通常の関数ですが、クラス内で宣言する必要があり、宣言時にfriendキーワードを追加します。

class Date
{
    
    
	friend ostream& operator<<(ostream& _cout, const Date& d);
	friend istream& operator>>(istream& _cin, Date& d);
public:
	Date(int year = 1900, int month = 1, int day = 1)
		: _year(year)
		, _month(month)
		, _day(day)
	{
    
    }
private:
	int _year;
	int _month;
	int _day;
};
ostream& operator<<(ostream& _cout, const Date& d)
{
    
    
	_cout << d._year << "-" << d._month << "-" << d._day;
	return _cout;
}
istream& operator>>(istream& _cin, Date& d)
{
    
    
	_cin >> d._year;
	_cin >> d._month;
	_cin >> d._day;
	return _cin;
}
int main()
{
    
    
	Date d;
	cin >> d;
	cout << d << endl;
	return 0;
}

【イラスト】

  • フレンド関数は、クラスのプライベートおよび保護されたメンバーにアクセスできますが、クラスのメンバー関数にはアクセスできません。
  • フレンド関数は const で変更できません
  • フレンド関数は、クラスアクセス修飾子による制限を受けず、クラス定義内のどこでも宣言できます。
  • 関数は複数のクラスのフレンド関数になることができます
  • フレンド関数を呼び出す原理は通常の関数と同じです

3.2. フレンドクラス

フレンド クラスのすべてのメンバー関数は、別のクラスのフレンド関数になることができ、別のクラスの非パブリック メンバーにアクセスできます。

  • 友情関係は一方向であり、交換可能ではありません。
    たとえば、上記の Time クラスと Date の場合、Time クラス内で Date クラスをその友達クラスとして宣言すると、Time クラスのプライベート メンバー変数に直接アクセスできます。 Date クラスにありますが、Time クラスの Date クラスのプライベート メンバー変数にアクセスすることはできません。
  • 友人関係は伝わらない
    CがBの友人、BがAの友人であれば、CはAの友人とは言えない
  • 友人関係は継承できない
class Time
{
    
    
	friend class Date; //声明日期类为时间类的友元类,则在日期类中就直接访问Time类
	//中的私有成员变量
public:
	Time(int hour = 0, int minute = 0, int second = 0)
		: _hour(hour)
		, _minute(minute)
		, _second(second)
	{
    
    }
private:
	int _hour;
	int _minute;
	int _second;
};
class Date
{
    
    
public:
	Date(int year = 1900, int month = 1, int day = 1)
		: _year(year)
		, _month(month)
		, _day(day)
	{
    
    }
	void SetTimeOfDate(int hour, int minute, int second)
	{
    
    
		// 直接访问时间类私有的成员变量
		_t._hour = hour;
		_t._minute = minute;
		_t._second = second;
	}
private:
	int _year;
	int _month;
	int _day;
	Time _t;
};

4. 内部クラス

概念: クラスが別のクラス内で定義されている場合、その内部クラスは内部クラスと呼ばれます。内部クラスは独立したクラスであり、外部クラスに属しておらず、ましてや外部クラスのオブジェクトを通じて内部クラスのメンバーにアクセスすることはできません。外部クラスには、内部クラスへの特権アクセスがありません。

注: 内部クラスは外部クラスのフレンド クラスであり、内部クラスは外部クラスのオブジェクト パラメーターを通じて外部クラスのすべてのメンバーにアクセスできます。しかし、外側の階級は内側の階級の友人ではありません。

特性:

  1. 内部クラスは、外部クラスの public、protected、および private で定義できます。
  2. 内部クラスは、外部クラスのオブジェクト/クラス名を使用せずに、外部クラスの静的メンバーに直接アクセスできることに注意してください。
  3. sizeof(outer class) = 外部クラス、内部クラスとは関係ありません
class A
{
    
    
private:
	static int k;
	int h;
public:
	class B // B天生就是A的友元
	{
    
    
	public:
		void foo(const A& a)
		{
    
    
			cout << k << endl;//OK
			cout << a.h << endl;//OK
		}
	};
};
int A::k = 1;
int main()
{
    
    
	A::B b;
	b.foo(A());

	return 0;
}

5. 匿名オブジェクト

ここに画像の説明を挿入

6. オブジェクトのコピー時のコンパイラの最適化

ここに画像の説明を挿入
パラメーターを渡して値を返すプロセスでは、通常、コンパイラーはオブジェクトのコピーを減らすためにいくつかの最適化を行いますが、これはシナリオによっては非常に役立ちます。


class A
{
    
    
public:
	A(int a=0)
		:_a(a)
	{
    
    
		cout << "A(int a=0)" << endl;
	}
	A(const A& aa)
		:_a(aa._a)
	{
    
    
		cout << "A(const A& aa)" << endl;
	}
	A& operator=(const A& aa)
	{
    
    
		cout << "A& operator=(const A& aa)" << endl;
		if (this != &aa)
		{
    
    
			_a = aa._a;
		}
		return *this;
	}

	~A()
	{
    
    
		cout << "~A()" << endl;
	}

private:
	int _a;
};

void f1(A aa)
{
    
    }

void f2(const A& aa)
{
    
    }

A f2()
{
    
    
	A aa;
	return aa;
}

int main()
{
    
    
	// 传值传参
	A aa1;
	f1(aa1);
	cout <<"-------------------"<< endl;
	//传引用传参
	A aa2;
	f2(aa2);
	cout << "-------------------" << endl;
	// 隐式类型,连续构造+拷贝构造->优化为直接构造
	f1(1);
	cout << "-------------------" << endl;
	// 一个表达式中,连续拷贝构造+拷贝构造->优化一个拷贝构造
	A aa3 = f2();
	cout << "-------------------" << endl;
	// 一个表达式中,连续拷贝构造+赋值重载->无法优化
	aa1 = f2();
	cout << endl;


	cout << "-------------------" << endl;
	return 0;
}

ここに画像の説明を挿入
ここに画像の説明を挿入
ここに画像の説明を挿入

7. クラスとオブジェクトを再理解する

クラスは、特定のタイプのエンティティ (オブジェクト) の記述であり、オブジェクトのプロパティとメソッドを記述します。記述が完了すると、新しいカスタム タイプが形成され、このカスタム タイプのオブジェクトを使用して特定のタイプをインスタンス化できます。

おすすめ

転載: blog.csdn.net/zxj20041003/article/details/130543470