シリーズ記事ディレクトリ
記事ディレクトリ
序文
クラスとオブジェクトは C++ において非常に重要な概念です。
コンストラクターについて話しましょう
コンストラクター本体の割り当て
オブジェクトを作成するとき、コンパイラはコンストラクターを呼び出して、オブジェクト内の各メンバー変数に適切な初期値を与えます。
class Date
{
public:
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year = 2023;//这些是声明,2023是缺省值
int _month;
int _day;
};
上記のコンストラクターが呼び出された後、オブジェクトはすでに初期値を持っていますが、それはオブジェクト内のメンバー変数の初期化とは言えず、コンストラクター本体内のステートメントは初期化ではなく、初期値の代入とのみ呼ぶことができます。初期化は 1 回しか初期化できず、コンストラクター本体は複数回割り当てることができるためです。
初期化リスト
初期化リスト:コロンで始まり、データ メンバーのカンマ区切りのリストが続き、各「メンバー変数」の後に括弧で囲まれた初期値または式が続きます。
どのオブジェクトがコンストラクターを呼び出すか、初期化子リストはそのすべてのメンバー変数が定義される場所です。
初期化リストに記述されているかどうかに関係なく、コンパイラは初期化リスト内の各変数の初期化を定義します。
class Date
{
public:
Date(int year, int month, int day)
: _year(year)//这里成员变量的定义,没在函数体内,而是在赋值前定义
, _month(month)
, _day(day)
{
}
private:
int _year;
int _month;
int _day;
};
class A
{
public:
A()
:n(10)
//如果去掉了:n(10),会导致未提供n的初始值
{
_a1 = 4;
}
void Print() {
cout << _a1 << " " << _a2 << endl;
}
private:
int _a1 = 3;//声明 3是缺省值
int _a2 = 2;
const int n;
};
int main() {
A aa;
aa.Print();
}
-
各メンバー変数は、初期化リストに 1 回だけ出現できます (初期化は 1 回だけ初期化できます)。
-
クラスには、初期化のために初期化リストに配置する必要がある次のメンバーが含まれています:
参照メンバー変数、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
};
- 初期化リストの初期化を使用するようにしてください。初期化リストを使用するかどうかに関係なく、カスタム型のメンバー変数の場合は、最初に初期化リストを使用して初期化する必要があります。
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);
}
- クラス内でメンバー変数が宣言される順序は、初期化リスト内の順序に関係なく、初期化リスト内で初期化される順序になります。
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是随机值
明示的なキーワード
暗黙的な型変換
コンストラクターは、オブジェクトを構築して初期化するだけでなく、単一のパラメーターの型変換、またはデフォルト値のない最初のパラメーターを除くデフォルト値を持つコンストラクターの機能も持ちます。
C++11 は、暗黙的な型変換のための複数引数コンストラクターの呼び出しをサポートします。
class A
{
public:
A(int a)
:_a1(a)
{
cout << "A(int a)" << endl;
}
A(int a1, int a2)
:_a1(a1)
,_a2(a2)
{
}
A(const A& aa)
:_a1(aa._a1)
{
cout << "A(const A& aa)" << endl;
}
private:
int _a1;
int _a2;
};
int main()
{
//单参数构造函数 c++98
A aa1(1); //构造函数
A aa2 = 1;//隐式类型转换 构造 + 拷贝 =》 构造
//产生临时对象
//多参数构造函数 C++11
A aa3 = {
1, 2};
//A& ref = 10;
const A& ref = 10;
const A& ret = {
10, 20};
}
class Date
{
public:
// 1. 单参构造函数,没有使用explicit修饰,具有类型转换作用
// explicit修饰构造函数,禁止类型转换---explicit去掉之后,代码可以通过编译
explicit 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修饰构造函数,禁止了单参构造函数类型转换的作用
}
上記のコードは可読性があまり良くありません。コンストラクターを明示的に変更すると、コンストラクターの暗黙的な変換が禁止されます。
静的メンバー
staticとして宣言されたクラス メンバーはクラスの static メンバーと呼ばれ、staticで変更されたメンバー変数はstaticメンバー変数、static で変更されたメンバー関数はstatic メンバー関数と呼ばれます。静的メンバー変数はクラスの外部で初期化する必要があります。
面接の質問
クラスを実装し、プログラム内で作成されるクラス オブジェクトの数を計算します。
class A
{
public:
A() {
++_scount; }
A(const A& t) {
++_scount; }
~A() {
--_scount; }
static int GetACount() {
return _scount; }
private:
static int _scount;
//静态成员变量,不属于某个对象,属于所有对象,属于类
};
int A::_scount = 0;//定义初始化
void TestA()
{
cout << A::GetACount() << endl;
A a1, a2;
A a3(a1);
cout << A::GetACount() << endl;
}
-
静的メンバーはすべてのクラス オブジェクトで共有され、特定のオブジェクトに属さず、静的領域に格納されます。
-
静的メンバー変数はクラスの外部で定義する必要があり、定義時に static キーワードは追加されず、クラス内でのみ宣言されます。
-
クラスの静的メンバーには、クラス名::静的メンバーまたはオブジェクトを使用してアクセスできます。静的メンバー、静的変数のパブリック変更が必要です
-
静的メンバー関数には非表示の this ポインターがないため、非静的メンバーにアクセスできません。
-
静的メンバーはクラスのメンバーでもあり、public、protected、private のアクセス修飾子の対象となります。
//实现一个类,计算程序中创建出了多少个类对象。
class A
{
public:
A(int a = 0)
{
++count;
}
A(const A& aa)
{
++count;
}
// 静态成员函数 -- 没有this指针
static int GetCount()
{
// _a++; // 不能直接访问非静态成员
return count;
}
private:
// 不属于某个对象,所于所有对象,属于整个类
static int count; // 声明
int _a = 0;
};
int A::count = 0; // 定义初始化
void func()
{
A aa1;
A aa2(aa1);
A aa3 = 1;
A aa4[10];
}
int main()
{
func();
//cout << A::count << endl;
//cout << aa2.count << endl;
//cout << aa3.count << endl;
//A* ptr = nullptr;
//cout << ptr->count << endl;
//A aa;
//cout << aa.GetCount()-1 << endl;
cout << A::GetCount()<< endl;
return 0;
}
-
静的メンバー関数は非静的メンバー関数を呼び出すことができますか?
いいえ、静的メンバー関数 – いいえ、このポインターはありません -
非静的メンバー関数はクラスの静的メンバー関数を呼び出すことができますか?
できる
友元
友人はカプセル化を打破する方法を提供し、時には利便性を提供します。ただし、フレンドは結合度を高め、カプセル化を破壊するため、フレンドをより頻繁に使用するべきではありません。
フレンドはフレンド機能とフレンドクラスに分かれています。
フレンド機能
問題: ここで、operator<< をオーバーロードしようとしましたが、operator<< をメンバー関数にオーバーロードする方法がないことがわかりました。cout の出力ストリーム オブジェクトと暗黙的な this ポインターが最初のパラメーターの位置をプリエンプトしているためです。this ポインターのデフォルトは、左側のオペランドである最初のパラメーターです。ただし、実際の使用では、cout が通常使用される最初の仮パラメータ オブジェクトである必要があります。したがって、operator<< をグローバル関数にオーバーロードします。ただし、クラス外のメンバーにアクセスする方法がなくなるため、同じ理由で、現時点では、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 関数は、クラスのプライベート メンバーに直接アクセスできます。クラスの外で定義され、どのクラスにも属さない通常の関数ですが、クラス内で宣言する必要があり、宣言時に friends キーワードを追加する必要があります。
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 で変更できません
-
フレンド関数は、クラスアクセス修飾子による制限を受けず、クラス定義内のどこでも宣言できます。
-
関数は複数のクラスのフレンド関数になることができます
-
フレンド関数を呼び出す原理は通常の関数と同じです
フレンドクラス
フレンド クラスのすべてのメンバー関数は、別のクラスのフレンド関数になることができ、別のクラスの非パブリック メンバーにアクセスできます。
-
友情関係は一方通行で交換不可能です。
たとえば、上記の Time クラスと Date クラスは、Time クラスのフレンド クラスとして Date クラスを宣言すると、Date クラスで Time クラスのプライベート メンバー変数に直接アクセスできますが、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;
};
内部クラス
概念:クラスが別のクラス内で定義されている場合、その内部クラスは 内部クラス と呼ばれます。内部クラスは独立したクラスであり、外部クラスに属しておらず、ましてや外部クラスのオブジェクトを通じて内部クラスのメンバーにアクセスすることはできません。外部クラスには、内部クラスへの特権アクセスがありません。
注:内部クラスは、外部クラスのフレンド クラスです。フレンド クラスの定義を参照してください。内部クラスは、外部クラスのオブジェクト パラメータを通じて、外部クラスのすべてのメンバーにアクセスできます。しかし、外側の階級は内側の階級の友人ではありません。
特性:
-
内部クラスは、外部クラスの public、protected、および private で定義できます。
-
内部クラスは、外部クラスのオブジェクト/クラス名を使用せずに、外部クラスの静的メンバーに直接アクセスできることに注意してください。
-
sizeof(outer class) = 外部クラス。内部クラスとは関係ありません。
class A
{
private:
static int k;
int h;
public:
class B // B天生就是A的友元,跟A是独立,只是受到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;
}
匿名オブジェクト
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().Sum_Solution(10);
return 0;
}
匿名オブジェクト、ライフサイクルはこの行のみ
オブジェクトをコピーする際の一部のコンパイラの最適化
パラメーターを渡して値を返すプロセスでは、通常、コンパイラーはオブジェクトのコピーを減らすためにいくつかの最適化を行いますが、これはシナリオによっては非常に役立ちます。
構築 + コピー => 構築
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;
}
void f3(const A& aa)//引用传参
{
}
void f4()//返回匿名对象
{
return A();
}
int main()
{
//构造 + 拷贝构造 -> 优化为直接构造
A aa1 = 1;
// 传值传参
A aa1;
f1(aa1);// 拷贝构造,无优化
// 隐式类型,连续构造+拷贝构造->优化为直接构造
f1(1);
// 一个表达式中,连续构造+拷贝构造->优化为一个构造
f1(A(2));
----------------------------------------------------------------
f3(aa1); //无优化
f3(1); //直接构造,无优化
f3(A(2)); //无优化
-----------------------------------------------------------------
// 传值返回
//构造 + 拷贝构造
f2();
cout << endl;
// 一个表达式中,构造+拷贝构造+拷贝构造->优化成 构造+拷贝构造
A aa2 = f2();
cout << endl;
// 构造 + 构造 + 拷贝构造 + 赋值重载
A aa2;
aa2 = f2();
// 一个表达式中,连续拷贝构造+赋值重载->无法优化
aa1 = f2();
cout << endl;
-----------------------------------------------------------------
//构造 + 拷贝构造 -> 构造
func4();
//构造 + 拷贝构造 + 拷贝构造 -> 构造
A aa3 = func4();
return 0;
}
戻り値オブジェクトを受け取る際は、コピー構築メソッドで受け取るようにし、受け取り側関数内で戻り値オブジェクトを代入せず、
const&パラメータ受け渡しを使用し、可能な限り匿名オブジェクト関数を返すようにしてください。
クラスとオブジェクトをもう一度理解する
現実の物理コンピュータは知りません。コンピュータはバイナリ形式のデータしか知りません。コンピュータに現実の実体を認識させたい場合は、コンピュータがエンティティを認識できるようになる前に、ユーザーは何らかのオブジェクト指向言語でエンティティを記述し、オブジェクトを作成するプログラムを作成する必要があります。たとえば、コンピュータに洗濯機を認識させるには、次のものが必要です。
-
ユーザーは、まず洗濯機という実体の現実を抽象化する、つまり洗濯機を人間の思考レベルで理解し、洗濯機がどのような属性を持ち、どのような機能を持っているのか、つまり洗濯機を抽象的に認知するプロセスを必要とする。
-
1 以降、人々は頭の中で洗濯機について明確に理解していますが、現時点ではコンピューターについてはまだ明確ではありません。人々の想像の中で洗濯機をコンピューターに認識させるには、オブジェクト指向言語 (C++、Java、Python など) を使用して洗濯機をクラスの観点から記述し、コンピューターに入力する必要があります。
-
2以降はコンピュータ内に洗濯機クラスが存在しますが、洗濯機クラスはコンピュータの観点から洗濯機オブジェクトを記述するだけであり、洗濯機クラスを通じて特定の洗濯機オブジェクトをインスタンス化することができ、このときコンピュータは洗濯機が何であるかを知ることができます。
-
ユーザーはコンピュータ内の洗濯機オブジェクトを使用して、実際の洗濯機エンティティをシミュレートできます。
クラスとオブジェクトの段階では、クラスが特定の種類のエンティティ (オブジェクト) を記述し、そのオブジェクトの属性とメソッドを記述し、記述が完了した後に新しいカスタム タイプを形成することを誰もが理解する必要があり、それによって初めて、特定のオブジェクトをこのカスタム タイプでインスタンス化できるようになります。
要約する
クラスとオブジェクトは、C++ の非常に重要な概念の 1 つです。
逆境と苦難は人格訓練の最高の学校です。- ソクラテス