C++ 拷贝构造函数、拷贝赋值运算符、析构函数

每一次都会忘,做个笔记吧。想到哪里写到哪里。

拷贝构造函数

第一个参数必须是自身类类型的引用,且任何额外参数都有默认值。(为什么必须是引用?见后解释) 合成拷贝构造函数:如果我们没有为一个类定义拷贝构造函数,则编译器会为我们定义一个。同合成的默认构造函数不同的是,即使我们定义了其他构造函数,编译器也会为我们合成一个拷贝构造函数。(一旦自己定义了构造函数,则不会合成默认构造函数) 拷贝初始化与直接初始化 直接初始化:要求编译器使用普通的函数匹配来选择与我们提供的参数最匹配的构造函数。

拷贝初始化:要求编译器将右侧运算对象拷贝到正在创建的对象中,如果需要的话,还要进行类型转换。

string dots(10, '.');               //直接初始化
string s(dots);                     //直接初始化
string s2 = dots;                   //拷贝初始化
string null_book = "9-999-8999";    //拷贝初始化
string nines = string(100, '9');    //拷贝初始化
使用‘=’号的是拷贝初始化,不使用等号的是直接初始化。

拷贝初始化发生在以下情况

1. 用 = 定义变量时发生。
2. 将一个对象作为实参传递给一个非引用类型的形参。
3. 从一个返回类型为非引用类型的函数返回一个对象。
4. 用花括号列表初始化一个数组中的元素或一个聚合类中的成员。(聚合类是指没有用户定义的构造函数,没有私有和保护的非静态数据成员,没有基类,没有虚函数)。
拷贝构造函数第一个参数必须是引用原因:由于拷贝构造函数被用来初始化非引用类类型的参数。如果其自身参数不是引用类型,则调用永远也不会成功——为了调用拷贝构造函数,我们必须拷贝它的实参,但为了拷贝实参,我们又必须调用拷贝构造函数,如此无限循环。

拷贝赋值运算符

与类控制其对象如何初始化一样,类也可以控制器对象如何赋值:

Sales_data trans, accum;
trans = accum;  //使用Sales_data的拷贝赋值运算符
与拷贝构造函数一样,如果类未定义自己的拷贝赋值运算符,编译器也会为它合成一个。 重载赋值运算符 重载运算符本质上是函数,其名字由operator关键字后接表示要定义的运算符的符号组成。因此,赋值运算符就是一个名为operator=的函数。类似于任何其他函数,运算符函数也有一个返回类型和一个参数列表。 如果是一个运算符是一个成员函数,其左侧运算对象就绑定到隐式的this参数。对于一个二元运算符,例如赋值运算符,其右侧运算对象作为显式参数传递。

拷贝赋值运算符接受一个与其类相同类型的参数:

class Foo{
public:
    Foo& operator=(const Foo&);  //赋值运算符
    //...
};

为了与内置类型的赋值保持一致,赋值运算符通常返回一个指向其左侧运算对象的引用。注意,标准库通常要求保存在容器中的类型要有其赋值运算符,且其返回值是左侧运算对象的引用。

析构函数

析构函是类的一个成员函数,名字由波浪号接类名构成。它没有返回值,也不接受参数:

class Foo{
public:
    ~Foo();   //析构函数
    //...
};
由于析构函数不接受参数,因此它不能被重载。对于一个给定类,只会由唯一一个析构函数。 在一个构造函数中,成员的初始化时在函数体执行之前完成的,且按照它们在类中出现的顺序进行初始化。在一个析构函数中,首先执行函数体,然后销毁成员。成员按初始化顺序的逆序进行销毁

无论何时一个对象被销毁,就会自动调用其析构函数:

1. 变量在离开其作用域时被销毁
2. 当一个对象被销毁时,其成员被销毁
3. 容器(无论是标准容器还是数组)被销毁时,其元素被销毁
4. 对于动态分配的对象,当对指向它的指针应用delete运算符时被销毁
5. 对于临时对象,当创建它的完整表达式结束时被销毁

相关推荐