目次
1. コンストラクターについてもう一度話します
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;
};
パラメータなしでオブジェクトをインスタンス化したい場合でも、この関数がそれを処理できるように、コンストラクターにすべてのデフォルトを与えるようにしています。
上記コンストラクタ呼び出し後、オブジェクトにはすでに初期値が設定されていますが、オブジェクト内のメンバ変数の初期化とは言えず、コンストラクタ本体内の文は初期値ではなく初期値代入としか言えません。 。初期化は 1 回しか行えず、コンストラクター本体は複数回割り当てることができるためです。
1.2 初期化リスト
初期化リスト:コロンで始まり、データ メンバーのカンマ区切りのリストが続き、各「メンバー変数」の後に括弧で囲まれた初期値または式が続きます。
引き続き Date クラスを見てみましょう。
class Date
{
public:
// 初始化列表
Date(int year, int month, int day)
: _year(year)
, _month(month)
, _day(day)
{}
private:
// 成员变量声明
int _year;
int _month;
int _day;
};
上記のコードは初期化リストの実装です。
1.2.1 初期化リストの意味
初期化子リストは、オブジェクトのメンバー変数が定義される場所です。
一部の変数は、定義されている場所でのみ初期化できます。
a. const 変更変数、b. 参照変数、c. カスタム型 (デフォルトのコンストラクターなし)。
class A
{
public:
A(int a)
:_a(a)
{}
private:
int _a;
};
class Date
{
public:
// 初始化列表
Date(int year, int month, int day, int& i)
: _year(year)
, _month(month)
, _day(day)
, _x(1)
, _refi(i)
, _a(1)
{}
private:
// 成员变量的声明
int _year;
int _month;
int _day;
// 定义时必须初始化
const int _x;
int& _refi;
A _a;
};
メンバー変数のデフォルト値は初期化リストに与えられます。初期化リストが組み込み型を明示的に定義していない場合、コンパイラはデフォルト定義を定義してランダムな値を与え、デフォルトを与えて使用する値を初期化します。初期化された値。
定義時に初期化する必要があるメンバー変数について、関数本体で初期値を代入する様子を見てみましょう。
ここで問題が発生するため、初期化子リストはこれに最適です。
知らせ:
1. 各メンバー変数は初期化リストに1 回だけ出現できます(初期化は 1 回だけ初期化できます)
2. クラスには次のメンバーが含まれており、これらのメンバーは初期化のために初期化リストに配置する必要があります:
a. 参照メンバー変数
b. const メンバー変数
c. カスタム型のメンバー (クラスにはデフォルトのコンストラクターがありません)
3. 初期化リストを使用するかどうかに関係なく、カスタム型のメンバー変数については、最初に初期化リストの初期化を使用する必要があるため、初期化リストの初期化を使用してみてください。
4.クラス内でメンバー変数が宣言される順序は、初期化リスト内の順序に関係なく、初期化リスト内で初期化される順序になります。
初期化後、コンストラクター本体で値を割り当てることもできます。
1.3 明示的なキーワード
コンストラクターはオブジェクトを構築して初期化するだけでなく、単一パラメーターの型変換機能や、デフォルト値のない最初のパラメーターを除くデフォルト値を持つコンストラクターの機能も備えています。
class Date
{
public:
Date(int year)
: _year(year)
{}
private:
int _year;
};
int main()
{
Date d1(2023);
// 隐式类型转换
Date d2 = 2023;// 2023调用构造函数生成临时对象,再用临时对象去拷贝构造d2
return 0;
}
2023 は int 型ですが、2023 には暗黙的な型変換があるため、Date 型の d2 に代入してもエラーは報告されません。Hermit 型変換を発生させたくない場合は、コンストラクターの前に明示的な修飾子を追加します。
コンストラクターを明示的に修飾すると、コンストラクターの暗黙的な変換が禁止されます。
拡大:
C++11 では、複数のパラメーターの暗黙的な型変換が許可されています。
class Date
{
public:
Date(int year, int month, int day)
: _year(year)
, _month(month)
, _day(day)
{}
private:
int _year;
int _month;
int _day;
};
int main()
{
// 隐式类型转换
Date d = { 2023, 8, 7 };// 多参数隐式类型转换要用{}将多个参数括起来
const Date& d1 = { 2023, 1, 1 };// 转换时生成的临时变量具有常性,因此需要const修饰
return 0;
}
複数パラメータの暗黙的な型変換では、{} を使用して複数のパラメータを囲む必要があります。
2. 静的メンバー
2.1 問題の導入
クラス A が何個作成され、何個が使用されているか数えてみましょう。
// 创建了多少个对象
int n = 0;
// 正在使用的有多少个对象
int m = 0;
class A
{
public:
A()
{
++n;
++m;
}
A(const A & t)
{
++n;
++m;
}
~A()
{
--m;
}
};
A func(A aa)
{
return aa;
}
int main()
{
A a1;
A a2;
cout << n << " " << m << endl;
A();// 匿名对象,在本行创建并在本行销毁
cout << n << " " << m << endl;
func(a1);
cout << n << " " << m << endl;
return 0;
}
操作結果:
グローバル変数は達成可能であることがわかりますが、グローバル変数は安全ではなく (グローバル変数はグローバルに変更できる)、カプセル化には適さないため、この方法は使用しません。
これを実現するためにメソッドを変更しましょう。
カウンタをクラスに入れてプライベートとして定義しましたが、これだけではまだ十分ではありません。オブジェクトを作成するたびに、n と m が表示されます。目的はオブジェクトがパブリックであるため、静的変更を使用します。この 2 つの質問は問題を解決します。
class A
{
public:
A()
{
++n;
++m;
}
A(const A & t)
{
++n;
++m;
}
~A()
{
--m;
}
//private:
// 创建了多少个对象
static int n;
// 正在使用的有多少个对象
static int m;
};
int A::n = 0;
int A::m = 0;
A func(A aa)
{
return aa;
}
int main()
{
cout << "sizeof(A):" << sizeof(A) << endl;
A a1;
A a2;
cout << A::n << " " << A::m << endl;
A();// 匿名对象,在本行创建并在本行销毁
cout << a1.n << " " << a2.m << endl;
func(a1);
cout << a1.n << " " << a2.m << endl;
return 0;
}
静的変更後、n と m は特定のオブジェクトではなくすべてのオブジェクトの静的メンバー変数であるため、オブジェクト内には存在せず、静的領域に存在します (sizeof は空の種類である 1 として計算されます) )。
第二に、クラスはメンバ変数の宣言のみであり、静的メンバ変数はクラスの外で定義する必要があり、定義時に static を追加する必要はありません。
静的メンバーをプライベートとして定義する場合、n、m にアクセスするための Get インターフェイスを提供する必要があります。
class A
{
public:
A()
{
++n;
++m;
}
A(const A & t)
{
++n;
++m;
}
~A()
{
--m;
}
int GetN()
{
return n;
}
int GetM()
{
return m;
}
private:
// 创建了多少个对象
static int n;
// 正在使用的有多少个对象
static int m;
};
int A::n = 0;
int A::m = 0;
A func(A aa)
{
return aa;
}
int main()
{
A a1;
A a2;
cout << a1.GetN() << " " << a1.GetM() << endl;
A();// 匿名对象,在本行创建并在本行销毁
cout << a1.GetN() << " " << a1.GetM() << endl;
func(a1);
cout << a1.GetN() << " " << a1.GetM() << endl;
return 0;
}
2 つの匿名オブジェクトのみを作成し、n と m を確認したい場合は、オブジェクトを呼び出すことはできません。呼び出すオブジェクトを作成すると、これは n と m に影響するため、関数も static に変更します。それにアクセスするにはクラスフィールドを追加します。
class A
{
public:
A()
{
++n;
++m;
}
A(const A & t)
{
++n;
++m;
}
~A()
{
--m;
}
// 静态成员函数的特点:没有this指针
static int GetN()
{
return n;
}
static int GetM()
{
return m;
}
private:
// 静态成员变量属于所有A对象,属于整个类
// 创建了多少个对象
static int n;
// 正在使用的有多少个对象
static int m;
};
int A::n = 0;
int A::m = 0;
A func(A aa)
{
return aa;
}
int main()
{
A a1;
A a2;
cout << A::GetN() << " " << A::GetM() << endl;
A();// 匿名对象,在本行创建并在本行销毁
cout << A::GetN() << " " << A::GetM() << endl;
func(a1);
cout << A::GetN() << " " << A::GetM() << endl;
return 0;
}
注: this ポインターがないため、静的メンバー関数内では非静的メンバー関数にアクセスできません。一般に、静的に変更されたメンバー変数は、静的に変更されたメンバー関数と組み合わせて使用されます。
2.2 特徴
1.静的メンバーはすべてのクラス オブジェクトで共有され、特定のオブジェクトに属さず、静的領域に格納されます。
2.静的メンバー変数はクラスの外部で定義する必要があり、定義時に static キーワードは追加されず、宣言されるだけです。クラス内3. クラスの静的メンバーには、クラス名::静的メンバーまたはobject.static メンバーを
使用してアクセス4. 静的メンバー関数にはこのポインターが隠されておらず、非静的メンバーにはアクセスできません。 5. 静的メンバーはクラスのメンバーでもあります。 、パブリック、プロテクト、およびプライベートのアクセス制限の対象となり、文字数制限が適用されます。
3、友元
フレンドはフレンド機能とフレンドクラスに分かれています。
友人はカプセル化を打破する方法を提供し、時には利便性を提供します。ただし、フレンドはカプセル化を破壊するため、フレンドをより頻繁に使用するべきではありません。
3.1 フレンド機能
質問:前回の記事では、オーバーロードする必要がある Date クラスのすべての演算子を実装しましたが、カスタム タイプのオブジェクトを出力するために cout を使用する必要がある場合は、ストリーム キャレットをオーバーロードする必要があります。
出力される形式は cout << " " << endl;
その場合、オーバーロード ストリーム キャレットをクラス内に置くことはできません。クラスの最初のパラメータは暗黙的な this ポインタであり、最初のパラメータ location を求めて cout と競合するため、クラスの外でのみ定義できます。
外に置くとクラスのメンバー変数にアクセスできなくなるので、ここで友達が遊ぶ番です。
friends 関数は、クラスのプライベートメンバーに直接アクセスできます。クラスの外で定義された通常の関数であり、どのクラスにも属しませんが、クラス内で宣言する必要があり、宣言時にfriendsキーワードを追加する必要があります。 。
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;
}
private:
int _year;
int _month;
int _day;
};
ostream& operator<<(ostream& out, const Date& d)
{
out << d._year << " " << d._month << " " << d._day;
return out;
}
istream& operator>>(istream& in, Date& d)
{
in >> d._year >> d._month >> d._day;
return in;
}
int main()
{
Date d1;
cin >> d1;
cout << d1;
}
操作結果:
例証します:
フレンド関数は、クラスのプライベートおよび保護されたメンバーにアクセスできますが、クラス のメンバー関数
にはアクセスできません。フレンド関数はconst で変更できません (フレンド関数にはこのポインタがありません)。
フレンド関数はクラス定義のどこでも宣言できますが、クラス定義内の任意の場所で宣言できます。クラスアクセスによる制限 指定子の制限
関数は複数のクラスのフレンド関数にすることができる
フレンド関数の呼び出し原理は通常の関数と同じ
3.2 フレンドクラス
フレンド クラスのすべてのメンバー関数は、別のクラスのフレンド関数になることができ、別のクラスの非パブリック メンバーにアクセスできます。
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
{
// 友元
friend ostream& operator<<(ostream& out, const Date& d);
friend istream& operator>>(istream& in, Date& d);
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;
};
ostream& operator<<(ostream& out, const Date& d)
{
out << d._year << " " << d._month << " " << d._day;
return out;
}
istream& operator>>(istream& in, Date& d)
{
in >> d._year >> d._month >> d._day;
return in;
}
1. 友情関係は一方通行であり、交換可能ではありません。
たとえば、上記の Time クラスと Date クラスは、Time クラスのフレンド クラスとして Date クラスを宣言すると、Date クラスで Time クラスのプライベート メンバー変数に直接アクセスできますが、プライベート メンバー変数にアクセスしたいとします。 Date クラスの Time クラスではありません。
2. 友人関係は伝わらない
CがBの友人であり、BがAの友人である場合、CがAの友人であることは説明できません。
4. 内部クラス
概念:クラスが別のクラス内で定義されている場合、その内部クラスは内部クラスと呼ばれます。クラス内に別のクラスを定義することですが、内部クラスは外部クラスに属さない独立したクラスであり、外部クラスのオブジェクトを通じて内部クラスのメンバーにアクセスすることはできません。外部クラスには、内部クラスへの特権アクセスがありません。
class A
{
public:
class B
{
private:
int _b;
};
int _a;
};
int main()
{
cout << sizeof(A) << endl;
}
ここでの出力は 4 であるため、sizeof (外部クラス) = 外部クラスのサイズがわかります。内部クラスは内部にネストされていますが、内部クラスは外部クラスのサイズにカウントされません。
次に、内部クラスはデフォルトで外部クラスのフレンド クラスになります。
class A
{
public:
class B
{
public:
void func()
{
A a;
a._a = 1;
cout << a._a << endl;
}
private:
int _b;
};
private:
int _a;
};
int main()
{
A::B b;
b.func();
return 0;
}
クラス B に関数を定義します。この関数は、クラス A のメンバー変数 _a にアクセスし、それに値を代入して、正常に実行できるかどうかを確認します。
ここで、クラス A のメンバー変数にクラス B でアクセスし、値を割り当てることができることがわかります。クラス B はクラス A の友達であることがわかります。
ただし、AクラスはBクラスの友達ではなく、友達は一方通行です。
特性:
1. 内部クラスは、外部クラスで public、protected、または private として定義できます。
2. 内部クラスは、外部クラスのオブジェクト/クラス名を使用せずに、外部クラスの静的メンバーに直接アクセスできることに注意してください。
3. sizeof (外部クラス) = 外部クラス。内部クラスとは関係ありません。