前言:
先来看C++对象的赋值
Student xiaoming = new Student(“一年级”,“一班”);
Student xiaohong = xiaoming;
C++支持对象的赋值,上面的代码是没有问题的,对象xiaohong拥有和对象xiaoming一样的属性(同样是“一年级一班”)。
问题1:以上代码对象的赋值是怎么实现的?
答:一个对象xiaohong的生成,一般我们会想到构造函数。但是构造函数往往伴随着类成员属性的初始化,而且对象xiaoming初始化后,属性还可以改变,所以构造函数这条路就行不通了。C++编译器提供了一种特殊的构造函数,即默认拷贝构造函数,默认拷贝构造函数对对象中的属性进行依次拷贝。
问题2:拷贝构造函数什么时候被调用?
答:1、对象在创建时使用其他的对象初始化时;2、对象作为函数的参数进行值传递时。
问题3:拷贝构造函数的使用会引入什么风险?
答:会引入“深拷贝”和“浅拷贝”问题带来的重复释放资源的错误。
问题4:如何规避风险?
答:可以自定义拷贝构造函数,增加分配资源操作。
正文
1、先看一段代码,这段代码展示了拷贝构造函数和赋值运算符的使用方法。
// copyConstructorTest.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include<iostream>
class Student {
public:
//构造函数 constructor
Student() {
std::cout << "constructor!" << std::endl;
}
//析构函数 destructor
~Student() {
std::cout << "destructor!" << std::endl;
}
//拷贝构造函数 copy constructor
Student(const Student& stu) {
std::cout << "copy constructor!" << std::endl;
}
//赋值运算符 assignment operator
Student& operator=(const Student&) {
std::cout << "assignment operator!" << std::endl;
return *this;
}
private:
std::string name;
int age;
};
int main()
{
Student s1;
Student s2 = s1; //s2是新生成对象,调用拷贝构造函数
Student s3;
s3 = s1; //s3是已存在对象,调用赋值运算符
return 0;
}
2、深拷贝和浅拷贝
上述代码中,类的成员变量都是基本数据类型,在复制的时候,会新开辟一块内存来存值,这种拷贝方式叫做深拷贝。但是,如果类成员中有指针变量,则会出现浅拷贝,即同一块内存多了一个指针指向它,这样一来,在释放资源的时候,释放第一个指针时把内存释放,在释放第二个指针的时候,内存已经不存在了,所以报错。如下代码:
// copyConstructorTest.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include<iostream>
class Student {
public:
//构造函数 constructor
Student() {
std::cout << "constructor!" << std::endl;
}
//析构函数 destructor
~Student() {
std::cout << "destructor!" << std::endl;
delete address; // 浅拷贝会重复释放指向同一块内存的2个指针
}
//拷贝构造函数 copy constructor
Student(const Student& stu) {
std::cout << "copy constructor!" << std::endl;
}
//赋值运算符 assignment operator
Student& operator=(const Student&) {
std::cout << "assignment operator!" << std::endl;
return *this;
}
private:
std::string name;
int age;
char* address; //如果类成员变量中有指针,则涉及到浅拷贝
};
int main()
{
Student s1;
Student s2 = s1; //s2是新生成对象,调用拷贝构造函数
Student s3;
s3 = s1; //s3是已存在对象,调用赋值运算符
return 0;
}
3、解决指针浅拷贝的问题
解决方法就是在拷贝构造函数中,为指针新开辟一块内存空间,如下代码;
// copyConstructorTest.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include<iostream>
#include<string>
class Student {
public:
//构造函数 constructor
Student(std::string name, int age, char* address) {
this->name = name;
this->age = age;
this->address = new char[strlen(address) + 1]; //指针在构造时要开辟内存空间
strcpy(this->address, address);
std::cout << "constructor!" << std::endl;
}
//析构函数 destructor
~Student() {
std::cout << "destructor!" << std::endl;
delete[] address; // "[]"大括号加不加要看new的时候有没有
}
//拷贝构造函数 copy constructor
Student(const Student& stu) {
this->name = stu.name; //拷贝构造函数也是构造函数,所有成员变量都要复制
this->age = stu.age;
std::cout << "copy constructor!" << std::endl;
this->address = new char[strlen(stu.address) + 1]; //深拷贝,即内存也要复制一份
strcpy(this->address, stu.address);
}
//赋值运算符 assignment operator
Student& operator=(const Student&) {
std::cout << "assignment operator!" << std::endl;
return *this;
}
friend std::ostream& operator<<(std::ostream& os, Student& stu) {
os << stu.name << ":" << stu.age << ":" << stu.address << std::endl;
return os;
}
private:
std::string name;
int age;
char* address; //如果类成员变量中有指针,则涉及到浅拷贝
};
int main()
{
Student s1("XiaoMing", 20, "XXX");
std::cout << s1 << std::endl;
Student s2 = s1; //s2是新生成对象,调用拷贝构造函数
std::cout << s2 << std::endl;
Student s3("XiaoHong", 22, "YYY");
std::cout << s3 << std::endl;
s3 = s1; //s3是已存在对象,调用赋值运算符
std::cout << s3 << std::endl;
return 0;
}
总结
如果你的类里面有指针变量,就要考虑深浅拷贝带来的问题。