11. 拷贝构造函数和拷贝赋值函数
C++ 两个同类型对象的复制时默认是成员变量的值依次赋值,这个由 C++ 编译器为每个类默认添加的两个成员函数:拷贝构造函数和拷贝赋值函数来决定了。如果需要打破这种默认的复制方法则需要自己编写这两个成员函数来实现自定义的功能。通常用这两个成员函数来控制对象赋值的过程。
拷贝构造函数的语法
class 类名 {
public:
类名(const 类名 & obj): 构造函数的初始化列表 { // 拷贝构造函数
...
}
...
};
说明
- 拷贝构造函数的参数必须是同类对象的常引用(
const 类名 &)。 - 拷贝构造函数会在创建对象时直接用同类对象赋初始值时被自动调用。
以下两种情况才会调用拷贝构造函数
Person p1("老魏", 35);
Person p2 = p1; // 调用拷贝构造函数
Person p3(p1); // 调用拷贝构造函数
拷贝赋值函数的语法
class 类名 {
public:
类名& operator=(const 类名& obj) { // 拷贝赋值函数
...
}
...
}
说明
- 拷贝赋值函数的参数必须是同类对象的常引用(
const 类名 &)。 - 拷贝赋值函数的返回值通常是对象自身的引用,类型为:
类名 &。 - 拷贝赋值函数会在对象创建后,再使用赋值语句对对象赋值时被调用。
- 通常一个类内自定义了拷贝构造函数则一定要自定义拷贝赋值函数。
以下情况才会调用拷贝构造函数
Person p1("老魏", 35);
Person p4("魏明择", 1);
p4 = p1; // 调用拷贝赋值函数
示例
以下重写 Person 类,使其能够实现深拷贝,让各个对象的数据各自独立。
// filename: copy_constructor.cpp
#include <iostream>
#include <string.h>
using namespace std;
class Person {
public:
// 构造函数
Person(const char * name, int a):age(a) {
if (NULL == name) { // 分配一个字节存放尾零
pname = (char*)malloc(1);
*pname = '\0';
} else {
pname = (char*)malloc(strlen(name)+1);
strcpy(pname, name);
}
cout << "构造函数被调用,动态分配了内存!" << endl;
}
// 拷贝构造函数
Person(const Person &obj):age(obj.age) {
this->pname = (char*)malloc(strlen(obj.pname)+1);
strcpy(this->pname, obj.pname);
cout << "拷贝构造函数被调用,重新动态分配了新内存!" << endl;
}
// 拷贝赋值函数
Person& operator=(const Person &obj) {
free(this->pname);
this->pname = (char*)malloc(strlen(obj.pname)+1);
strcpy(this->pname, obj.pname);
this->age = obj.age;
return *this;
}
// 析构函数
~Person(void) {
// 释放内存
cout << "析构函数被调用,即将释放内存" << endl;
free(pname);
}
public:
void printInfo() {
cout << pname << "今年" << age << "岁\n";
}
private:
char * pname; // 姓名
int age; // 年龄
};
int main(int argc, char * argv[]) {
Person p1("老魏", 35);
Person p2 = p1;
p1.printInfo();
p2.printInfo();
return 0;
}
编译和运行结果如下:
weimz@mzstudio:~$ g++ -o copy_constructor copy_constructor.cpp
weimz@mzstudio:~$ ./copy_constructor
构造函数被调用,动态分配了内存!
拷贝构造函数被调用,重新动态分配了新内存!
老魏今年35岁
老魏今年35岁
析构函数被调用,即将释放内存
析构函数被调用,即将释放内存
实验
自定义一个使用动态分配内存来存储数据的类(类内含有指针成员变量),然后添加拷贝构造函数和拷贝赋值函数来实现深拷贝,实现对象的自动复制。