クラスのデフォルトのメンバー関数
1. コンストラクター
コンストラクタは、クラス名と同じ名前を持つ特別なメンバー関数です。コンストラクタは、クラス型オブジェクトの作成時にコンパイラによって自動的に呼び出され、各メンバー関数が初期値を持ち、コンストラクタのライフ サイクル全体で 1 回だけ呼び出されることを保証します。オブジェクト。
特性
コンストラクターは特別なメンバー関数です。コンストラクターの名前はコンストラクターと呼ばれていますが、コンストラクターの主なタスクはオブジェクトを作成するためのスペースを開くことではなく、オブジェクトを初期化することであることに注意してください。
key: スペースを開くことは関数スタック フレームの問題であり、スペースを開くために関数を呼び出します。
- 関数名はクラス名と同じである必要があります
- 戻り値なし(voidを記述する必要はありません)
- オブジェクトがインスタンス化されるときに、対応するコンストラクターを自動的に呼び出します。
- コンストラクターはオーバーロードできる
class Time
{
public:
//无参的构造函数
Time()
{
cout << "None Pram" << endl;
}
//带参数的构造函数
Time (int hour, int minute, int second)
{
_hour = hour;
_minute = minute;
_second = second;
cout << _hour << ":" << _minute << ":" << _second << endl;
}
private:
int _hour;
int _minute;
int _second;
};
int main()
{
Time t1; //调用无参的构造函数
Time t2(9, 19, 30); //调用带参的构造函数
Time t3(); //该方法调用无参构造函数会报错,该种写法是写一个返回值为Time类型函数的写法。
}
- クラスに明示的に定義されたコンストラクターがない場合、C++ コンパイラーはデフォルトでパラメーターを持たないデフォルトのコンストラクターを生成します。ユーザーがコンパイラーを明示的に定義すると、コンパイラーは自動的に生成されなくなります。
class Time
{
public:
带参数的构造函数
//Time(int hour, int minute, int second)
//{
// _hour = hour;
// _minute = minute;
// _second = second;
// cout << _hour << ":" << _minute << ":" << _second << endl;
//}
private:
int _hour;
int _minute;
int _second;
};
int main()
{
//1.将显示定义的构造函数屏蔽以后,编译器会默认自动生成一个
//无参的默认构造函数,因此不会报错。
//2.如果将显示定义的构造函数展开,编译器不在生成,因为用户定义了一个。
//此时如果还这样声明变量,就会报错,要以自己定义的方式传参。
Time t1;
}
-
コンパイラーによって生成されるデフォルトのコンストラクターでは、実際には Time クラスのメンバー変数が初期化されません。
コンパイラーによって生成されるデフォルトのコンストラクターは、組み込み型 (int/char/double/pointer) を処理せず、カスタム型 (struct、class、union) などに対して独自のデフォルト コンストラクターを呼び出します。(組み込み型を処理しないのは C++ の欠陥です)。C++11 では、この欠陥を補う別の機能が追加されています。つまり、組み込み型のメンバー変数がクラス内で宣言されるときに、デフォルト値を与えることができます。 -
引数のないコンストラクター、完全なデフォルト コンストラクター、およびコンパイラーなしで生成されたコンストラクターはすべてデフォルト コンストラクターと見なされますが、存在できるデフォルト コンストラクターは 1 つだけです。
2. デストラクター
コンストラクターの機能とは異なり、デストラクターはオブジェクト自体の破棄を完了しません。ローカル オブジェクトの破棄はコンパイラーによって行われます。オブジェクトが破棄されると、デストラクターが自動的に呼び出され、オブジェクト内のリソースのクリーンアップ作業が完了します。オブジェクトは、ライフサイクルに達すると自動的に破棄されます。
特性
- デストラクター名はクラス名の前の文字 ~ です。
- パラメータなし、戻り値の型なし
- クラスにはデストラクターを 1 つだけ含めることができます。明示的に定義されていない場合、システムはデフォルトのデストラクターを自動的に生成します。注:デストラクターはオーバーロードできません
- オブジェクトのライフサイクルの終了時に、C++ コンパイラ システムは自動的にデストラクターを呼び出します。
class Stack
{
public:
Stack(int capacity=3)
{
_arr = (int*)malloc(sizeof(int) * capacity);
if (_arr == nullptr)
{
printf("malloc failed");
}
_capacity = capacity;
_size = 0;
printf("malloc success\n");
}
~Stack()
{
if (_arr)
{
free(_arr);
_capacity = 0;
_size = 0;
}
cout << "~stcak" << endl;
}
private:
int* _arr;
int _capacity;
int _size;
};
int main()
{
Stack s1;
return 0;
}
- デストラクターが明示的に定義されていない場合、コンパイラーはデフォルトでデストラクターを自動的に生成します。組み込み型は処理されず、カスタム型に対して独自のデストラクターを呼び出します。
- クラスにアプリケーション リソースがない場合は、デストラクターを記述せずに、コンパイラーによって生成されたデフォルトを直接使用できます。そうしないと、リソースリークが発生します。
3. コンストラクターのコピー
オブジェクトを作成するときは、既存のオブジェクトとまったく同じような新しいオブジェクトを作成できます。この関数には、このクラス型のオブジェクトへの参照である仮パラメータが 1 つだけあります (通常は const で変更されます)。
特性
- コピー コンストラクターは、コンストラクターのオーバーロードされた形式です。
- コピー コンストラクターのパラメーターは 1 つだけであり、型 object への参照である必要があります。値を渡す呼び出しごとに、渡された値はコピー構造であるため、値を渡すメソッドを使用すると無限呼び出しが発生します (値は一時的にコピーされます)。過去に渡されたパラメータをシェイプにコピーします)。これにより、無限再帰が発生します。
class Time
{
public:
//全缺省的构造函数
Time(int hour=0, int minute=0, int second=0)
{
_hour = hour;
_minute = minute;
_second = second;
cout << _hour << ":" << _minute << ":" << _second << endl;
}
//复制本应该有两个形参,为什么只写了一个?
//因为还隐含了一个this指针。
//使用const是防止传进来的d被修改
Time(const Time& d)
{
this->_hour = d._hour;
_minute = d._minute;
_second = d._second;
}
private:
int _hour;
int _minute;
int _second;
};
int main()
{
Time t1;
Time t2(t1); //拷贝构造的语法,获得一个以t1完全一样的t2
return 0;
}
質問 1: コピーの構築を完了するために = 記号を使用しないのはなぜですか?
d2 と d3 はどちらもコピー構築のタスクを完了できますが、d1 は直感的で明確ではなく、割り当てと間違えられやすいです。
質問 2: ポインタでもコピー構築のタスクを完了できますが、なぜ参照を使用するのでしょうか?
d4のアドレスを渡すのはちょっと冗長で面倒ですが、d1のアドレスをd5に割り当てるというのはどういう意味でしょうか?明らかに、d2、d4、および d5 を比較すると、参照の使用が通常の人々の論理に最も一致していることがわかります。
- 明示的に定義されていない場合、コンパイラはデフォルトのコピー コンストラクターを生成します。デフォルトのコピー コンストラクター オブジェクトは、メモリ ストレージに従ってバイト オーダーでコピーされます。この種のコピーは、シャロー コピーまたはバリュー コピーと呼ばれます。
class TestCls {
public:
TestCls()
{
cout << "TestCls()" <<endl;
p = new int;
}
TestCls(const TestCls& d)
{
cout << "TestCls(const TestCls& testCls)" << endl;
a = d.a;
//p = testCls.p; //如果是编译器默认生成的,会直接把指针的地址赋给p
p = new int;
*p = *(d.p); //为拷贝类的p指针分配空间,实现深度拷贝
}
~TestCls()
{
delete p;
std::cout << "~TestCls()" << std::endl;
}
private:
int a;
int* p;
};
int main()
{
TestCls t1;
TestCls t2(t1);
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;
return 0;
}
このコードでは、d1 のメンバー変数と _t のメンバー変数がメモリ内で隣り合っていることに注意してください。
-
リソース アプリケーションがクラスに関与していない場合は、コピー コンストラクターを記述してもしなくても構いません。リソース アプリケーションが関与すると、コピー コンストラクターを記述する必要があります。それ以外の場合は、コピー コンストラクターは浅いコピーになります (ポインターにのみ割り当てられます)。
-
コピー構築の一般的なシナリオ
既存のオブジェクトを使用して新しいオブジェクトを作成します。関数パラメータの型はクラス型オブジェクトで、関数の戻り値はクラス型オブジェクトです。
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;
}
したがって、効率を高めるために、一般オブジェクトにパラメータを渡す場合は参照型を使用し、返す場合は実際のシナリオに応じて可能な限り参照を使用するようにしてください。
4. 代入オーバーロード機能
演算子のオーバーロード
コードの可読性を高めるために、C++ では特殊な関数名を持つ関数である演算子のオーバーロードが導入されています。
組み込み型は演算子を直接使用して操作でき、コンパイラはその操作方法を知っていますが、カスタム型は演算子を直接使用できず、コンパイラは操作方法を知らないため、演算子のオーバーロードを自分で実装する必要があります。
関数定義の構文:
戻り値の型 演算子 演算子(パラメータリスト)
- 新しい演算子 (operator@ など) は作成できません。
- オーバーロードされた演算子にはクラス型パラメータが必要です
- 組み込み型の演算子として、その意味を変更することはできません。たとえば、組み込み型 + はその意味を変更できません。
- クラス メンバー関数としてオーバーロードされると、メンバー関数の最初のパラメーターが非表示の this であるため、その仮パラメーターはオペランドの数より 1 少ないように見えます。
- .* (めったに見られません) :: (スコープ修飾子) sizeof ?: (三項演算子) . (関数呼び出し記号) 上記 5 つの記号はオーバーロードできないことに注意してください
class Date
{
public:
//构造函数
Date(int year = 1970, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
// 运算符重载
//相当于bool operator==(Date* const this, const Date& d);
//注意:这里左操作数是this,右操作数是d。
bool operator==(const Date& d)
{
return _year == d._year
&& _month == d._month
&& _day == d._day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2023, 8, 3);
Date d2(d1);
Date d3(1, 1, 1);
//相当于
//cout<<d1.operator==(&d1, d2)<<endl;
cout << (d1 == d2) << endl; //结果为1
cout << (d1 == d3) << endl; //结果为0
return 0;
}
代入演算子のオーバーロード
つまり、既存のオブジェクト (すでに存在する) に値を割り当て、構築をコピーします (新しいオブジェクトに値を割り当てます)。
- パラメータのタイプ: const T&、参照を渡すと効率が向上します
- 戻り値の型: T&、戻り参照により戻りの効率が向上します。戻り値の目的は、継続的な代入をサポートすることです。
- 自分自身に値を割り当てているかどうかを確認してください
- Return *this: 連続代入でエラーがないことを保証します。
// 赋值运算符重载
//参数加引用和const,传参更快,防止参数修改
//返回引用,防止拷贝构造。
Date& operator=(const Date& d)
{
//防止 d1 = d1;
if(this != &d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this;
}
//可以这样调用
d2 = d1; // d2.operator=(&d2, d1) 第一个参数是地址,第二个参数是传引用
d3 = d2 = d1;
-
代入演算子は、クラスのメンバー関数としてのみオーバーロードでき、グローバル関数としてオーバーロードできません
。代入演算子はオーバーロードされているため、ユーザーが表示しない定義がクラス内にある場合、コンパイラは自動的にクラスに一人。このとき、もう一つグローバルに宣言するとクラス内の宣言と競合してしまいます。 -
ユーザーが実装を表示しない場合、コンパイラはデフォルトでデフォルトの代入演算子オーバーロードを生成します。これは値の形式でバイトごとにコピーされます。組み込み型のメンバー変数は直接割り当てられ、カスタム クラスは自己実装されたものを呼び出す必要があります。
const メンバー関数
constで修飾したメンバー関数を使用して
記事のアドレスを参照します
アドレス演算子のオーバーロード
//写在类里面
//取地址运算符重载
Date* operator&()
{
return this;
}
//通过这样设置可以保证,类外面的成员只能访问地址,不能修改地址。
const Date* operator&()const
{
return this;
}
通常の状況では、このメンバー関数を記述しないと、コンパイラーが自動的にそれを生成します。したがって、通常は自分で記述する必要はありません。
では、このメンバー関数はいつ作成すればよいのでしょうか?
たとえば、このタイプのオブジェクトのアドレスを他の人に取得したくない場合は、nullptr を返すと、このオブジェクトのアドレスを取得できなくなります。