一、类默认成员函数
在原有4个特殊成员函数(构造函数、析构函数、复制构造函数、赋值运算符)的基础上。C++11又新增了两个:移动构造函数与移动赋值运算符。构造函数与析构函数前面已经介绍过,下面分别介绍其余四个函数
1、复制构造函数
复制构造函数(也可以叫拷贝构造函数)是一种特殊的构造函数,它在创建对象时,使用同一类中之前创建的对象来初始化新创建的对象。如果在类中没有定义拷贝构造函数,编译器会自行定义一个。下面是一个复制构造函数常见的结构:
classname (const classname &obj) {
// 构造函数的主体
}
1.1、浅复制与深复制
默认的复制构造函数执行的是浅复制,复制时逐个复制非静态成员(成员复制也称为浅复制),复制的是成员的值。如果成员本身就是类对象,则将使用这个类的复制构造函数来复制成员对象。默认的复制构造函数在一些情况下会出问题,例如:
class Person{
private:
char *name;
public:
Person(const char *name, int len) {
this->name = new char[len + 1];
strcpy(this->name, name);
}
~Person() {
delete [] this->name;
}
void Display() {
cout << "name = " << this->name << endl;
}
};
int main()
{
const char *name = "zhangsan";
Person *p = new Person(name, 8);
p->Display(); // 1.输出name = zhangsan
Person tmp(*p); // 2.调用拷贝构造函数把p指向的对象对应的值复制给tmp对象
delete p; // 3.释放p指向的内存
tmp.Display(); // 4.调用tmp.Display,输出是乱码
return 0;
}
上面的步骤4输出的是乱码,程序运行时使用已经创建的*p来初始化tmp对象,默认执行的是浅复制,此时tmp里面name与p指向的对象包含的name成员指向的是同一块地址。当释放p指向的对象时,析构函数把name指向的内存也给释放掉了,导致后续执行tmp.Display()时,此时访问的name就是一块非法内存。
1.2、显示定义复制构造函数
默认的浅复制只复制值,下面显示定义复制构造函数进行深复制,也就是说复制name成员时,不是简单的复制值,而是创建一块内存来保存字符串。下面是自定义的复制构造函数:
Person(const Person &r) {
this->name = new char[r.len];
strcpy(this->name, r.name);
}
注意:当类中有成员使用new动态分配内存时,需要进行深度复制
1.3、复制构造函数何时被调用?
复制构造函数的作用是创建一个新对象,并使用一个已有的对象来初始化新创建的对象,在下面几个场景会调用复制构造函数
- 使用一个已有的对象来初始化一个新对象
- 当生成对象副本时,例如:函数直接传递对象或函数返回对象时
1.4、派生类的复制构造函数
派生类进行拷贝构造时,如果想让基类的成员也同时拷贝,就一定要在派生类拷贝构造函数初始化列表中显示调用基类拷贝构造函数,否则它会调用基类的默认构造函数,例如:
class Base
{
public:
Base(int val = 0):m_x(val){
cout << "Base constructor" << endl;
}
Base(const Base& oth):m_x(oth.m_x){
cout << "Base copy constructor" << endl;
}
public:
int m_x;
};
class Derived:public Base
{
public:
Derived(int val):Base(val), m_y(val){}
Derived(const Derived& oth):m_y(oth.m_y){}
public:
int m_y;
};
int main()
{
Derived d1(10);
Derived d2 = d1;
cout << "d2.m_x : " << d2.m_x << endl;
cout << "d2.m_y : " << d2.m_y << endl;
return 0;
}
输出结果如下: 调用派生类的复制构造函数时,没有在初始化列表显示调用基类的复制构造函数。因此。调用基类的默认构造函数m_x的值被赋值为0。如果Base类的构造函数,参数没有带默认值,则不是默认构造函数,上面的程序在编译时就会报错,因为,Derived类对应的复制构造函数找不到基类的默认构造函数。
Base constructor
Base constructor
d2.m_x : 0
d2.m_y : 10
Process returned 0 (0x0) execution time : 0.030 s
Press any key to continue.
注意:带默认参数的构造函数也是默认构造函数,例如:上面Base类的构造函数就是一个默认构造函数。
2、赋值运算符
与复制构造函数类似,赋值运算符的隐式实现也对成员逐个复制。如果成员本身就是类对象,程序将使用为这个类定义的赋值运算符来复制该成员,但是静态数据成员不受影响。下面是赋值运算符常用的结构:
classname & classname::operator=(const classname &s) {
// 函数主体
}
注意:函数返回对象的引用,目的是为了进行连续赋值
2.1、赋值运算符何时被调用?
将已有的对象赋给另一个对象时,将使用重载的运算符函数
const char *name = "zhangsan";
Person zhangsan(name, 8);
Person lisi(name, 8);
lisi = zhangsan; // 调用赋值运算符函数
2.2、如果一个类其实例不希望被复制,应当如何做?
可以把拷贝构造函数和operator=()声明成private 而不实现并增加注释,使用 private 可以在编译阶段防止误用, 而不声明则会生成默认的函数。例如:
private:
Person(const Person &r) {
// 对象不能被复制
}
Person& operator=(const Person &s) {
// 对象不能被复制
}