拷贝构造函数
基本形式
注意形参类型是类类型,不能声明为explicit,因为有些地方需要隐式转换
class Foo
{
public:
Foo();
Foo(const Foo&);
};
合成拷贝构造函数
你不声明构造函数,也会给你一个合成的。浅拷贝问题。
调用时机
1.非引用类型形参
2.返回非引用类型
3.使用{}初始化一个数组
4.标准库容器的push或insert操作。emplace调用的是构造函数。
为啥形参是引用的
不是引用的话,会一直调用拷贝构造函数,会死循环下去。
拷贝初始化与explicit构造函数
vector构造函数为explicit类型,所以以下写法不正确。
vector<int> v1(10); //直接初始化
veector<int> v2=10; //xxx,构造不被允许
解释:其实veector<int> v2=10; 包含两句意思,第一句是调用构造函数创一个临时对象,第二句是拷贝。
下列语句也不行:
void f(vector<int>);
f(10); //xxx,也是不允许构造,即第一步都不行
f(vector<int>(10)); //正确
编译器优化:
有时编译器会做出优化。把两步优化成一步。前提是拷贝构造是private,不是private直接在第二步拷贝时就报错了。
string null_book = "000000"; //包含两步,创建对象和拷贝构造
//优化成
string null_book("000000");
赋值运算符
基本形式
class Foo
{
public:
Foo& operator=(const Foo&);
};
当重载运算符函数是成员函数时,第一个参数就绑定到this指针。
Sales_data a1,a2;
a1 = a2; //调用=运算符
合成 赋值运算符
你不写也会送你一个合成的。浅拷贝问题。
析构函数
基本形式
class Foo
{
public:
~Foo();
};
函数执行流程
构造函数先执行初始化列表,再执行函数体;析构函数先执行函数体,再销毁成员(逆序销毁)。
类的析构函数和指针类型成员
调用成员的析构函数,内置类型的成员没有析构函数。
注意不会自动帮内置指针成员调用delete p;
而智能指针是类类型,具有析构函数。
调用时机(句句箴言)
1.离开作用域被销毁时。
2.也会调用对象的成员的析构函数。
3.容器(标准库容器或数组)被销毁时,也调用元素的析构。
4. 对new这些动态创建的对象,你得调用delete,才会帮你调用析构。
5. 临时对象,它的完整表达式结束时。
注意局部指针或引用离开作用域时,不会自动帮你调用。
合成析构函数
你不写,也会帮你合成一个,但功能受限。
三五法则
1.一般你定义了析构函数,说明里面有动态内存释放,所以你必须得定义析构函数。
class HasPtr
{
public:
HasPtr(const string&s):ps(new string(s)){}
~HasPtr(){
delete ps; //需要手动释放
}
private:
string *ps;
};
如果你不自己定义拷贝函数的话,就会用合成的,会发生浅拷贝。
HasPtr f(HasPtr p) //这里一次拷贝
{
HasPtr ret = p; //这里一次拷贝
return ret; //xxx,p和ret都被销毁了,delete两次
}
更有甚者,你一调用就销毁实参的成员了。
HasPtr p("some");
f(p); //会释放p.ps
HasPtr q(p); //xxx,都没了ps了
2. 定义了拷贝函数也必须定义赋值运算符,反之亦然,如果没有成员涉及动态内存分配,不需要再定义析构函数。
比如为对象分配一个新的序号