序文
この章は C++ のクラスとオブジェクトの最終章ですが、この章の難易度はそれほど高くありません。クラスの隅々まで書かれています。暗記して理解するだけで十分です。クラスとオブジェクトを長く学習すると、オブジェクト指向についても理解が深まることがわかります 最後に、コーナーでいくつかの知識ポイントを学習した後、クラスとオブジェクトの理解について話しましょう。
クラスとオブジェクト (4)
まずコンストラクターについて話します
コンストラクターについては「クラスとオブジェクト (2)」で詳しく紹介しましたが、コンストラクターは複雑すぎるため、ここでも引き続きコンストラクターについて説明する必要があります (C++ の父は最初にコンストラクターを適切に設計しませんでした。パッチ適用によりコンストラクターは非常に複雑になります) が、今回はコンストラクターについての話は以前ほど難しくなく、コンストラクターに関する断片的な知識について話します。まずコードを見て、もう一度考えてみましょう。
#include<iostream>
using namespace std;
class Date
{
public:
Date(int year=10, int month=10, int day=10)
{
_year = year;
_month = month;
_day = day;
_a = 10; //编译失败,const修饰的变量不能改变
}
private:
int _year;
int _month;
int _day;
const int _a;//编译失败,const 变量未初始化
};
オブジェクトを作成するとき、コンパイラはコンストラクターを呼び出して、オブジェクト内の各メンバー変数に適切な初期値を与えます。上記のコンストラクターが呼び出された後、オブジェクトはすでに初期値を持っていますが、オブジェクト内のメンバー変数を呼び出すことはできません。変数の初期化では、コンストラクター本体内のステートメントは、初期値の代入のみを呼び出すことができ、初期化は呼び出すことはできません。初期化は 1 回しか行えず、コンストラクター本体は複数回割り当てることができるためです。
この種の詳細は少し細かすぎると思われるかもしれませんが、そうではありません。一部の変数は初期化にとって非常に重要です。たとえば、変更された変数は 1 回しか初期化できず、割り当てることもできません。そのため、慎重に区別する必要がありますconst
。初期化と代入の間。
const
変更した変数を上記のコードのメンバー変数に追加して、何が起こるか見てみましょう。
const
コンパイルに失敗したことが分かりましたが、そのような変更された変数をクラス内で定義できないほど難しいのでしょうか? いいえ、実際には、「クラスとオブジェクト (2)」で説明したデフォルト値に従って変数に値を割り当てることができますconst
が、デフォルト値は C++ の更新後にのみ表示されたので、問題をどのように解決したかC++ アップデート前の C++?
答えは初期化リストです。
1. 初期化リスト
まず、問題のオブジェクト定義について考えるとき、オブジェクト内のメンバーはどこで定義されているのでしょうか? この問題を解決すると、変更された変数を定義するときに初期化する必要がある、つまり定義と初期化が一緒であるため、変更された変数を定義できconst
ないという上記の問題が解決されます。答えは、オブジェクト内のメンバーが初期化リストで具体的に定義されているためです。そのため、初期化リストについて一緒に理解しましょう。const
a. 定義
初期化リスト:コロン で始まり、データ メンバーのコンマ区切りリストが続き、各「メンバー変数」の後に括弧で囲まれた初期値または式が続きます。
コード例:
#include<iostream>
using namespace std;
class Date
{
public:
Date(int year , int month , int day)
:_year(year)//初始化列表,也是成员变量定义的地方,这里才是真正的初始化
,_month(month)
,_day(day)
,_a(10)
{
}
private:
int _year;
int _month;
int _day;
const int _a;
};
int main()
{
Date d1(10,10,10);
return 0;
}
b.特徴
①各メンバー変数は初期化リストに一度しか出現できません(初期化は一度だけ可能です)
②クラスには次のメンバーが含まれており、初期化のために初期化リストに配置する必要があります。
- 参照メンバー変数 (定義時に初期化する必要があります)
- const メンバー変数 (定義時に初期化する必要があります)
- カスタム型のメンバーとクラスにデフォルトのコンストラクターがない場合(デフォルトのコンストラクターがないということは、初期化中にパラメーターを渡す必要があることを意味します)
- 最初にカスタム型のメンバーを確認し、クラスにデフォルトのコンストラクターがない場合は、初期化リストで初期化を行いません。
2. カスタム型のメンバーを調べ、クラスにデフォルトのコンストラクターがある場合、それを初期化リストで初期化しません。
結論は次のとおりです。初期化リストに何も書かないと、コンパイラは組み込み型を処理せず、カスタム型のデフォルト構造を呼び出します。
提案: 初期化リストの初期化を使用してみてください。初期化リストを使用するかどうかに関係なく、カスタム型メンバー変数の場合は、最初に初期化リストの初期化を使用する必要があります。
③クラス内でメンバ変数が宣言される順序は、初期化リストでの順序に関係なく、初期化リストで初期化される順序となります。
次のコードの結果を考えてみましょう。
#include<iostream>
using namespace std;
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();
}
結果はあなたを驚かせるかもしれません、それを注意深く分析しましょう
2. 明示的なキーワード
コンストラクターは、オブジェクトを構築して初期化するだけでなく、単一のパラメーターの型変換、またはデフォルト値のない最初のパラメーターを除くデフォルト値を持つコンストラクターの機能も持ちます。
注: 定義によれば、ここでのコンストラクターはパラメーターを渡す必要があります。
次のコードを見てみましょう
#include<iostream>
using namespace std;
class A
{
public:
A(int a)
:_a1(a)
, _a2(_a1)
{
}
private:
int _a1;
int _a2;
};
int main() {
A aa1(1);
A aa2 = 1;//这里不是拷贝构造(拷贝构造是用一个对象初始化一个对象),
//这里也不是赋值重载(赋值重载是用一个已经初始化的对象赋值给另一个已经初始化过的对象)
//是否可以编译通过?
return 0;
}
答えは、コンパイル可能であり、監視することでその価値を確認できるということです。
では、なぜここでコンパイルできるのでしょうか? 答えは、暗黙的な型変換です。
参照を説明するときに、次のコードが通るのは、i が暗黙的に double 型の一時変数に変換され、その一時変数が定数であり、d がこの double 型の一時変数を参照しているためであると述べたことがあります。
int i = 10;
const double&d = i;
ここでも同様で、具体的なプロセスは次のとおりです。
単一パラメータの型変換と代入の原理を理解した後、複数のパラメータを実行する方法を見てみましょう。
#include<iostream>
using namespace std;
class A
{
public:
A(int a,int b)
:_a1(a)
, _a2(b)
{
}
private:
int _a1;
int _a2;
};
int main() {
A aa1(1,2); //调用构造函数
A aa2 = {
1,2}; //多个参数类型转化
return 0;
}
ここでは、値を与える{ }
ために複数のパラメーターを使用している
単一パラメータの変換は C++98 でサポートされ、複数パラメータの変換は C++11 でサポートされていることに注意してください。
しかし、これが起こらないようにしたい場合もあります。その場合、コンストラクターを明示的に変更する必要があり、コンストラクターの暗黙的な変換が禁止されます。
同じコードを追加してみましょう。explicit
ここでコンストラクターについての説明がついに終わりました…後ほどたくさん復習する必要があります。
2 つの静的メンバー
1. 定義
staticとして宣言されたクラス メンバーはクラスの static メンバーと呼ばれ、static で変更されたメンバー変数はstatic メンバー変数、static で変更されたメンバー関数はstatic メンバー関数と呼ばれます。
静的メンバー変数はクラスの外で初期化する必要があります。初期化子リストは非静的メンバーのみを初期化できるため、
コード例:
#include<iostream>
using namespace std;
class A
{
public:
A()
:_a(10)
,_b('a')
{
}
private:
int _a;
char _b;
static int c;//这里不能给缺省值,缺省值是给初始化列表使用的,
//初始化列表只能初始化非静态成员
};
int A::c = 10;
int main()
{
A aa;
return 0;
}
2. 特徴
- 静的メンバーはすべてのクラス オブジェクトで共有され、特定のオブジェクトに属さず、静的領域に格納されます。
- 静的メンバー変数はクラスの外部で定義する必要があり、定義時に static キーワードは追加されず、クラス内でのみ宣言されます。
- クラスの静的メンバーには、クラス。
- 静的メンバーはクラスのメンバーでもあり、public、protected、private のアクセス修飾子の対象となります。
- 静的メンバー関数には非表示の this ポインターがないため、非静的メンバーにアクセスできません。
3. 練習する
そういえば、インタビューの質問を見てみましょう。
クラスを実装し、プログラム内で作成されるクラス オブジェクトの数を計算してください。
まず問題を分析します. 作成されたクラス オブジェクトの数を数えるには, クラス オブジェクトの統一された特性を調べる必要があります. 作成されたすべてのオブジェクトはコンストラクターを通過する必要があることがわかります (コピー構築もコンストラクターのオーバーロードです)したがって、静的メンバーの統計変数を定義する必要があるだけです (特徴 1:静的メンバーはすべてのクラス オブジェクトで共有され、特定のオブジェクトに属しません)。オブジェクトが作成されるたびに、次の値を追加するだけです。この静的統計変数に 1 つを追加します。
コード例:
#include<iostream>
using std::cout;
using std::endl;
class A
{
public:
A()
:_a(10)
{
_count++;
}
A(const A& tmp)
{
_a = tmp._a;
_count++;
}
private:
static int _count;
int _a;
};
int A::_count = 0;
int main()
{
A aa1;
A aa2;
A aa3(aa1);
A aa4(aa2);
return 0;
}
さらに 2 つの質問について考えてみましょう。
- 静的メンバー関数は非静的メンバー関数を呼び出すことができますか?
答えはいいえだ!静的メンバー関数にはポインタがありませんthis
。非静的メンバー関数を呼び出すには、this
ポインタを渡す必要があります。静的メンバー関数は、非静的メンバー関数にポインタを渡すことができないthis
ため、呼び出すことはできません。 - 非静的メンバー関数はクラスの静的メンバー関数を呼び出すことができますか?
答えは「はい」です。非静的メンバ関数にはthis
ポインタがあります。静的メンバ関数の呼び出しにはポインタを渡す必要はありませんthis
。非静的メンバ関数はポインタを静的メンバ関数に渡すことができるためthis
、それらを呼び出すことができます。
3. 友達
クラス外のクラス内のプライベート メンバーにアクセスすることは一般に不可能であることはわかっていますが、クラス外のクラス内のプライベート メンバーにアクセスしたい場合は、フレンドなどの特別な手段が必要です。
1 はじめに
友人はカプセル化を打破する方法を提供し、時には利便性を提供します。ただし、フレンドは結合度を高め、カプセル化を破壊するため、フレンドをより頻繁に使用するべきではありません。
フレンドはフレンド機能とフレンドクラスに分かれています。
2.フレンド機能
フレンド関数はクラスのプライベートメンバに直接アクセスでき、クラスの外で定義されクラスに属さない通常の関数ですが、クラス内で宣言する必要があり、宣言時にキーワードを追加する必要がありますfriend
。
#include<iostream>
using namespace std;
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;
}
a.特徴
①フレンド関数は、クラスのプライベートおよび保護されたメンバーにアクセスできますが、クラスのメンバー関数には
アクセスできません。 ②フレンド関数はconstで変更できません(フレンド関数にはポインタがありませんthis
)
。 ③フレンド関数はクラス定義のどこでも宣言でき、クラスの影響を受けません。アクセス修飾子の制限
④ 関数は複数のクラスのフレンド関数になることができる
⑤ フレンド関数の呼び出し原理は通常の関数と同じ
3. フレンドクラス
フレンド クラスのすべてのメンバー関数は、別のクラスのフレンド関数になることができ、別のクラスの非パブリック メンバーにアクセスできます。
a.特徴
- 友情関係は一方通行であり、交換することはできません。
たとえば、次の Time クラスと Date クラスでは、Date クラスを Time クラスのフレンド クラスとして宣言すると、Date クラスで Time クラスのプライベート メンバー変数に直接アクセスできますが、Date クラスのプライベート メンバーにアクセスしたいとします。 Time クラスの Date クラス Variables にはありません。 - 友人関係は伝わらない
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;
};
第四に、内部階級
1. 定義
概念: クラスが別のクラス内で定義されている場合、その内部クラスは内部クラスと呼ばれます。内部クラスは独立したクラスであり、外部クラスに属しておらず、ましてや外部クラスのオブジェクトを通じて内部クラスのメンバーにアクセスすることはありません。外部クラスには、内部クラスへの特権アクセスがありません。
注意: 内部クラスは外部クラスのフレンド クラスです。フレンド クラスの定義を参照してください。内部クラスは、外部クラスのオブジェクト パラメータを通じて外部クラスのすべてのメンバーにアクセスできます。しかし、外側の階級は内側の階級の友人ではありません。
2. 特徴:
- 内部クラスは、外部クラスで public、protected、および private として定義でき、アクセス修飾子の影響を受けます。
- 内部クラスは、外部クラスのオブジェクト/クラス名を使用せずに、外部クラスの静的メンバーに直接アクセスできることに注意してください。
- sizeof(outer class) = 外部クラス、内部クラスとは何の関係もありません。
コード例
//内部类
#include<iostream>
using namespace std;
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. 匿名オブジェクト
1. 定義
匿名オブジェクトとは、オブジェクトを定義するが名前がないことを意味します (C 言語には匿名構造体もあります)。匿名オブジェクトの使用シナリオは通常、次のとおりです: 変数は一時的に必要ですが、必要はありません。大きな役割を果たします。
定義形式:クラス名()
//例如 A是一个类
A();//定义一个匿名对象
2. 特徴
匿名オブジェクトのライフサイクルはその行のみであり、次の行の後でデストラクターが自動的に呼び出されます。
//匿名对象
#include<iostream>
using namespace std;
class A
{
public:
A(int a = 0)
:_a(a)
{
cout << "A(int a)" << endl;
}
~A()
{
cout << "~A()" << endl;
}
private:
int _a;
};
class Solution {
public:
int Sum_Solution(int n) {
//...
return n;
}
};
int main()
{
A aa1;
// 不能这么定义对象,因为编译器无法识别下面是一个函数声明,还是对象定义
//A aa1();
// 但是我们可以这么定义匿名对象,匿名对象的特点不用取名字,
// 但是他的生命周期只有这一行,我们可以看到下一行他就会自动调用析构函数
A();
A aa2(2);
// 匿名对象在这样场景下就很好用,如果不定义匿名对象我们就要创建一个Solution这样的对象。
Solution().Sum_Solution(10);
return 0;
}
6. オブジェクトのコピー時のコンパイラの最適化
コンパイラの開発と反復により、現在のコンパイラは非常にインテリジェントになっています。一般に、あまり古いコンパイラではパラメータを渡したり値を返したりするプロセスで、コンパイラはオブジェクトのコピーを減らすためにいくつかの最適化を行います。いくつかのシナリオでは依然として非常に役立ちます。
一緒に見てみましょう!
//拷贝对象时的一些编译器优化
#include<iostream>
using namespace std;
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;
}
A f3()
{
return A();
}
int main()
{
// 传值传参 不优化
A aa1;
f1(aa1);
cout << "----------------------------------------------------" << endl;
// 传值返回
f2(); //不优化 调用一个构造函数+一个拷贝构造 因为是两行代码编译器不敢擅自优化
cout << "----------------------------------------------------" << endl;
// 隐式类型,构造+拷贝构造->优化为直接构造
f1(1);
// 一个表达式中,构造+连续拷贝构造->优化为一个构造
f1(A(2));
cout << "----------------------------------------------------" << endl;
// 一个表达式中,连续拷贝构造+拷贝构造->优化一个拷贝构造
A aa2 = f2();
cout << "----------------------------------------------------" << endl;
// 一个表达式中,连续拷贝构造+赋值重载->无法优化
aa1 = f2();
cout << "----------------------------------------------------" << endl;
f3(); //一行中构造+拷贝构造 ->优化为一个构造
A aa3 = f3(); //一行中构造+拷贝构造+拷贝构造 ->优化为一个构造
return 0;
}
7. クラスとオブジェクトを再理解する
現実の物理コンピュータは知りません。コンピュータはバイナリ形式のデータしか知りません。コンピュータに現実の実体を認識させたい場合は、コンピュータがエンティティを認識できるようになる前に、ユーザーは何らかのオブジェクト指向言語でエンティティを記述し、オブジェクトを作成するプログラムを作成する必要があります。たとえば、コンピュータに洗濯機を認識させるには、次のものが必要です。
- ユーザーは、まず洗濯機という実体の現実を抽象化する、つまり人間の思考レベルで洗濯機を理解し、その洗濯機がどのような属性を持ち、どのような機能を持っているのか、つまり抽象的な認知のプロセスを理解する必要があります。洗濯機の
- 1 以降、人々は洗濯機について頭の中で明確に理解していますが、コンピューターは現時点ではまだ不明確です。人々の想像の中で洗濯機をコンピューターに認識させるには、ある種のオブジェクト指向言語を渡す必要があります (例:C++、Java、Pythonなど)洗濯機をクラスで記述し、コンピュータに入力します。
- 2以降はコンピュータ内に洗濯機クラスが存在しますが、洗濯機クラスはコンピュータの観点から洗濯機オブジェクトを記述するだけであり、洗濯機クラスを通じて特定の洗濯機オブジェクトをインスタンス化することができます。コンピューターは洗濯機で洗えます。それは何ですか。
- ユーザーはコンピュータ内の洗濯機オブジェクトを使用して、実際の洗濯機エンティティをシミュレートできます。
クラスとオブジェクトの段階では、クラスが特定のタイプのエンティティ (オブジェクト) を記述し、オブジェクトが持つ属性とメソッドを記述し、記述が完了した後に新しいカスタム タイプを形成し、特定のオブジェクトをインスタンス化できることを全員が認識する必要があります。このカスタム タイプのみ。