C++ - クラスとオブジェクト (パート 2)
1 つ、const メンバ関数
通常、カスタム型のオブジェクトをパラメーターとして渡す場合は、参照渡しを行うため、コピーの構築を減らすことができ、同時に const を追加して変更することで、参照によってエンティティが変更されないようにすることができます。
class A
{
public:
A(int x = 0)
: _a(x)
{
}
void Print()
{
cout << _a << endl;
}
private:
int _a;
};
void func(const A &a)
{
a.Print();
}
int main()
{
A a1;
return 0;
}
この場合、仮パラメータ a を介して Print() 関数を呼び出すと、エラーが発生します。その理由は、メンバー関数を呼び出すときに既定のパラメーターである this ポインターが存在するため、ここでパーミッションが拡大されるためです。ただし、a の this ポインターは const によって変更されます。つまり、const *これ受け継がれた*これ、これは明らかです権威の拡大この問題を解決するには、constメンバー関数の概念を導入する必要があります。
const で変更された "メンバー関数" は、const メンバー関数と呼ばれます. const で変更されたクラス メンバー関数は、メンバー関数の暗黙の this ポインターを実際に変更し、クラスのどのメンバーもメンバー関数で変更できないことを示します。また、メンバー関数の仮引数リストの後に const を追加します。
class A
{
public:
A(int x = 0)
: _a(x)
{
}
void Print() const
{
cout << _a << endl;
}
private:
int _a;
};
次の 4 つの質問を検討してください。
- const オブジェクトは非 const メンバー関数を呼び出すことができますか?
答えはいいえだ。
const *this を *this に渡すと、権限が拡大されます。 - 非 const オブジェクトは const メンバー関数を呼び出すことができますか?
答えはイエスです。
*これは const に渡すことができます *これは、権限の削減です。 - const メンバー関数は、他の非 const メンバー関数を呼び出すことができますか?
答えはいいえだ。
const メンバー関数では、*this によって const が変更され、他の非 const メンバー関数を呼び出すと、権限が拡大されます。 - 非 const メンバー関数は、他の const メンバー関数を呼び出すことができますか?
答えはイエスです。
*this は const に渡すことができます *this、ここでパーミッションの絞り込みが行われます。
次に、コンストラクターについて話します
1.初期化リスト
クラスのインスタンス化はオブジェクトが定義されたときであると前に述べましたが、オブジェクト内のメンバー変数はいつ定義されるのでしょうか?
const int p; int& a; など、一部の変数は定義時に初期化する必要があるため、
初期化リストは各メンバー変数が定義されている場所です。初期化リスト: コロンで始まり、その後にカンマ区切りのデータ メンバーのリストが続きます。各「メンバー変数」の後には、括弧で囲まれた初期値または式が続きます。たとえば、次のようにします。
class A
{
public:
A(int a = 0, int b = 0, int c = 0)
: _a(a), _b(b), _c(c)
{
}
void Print() const
{
cout << _a << endl;
}
private:
int _a;
int _b;
int _c;
};
コンストラクターを呼び出すと、初期化リストの内容が最初に実行され、次にコンストラクター { } 内の内容が実行されます. { } の内容は実際には定義されたオブジェクトへの割り当てであり、初期化リストはメンバー変数定義の初期化場所。
知らせ:
- 各メンバー変数は、初期化リストに 1 回だけ表示できます (初期化は 1 回だけ初期化できます)。
- 参照メンバー変数,const メンバー変数、カスタム型メンバー変数、およびデフォルトコンストラクタがない場合、初期化子リストを使用して初期化する必要があります。
- 初期化リストを明示的に記述したかどうかに関係なく、コンパイラは最初に初期化リストを使用して初期化します。明示的な書き込みがない場合、コンパイラはメンバー変数のデフォルト値があるかどうかを確認します。
- クラスのメンバー変数宣言順、これは初期化子リストにあります初期化シーケンス。
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();
}
_a2 は _a1 より前に定義されているため、初期化リストでは _a2 が最初に初期化されるため、ランダムな値に初期化され、_a1 は 1 に初期化されます。
2、明示的なキーワード
コンストラクターは、オブジェクトを構築して初期化するだけでなく、デフォルト値を持たない最初のパラメーターを除いて、単一のパラメーターまたはデフォルト値を持つコンストラクターの型変換の機能も備えています。
class A
{
public:
A(int a)
: _a(a)
{
}
private:
int _a;
};
int main()
{
A a1 = 8;
return 0;
}
ここでの 8 は、クラス A のコンストラクターを呼び出し、暗黙の型を型 A に変換し、コピー構築によって a1 を初期化し、コンパイラーがそれをコンストラクターに最適化します (後述)。
class Date
{
public:
Date(int year, int month = 1, int day = 1)
: _year(year), _month(month), _day(day)
{
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1 = 2023;
return 0;
}
ここでも暗黙の型変換が行われますが、この型変換を防ぐには、コンストラクターの暗黙の型変換を禁止するようにメンバー関数を explicit で変更します。
class Date
{
public:
explicit Date(int year, int month = 1, int day = 1)
: _year(year), _month(month), _day(day)
{
}
private:
int _year;
int _month;
int _day;
};
3 つの静的メンバー
静的に宣言されたクラス メンバーはクラスの静的メンバーと呼ばれ、静的で変更されたメンバー変数は静的メンバー変数と呼ばれ、静的で変更されたメンバー関数は静的メンバー関数と呼ばれます。
特性:
- 静的メンバー変数は、クラスの外部で定義および初期化する必要があります. 定義時に static キーワードは追加されず、クラスは宣言されるだけです.
class A
{
public:
A(int a = 1)
: _a(a)
{
}
private:
int _a;
static int _b;
};
int A::_b = 1;
- 静的メンバーは、特定のオブジェクトに属さず、すべてのクラス オブジェクトで共有され、静的領域に格納されます。
- クラスの静的メンバーには、クラス名::静的メンバー (パブリック スコープにある場合) または object.static メンバーを渡すことができます。
- 静的メンバー関数には非表示の this ポインターがなく、非静的メンバーにはアクセスできません。
- 静的メンバーはクラスのメンバーでもあり、public、protected、private アクセス修飾子に従います。
考える:
- クラスを実現し、プログラムで作成されるオブジェクトの数を計算しますか?
分析: オブジェクトを作成するときにコンストラクタまたはコピー構築が呼び出されるため、この 2 つの関数を何とかするだけで十分です。カウンターを定義し、これら 2 つの関数を呼び出すたびにカウンターに 1 を追加することができます. グローバル変数をカウンターとして使用することを考える人もいますが、この方法は安全ではありません。
したがって、このカウンターとして静的メンバー変数を使用します。静的イディオム変数はオブジェクトによって所有されていませんが、クラス全体に属しています。
class A
{
public:
A()
{
_count++;
}
A(const A &a)
{
_count++;
}
private:
static int _count;
};
void func()
{
A a1;
A a2(a1);
A a3;
}
int main()
{
func();
return 0;
}
上記の場合、関数を呼び出し、関数内に多くのオブジェクトを作成しましたが、別のオブジェクトを作成し、それを使用して _count にアクセスし、最後に _count 値から 1 を引いた値にしない限り、この関数から _count にアクセスすることはできません。
実際、これは必要ではありません.クラスのパブリックスコープの下に静的メンバー関数を定義するだけでよく、関数は_countの値を返すため、クラスのスコープを指定してこれに直接アクセスできます.メンバー関数、新しいオブジェクトを作成する必要がなくなりました。
class A
{
public:
A()
{
_count++;
}
A(const A &a)
{
_count++;
}
static int sum()
{
return _count;
}
private:
static int _count;
};
void func()
{
A a1;
A a2(a1);
A a3;
}
int main()
{
func();
cout << A::sum() << endl;
return 0;
}
- 静的メンバー関数は非静的メンバー関数を呼び出すことができますか?
静的メンバー関数にはこのポインターの既定のパラメーターがないため、非静的メンバー関数を呼び出すことはできません。
- 非静的メンバー関数は、クラスの静的メンバー関数を呼び出すことができますか?
静的メンバー関数はクラス全体に属し、非静的メンバー関数からアクセスできます。
4、友達
友人は、カプセル化から抜け出す方法を提供し、時には便利です。ただし、フレンドは結合度を高め、カプセル化を破壊するため、フレンドを頻繁に使用しないでください。
フレンドは、フレンド関数とフレンド クラスに分けられます。
1. 友達としてのグローバルな機能
前に演算子のオーバーロードについて説明しましたが、メンバー関数で << 演算子をオーバーロードすると、ストリーム挿入 (<<) 演算子が 2 つのオペランドを持つため問題が発生し、メンバー関数でオーバーロードすると this ポインターがは第 1 オペランドとして機能し、第 2 オペランドは ostream クラスのオブジェクトであるため、使用すると次のような状況が発生します。
a1 << cout
- ostream クラス
cout は実際には ostream クラスのオブジェクトです。カウト <<このフォームはデータを出力します。実際、彼は演算子のオーバーロードと関数のオーバーロードを行っています。
上記の状況を解決するために、演算子のオーバーロードにグローバル関数を使用します。
class A
{
public:
A(int a = 0)
: _a(a)
{
}
private:
int _a;
};
ostream &operator<<(ostream &_cout, const A &a)
{
cout << a._a << endl;
}
しかし、この書き方は間違っていて、クラスのprivateメンバはクラス外から直接アクセスできないので、この関数をクラスAのフレンド関数として使う必要があります。
class A
{
friend ostream &operator<<(ostream &_cout, const A &a);
public:
A(int a = 0)
: _a(a)
{
}
private:
int _a;
};
ostream &operator<<(ostream &_cout, const A &a)
{
cout << a._a << endl;
}
説明します:
- フレンド関数は、クラスのプライベートおよび保護されたメンバーにアクセスできますが、クラスのメンバー関数にはアクセスできません
- フレンド関数は const で変更できません (this ポインターはありません)
- フレンド関数は、クラス アクセス修飾子による制限を受けずに、クラス定義の任意の場所で宣言できます。
- 関数は複数のクラスのフレンド関数になることができます
- フレンド関数の呼び出し原理は通常の関数と同じ
2. 友達としての授業
フレンド クラスのすべてのメンバー関数は、別のクラスのフレンド関数にすることができ、別のクラスの非パブリック メンバーにアクセスできます。
- 友情関係は一方通行であり、交換することはできません。
class B;
class A
{
friend class B;
friend ostream &operator<<(ostream &_cout, const A &a);
public:
A(int a = 0)
: _a(a)
{
}
private:
int _a;
};
class B
{
public:
B()
{
}
private:
int _b;
};
B は A のフレンド クラスであり、クラス B のすべてのメンバー関数はクラス A のプライベート メンバーにアクセスできますが、クラス A は B のフレンド クラスではなく、クラス A のメンバー関数はクラス B のプライベート メンバーにアクセスできません。
- 友人関係が伝わらない
CがBの友人で、BがAの友人である場合、CがAの友人であるとは説明できない。 - 友達関係は引き継がれません。
3.メンバーは友達として機能します
class B;
class A
{
friend void B::func(const A &a);
friend ostream &operator<<(ostream &_cout, const A &a);
public:
A(int a = 0)
: _a(a)
{
}
private:
int _a;
};
class B
{
public:
B()
{
}
void func(const A &a)
{
cout << a._a << endl;
}
private:
int _b;
};
知らせ: こんな風に書くとコンパイルに失敗します. コンパイラはコンパイル時に上だけ見て, 下は見ません. コンパイラがフレンドの void B::func(const A &a); の行まで来たら, しません.この関数があることを知っているので、この関数の前にこの関数を宣言する必要がありますが、メンバー関数はクラスで宣言されているため、クラス B の定義はクラス A の上に配置する必要があります。
class A;
class B
{
public:
B()
{
}
void func(const A &a)
{
cout << a._a << endl;
}
private:
int _b;
};
class A
{
friend void B::func(const A &a);
friend ostream &operator<<(ostream &_cout, const A &a);
public:
A(int a = 0)
: _a(a)
{
}
private:
int _a;
};
五、インナークラス
== 概念: == クラスが別のクラス内で定義されている場合、この内部クラスは内部クラスと呼ばれます。内部クラスは独立したクラスであり、外部クラスのオブジェクトを介して内部クラスのメンバーにアクセスするどころか、外部クラスには属しません。外側のクラスには、内側のクラスへの特権アクセスはありません。
== 注: == 内部クラスは外部クラスのフレンド クラスです。フレンド クラスの定義を参照してください。内部クラスは、外部クラスのオブジェクト パラメーターを介して外部クラスのすべてのメンバーにアクセスできます。しかし、外側のクラスは内側のクラスの友達ではありません。
特性:
- 内部クラスは、外部クラスの public、protected、および private で定義できます。
- 内部クラスは、外部クラスのオブジェクト/クラス名なしで、外部クラスの静的メンバーに直接アクセスできることに注意してください。
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;
}
- sizeof(アウタークラス) = アウタークラス、インナークラスとは関係ありません。
cout << sizeof(A) << endl;
クラス A に属する変数は h だけです。
6つの匿名オブジェクト
匿名オブジェクトとは、オブジェクトを定義するためにクラスをインスタンス化するときに、オブジェクトの名前を記述する必要がないことを意味します。
class A
{
public:
A(int a = 0)
: _a(a)
{
}
private:
int _a;
};
int main()
{
A a1();
return 0;
}
このように a1 を定義するオブジェクトは、完全なデフォルト コンストラクターを使用していますが、これは間違っており、関数宣言 (関数名 a1、戻り値の型 A、パラメーターが空) と競合します。
ただし、この方法で匿名オブジェクトを作成することは可能です。
class A
{
public:
A(int a = 0)
: _a(a)
{
}
private:
int _a;
};
int main()
{
A();
return 0;
}
== 注意: == 無名オブジェクトの宣言サイクルはこの行にあり、デストラクタはこの行の後で呼び出されます。
7. オブジェクトをコピーするときのコンパイラの最適化
パラメーターを渡して値を返すプロセスでは、通常、コンパイラーはオブジェクトのコピーを減らすためにいくつかの最適化を行いますが、これはシナリオによっては依然として非常に役立ちます。
class A
{
public:
A(int a = 0)
: _a(a)
{
cout << "A(int a)" << 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)
{
}
A f2()
{
A aa;
return aa;
}
- 値渡し
int main()
{
A aa1;
f1(aa1);
cout << endl;
return 0;
}
aa1 が作成されるときに構築が発生し、パラメーターを値で渡すときにコピー構築が発生します。
int main()
{
f1(1);
f1(A(2));
return 0;
}
1つ目の方法: 1.まず暗黙の型変換が発生し、コンストラクタが1回呼び出されます.暗黙の型から変換されたオブジェクトは、コピー構築のために仮パラメータに渡されますが、1行で発生するため、コンパイラはそれを構造に最適化します。
2 番目の方法: 匿名オブジェクトを作成し、パラメーターを直接渡す. 最初にコンストラクターがあり、次にコピー構造がありますが、コンパイラーはそれを構造に最適化します。
- 値で返す
A f2()
{
A aa;
return aa;
}
int main()
{
A aa2 = f2();
return 0;
}
aa オブジェクトは構造を作成し、値を渡してコピー構造を返し、戻り値と別のコピー構造で aa2 を初期化しますが、コンパイラはそれを 1 つの構造 + 1 つのコピー構造に最適化します。
A f2()
{
return A();
}
int main()
{
A aa2 = f2();
return 0;
}
返される匿名オブジェクトを渡すと、コンパイラはそれを構造に最適化します。
要約する
- 戻り値オブジェクトを受け取り、構造体をコピーして受け取る、代入しないで受け取る
- 関数でオブジェクトを返すときは、匿名オブジェクトを返すようにしてください
- const& を使用してパラメーターを渡すようにしてください
知らせ
異なるコンパイラによって行われる最適化も異なる場合があり、新しいコンパイラほど最適化が大きくなります。コンパイラによって行われた最適化が私の例の最適化と異なる場合は正常です。
八、もう一度クラスとオブジェクトを理解する
実生活の物理コンピューターは知りません。コンピューターはバイナリ形式のデータしか知りません。コンピュータに実際のエンティティを認識させたい場合
、ユーザーはオブジェクト指向言語を使用してエンティティを記述し、
コンピュータが認識できるようになる前にオブジェクトを作成するプログラムを作成する必要があります。たとえば、コンピューターに洗濯機を認識させるには、次のものが必要です。
- ユーザーはまず、洗濯機の実体の現実を抽象化する必要があります。つまり、洗濯機を人間の思考のレベルで理解し、洗濯機がどのような属性を持ち、どのような機能を持っているか、つまり抽象的な認識のプロセスを理解する必要があります。洗濯機の
- 1以降、人々は頭の中で洗濯機について明確に理解していますが、コンピューターは現時点ではまだ不明です.コンピューターに人々の想像上の洗濯機を認識させるには、ある種のオブジェクト指向言語を渡す必要があります(例: C++、Java、Python など) 洗濯機をクラスで記述し、コンピューターに入力します。
- 2以降、コンピュータには洗濯機クラスがありますが、洗濯機クラスはコンピュータの観点から洗濯機オブジェクトを記述するだけです. 洗濯機クラスを通じて、それぞれの特定の洗濯機オブジェクトをインスタンス化することができます. このとき,コンピューターは洗濯機を洗うことができます。
- ユーザーは、コンピュータ内の洗濯機オブジェクトを使用して、実際の洗濯機エンティティをシミュレートできます。
クラスとオブジェクトの段階では、クラスが特定のタイプのエンティティ (オブジェクト) を記述し、オブジェクトが持つ属性とメソッドを記述し、記述が完了した後に新しいカスタム タイプを形成することを誰もが認識しなければなりません。特定のオブジェクトをインスタンス化する