CSDN の皆さん、こんにちは。Xiaoyalan の C++ コラムが更新されるのは久しぶりです。早速、クラスとオブジェクトの世界に入りましょう。!!
クラスの 6 つのデフォルトのメンバー関数
コンストラクタ
デストラクター
コピーコンストラクター
クラスの 6 つのデフォルトのメンバー関数
クラスにメンバーが存在しない場合、そのクラスは単に空のクラスと呼ばれます。
空のクラスには本当に何もないのでしょうか?いいえ、どのクラスも何も書き込まない場合、コンパイラは次の 6 つのデフォルトのメンバー関数を自動的に生成します。
デフォルトのメンバー関数: ユーザーによる明示的な実装を行わずにコンパイラによって生成されたメンバー関数は、デフォルトのメンバー関数と呼ばれます。
class Date {};
コンストラクタ
コンセプト
次の 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(2023, 8, 9); d1.Print(); Date d2; d2.Init(2023, 8, 10); d2.Print(); return 0; }
Dateクラスの場合、Initパブリックメソッドでオブジェクトに日付を設定することができますが、オブジェクトを作成するたびにこのメソッドを呼び出して情報を設定するのは少々面倒です。創造された?
コンストラクターは、クラス名と同じ名前を持つ特別なメンバー関数であり、クラス型オブジェクトの作成時にコンパイラーによって自動的に呼び出され、各データ メンバーが適切な初期値を持つようにし、生涯に 1 回だけ呼び出されます。オブジェクトのサイクル。
特性
コンストラクターは特別なメンバー関数です。コンストラクターの名前はコンストラクターと呼ばれていますが、コンストラクターの主なタスクはオブジェクトを作成するためのスペースを開くことではなく、オブジェクトを初期化することであることに注意してください。
つまり、コンストラクターは Init 関数に似ています。!!
その特徴は次のとおりです。
- 関数名はクラス名と同じです。
- 戻り値はありません(voidを記述する必要はありません)。
- コンパイラは、オブジェクトがインスタンス化されるときに、対応するコンストラクターを自動的に呼び出します。
- コンストラクターはオーバーロードできます (本質的には、複数のコンストラクターを作成し、複数の初期化メソッドを提供することです)。
class Date { public: //无参构造函数 Date() { cout << "Date()" << endl; _year = 1; _month = 1; _day = 1; } //带参构造函数 Date(int year, int month, int day) { cout << "Date(int year, int month, int day)" << endl; _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.Print(); // 调用带参的构造函数 Date d2(2023, 8, 9); d2.Print(); // 注意:如果通过无参构造函数创建对象时,对象后面不用跟括号,否则就成了函数声明 // 以下代码的函数:声明了d3函数,该函数无参,返回一个日期类型的对象 // warning C4930: “Date d3(void)”: 未调用原型函数(是否是有意用变量定义的?) Date d3(); d3.Print(); return 0; }
//無し参构造関数数
Date()
{ cout << "Date()" << endl; _年 = 1; _月 = 1; _日 = 1; } //带参构造関数数 Date(int year, int month, int day) { cout << "Date(int year, int month, int day)" << endl; _year = 年; _month = 月; _day = 日; }
上記の 2 つの関数は実際に組み合わせて、完全なデフォルト パラメーターの形式で記述することができます。
//带参构造函数 Date(int year = 1, int month = 1, int day = 1) { cout << "Date(int year, int month, int day)" << endl; _year = year; _month = month; _day = day; }
この書き方はより柔軟で、1 つのパラメータを渡すことも、2 つのパラメータを渡すことも、3 つのパラメータを渡すことも、パラメータを渡さないこともできます。
次の例を考えてみましょう。
class Stack { public: Stack(size_t n = 4) { if (n == 0) { a = nullptr; top = capacity = 0; } else { a = (int*)malloc(sizeof(int) * n); if(a == nullptr) { perror("realloc fail"); exit(-1); } top = 0; capacity = n; } } void Push(int x) { if (top == capacity) { size_t newcapacity = capacity == 0 ? 4 : capacity * 2; int* tmp = (int*)realloc(a, sizeof(int) * newcapacity); if (tmp == nullptr) { perror("realloc fail"); exit(-1); } if (tmp == a) { cout << capacity << "原地扩容" << endl; } else { cout << capacity << "异地扩容" << endl; } a = tmp; capacity = newcapacity; } a[top++] = x; } int Top() { return a[top - 1]; } void Pop() { assert(top > 0); --top; } void Destroy() { free(a); a = nullptr; top = capacity = 0; } bool Empty() { return top == 0; } private: // 成员变量 int* a; int top; int capacity; }; int main() { Stack st1; st1.Push(1); st1.Push(2); st1.Push(3); st1.Push(4); while (!st1.Empty()) { cout << st1.Top() << " "; st1.Pop(); } cout << endl; st1.Destroy(); //Stack st2(1000); Stack st2; for (size_t i = 0; i < 1000; i++) { st2.Push(i); } while (!st2.Empty()) { cout << st2.Top() << " "; st2.Pop(); } cout << endl; st2.Destroy(); return 0; }
コンストラクターはデフォルトのメンバー関数であり、記述されていない場合はコンパイラーが自動的に生成します。
コンパイルによって生成されるデフォルトのコンストラクターの特徴は次のとおりです。
- 書かなければ生成され、書かなければ生成されません。
- 組み込み型のメンバーは処理されません (C++11、デフォルト値のサポートを宣言)。
- カスタム型のメンバーのみが処理され、このメンバーのデフォルトのコンストラクターが呼び出されます。
クラスにコンストラクターが明示的に定義されていない場合、C++ コンパイラーはパラメーターのないデフォルトのコンストラクターを自動的に生成します。ユーザーがコンパイラーを明示的に定義すると、コンパイラーは生成されなくなります。
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; }
したがって、デフォルトで生成されるコンストラクターは一般に無価値ですが、シナリオによっては非常に価値があります。Xiao Yalan が以前にトピックを書きましたが、これは 2 つのスタックを持つキューを実装するというものです。コンストラクターは作成してもしなくても構いません。
// 两个栈实现一个队列 class MyQueue { private: Stack _pushst; Stack _popst; };
要約: 一般に、独自のコンストラクターを作成し、初期化方法を決定する必要があります。
メンバー変数はすべてカスタム型であるため、コンストラクターを作成しないことを検討できます。
多くの人は、コンパイラによって生成されるデフォルトのメンバー関数について疑問を抱いています。コンストラクタが実装されていない場合、コンパイラはデフォルトのコンストラクタを生成します。しかし、デフォルトのコンストラクターは役に立たないようです? d オブジェクトはコンパイラによって生成されたデフォルトのコンストラクターを呼び出しますが、d オブジェクト _year/_month/_day は依然としてランダムな値です。つまり、コンパイラによって生成されるデフォルトのコンストラクタはここでは役に立たないということですか??
回答: C++ では、型を組み込み型 (基本型) とカスタム型に分類します。組み込み型は、int/char... など、言語によって提供されるデータ型です。カスタム型は、class/struct/union などを使用して自分たちで定義する型です。次のプログラムを見ると、コンパイラがデフォルトの構造体を生成することがわかります。関数はカスタム型メンバー _t のデフォルトのコンストラクタを呼び出します。
int* と Date* (ポインター) は両方とも組み込み型です。!!
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; }
パラメーターなしのコンストラクターとデフォルト コンストラクターは両方ともデフォルト コンストラクターと呼ばれ、デフォルト コンストラクターは 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; } int main() { Test(); return 0; }
上記のコードはコンパイルできません。
引数のないコンストラクターがマスクされている場合、またはデフォルトのコンストラクターがマスクされている場合は、コンパイルをパスできます。!!!
デストラクター
コンセプト
前のコンストラクターの研究を通じて、オブジェクトがどのようにして誕生し、そのオブジェクトがどのように消滅したのかがわかります。
デストラクター: コンストラクターの機能とは異なり、デストラクターはオブジェクト自体の破棄を完了しません。ローカル オブジェクトの破棄はコンパイラーによって行われます。オブジェクトが破棄されると、自動的にデストラクターが呼び出され、オブジェクト内のリソースのクリーンアップが完了します。
つまり、デストラクターは Destroy 関数に似ています。!!
特性
デストラクターは、次のような特性を持つ特別なメンバー関数です。
- デストラクター名には、クラス名の前に文字 ~ が付加されます。
- パラメータも戻り値の型もありません。
- クラスにはデストラクターを 1 つだけ含めることができます。明示的に定義されていない場合、システムはデフォルトのデストラクターを自動的に生成します。注: デストラクターはオーバーロードできません。
- オブジェクトのライフサイクルが終了すると、C++ コンパイル システムは自動的にデストラクターを呼び出します。
デフォルトのデストラクターはデフォルトのコンストラクターに似ています。組み込み型のメンバーは処理されず、カスタム型のメンバーはこのメンバーのデストラクターを呼び出します。
class Date { public: Date(int year = 1, int month = 1, int day = 1) { cout << "Date(int year = 1, int month = 1, int day = 1)" << endl; _year = year; _month = month; _day = day; } void Print() { cout << _year << "/" << _month << "/" << _day << endl; } ~Date() { cout << "~Date()" << endl; } private: int _year = 1; // 声明给的缺省值 int _month = 1; int _day = 1; }; class Stack { public: Stack(size_t n = 4) { cout << "Stack(size_t n = 4)" << endl; if (n == 0) { a = nullptr; top = capacity = 0; } else { a = (int*)malloc(sizeof(int) * n); if (a == nullptr) { perror("realloc fail"); exit(-1); } top = 0; capacity = n; } } ~Stack() { cout << "~Stack()" << endl; free(a); a = nullptr; top = capacity = 0; } void Push(int x) { if (top == capacity) { size_t newcapacity = capacity == 0 ? 4 : capacity * 2; int* tmp = (int*)realloc(a, sizeof(int) * newcapacity); if (tmp == nullptr) { perror("realloc fail"); exit(-1); } if (tmp == a) { cout << capacity << "原地扩容" << endl; } else { cout << capacity << "异地扩容" << endl; } a = tmp; capacity = newcapacity; } a[top++] = x; } int Top() { return a[top - 1]; } void Pop() { assert(top > 0); --top; } void Destroy() { free(a); a = nullptr; top = capacity = 0; } bool Empty() { return top == 0; } private: // 成员变量 int* a; int top; int capacity; }; // 两个栈实现一个队列 class MyQueue { private: Stack _pushst; Stack _popst; }; int main() { Date d1; Date d2; Stack st1; Stack st2; return 0; }
実際、日付クラスはデストラクターを記述する必要はありません。!!
スタックのようなデータ構造の場合は、デストラクターを記述する必要があります。!!
コンパイラによって自動的に生成されたデストラクタに関して何かが行われるのでしょうか? 次のプログラムでは、コンパイラによって生成されたデフォルトのデストラクターがカスタム型メンバーのデストラクターを呼び出すことがわかります。
クラスにアプリケーション リソースがない場合、デストラクターを記述することはできず、コンパイラーによって生成されたデフォルトのデストラクターを直接使用できます。
Dateクラス; Stackクラスなど、リソースアプリケーションがある場合は必ず書かないとリソース漏洩の原因となります。
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类的析构函数? // 因为:main方法中创建了Date对象d,而d中包含4个成员变量,其中_year, _month, _day三个是 // 内置类型成员,销毁时不需要资源清理,最后系统直接将其内存回收即可;而_t是Time类对象,所以在 // d销毁时,要将其内部包含的Time类的_t对象销毁,所以要调用Time类的析构函数。但是:main函数 // 中不能直接调用Time类的析构函数,实际要释放的是Date类对象,所以编译器会调用Date类的析构函 // 数,而Date没有显式提供,则编译器会给Date类生成一个默认的析构函数,目的是在其内部调用Time // 类的析构函数,即当Date对象销毁时,要保证其内部每个自定义对象都可以正确销毁 // main函数中并没有直接调用Time类析构函数,而是显式调用编译器为Date类生成的默认析构函数 // 注意:创建哪个类的对象则调用该类的析构函数,销毁那个类的对象则调用该类的析构函数
コピーコンストラクター
コンセプト
現実には、あなたと同じ自分、つまり双子が存在するかもしれません。
オブジェクトを作成するとき、既存のオブジェクトとまったく同じ新しいオブジェクトを作成できますか?
コピー コンストラクター: 仮パラメーターは 1 つだけあり、このクラス型のオブジェクト (通常は const 装飾) への参照であり、既存のクラス型オブジェクトを使用して新しいオブジェクトを作成するときにコンパイラーによって自動的に呼び出されます。
特徴
コピー コンストラクターも、次の特性を持つ特別なメンバー関数です。
- コピー コンストラクターは、コンストラクターのオーバーロードされた形式です。
- コピーコンストラクタのパラメータは1つだけであり、クラス型オブジェクト(同じ型のオブジェクト)への参照である必要があります。値渡しの方法を使用すると、無限大が発生するため、コンパイラは直接エラーを報告します。再帰呼び出し。
class Date { public: Date(int year = 1900, int month = 1, int day = 1) { _year = year; _month = month; _day = day; } // Date(const Date& d)// 正确写法 Date(const Date d)// 错误写法:编译报错,会引发无穷递归 { _year = d._year; _month = d._month; _day = d._day; } private: int _year; int _month; int _day; }; int main() { Date d1; Date d2(d1); return 0; }
class Date { public: Date(int year = 1, int month = 1, int day = 1) { _year = year; _month = month; _day = day; } Date(Date& d) { cout << "Date(Date& d)" << endl; _year = d._year; _month = d._month; _day = d._day; } void Print() { cout << _year << "/" << _month << "/" << _day << endl; } private: // 内置类型 int _year; int _month; int _day; }; typedef int DataType; class Stack { public: Stack(size_t capacity = 3) { _array = (DataType*)malloc(sizeof(DataType) * capacity); if (NULL == _array) { perror("malloc申请空间失败!!!"); return; } _capacity = capacity; _size = 0; } Stack(Stack& s) { cout << "Stack(Stack& s)" << endl; // 深拷贝 _array = (DataType*)malloc(sizeof(DataType) * s._capacity); if (NULL == _array) { perror("malloc申请空间失败!!!"); return; } memcpy(_array, s._array, sizeof(DataType) * s._size); _size = s._size; _capacity = s._capacity; } void Push(DataType data) { _array[_size] = data; _size++; } ~Stack() { cout << "~Stack()" << endl; free(_array); _array = nullptr; _size = _capacity = 0; } private: // 内置类型 DataType* _array; int _capacity; int _size; }; void func1(Date d) { d.Print(); } // 期望呢,s要插入一些数据,s的改变,不影响s1 void func2(Stack s) { s.Push(1); s.Push(2); } int main() { Date d1(2023, 7, 21); func1(d1); Stack s1; func2(s1); Stack s2(s1); // 以下两个写法是等价的,都是拷贝构造 Date d2(d1); Date d3 = d1; return 0; }
明示的に定義されていない場合、コンパイラはデフォルトのコピー コンストラクターを生成します。デフォルトのコピー コンストラクター オブジェクトは、メモリ ストレージに従ってバイト オーダーでコピーされます。この種のコピーは、浅いコピー、または値のコピーと呼ばれます。
class Time { public: Time() { _hour = 1; _minute = 1; _second = 1; } Time(const Time& t) { _hour = t._hour; _minute = t._minute; _second = t._second; cout << "Time::Time(const 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 d1; // 用已经存在的d1拷贝构造d2,此处会调用Date类的拷贝构造函数 // 但Date类并没有显式定义拷贝构造函数,则编译器会给Date类生成一个默认的拷贝构造函数 Date d2(d1); return 0; }
注: コンパイラによって生成されるデフォルトのコピー コンストラクターでは、組み込み型はバイト モードで直接コピーされますが、カスタム型はコピー コンストラクターを呼び出すことによってコピーされます。
以前のコンストラクター機能とは異なり、デフォルトで生成されるコピー構造を作成したりコンパイルしたりすることはありません。
- 組み込み型、値のコピー
- カスタム タイプ。そのコピーを呼び出します
概要: Date はコピー構築を実装する必要がなく、デフォルトの生成で使用できます。
スタックはディープコピーのコピー構造を自分で実装する必要があり、デフォルトの生成では問題が発生します
MyQueue はデフォルトで生成されるいくつかの関数に非常に便利で、人生の勝者です
クラスMyQueue
{ プライベート: スタック_pushst; スタック_popst; };MyQueue mq1;
MyQueue mq2 = mq1;
コンパイラによって生成されるデフォルトのコピー コンストラクターは、すでにバイト順の値をコピーできますが、自分で明示的に実装する必要がありますか? もちろん Date クラスのようなクラスは不要です。以下のクラスはどうでしょうか?検証してみてはいかがでしょうか?
// 这里会发现下面的程序会崩溃掉?这里就需要我们以后学的深拷贝去解决。 typedef int DataType; class Stack { public: Stack(size_t capacity = 10) { _array = (DataType*)malloc(capacity * sizeof(DataType)); if (nullptr == _array) { perror("malloc申请空间失败"); return; } _size = 0; _capacity = capacity; } void Push(const DataType& data) { // CheckCapacity(); _array[_size] = data; _size++; } ~Stack() { if (_array) { free(_array); _array = nullptr; _capacity = 0; _size = 0; } } private: DataType* _array; size_t _size; size_t _capacity; }; int main() { Stack s1; s1.Push(1); s1.Push(2); s1.Push(3); s1.Push(4); Stack s2(s1); return 0; }
注: クラスにリソース アプリケーションが関与していない場合は、コピー コンストラクターを記述してもしなくても構いません。リソース アプリケーションが関与すると、コピー コンストラクターを記述する必要があります。それ以外の場合は、コピー コンストラクターは浅いコピーになります。
コピー コンストラクターの一般的な呼び出しシナリオ:
- 既存のオブジェクトを使用して新しいオブジェクトを作成する
- 関数パラメータの型はクラス型オブジェクトです
- 関数の戻り値の型がクラス型オブジェクトである
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;
}
プログラムの効率を高めるため、一般オブジェクトにパラメータを渡す場合は、できるだけ参照型を使用し、返すときも現場に合わせて可能な限り参照を使用するようにしてください。
すべてのソースコードは次のとおりです。
#include<iostream>
#include<assert.h>
名前空間 std を使用します。
class Date
{ public: void Init(int year, int month, int day) { _year = year; _month = 月; _day = 日; } void Print() { cout << _year << "-" << _month << "-" << _day << endl; }
プライベート:
int _year;
int _month;
int_day;
};int main()
{ 日付 d1; d1.Init(2023, 8, 9); d1.Print(); 日付 d2; d2.Init(2023, 8, 10); d2.Print(); 0を返します。}
class Date
{ public: 無し参构造関数数 //Date() //{ // cout << "Date()" << endl; // _年 = 1; // _month = 1; // _日 = 1; //} 带参构造関数数 //Date(int year, int month, int day) //{ // cout << "Date(int year, int month, int day)" << endl; // _年 = 年; // _month = 月; // _day = 日; //} //带参构造関数数 Date(int year = 1, int month = 1, int day = 1) { cout << "Date(int year, int month,
_month = month;
_day = day;
}
void Print()
{ cout << _year << "-" << _month << "-" << _day << endl; } private: int _year; int _month; int _day; }; int main() { // 引数なしのコンストラクタを呼び出す Date d1; d1.Print(); // 引数と引数のコンストラクタを呼び出す Date d2(2023, 8, 9); d2.Print(); //注意 : オブジェクトが引数のないコンストラクターを通じて作成された場合、オブジェクトの後に括弧を続ける必要はありません。そうでない場合、関数宣言になります。 // 次のコードの関数: d3 関数が宣言されており、関数には括弧がありません 。 //警告 C4930 : "Date d3(void)": プロトタイプ関数が呼び出されません (意図的に変数で定義されていますか?) //Date d3(); //d3.Print();
日付 d3(2023);
d3.Print();
日付 d4(2023, 8);
d4.Print();
0を返します。
}
class Stack
{ public: Stack(size_t n = 4) { if (n == 0) { a = nullptr; 上部 = 容量 = 0; } else { a = (int*)malloc(sizeof(int) * n); if(a == nullptr) { perror("realloc 失敗"); 終了(-1); }
トップ = 0;
容量 = n; void Push(int x) { if (top == 容量)
{ size_t newcapacity = 容量 == 0 ? 4 : 容量 * 2; int* tmp = (int*)realloc(a, sizeof(int) * newcapacity); if (tmp == nullptr) { perror("realloc 失敗"); 終了(-1); } if (tmp == a) { cout << 容量 << "原地扩容" << endl; } それ以外の場合は {
cout << 容量 << "リモート拡張" << endl;
}a = 温度;
容量 = 新しい容量;
}a[トップ++] = x;
}int Top()
{ return a[top - 1]; }void Pop()
{ assert(top > 0); - 上; }void Destroy()
{ free(a); a = nullptr; 上部 = 容量 = 0; }bool Empty()
{ return top == 0; } private: // メンバー変数 int* a; int top; int Capacity; };int main()
{ スタック st1; st1.プッシュ(1); st1.プッシュ(2); st1.プッシュ(3); st1.プッシュ(4); while (!st1.Empty()) { cout << st1.Top() << " "; st1.Pop(); } cout << endl;
st1.Destroy();
スタックst2(1000);
//スタックst2;
for (size_t i = 0; i < 1000; i++)
{ st2.Push(i); }while (!st2.Empty())
{ cout << st2.Top() << " "; st2.Pop(); } cout << endl;st2.Destroy();
0を返します。
}
class Date
{ public:ユーザーがコンストラクターを明示的に定義した場合、コンパイラーは //Date(int year, int month, int day) //{ // _year = year; // _month = month; // _day を生成しなくなります。= day; //} void Print() { cout << _year << "-" << _month << "-" << _day << endl; }
プライベート:
int _year;
int _month;
int_day;
};int main()
{ // Date クラスのコンストラクターをマスクした後、コードはコンパイルできます。コンパイラーはパラメーターのないデフォルトのコンストラクターを生成するためです。 // Date クラスのコンストラクターを解放すると、コードはコンパイルに失敗します。コンストラクターを明示的に定義すると、コンパイラーは // 引数のないコンストラクターを生成しなくなり 、解放後にエラーを報告します: エラー C2512: “Date”: 利用可能な適切なデフォルト コンストラクターがありません Date d1; d1.Print(); return 0 ; }// コンストラクタはデフォルトのメンバー関数でもあります。これを書かなければ、コンパイラが自動的に生成します。
// コンパイルによって生成されるデフォルト構造体の特徴:
// 1. 書かなければ生成されます。 // 2. 組み込み型のメンバーは処理されません
(C++11、宣言はデフォルト値をサポートします)
// 3. カスタム型のメンバーは処理され、このメンバーのデフォルトのコンストラクターがコールバックされます。// 要約: 一般に、独自のコンストラクターを作成し、初期化方法を決定する必要があります
。 // すべてのメンバー変数はカスタム型であるため、コンストラクターを作成しないことを検討できます
class Time
{ public: Time() { cout << "Time()" << endl; _時間 = 0; _分 = 0; _秒 = 0; }
プライベート:
int _hour;
int _分;
int _秒;
};class Date
{ private: // 基本型(組み込み型) int _year; int _month; int _day; // カスタム型 Time _t; };int main()
{ 日付 d; 0を返します。}
class Time
{ public: Time() { cout << "Time()" << endl; _時間 = 0; _分 = 0; _秒 = 0; プライベート: int _hour; int _分; int _秒; };
class Date
{ private: // 基本型(組み込み型) int _year = 1970; int _month = 1; int _day = 1; // カスタム型 Time _t; };int main()
{ 日付 d; 0を返します。}
クラス Date
{ public: Date() { _year = 1900; _月 = 1; _日 = 1; Date (int 年 = 1900、int 月 = 1、int 日 = 1) { _year = 年; _month = 月; _day = 日; }
プライベート:
int _year;
int _month;
int_day;
};// 次のテスト関数はコンパイルされますか?
void Test()
{ 日付 d1; } int main() { Test(); return 0; }
class Date
{ public: Date(int year = 1, int month = 1, int day = 1) { cout << "Date(int year = 1, int month = 1, int day = 1)" << endl;
_year = 年;
_month = 月;
_day = 日;
}
void Print()
{ cout << _year << "/" << _month << "/" << _day << endl; }~Date()
{ cout << "~Date()" << endl; }private:
int _year = 1; // 宣言されたデフォルト値
int _month = 1;
int _day = 1;
};class Stack
{ public: Stack(size_t n = 4) { cout << "Stack(size_t n = 4)" << endl;
if (n == 0)
{ a = nullptr; 上部 = 容量 = 0; } else { a = (int*)malloc(sizeof(int) * n); if (a == nullptr) { perror("realloc 失敗"); 終了(-1); }
トップ = 0;
容量 = n;
}
}~Stack()
{ cout << "~Stack()" << endl; 無料(a); a = nullptr; 上部 = 容量 = 0; }void Push(int x)
{ if (top == 容量) { size_t newcapacity = 容量 == 0 ? 4 : 容量 * 2; int* tmp = (int*)realloc(a, sizeof(int) * newcapacity); if (tmp == nullptr) { perror("realloc 失敗"); 終了(-1); } if (tmp == a) { cout << 容量 << "原地扩容" << endl; } else { cout << 容量 << "异地扩容" << endl; }
a = 温度;
容量 = 新しい容量;
}a[トップ++] = x;
}int Top()
{ return a[top - 1]; }void Pop()
{ assert(top > 0); - 上; }void Destroy()
{ free(a); a = nullptr; 上部 = 容量 = 0; }bool Empty()
{ return top == 0; } private: // メンバー変数 int* a; int top; int Capacity; };// 2 つのスタックがキューを実装します
class MyQueue
{ private: Stack _pushst; Stack _popst; }; int main() { Date d1; Date d2; }
スタックst1;
スタックst2;
0を返します。
}
class Time
{ public: ~Time() { cout << "~Time()" << endl; プライベート: int _hour; int _分; int _秒; };
class Date
{ private: // 基本型(組み込み型) int _year = 1970; int _month = 1; int _day = 1;// カスタムタイプ
Time _t;
};int main()
{ 日付 d; 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 クラスのデフォルトのデストラクタを生成します。その目的は、
その中にある Time // クラスのデストラクタを呼び出すことです。つまり、Date オブジェクトが破棄されるときは、次のことが必要です。内部の各カスタム オブジェクトが正しく破棄できることを確認します
// main 関数は Time クラスのデストラクターを直接呼び出しませんが、Date クラスのコンパイラーによって生成されたデフォルトのデストラクターを明示的に呼び出します // 注 : どのクラスのオブジェクトである
か作成された場合は、そのクラスのデストラクタを呼び出して、そのクラスを破棄します。 クラスのオブジェクトは、クラスのデストラクタを呼び出します。
クラス Date
{ public: Date(int year = 1, int month = 1, int day = 1) { _year = year; _month = 月; _day = 日; }
Date(Date& d)
{ cout << "Date(Date& d)" << endl;_年 = d._年;
_month = d._month;
_日 = d._日;
}void Print()
{ cout << _year << "/" << _month << "/" << _day << endl; } private: // 内置类型 int _year; int _month; int_day; };
typedef int データ型;
class Stack
{ public: Stack(size_t 容量 = 3) { _array = (DataType*)malloc(sizeof(DataType) * 容量); if (NULL == _array) { perror("malloc申请空间失败!!!"); 戻る; }
_capacity = 容量;
_サイズ = 0;
}Stack(Stack& s)
{ cout << "Stack(Stack& s)" << endl; // ディープコピー _array = (DataType*)malloc(sizeof(DataType) * s._capacity); if (NULL == _array) { perror("malloc アプリケーション スペースに失敗しました!!!"); return; }
memcpy(_array, s._array, sizeof(DataType) * s._size);
_size = s._size;
_capacity = s._capacity;
}void Push(DataType データ)
{ _array[_size] = データ; _サイズ++; }~Stack()
{ cout << "~Stack()" << endl; フリー(_array); _array = nullptr; _サイズ = _容量 = 0; } private: // 内置类型 DataType* _array; int _capacity; int _size; };void func1(Date d)
{ d.Print(); } // s が何らかのデータを挿入することが予想され、s の変更は s1 には影響しませんvoid func2(Stack s) { s.Push(1); s .Push(2); }
int main()
{ 日付 d1(2023, 7, 21); 関数1(d1);スタック s1;
関数2(s1);スタック s2(s1);
// 以下の 2 つの書き方は同等で、どちらもコピー構築
Date d2(d1);
Date d3 = d1;
0を返します。
}
class Date
{ public: Date(int year = 1900, int month = 1, int day = 1) { _year = year; _month = month; _day = day; } // Date(const Date& d)// 正しい書き方 Date( const Date& d)// 書き方が間違っています: コンパイルエラーになり、無限再帰が発生します { _year = d._year; _month = d._month; _day = d._day; } private: int _year; int _month; int _day ; };
int main()
{ 日付 d1; 日付 d2(d1); 0を返します。}
クラス時間
{ public: Time() { _hour = 1; _分 = 1; _秒 = 1; } Time(const Time& t) { _hour = t._hour; _分 = t._分; _秒 = t._秒; cout << "Time::Time(const Time&)" << endl; プライベート: int _hour; int _分; int _秒; };
class Date
{ private: // 基本型(組み込み型) int _year = 1970; int _month = 1; int _day = 1;// カスタムタイプ
Time _t;
};int main()
{ Date d1; // 既存の d1 を使用して d2 をコピーして構築します。ここでは Date クラスのコピー コンストラクターが呼び出されます。 // ただし、Date クラスはコピー コンストラクターを明示的に定義していないため、コンパイラーはDate クラス デフォルトのコピー コンストラクターを生成します Date d2(d1); return 0; }// ここで、次のプログラムがクラッシュすることがわかりますか? ここでは、後で解決するために学習するディープコピーが必要です。
typedef int DataType;
class Stack
{ public: Stack(size_t Capacity = 10) { _array = (DataType*)malloc(capacity * sizeof(DataType)); if (nullptr == _array) { perror("malloc はスペースの適用に失敗しました") ; return; } _size = 0; _capacity = 容量; } void Push(const DataType& data) { // CheckCapacity(); _array[_size] = data; _size++; } ~Stack() { if (_array)
{ 無料(_array); _array = nullptr; _容量 = 0; _サイズ = 0; } }プライベート:
DataType* _array;
サイズ_t _サイズ;
サイズ_t_容量;
};int main()
{ スタック s1; s1.プッシュ(1); s1.プッシュ(2); s1.プッシュ(3); s1.プッシュ(4); スタック s2(s1); 0を返します。}class Date
{ public: Date(int year, int minutes, 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; プライベート: int _year; int _month; int_day; }; Date Test(Date d) { Date temp(d); 戻り温度; } int main() { 日付 d1(2022, 1, 13); テスト(d1);
0を返します。
}
さて、Xiao Yalan の今日の学習コンテンツはこれで終わりです。引き続き頑張っていきましょう。!!!