《C++ Primer》看到这一章我都惊呆了,C++竟然还能有这种功能:类可以控制该类型对象拷贝、赋值、移动和销毁时做什么。
以上操作通过:拷贝构造函数、移动构造函数、拷贝赋值运算符、移动赋值运算符、以及析构函数构成。
拷贝构造函数:
定义: 如果一个构造函数的第一个参数是自身类类型的引用,且任何额外参数都有默认值,则此构造函数是拷贝构造函数。
本质上是个构造函数,所以它出现就会构造新对象,没有新对象它就没出现。
拷贝构造函数的第一个参数必须是一个引用类型,因为如果是值传递方式穿参的话,会立即再次调用拷贝构造函数,这样就形成无限递归了!危险!
如果我们没有自定义一个拷贝构造函数,编译器会自动帮我们合成一个。默认拷贝构造函数会将其参数成员逐个拷贝到正在创建的对象中。
以下情况会发生拷贝初始化:
1. 用=定义变量。
2. 将一个对象作为实参传递给一个非引用类型的形参。
3. 从一个返回类型为非引用类型的函数返回一个对象。
4. 列表初始化一个数组中的元素或一个聚合类中的成员。
有个小坑是,A aa = a;
像这样定义并初始化一个A类型的变量aa,只会调用拷贝构造函数,不会调用拷贝赋值运算符。
#include <iostream>
using namespace std;
class A{
public:
A(int i=0):n(i){}
A(A &a, int i=0):n(i){
cout<< a.getI() <<endl;
cout<< getI() <<endl;
cout<<"hello"<<endl;
}
const int getI(){
return n;
}
private:
int n = 0;
};
int main(){
A a(998);
A b = a;//调用拷贝构造函数
//输出结果为:
//998
//0
//hello
//证明 a 被当成了拷贝构造函数的第一个参数
return 0;
}
拷贝赋值运算符
这其实就是个操作符重载。本质上是个名为operator=
的函数,所有有形参有返回值,有函数体,都有。
当然要成为一个伟大的赋值运算符,当然要付出一点代价:
1. 名字必须是operator=
2. 必须定义为成员函数
3. 左侧运算对象就绑定到隐式的this参数上
4. 接受一个与其所在类相同类型的参数
5. 为了与内置类型的赋值保持一致,返回指向左侧对象的引用。
如果类没有定义自己的拷贝赋值操作符,编译器会自动生成一个。它会将右侧运算对象的每个非static成员赋予左侧运算对象的对应成员。
#include <iostream>
using namespace std;
class A{
public:
A(int i=0):n(i){}
A(A &a, int i=0):n(i){
cout<< a.getI() <<endl;
cout<< getI() <<endl;
cout<<"hello"<<endl;
}
A &operator=(const A &r){
n = r.n;
cout<<"copying..."<<endl;
return *this;
}
const int getI(){
return n;
}
private:
int n = 0;
};
int main(){
A a(998);
A b(333);
b = a;
cout<< b.getI()<<endl;
//copying...
//998
return 0;
}
析构函数:
析构函数释放对象使用的资源,并销毁对象的非static数据成员。
跟构造函数一样的是:没有返回类型,如果没自定义,编译器就会生成一个默认的。
跟构造函数不一样的是:没有构造函数初始化列表,名字是类名前加一个波浪号~,析构函数不接受参数,所以不能被重载,不能定义为delete。
note:析构函数先执行函数体,然后销毁成员,顺序按初始化的逆序。销毁一个内置类型的指针不会delete它指向的对象。