コンストラクターとデストラクター - 超詳しく解説
1. コンストラクター
1.1 コンセプト
次の Date クラスの場合:
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パブリックメソッドでオブジェクトに日付を設定することができますが、オブジェクトが作成されるたびにこのメソッドを呼び出して情報を設定するのは少々面倒です。創造された?
コンストラクターは、クラスと同じ名前を持つ特別なメンバー関数です。コンストラクターは、クラス型オブジェクトの作成時にコンパイラによって自動的に呼び出され、各データ メンバーが適切な初期値を持つようにし、コンストラクターのライフサイクル全体で 1 回だけ呼び出されます。オブジェクトです。
1.2 特徴
コンストラクターは特別なメンバー関数です。コンストラクターの名前は構築ですが、コンストラクターの主なタスクはオブジェクトを作成するためのスペースを開くことではなく
その特徴は次のとおりです。
1. 関数名はクラス名と同じです。
2. 戻り値はありません。
3. コンパイラは、オブジェクトがインスタンス化されるときに、対応するコンストラクターを自動的に呼び出します。
4. コンストラクターはオーバーロードできます。(オーバーロードについてよくわからない場合は、ここをクリックしてください -> C++ 関数のオーバーロード)
例:
class Date
{
public:
// 1.无参构造函数
Date()
{
}
// 2.带参构造函数
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
// 3.带缺省值的构造函数
Date(int year=2023, int month=1, int day=1)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
void TestDate()
{
Date d1; // 调用无参构造函数
Date d2(2015, 1, 1); // 调用带参的构造函数
// 注意:如果通过无参构造函数创建对象时,对象后面不用跟括号,否则就成了函数声明
Date d3();
}
注: 引数のないコンストラクターを通じてオブジェクトを作成する場合、オブジェクトの後にかっこを付ける必要はありません。かっこを付けないと関数宣言になります。
5. クラス内に明示的に定義されたコンストラクターがない場合、C++ コンパイラーはパラメーターのないデフォルトのコンストラクターを自動的に生成しますが、ユーザーが明示的に定義すると、コンパイラーはそれを生成しなくなります。
例:
5.1 Date クラスのコンストラクターをシールドした後、コンパイラーはパラメーターのないデフォルトのコンストラクターを生成し、ランダムな値をオブジェクトに割り当てるため、コードをコンパイルできます (コンパイラーによって値が異なる方法で割り当てられる場合があり、コンパイラーによっては、初期化は0です)
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 d;
d.Print();
return 0;
}
5.2 Date クラスのコンストラクターを解放する: 明示的に定義されたコンストラクターは仮パラメーターを取るため、コンストラクターが明示的に定義されるとコンパイラーはそれを生成しなくなるため、コードのコンパイルは失敗します。引数のないコンストラクター。解放後にエラーが報告されます: エラー C2512: "日付": 使用可能な適切なデフォルト コンストラクターがありません。
class Date
{
public:
void Print()
{
cout << _year << "/" << _month << "/" << _day << endl;
}
// 如果用户显式定义了构造函数,编译器将不再生成
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d;//此时这里会报错
d.Print();
return 0;
}
6. コンパイラーによって生成されるデフォルトのメンバー関数に関して、多くのベテランは疑問を抱くでしょう。コンストラクターが実装されていない場合、コンパイラーはデフォルトのコンストラクターを生成します。しかし、デフォルトのコンストラクターは役に立たないようです? d オブジェクトはコンパイラによって生成されたデフォルトのコンストラクターを呼び出しますが、d オブジェクト _year/_month/_day は依然としてランダムな値です。言い換えれば、コンパイラーによって生成されたデフォルトのコンストラクターはここでは役に立たないということですか? ?
回答: C++ では、型を組み込み型 (基本型) とカスタム型に分類します。組み込み型は、
int/char... などの言語によって提供されるデータ型です。カスタム型は、class/struct/union などを使用して自分たちで定義する型です。次のプログラムを見ると、次のことがわかります
。コンパイラーはデフォルトのコンストラクターを生成します。カスタム型メンバー _t のデフォルトのメンバー関数が呼び出されます
。
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;
}
注: C++11 では、組み込み型のメンバーが初期化されない、つまりクラス内で宣言されたときに組み込み型のメンバー変数にデフォルト値が与えられるという不具合に対するパッチが作成されました。
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 = 1970;
int _month = 1;
int _day = 1;
// 自定义类型
Time _t;
};
int main()
{
Date d;
return 0;
}
7. パラメーターなしのコンストラクターと完全なデフォルトのコンストラクターは両方ともデフォルト コンストラクターと呼ばれ、存在できるデフォルト コンストラクターは 1 つだけです。
class Date
{
public:
Date()
{
_year = 1900;
_month = 1;
_day = 1;
}
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
// 以下测试函数能通过编译吗?
void Test()
{
Date d1;
}
注: 引数のないコンストラクター、すべてデフォルトのコンストラクター、および作成していないコンパイラーによってデフォルトで生成されるコンストラクターは、すべてデフォルトのコンストラクターと見なされます。
2. デストラクター
2.1 コンセプト
コンストラクターに関するこれまでの研究を通じて、オブジェクトがどのようにして誕生し、そのオブジェクトがどのように消滅するのかが分かりました。
デストラクタ: コンストラクタの機能とは異なり、デストラクタはオブジェクト自体の破棄を完了しません。ローカル オブジェクトの破棄はコンパイラによって完了します。オブジェクトが破棄されると、デストラクターが自動的に呼び出され、オブジェクト内のリソースのクリーンアップが完了します。
2.2 特徴
デストラクターは、次の特性を持つ特別なメンバー関数です。
1. デストラクター名には、クラス名の前に文字 ~ が付きます。
2. パラメータも戻り値の型もありません。
3. クラスにはデストラクターを 1 つだけ含めることができます。明示的に定義されていない場合、システムはデフォルトのデストラクターを自動的に生成します。注: デストラクターはオーバーロードできません。
4. オブジェクトのライフサイクルが終了すると、C++ コンパイル システムが自動的にデストラクターを呼び出します。
typedef int DataType;
class Stack
{
public:
Stack(size_t capacity = 3)
{
cout << "Stack()" << endl;
_array = (DataType*)malloc(sizeof(DataType) * capacity);
if (NULL == _array)
{
perror("malloc申请空间失败!!!");
return;
}
_capacity = capacity;
_size = 0;
}
void Push(DataType data)
{
// CheckCapacity();
_array[_size] = data;
_size++;
}
// 其他方法...
~Stack()
{
cout << "~Stack()" << endl;
if (_array)
{
free(_array);
_array = NULL;
_capacity = 0;
_size = 0;
}
}
private:
DataType* _array;
int _capacity;
int _size;
};
void TestStack()
{
Stack s;
s.Push(1);
s.Push(2);
}
int main()
{
TestStack();
}
5. コンパイラによって自動生成されるデストラクタについては、何かを実現しますか? 次のプログラムでは、コンパイラによって生成されたデフォルトのデストラクターが、カスタム型メンバーのデストラクターを呼び出すことがわかります。
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;
};
int main()
{
Date d;
return 0;
プログラムの実行終了後の出力: ~Time()
mainメソッド内で直接Timeクラスのオブジェクトを作成していないのに、なぜ最後にTimeクラスのデストラクタが呼び出されるのですか?
理由: Date オブジェクト d は main メソッドで作成され、 d には 4 つのメンバー変数が含まれており、そのうち _year、_month、および _day は組み込み型メンバーです。リソースが破棄されたときにクリーンアップする必要はありません。システムはそのメモリを直接リサイクルできます。 ;
そして、_t は Time クラスのオブジェクトであるため、d が破棄されるときは、その中に含まれる Time クラスの _t オブジェクトも破棄する必要があるため、Time クラスのデストラクターを呼び出す必要があります。
ただし、Time クラスのデストラクタを main 関数で直接呼び出すことはできません。実際に解放されるのは Date クラスのオブジェクトであるため、コンパイラは Date クラスのデストラクタを呼び出します。Date が明示的に指定されていない場合、コンパイラは Date クラスのデストラクタを呼び出します。これは、Date クラス Generate に、Time クラスのデストラクターを内部で呼び出すことを目的としたデフォルトのデストラクターを提供します。つまり、Date オブジェクトが破棄されるときに、その内部の各カスタム オブジェクトが正しく破棄できることを確認する必要があります。
main 関数は、Time クラスのデストラクターを直接呼び出しませんが、Date クラスのコンパイラーによって生成されたデフォルトのデストラクターを明示的に呼び出します。
注: そのクラスのオブジェクトが作成されるときにそのクラスのデストラクターが呼び出され、そのクラスのオブジェクトが破棄されるときにそのクラスのデストラクターが呼び出されます。
6. クラス内にリソース アプリケーションがない場合は、デストラクタを記述する必要はなく、Date クラスなど、コンパイラによって生成されたデフォルトのデストラクタを直接使用できますが、リソース アプリケーションがある場合は、デストラクタを記述する必要があります。そうしないと、Stack クラスなどのリソース リークが発生します。
(章の終わり)