c++—类和对象(下)-八大成员函数 构造函数概念构造函数是一个特殊的成员函数定义类对象时自动调用用于对对象进行初始化特性函数名与类名相同该函数没有返回值参数可以自己设置构造函数可以重载也就是说可以有多个参数列表不同的构造函数如果自己没有显示定义任意构造函数包括拷贝构造移动构造等编译器会为我们生成无参默认构造函数构造函数的调用class A { public: A(){} A(int x, int y) {} }; int main() { A a;//调用无参构造 A b(1,2);//调用有参构造 }默认构造函数的缺陷编译器自动生成的默认构造函数有如下特性对内置类型数据没有定义要不要去初始化有可能处理也有可能不处理。对自定义类型数据会调用这个自定义类型里面无参构造函数不论是默认构造函数还是自定义的无参构造函数去初始化这个自定义类型数据。这里又分成三种情况一种是这个自定义类型里没有无参构造函数比如自定义了有参构造编译器不会生成默认构造函数而我们也没有自定义无参构造找不到默认构造函数就会报错。第二种是这个自定义类型里有默认构造函数这时编译器自动生成调用了默认构造函数之后对这个自定义类型里的内置类型不处理遇到自定义类型重复以上步骤。第三种是这个自定义类型里有自定义的无参构造函数他一般能正确初始化这个自定义类型因为这是由我们控制的嘛。最终对于内置类型有可能处理有可能不处理。所以我们一般要自己显示写构造函数。当然在某些情况下也可能不需要我们写构造函数比如下面的Queuec11为这个缺陷打上了补丁在类中定义成员变量的时候就可以赋予成员变量初始值之后无论哪种构造函数编译器生成的自己定义的都会使用这个初始值初始化该成员变量我们把被赋予的值叫做缺省值。注意如果构造函数内部也对内置类型做了相关初始化就以构造函数内部的初始化为准这也是为什么叫缺省值的原因可用可不用嘛。析构函数概念析构函数在对象生命周期结束后自动调用其目的是销毁对象内部的动态资源。特性析构函数名是~ 类名析构函数无参数无返回值一个类只能有一个析构函数。若未显式定义系统会自动生成默认析构函数。注意析构函数不能重载因为它没有参数无法区分对象生命周期结束时自动调用析构函数在同一生命周期内按属性定义的顺序对对象进行构造而析构的顺序和对象定义的顺序相反默认析构函数的缺陷class Example { std::string str; // 成员对象 int* ptr; // 原始指针 public: // 编译器生成的默认析构函数等价于 ~Example() noexcept { // 1. 调用成员对象的析构函数str.~string() // 2. 忽略原始指针ptr不会自动被delete // 3. 隐式标记为 noexcept表示不会抛异常 } };可以看出如果有动态资源最好还是自己写析构函数拷贝构造概念拷贝构造是指在创建对象的时候将已有对象的内容拷贝到该对象中。实际上拷贝构造就是构造函数的一个特殊分支本质上还是初始化。特性拷贝构造是构造函数的一种重载形式。所以除了参数拷贝构造和构造函数的其他部分没什么不同。拷贝构造的参数只有一个且必须是类的类型的引用用指针也可以只是相对引用来说比较麻烦如果直接传值的话会报错因为会引发无穷递归想要把d1拷贝给d2,先得把d1拷贝给形参date然后在用date拷贝d2。而想把d1拷贝给date就得先把d1拷贝给新的形参date这样一直循环发生无穷递归。若未显式定义编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数按字节拷贝对象类似于memcpy这种拷贝叫做浅拷贝或者值拷贝。如果对象中有指向动态资源的指针的时候还使用浅拷贝那这两个指针就会指向同一块空间这样两个对象就不是独立的了这不是我们想要的。这时候就要深拷贝需要我们自己定义一个拷贝构造函数在里面开创新的空间然后把里面内容改成和要拷贝的对象里指针所指向空间一样的。使用拷贝构造的使用方法有两种A st2(st1)或者A st2 st1。举个例子class A { }; int main() { A x; A y(x);//调用拷贝构造 A z x;//调用拷贝构造 }赋值运算符重载赋值运算符重载也是运算符重载但它有以下注意点和其他运算符重载不同赋值重载必须是成员函数不能定义在全局。同构造析构拷贝函数一样如果没有在类里实现赋值重载类中会自动生成默认赋值运算符重载该默认成员函数的行为与拷贝构造类似所以定义了全局的赋值运算符重载也没用因为类中的默认赋值运算符重载会先匹配到。如果自己没定义赋值运算符重载编译器会生成默认的赋值运算符重载它按字节拷贝赋值。赋值重载和拷贝构造区分“Date st1 st2”是拷贝构造“st1 st2”是赋值重载简单来说拷贝构造针对的是刚要创建的对象赋值重载针对的是两个已存在的对象。赋值重载必须有返回值因为有连续赋值的情况。有两种返回方式一种是返回引用。一种是返回拷贝。总的来说返回引用效率高一点。赋值运算符重载调用过后如果返回对象的生命周期在对象返回后没有结束并且没有析构那就返回引用否则返回拷贝使用已经销毁对象的引用会导致不可预知的错误。取地址运算符重载和const取地址运算符重载取地址运算符重载和const取地址运算符重载也是运算符重载但它们有以下注意点和其他运算符重载不同取地址运算符重载必须是成员函数不能定义在全局。同构造析构拷贝函数一样如果没有在类里实现取地址运算符重载类中会自动生成默认取地址运算符重载所以定义了全局的取地址运算符重载也没用因为类中的默认取地址运算符重载会先匹配到。这一部分不需要自己编写一般用编译器默认生成的就好。就算自己实现也比较简单就是重载两份运算符而已class MyClass { public: MyClass* operator() { // 自定义取地址逻辑 return this; // 通常返回this指针 } }; class MyClass { public: const MyClass* operator() const { // 自定义const对象的取地址逻辑 return this; // 通常返回this指针 } };注意的是在成员函数中有const和不加const可以构成函数重载。移动构造和移动赋值重载c11移动构造和移动赋值重载分别是构造和赋值重载的两种形式他们以右值引用为参数在内部可以不进行拷贝而是直接交换右值的资源下面是这两种成员函数的样子class A { public: A(A x) //移动构造 { //.... } A operator(A x) //移动赋值重载 { //.... } };关于移动构造和移动赋值重载在引用一节有讲这里只说说编写他们要注意的地方如果你没有自己实现移动构造函数且没有实现析构函数 、拷贝构造、拷贝赋值重载中的任意一个。那么编译器会自动生成一个默认移动构造。默认生成的移动构造函数对于内置类型成员会执行逐成员按字节拷贝自定义类型成员则需要看这个成员是否实现移动构造如果实现了就调用移动构造没有实现就调用拷贝构造。如果你没有自己实现移动赋值重载函数且没有实现析构函数 、拷贝构造、拷贝赋值重载中的任意一个那么编译器会自动生成一个默认移动赋值。默认生成的移动构造函数对于内置类型成员会执行逐成员按字节拷贝自定义类型成员则需要看这个成员是否实现移动赋值如果实现了就调用移动赋值没有实现就调用拷贝赋值。(默认移动赋值重载跟上面移动构造完全类似)如果你提供了移动构造或者移动赋值编译器不会自动提供拷贝构造和拷贝赋值。运算符重载运算符重载是 C 中允许程序员为自定义类型重新定义运算符行为的特性使得类对象可以像内置类型一样使用运算符。运算符重载既可以是全局函数也可以是成员函数。两个类如何比较定义一个比较函数如下class A { public: A(int x) { a x;} int a; }; class B { public: B(int y) { b y; } int b; }; bool isequal(A x, B y) { return x.a y.b; } int main() { A x(1); B y(2); isequal(x, y); }这样的代码可读性不高也比较麻烦所以有了运算符重载。使用函数名为关键字operator加上某个运算符如operatoroperatoroperator。函数参数个数与操作符的操作数个数保持一致别忘了非静态类成员函数都有一个隐含的this参数代表调用者因此运算符重载作为非静态成员函数时并且运算符操作数为2时我们应该设置的参数个数为1。函数参数中必须有至少一个自定义类型的参数非静态成员函数自动就有自定义类型的参数this指针都是内置类型并没有意义。函数的返回值由运算符决定比如operator的返回值是booloperator的返回值为intdouble等。不能用其他符号链接operator构成函数名只能用内置运算符。而且在内置运算符中.*. sizeof也不能用来运算符重载。运算符重载函数收到参数的顺序和操作数的顺序一致编译器的规则是当遇到一个类使用运算符时自动调用运算符重载先在类里面找再到全局中找找不到报错。因此可以得出两种比较两个类的方法class A { public: A(int x) { a x;} bool operator(const B x) { return a x.b; } int a; }; class B { public: B(int y) { b y; } int b; }; int main() { A x(1); B y(2); cout(x y);//实际上xy 最终转换成x.operator(y)!!! }class B { public: B(int y) { b y; } int b; }; class A { public: A(int x) { a x;} int a; }; bool operator(const A y,const B x) { return y.a x.b; } int a; int main() { A x(1); B y(2); cout(x y);//实际上xy 最终转换成operator(x,y)!!! }特殊的运算符重载前置和后置的函数名相同但是前置应该返回的是后的值而后置返回之前的值他们的实现不同函数名相同这就需要函数重载。然而之前规定了运算符的参数个数与操作数个数一致所以前置和后置函数的参数都只有this指针。迫不得已又规定给后置的参数中多加一个int来区分这两个函数。这个int参数不需要使用仅仅为了区分前置和后置成员函数的删除class A { A() delete;//删除默认构造函数 A operator(const A x) delete;//删除赋值重载函数 void func() delete;//删除普通函数 };成员函数删除规则删除拷贝构造/移动构造会连带删除默认构造如果类需要默认构造需显式定义或使用 default。删除析构函数会导致所有默认构造和拷贝控制被删除此类对象无法被构造、拷贝或销毁通常仅用于特殊场景如单例模式禁止析构。继承链中父类的删除操作会影响子类子类无法绕过父类已删除的函数如父类禁止拷贝子类也无法拷贝。