<C++>クラスとオブジェクト(中) - クラスのデフォルトメンバ関数

1. クラスのデフォルトのメンバー関数

デフォルトのメンバー関数: ユーザーによる明示的な実装を行わずにコンパイラによって生成されたメンバー関数は、デフォルトのメンバー関数と呼ばれます。

クラスにメンバーが存在しない場合、そのクラスは単に空のクラスと呼ばれます。

空のクラスには本当に何もないのでしょうか? いいえ、どのクラスも何も書き込まない場合、コンパイラーは次のデフォルトのメンバー関数を自動的に生成します。

  1. デフォルト コンストラクター: クラスにコンストラクターが明示的に定義されていない場合、コンパイラーはデフォルト コンストラクターを生成します。このコンストラクターにはパラメーターがなく、オブジェクトの作成時に必要な初期化操作を実行するために使用されます。
  2. コピー コンストラクター: クラスにコピー コンストラクターが定義されていない場合、コンパイラーはデフォルトのコピー コンストラクターを生成します。このコンストラクターは、オブジェクトの初期化時に、既存のオブジェクトの値を新しいオブジェクトにコピーするために使用されます。
  3. 代入オーバーロード演算子: クラスに代入オーバーロード演算子が定義されていない場合、コンパイラはデフォルトのコピー代入演算子を生成します。この演算子は、既存のオブジェクトの値を別の既存のオブジェクトに割り当てるために使用されます。
  4. デストラクター: クラスにデストラクターが定義されていない場合、コンパイラーはデフォルトのデストラクターを生成します。デストラクターは、オブジェクトが破棄されて、オブジェクトが占有しているリソースを解放するときに呼び出されます。

2. コンストラクター

2.1 コンストラクターの概念

次の Date クラスの場合:

#include <iostream>
using namespace std;
class Date {
    
    
public:
    void Init(int year, int month, int day) {
    
    
        _year = year;
        _month = month;
        _day = day;
    }

    void Print() {
    
    
        cout << _year << "-" << _month << "-" << _day << endl;
    }

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

int main() {
    
    
    Date d1;
    d1.Init(2022, 7, 5);
    d1.Print();
    Date d2;
    d2.Init(2022, 7, 6);
    d2.Print();
    return 0;
}

クラスの場合はDate、Init public関数でオブジェクトの日付を設定できますが、オブジェクトが作成されるたびにこのメソッドを呼び出して情報を設定するのは少々面倒です。 ?

答えはコンストラクターを使用することです。

コンストラクターは、クラス名と同じ名前を持つ特別なメンバー関数であり、クラス型オブジェクトの作成時にコンパイラーによって自動的に呼び出され、各データ メンバーが適切な初期値を持つようにし生涯に 1 回だけ呼び出されます。オブジェクトのサイクル

2.2 コンストラクタの特徴

コンストラクターは特別なメンバー関数です。コンストラクターの名前はコンストラクターと呼ばれていますが、コンストラクターの主なタスクはオブジェクトを作成するためのスペースを開くことではなく、オブジェクトを初期化することであることに注意してください

その特徴は次のとおりです。

1. 関数名はクラス名と同じです。

2. 戻り値がありません

3. コンパイラは、オブジェクトがインスタンス化されるときに、対応するコンストラクターを自動的に呼び出します。つまり、オブジェクトの作成時にメンバー変数が初期化されます。

4. コンストラクターはオーバーロードできます。(クラスには複数のコンストラクター、つまり複数の初期化メソッドを含めることができます)

例:

#include <iostream>
using namespace std;
class Date {
    
    
public:
    Date() {
    
    
        _year = 1;
        _month = 1;
        _day = 1;
    }

    Date(int year, int month, int day) {
    
    
        _year = year;
        _month = month;
        _day = day;
    }

    
    void Print() {
    
    
        cout << _year << "年" << _month << "月" << _day << "日" << endl;
    }

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

int main() {
    
    
    Date d1;
    Date d2(2023, 2, 3); 
	
    d1.Print();
    d2.Print();   
	
    Date d3();
    //注意:如果通过无参构造函数创建对象时,对象后面不用跟括号,否则就成了函数声明

    return 0;
}

5.パラメーターなしのコンストラクターと完全なデフォルト コンストラクターはどちらもデフォルト コンストラクターと呼ばれデフォルト コンストラクターは 1 つだけです注: 引数のないコンストラクター、完全なデフォルト コンストラクター、およびデフォルトでコンパイラーによって生成されるように記述されていないコンストラクターはすべて、デフォルト コンストラクターと見なすことができます。

デフォルトのコンストラクターが 1 つしか存在できないのはなぜですか? 呼び出し時に曖昧さが発生するため

コンストラクターはパラメーターを渡さずに呼び出すことができます。一般に、各クラスがデフォルトのコンストラクターを提供することをお勧めします。

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

    
    void Print() {
    
    
        cout << _year << "年" << _month << "月" << _day << "日" << endl;
    }

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

完全なデフォルトのコンストラクターは、引数のないコンストラクターと共存できません。曖昧さが生じ、コンパイラーはどれを呼び出すべきかわかりません。

ここに画像の説明を挿入

6. クラスに明示的に定義されたコンストラクターがない場合、C++ コンパイラーはパラメーターのないデフォルトのコンストラクターを自動的に生成しますが、ユーザーがコンパイラーを明示的に定義すると、コンパイラーは生成されなくなります。

#include <iostream>
using namespace std;
class Date {
    
    
public:
    // 如果用户显式定义了构造函数,编译器将不再生成
    // Date(int year, int month, int day) {
    
    
    //     _year = year;
    //     _month = month;
    //     _day = day;
    // }

    void Print() {
    
    
        cout << _year << "-" << _month << "-" << _day << endl;
    }

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

int main() {
    
    
    // 将Date类中构造函数屏蔽后,代码可以通过编译,因为编译器生成了一个无参的默认构造函数
    // 将Date类中构造函数放开,代码编译失败,因为一旦显式定义任何构造函数,编译器将不再生成
    // 无参构造函数,放开后报错:error C2512: “Date”: 没有合适的默认构造函数可用
    Date d1;
    d1.Print();
    return 0;
}

しかし、出力がランダムな値であることがわかりました。

ここに画像の説明を挿入

多くの人は、コンパイラによって生成されるデフォルトのメンバー関数について疑問を抱いています。コンストラクタが実装されていない場合、コンパイラはデフォルトのコンストラクタを生成します。しかし、デフォルトのコンストラクターは役に立たないようです? d オブジェクトはコンパイラによって生成されたデフォルトのコンストラクターを呼び出しますが、d オブジェクトの値は依然としてランダムな値です。言い換えれば、コンパイラーによって生成されたデフォルトのコンストラクターはここでは役に立たないということですか?

回答: C++ では、型を組み込み型 (基本型) とカスタム型に分類します。組み込み型は、int/char... など、言語によって提供されるデータ型です。カスタム型は、class/struct/union などを使用して自分たちで定義する型です。次のプログラムを見ると、コンパイラーによって生成されるデフォルトのコンストラクターは、カスタム型メンバー _t で呼び出されるデフォルトのメンバー関数であることがわかります。

C++ では、デフォルトで生成されるコンストラクターを指定します。

1.組み込み型のメンバーは処理されません
2.カスタム型のメンバーは、カスタム型のクラスのコンストラクターを呼び出します。
注: C++11 では、組み込み型のメンバーが初期化されない、つまり、クラスで宣言されたときに組み込み型のメンバー変数にデフォルト値が与えられるという欠陥に対してパッチが適用されました。

デフォルトのコンストラクターのシナリオ:

#include <iostream>
using namespace std;

class Time {
    
    
public:
    Time() {
    
    
        cout << "Time()" << endl;
        _hour = 0;
        _minute = 0;
        _second = 0;
    }

private:
    int _hour;
    int _minute;
    int _second;
};

class Date {
    
    
private:
    // 基本类型(内置类型)
    int _year;
    int _month;
    int _day;
    // 自定义类型
    Time _t;
};

int main() {
    
    
    Date d;
    return 0;
}

ここに画像の説明を挿入

d オブジェクトによって生成されたデフォルトのコンストラクターがカスタム型 _t のコンストラクターを呼び出していることがわかります。

知らせ:

C++11では、組み込み型のメンバが初期化されない、つまりクラス内で宣言したときに組み込み型のメンバ変数にデフォルト値が与えられてしまう不具合に対してパッチが適用されました。

例:

class Date {
    
    
private:
    // 基本类型(内置类型)
    int _year = 2023;
    int _month = 8;
    int _day = 4;
    // 自定义类型
    Time _t;
};

2.3 初期化リスト

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

ただし、これをオブジェクト内のメンバー変数の初期化と呼ぶことはできません。また、コンストラクター本体内のステートメントは、初期化ではなく、初期値の代入とのみ呼ぶことができます。初期化は 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 回だけ出現できます(初期化は 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
};

3. 初期化リストを使用して初期化してみます。初期化リストを使用するかどうかに関係なく、カスタム型メンバー変数の場合は、最初に初期化リストを使用して初期化する必要があります。

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;   //调用Time()的构造函数
};

int main() {
    
    
    Date d(1);
}

4. 初期化リスト内の初期化順序は、クラス内のメンバー変数の宣言順序と一致しており、初期化リストの順序とは関係ありません。

#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();
}

最初にa2が初期化され、a1は初期化時に初期化リストを呼び出します。このときa1はランダムな値なので、a2(a1)以降のa2はランダムな値になります。

2.4 明示的

explicitはキーワードであり、通常、単一引数のコンストラクターを変更するために使用されます。その目的は、暗黙的な型変換を防ぐことです。これは、コンパイラーがコンストラクターを呼び出すときに暗黙的な型変換を実行するかどうかに影響します。

クラスのコンストラクターのパラメーターが 1 つだけで、explicitキーワードで変更されていない場合、コンパイラーは必要に応じてコンストラクターを自動的に実行し、パラメーターの型をクラスの型に変換し、一時オブジェクトを作成します。

コンストラクターをキーワードで修飾するexplicitと、この暗黙的な型変換を防ぐことができ、それによって予期しない動作や潜在的なエラーを防ぐことができます。これは、コードの動作があいまいになる場合がある、コンパイラによる不必要な自動型変換を回避するのに役立ちます。

例 1:

class Date {
    
    
public:
    //1.单参构造函数,没有使用explicit修饰,具有类型转换作用
    /*Date(int year) : _year(year) {}*/

    // 2,虽然有多个参数,但是创建对象时后两个参数可以不传递,没有使用explicit修饰,具有类型转换作用 - int转换为Date类
    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(2022);

    d1 = 2023;  //用一个整型变量给日期类型对象赋值
    //实际编译器背后会用2023构造一个无名对象,最后用无名对象给d1对象进行赋值
    return 0;
}

例 2:

explicit禁止されている型変換の使用

class Date {
    
    
public:
    // explicit修饰构造函数,禁止类型转换
    explicit 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(2022);

    d1 = 2023;   //err 编译器报错,没有与这些操作数匹配的 "=" 运算符,操作数类型为:  Date = int
    return 0;
}

例 3:

class MyClass {
    
    
public:
    explicit MyClass(int value) : val(value) {
    
    }

private:
    int val;
};

void func(MyClass obj) {
    
    
    // ...
}

int main() {
    
    
    MyClass obj1 = 42;  // 编译错误,因为构造函数是 explicit 的,禁止隐式类型转换
    MyClass obj2(42);   // 正确,显式调用构造函数
    func(42);           // 编译错误,因为构造函数是 explicit 的,禁止隐式类型转换
    func(obj2);         // 正确,调用 func 时显式地传递 MyClass 对象
    return 0;
}

概要:explicitキーワードを使用すると、暗黙的な型変換を回避できるため、コードの明瞭さと信頼性が向上します。

3. デストラクター

デストラクターは、オブジェクトが破棄されるときに必要なクリーンアップ操作とリソース解放操作を実行する特別なメンバー関数です。これは、オブジェクトの作成時に呼び出されるコンストラクターの逆であり、オブジェクトが破棄されるときにデストラクターが自動的に呼び出されますデストラクターの名前はクラス名と同じで、先頭にチルダが付きます~

デストラクターの主な目的は、動的に割り当てられたメモリの解放、ファイルのクローズ、他のリソースの解放など、オブジェクトの存続期間の終わりにリソースのクリーンアップを実行することです。C++ では、デストラクターはオブジェクトの破棄時にリソース管理を確保し、メモリ リークやリソース リークを防ぎます。

デストラクターの特徴:

  1. デストラクター名には、クラス名の前に文字 ~ が付加されます。
  2. パラメータも戻り値の型もありません。
  3. クラスにはデストラクターを 1 つだけ含めることができます。明示的に定義されていない場合、システムはデフォルトのデストラクターを自動的に生成します。注: デストラクターはオーバーロードできません
  4. オブジェクトのライフサイクルが終了すると、C++ コンパイル システムは自動的にデストラクターを呼び出します。
  5. オブジェクトは関数内で定義されており、関数呼び出しによってスタック フレームが作成され、スタック フレーム内のオブジェクトの構築と破棄も、先入れ後出しの原則。

コンパイラによって自動的に生成されたデストラクタに関して何かが行われるのでしょうか? 次のプログラムでは、コンパイラによって生成されたデフォルトのデストラクターがカスタム型メンバーのデストラクターを呼び出すことがわかります。

#include <iostream>
using namespace std;
class Time {
    
    
public:
    ~Time() {
    
    
        cout << "~Time()" << endl;
    }

private:
    int _hour;
    int _minute;
    int _second;
};

class Date {
    
    
private:
    // 基本类型(内置类型)
    int _year = 1970;
    int _month = 1;
    int _day = 1;
    // 自定义类型
    Time _t;   //释放Date的时候调用_t的析构函数
};

int main() {
    
    
    Date d;
    return 0;
}

出力結果:

~Time()

クラスにリソース アプリケーションが存在しない場合、デストラクターを記述することはできません。Date クラスなど、コンパイラによって生成されたデフォルトのデストラクターが直接使用されます。リソース アプリケーションがある場合は、それを記述する必要があり、存在しない場合は、 Stack クラスなどのリソース リークの原因となります。

4. コンストラクターのコピー

コピー コンストラクターは、既存のオブジェクトのコピーであるオブジェクトを作成するために使用される特別なコンストラクターです。通常、オブジェクトの初期化中や関数がパラメータを渡す場合などに、既存のオブジェクトと同じ値を持つ新しいオブジェクトを作成するために使用されます。コピー コンストラクターは、コピーされるオブジェクトへの参照というパラメーターを 1 つ受け取ります。

コピー機能の特徴は以下のとおりです。

1. コピー コンストラクターは、コンストラクターのオーバーロード形式です。

2. コピー コンストラクターのパラメーターは 1 つだけで、クラス型オブジェクトへの参照でなければなりません。値渡しの方法が使用されると、無限の再帰呼び出しが発生するため、コンパイラーは直接エラーを報告します。

3. 明示的に定義されていない場合、コンパイラはデフォルトのコピー コンストラクターを生成します。デフォルトのコピー コンストラクター オブジェクトは、メモリ ストレージに従ってバイト オーダーでコピーされます。この種のコピーは、シャロー コピー(または値コピー)と呼ばれます。

なぜ無限再帰が起こるのでしょうか?

コピー コンストラクターのパラメーターが非参照型であると仮定します。

class MyClass {
    
    
public:
    MyClass(MyClass other) {
    
      // 这里不是引用类型
        // ...
    }
};

コピー コンストラクターが呼び出されるとき、オブジェクトをパラメーターとして受け取ります。パラメーターが非参照型の場合、コピー コンストラクターに渡されたパラメーター オブジェクトは、コピー コンストラクターを通じてオブジェクトの一時コピーを作成します。ただし、一時オブジェクトを作成するプロセスでコピー コンストラクターが呼び出され、別の一時オブジェクトが作成されるなど、無限ループが発生し、無限再帰が発生します。

参照型のパラメーターを使用すると、この問題が回避されます。引数が参照型の場合、コピー コンストラクターに渡されるのは、作成された一時オブジェクトではなく、元のオブジェクト自体への参照です。これにより、無限ループの状況が回避されます。

正しい例:

#include <iostream>
using namespace std;
//正确的写法
class Date {
    
    
public:
    Date(int year = 2023, int month = 1, int day = 1) {
    
    
        _year = year;
        _month = month;
        _day = day;
    }

    Date(const Date &d) {
    
    
        _year = d._year;
        _month = d._month;
        _day = d._day;
    }

    void Print() {
    
    
        cout << _year << "/" << _month << "/" << _day << endl;
    }

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

int main() {
    
    
    Date d1(2021, 2, 3);
    Date d2(d1);
    d2.Print();
    return 0;
}

出力結果:

2021/2/3

4.1 浅いコピーと深いコピー

浅いコピーとは、ポインター変数を含む、オブジェクトのメンバー変数の値のみをコピーすることを指します浅いコピーでは、コピーされたオブジェクトは元のオブジェクトと同じリソースを共有するため、意図しない副作用が発生する可能性があります。リソースが解放または変更されると、両方のオブジェクトが影響を受けます。

コンパイラによって生成されるデフォルトのコピー コンストラクターは、すでにバイト順の値をコピーできますが、自分で明示的に実装する必要がありますか? もちろん Date クラスのようなクラスは不要です。以下のクラスはどうでしょうか?検証してみてはいかがでしょうか?

#include <iostream>
using namespace std;

//自动生成构造拷贝函数对自定义类型进行拷贝
typedef int DataType;
class Stack {
    
    
public:
    Stack(size_t capacity = 10) {
    
    
        cout << "Stack(size_t capacity = 10)" << endl;

        _array = (DataType*)malloc(capacity * sizeof(DataType));
        if (nullptr == _array) {
    
    
            perror("malloc申请空间失败");
            exit(-1);
        }

        _size = 0;
        _capacity = capacity;
    }

    void Push(const DataType& data) {
    
    
        // CheckCapacity();
        _array[_size] = data;
        _size++;
    }

    ~Stack() {
    
    
        cout << "~Stack()" << endl;

        if (_array) {
    
    
            free(_array);
            _array = nullptr;
            _capacity = 0;
            _size = 0;
        }
    }

private:
    DataType* _array; 
    size_t _size; 
    size_t _capacity; 
};

int main() {
    
    
    Stack st1;
    st1.Push(1);
    st1.Push(2);
    st1.Push(3);

    Stack st2(st1);
    return 0;
}

ここに画像の説明を挿入

プログラムが直接クラッシュしていることが分かりますが、原因は何でしょうか?
ここに画像の説明を挿入

知らせ:

リソース アプリケーションがクラスに関与していない場合は、コピー コンストラクターを記述してもしなくても構いません。リソース アプリケーションが関与すると、コピー コンストラクターを記述する必要があり、それはディープ コピーである必要があります。

ディープコピーとは、オブジェクトがコピーされるときに、オブジェクトのメンバー変数の値がコピーされるだけでなく、ポインタが指すリソース自体もコピーされることを意味します。こうすることで、新しいオブジェクトは元のオブジェクトから完全に独立し、一方のオブジェクトを変更しても他方のオブジェクトには影響しません。

例:

#include <iostream>
using namespace std;

typedef int DataType;
class Stack {
    
    
public:
    Stack(size_t capacity = 10) {
    
    
        cout << "Stack(size_t capacity = 10)" << endl;

        _array = (DataType*)malloc(capacity * sizeof(DataType));
        if (nullptr == _array) {
    
    
            perror("malloc申请空间失败");
            exit(-1);
        }

        _size = 0;
        _capacity = capacity;
    }

    void Push(const DataType& data) {
    
    
        // CheckCapacity();
        _array[_size] = data;
        _size++;
    }

    //stack类的拷贝构造深拷贝
    Stack(const Stack& st) {
    
    
        cout << "Stack(const Stack& st)" << endl;
        //深拷贝开额外空间,为了避免指向同一空间
        _array = (DataType*)malloc(sizeof(DataType) * st._capacity);
        if (nullptr == _array) {
    
    
            perror("malloc申请空间失败");
            exit(-1);
        }
        //进行字节拷贝
        memcpy(_array, st._array, sizeof(DataType) * st._size);
        _size = st._size;
        _capacity = st._capacity;
    }

    ~Stack() {
    
    
        cout << "~Stack()" << endl;

        if (_array) {
    
    
            free(_array);
            _array = nullptr;
            _capacity = 0;
            _size = 0;
        }
    }

private:
    DataType* _array;
    size_t _size;
    size_t _capacity;
};

class MyQueue {
    
    
public:
    //MyQueue什么都不写,会调用默认的构造函数,也就是Stack类的构造函数
    // 默认生成构造
    // 默认生成析构
    // 默认生成拷贝构造

private:
    //默认构造函数初始化 - 默认析构函数
    Stack _pushST;
    //默认构造函数初始化 - 默认析构函数
    Stack _popST;
    int _size = 0;
};

int main() {
    
    
    Stack st1;
    st1.Push(1);
    st1.Push(2);
    st1.Push(4);

    Stack st2(st1);
    cout << "=============================" << endl;

    MyQueue q1;
    //q1拷贝q2  q1中有两个Stack类和一个size,size直接拷贝,stack类是调用stack拷贝构造进行拷贝
    MyQueue q2(q1);

    return 0;
}

4.2 コピー コンストラクターの一般的な呼び出しシナリオ:

a. 既存のオブジェクトを使用して新しいオブジェクトを作成する

b. 関数パラメータの型がクラス型オブジェクトである

c. 関数の戻り値の型がクラス型オブジェクトである

例:

#include <iostream>
using namespace std;
class Date {
    
    
public:
    Date(int year, int minute, int day) {
    
    
        cout << "Date(int,int,int):" << this << endl;
    }

    Date(const Date &d) {
    
    
        cout << "Date(const Date& d):" << this << endl;
    }
    
    ~Date() {
    
    
        cout << "~Date():" << this << endl;
    }

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

Date Test(Date d) {
    
    
    Date temp(d);
    return temp;
}

int main() {
    
    
    Date d1(2022, 1, 13);
    Test(d1);
    return 0;
}

5. 代入のオーバーロード

デフォルトでは、C++ はクラスのデフォルトの代入演算子を生成しますが、クラスにポインターまたはリソースが含まれている場合は、浅いコピーの問題を回避するためにカスタム代入演算子が必要になる場合があります。

代入演算子をオーバーロードするには、クラスでoperator=呼び出される特別なメンバー関数を定義する必要があります。

フォーマット:

パラメーターの種類:const T&、参照を渡すことでパラメーター受け渡しの効率が向上します。

戻り値の型: T&、戻り参照により戻りの効率が向上します。戻り値の目的は、継続的な代入をサポートすることです。

自分自身に値を割り当てているかどうかを確認してください

*this を返す: 連続代入の意味を複合化する

:

class Date {
    
    
public:
    Date(int year = 1900, int month = 1, int day = 1) {
    
    
        _year = year;
        _month = month;
        _day = day;
    }

    Date(const Date &d) {
    
    
        _year = d._year;
        _month = d._month;
        _day = d._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;
};

特徴:

1. 代入演算子はクラスのメンバー関数としてのみオーバーロードでき、グローバル関数としてオーバーロードできません。

class Date {
    
    
public:
    Date(int year = 1900, int month = 1, int day = 1) {
    
    
        _year = year;
        _month = month;
        _day = day;
    }
    int _year;
    int _month;
    int _day;
};
// 赋值运算符重载成全局函数,注意重载成全局函数时没有this指针了,需要给两个参数
Date &operator=(Date &left, const Date &right) {
    
    
    if (&left != &right) {
    
    
        left._year = right._year;
        left._month = right._month;
        left._day = right._day;
    }
    return left;
}
// 编译失败:
// error C2801: “operator =”必须是非静态成员

理由:

代入演算子が明示的に実装されていない場合、コンパイラはデフォルトの代入演算子を生成します。このとき、ユーザーがグローバル代入演算子オーバーロードをクラス外で実装すると、クラス内のコンパイラによって生成されるデフォルトの代入演算子オーバーロードと競合するため、代入演算子オーバーロードはクラスのメンバー関数のみにすることができます。

3. ユーザーが明示的に実装しない場合、コンパイラはデフォルトの代入演算子のオーバーロードを生成します。これは、 value の形式でバイトごとにコピーされます注: 組み込み型のメンバー変数は直接割り当てられますが、カスタム型のメンバー変数は、割り当てを完了するために対応するクラスの代入演算子オーバーロードを呼び出す必要があります。

コンパイラによって生成されるデフォルトの代入演算子のオーバーロード関数はバイト順の値のコピーをすでに完了できるようになりましたが、それでも自分で実装する必要がありますか?

この問題は、ディープ コピーを伴うコピー コンストラクターと同じです。

リソース管理がクラスに関与していない場合、代入演算子が実装されているかどうかは問題ではありませんが、リソース管理が関与している場合は、代入演算子を実装する必要があります。

おすすめ

転載: blog.csdn.net/ikun66666/article/details/132199905