拷贝构造
拷贝构造函数 Copy Constructor
也是构造函数的一种
-
当利用已存在的对象创建一个新对象时(类似于拷贝),就会调用新对象的拷贝构造函数进行初始化。
-
拷贝构造函数的格式是固定的,接收一个const引用作为参数
class Car { int m_price; int m_length;public: Car(int price = 0, int length = 0) :m_price(price), m_length(length) { cout << "Car(int price = 0, int length = 0)" << endl; } // 拷贝构造函数 Car(int price = 0, int length = 0) :m_price(price), m_length(length) { cout << "Car(int price = 0, int length = 0)" << endl; } void display() { cout << "price=" << m_price << ", length=" << m_length << endl; } }; int main() { Car car1; Car car2(100); Car car3(100, 5); // 利用已经存在的car3对象创建了一个car4对象 // car4初始化时会调用拷贝构造函数 // 就算不写拷贝构造函数也可以 Car car4(car3); // 相当于 /* car4.m_price = car3.m_price; car4.m_length = car3.m_length; */ }
继承父类
class Person {
public:
int m_age;
Person(int age = 0) :m_age(age) {
}
Person(const Person &person) :m_age(person.m_age) {
}
};
class Student :public Person {
public:
int m_score;
Student(int age = 0, int score = 0) :Person(age), m_score(score) {}
Student(const Student &student) :m_score(student.m_score) {}
// 但Student的age并没有传进来
// 所以应该这样写
Student(const Student &student) :Person(student), m_score(student.m_score) {}
// 但如果不写的话,默认也会直接拷贝过去
};
一些注意点
class Car {
int m_price;
int m_length;
public:
Car(int price = 0, int length = 0) :m_price(price), m_length(length) {
cout << "Car(int price = 0, int length = 0)" << endl;
}
Car(const Car &car) :m_price(car.m_price), m_length(car.m_length) {
cout << "Car(const Car &car)" << endl;
}
void display() {
cout << "price=" << m_price << ", length=" << m_length << endl;
}
};
int main() {
Car car1(100, 5);
// Car(int price = 0, int length = 0)
Car car2(car1);
// Car(const Car &car)
Car car3 = car2;
// Car(const Car &car)
Car car4;
// Car(int price = 0, int length = 0)
car4 = car3;
car4.display();
// price=100, length=5 拷贝成功,但并没有调用拷贝构造函数
return 0;
}
第二种和第三种写法是等价的,都是调用拷贝构造函数
为什么car4 = car3;
并没有调用拷贝构造函数?
- 因为拷贝构造函数是在初始化的时候用到的,此时car4和car3都是已经存在的对象
- 所以这仅仅是赋值,并不是拷贝构造
子类的构造函数会默认调用父类无参的构造函数,如果子类的构造函数指明调用父类的拷贝构造函数,那就不会调用父类无参的构造函数。
浅拷贝、深拷贝
浅拷贝(shallow copy):指针类型的变量只会拷贝地址值
深拷贝(deep copy):将指针指向的内容拷贝到新的存储空间
class Car {
int m_price;
char *m_name;
public:
Car(int price = 0, const char *name = NULL) :m_price(price) {
if (name == NULL) return;
// 申请新的堆空间
m_name = new char[strlen(name) + 1] {};
// 拷贝字符串数据到新的堆空间
strcpy(m_name, name);
}
~Car() {
if (m_name == NULL) return;
delete[] m_name;
m_name = NULL;
}
void display() {
cout << "price is " << m_price << ", name is " << m_name << endl;
}
};
int main() {
const char *name = "bmw";
char name[] = {'b', 'm', 'w', '\0'};
Car *car = new Car(100, "bmw");
car->display();
delete[] name;
getchar();
return 0;
}
name的地址和m_name的地址是不一样的
因为m_name申请了新的堆空间并且把name的内容拷贝了过去。
-
编译器默认提供的拷贝是浅拷贝
比如拷贝构造
- 是将一个对象中所有成员变量的值拷贝到另一个对象
- 如果某个成员变量是指针,只会拷贝指针中存储的地址值,并不会拷贝指针指向的内存空间
- 可能会导致堆空间多次free的问题
-
如果要实现深拷贝,就需要自定义拷贝构造函数
- 将指针类型的成员变量所指向的内存空间,拷贝到新的内存空间
堆空间指向栈空间很危险
野指针,堆空间指向栈空间的指针,当栈空间的内容被回收
所以应该堆空间指向堆空间
但如果使用拷贝构造
int main() {
Car car1(100, "bmw");
Car car2 = car1;
car2.display();
getchar();
return 0;
}
两个car会指向同一块堆空间(浅拷贝:没有产生新的地址空间)
而且可能会导致double free,同一块堆空间被释放两次
所以应该是再申请一份堆空间,然后将car2指向新的堆空间地址,一人一份就不会有问题。(深拷贝:产生新的地址空间)
Car(const Car &car) :m_price(car.m_price) {
if (car.m_name == NULL) return;
// 申请新的堆空间
m_name = new char[strlen(car.m_name) + 1]{};
// 拷贝字符串数据到新的堆空间
strcpy(m_name, car.m_name);
}
现在完成了两个构造函数,分别都申请了新的堆空间,可以将申请堆空间这部分提取出来封装成函数。
class Car {
int m_price;
char *m_name;
void copy(const char *name = NULL) {
if (name == NULL) return;
// 申请新的堆空间
m_name = new char[strlen(name) + 1] {};
// 拷贝字符串数据到新的堆空间
strcpy(m_name, name);
}
public:
Car(int price = 0, const char *name = NULL) :m_price(price) {
copy(name);
}
Car(const Car &car) :m_price(car.m_price) {
copy(car.m_name);
}
如果类里的数据都是int类型,则不需要深拷贝。只有当类里有指针类型,需要指向某块内存空间的时候才使用深拷贝。
对象型参数和返回值
-
使用对象类型作为函数的参数或者返回值,可能会产生一些不必要的中间对象
-
作为参数
class Car { public: Car() { cout << "Car() - " << this << endl; } Car(const Car &car) { cout << "Car(const Car &) - " << this << endl; } ~Car() { cout << "~Car() - " << this << endl; } void run() { cout << "run()" << endl; } }; void test1(Car car) { } int main() { Car car1; test1(car1); getchar(); return 0; }
-
运行后打印一个普通构造函数和一个拷贝构造函数
int main() { Car car1; // Car() test1(car1); // Car(const Car &car) getchar(); return 0; }
为什么test1调用的是拷贝构造函数?
- 因为相当于
void test1(Car car = car1)
,使用一个已经存在的对象构建新的对象,就是拷贝构造
但不需要重新创建一个新的对象,我们把对象传进去是为了在函数内部能够使用这个对象的内容,没有必要创建新对象。
那么可以改成引用
void test1(Car &car) { }
这样就是直接引用传入的car的对象,就不会创建一个新的对象
-
-
作为返回值
Car test2() { Car car; // Car() return car; } int main() { Car car2; // Car() 调用普通构造函数 car2 = test2(); // 相当于car2 = car; return 0; }
结果:
产生三个新的对象,为什么会有最后一次调用拷贝构造呢?
因为在调用test2之后,test2在栈空间的内存会被释放掉,说明生成的car对象也会被释放掉,但现实中并不会这样,会把在test2空间生成的car对象拷贝到main空间中(因为是在main中调用的)
所以会利用test2中生成的car对象拷贝构造出来一个新的car对象,这个car对象是在main函数的空间中。
就像是
car2 = Car(test2());
所以会产生一些不必要的中间对象。
-
匿名对象(临时对象)
匿名对象:没有变量名, 没有被指针指向的对象,用完后马上调用析构
比如
int main() {
Car();
return 0;
}
Car()生成匿名对象,没有名字,但还是会创造新的对象。
构造完就析构掉
隐式构造
C++中存在隐式构造的现象:某些情况下,会隐式调用单参数的构造函数。
class Person {
int m_age;
public:
Person() {
cout << "Person() - " << this << endl;
}
Person(int age) :m_age(age) {
cout << "Person(int) - " << this << endl;
}
Person(const Person &person) {
cout << "Person(const Person &person) - " << this << endl;
}
~Person() {
cout << "~Person() - " << this << endl;
}
void display() {
cout << "display() - age is " << m_age << endl;
}
};
int main() {
/*
Person p1;
Person p2(10);
Person p3 = p2;
*/
Person p1 = 20;
getchar();
return 0;
}
这里Person p1 = 20;
是在干什么?
运行发现调用的单参数的构造函数。等价于Person p2(20)
。
如果再加一个函数,然后再main函数中调用。
void test1(Person person) {
}
int main() {
/*
Person p1;
Person p2(10);
Person p3 = p2;
*/
// Person p1 = 20;
test1(30);
getchar();
return 0;
}
这里test1(30);
相当于调用了test1(Person person = 30)
,和前面一样,也会调用单参数的构造函数。
再如果加一个函数,返回值是Person对象
Person test2() {
return 40;
}
int main() {
test2();
getchar();
return 0;
}
会将40传递给Person对象的单参数构造函数来构造Person对象。
这样就叫做隐式构造
如果是
int main() {
Person p1;
p1 = 40;
getchar();
return 0;
}
会调用一次无参构造、一次单参数构造和一次析构函数
无参构造:Person p1;
单参数构造:因为如果要p1 = 40;
左边是person对象,右边也得是person对象,所以右边调用单参数构造函数,但是隐式构造,将这个构造函数传递给p1后马上析构。
-
可以通过关键字explicit禁止掉隐式构造
explicit Person(int age) :m_age(age) { cout << "Person(int) - " << this << endl; }
不允许隐式构造