现有一个 Person 类,成员变量:姓名(string name)和年龄(int age),请给 Person 添加一个拷贝构造函数,让程序能够正确运行。
输入描述:
键盘输入用户名和年龄
输出描述:
通过 Person 类的showPerson()成员方法输出 Person 对象的姓名和年龄,中间使用空格隔开
cpp
//浅拷贝与深拷贝
1. 核心定义
浅拷贝(默认拷贝构造自动生成)
只复制成员变量本身的值。
如果成员是指针(int*),只会复制地址,新旧对象共用同一块堆内存。
深拷贝(自己手写拷贝构造)
不光复制变量值;遇到指针时,重新 new 一块独立内存,再把原数据复制过去。
新旧对象拥有完全独立的堆空间,互不干扰。
2. 举两个场景区分
场景 1:只有普通类型(int /string)
class Person{
string name;
int age;
};
没有动态指针,浅拷贝完全够用,不会出错
场景 2:包含动态数组 / 指针(重点坑点)
class Array{
int size;
int* arr; // 堆内存指针
};
浅拷贝:两个对象 arr 存同一个地址,指向同一段数组
析构时第一个对象执行 delete[] 释放内存,第二个对象再 delete[] → 重复释放,程序崩溃
Array(const Array& other){
size = other.size;
arr = other.arr; // 只复制地址,共用内存
}
深拷贝:新对象 new 新数组,拷贝数据,两块独立内存,析构互不影响
Array(const Array& other){
size = other.size;
arr = new int[size]; // 新开内存
for(int i=0;i<size;i++){
arr[i] = other.arr[i]; // 复制内容
}
}
| 对比项 | 浅拷贝-抄地址,共用一块内存 | 深拷贝-抄内容,重新开一块内存 |
|---|---|---|
| 指针处理 | 复制地址,共享堆内存 | 新建内存,复制数据,内存独立 |
| 内存开销 | 小,不开新堆空间 | 大,额外分配堆内存 |
| 析构风险 | 双对象共用内存,重复 delete 崩溃 | 各自内存,释放无冲突 |
| 使用场景 | 无动态指针、基础类型成员 | 类包含new动态开辟的指针成员 |
初始化类有两种方式:
一种叫构造函数,函数名称为类名,参数是成员变量的输入值,没有返回值,作用是输入成员变量的初始值,赋值给成员变量,这是用变量值初始化类;
另一种叫拷贝构造函数,函数名称也是类名,与拷贝构造不同的是,它的参数是一个类,就是就是我们当前这个类,它的作用是用参数给的这个类的成员变量复制赋值给当前类的成员变量。
cpp
#include <instream>
#include <string>//内部封装好了内存管理,拷贝时string自带深拷贝,默认拷贝构造就安全,不用手动new/strcpy
using namespace std;
class Person{
private:
string name;
int age;
//函数重载
public:
//普通构造函数
Person(string n,int a)
{
name = n;
age = a;
}
// 拷贝构造函数(核心要求)
Person(const Person& p)
{
name = p.name;//string底层自动分配新空间复制字符串,无需手动new、strcpy
age = p.age;
}
void showPerson()
{
cout << name << " " << age << endl;
}
};
int main()
{
string name;
int age;
cin >> name >> age;
Person p1(name,age);
Person p2 = p1;
// 等价于 Person p2(p1);
// 这行会自动调用拷贝构造函数
p1.showPerson();
p2.showPerson();
return 0;
}
Person(const Person& p)
-Person:和类同名,是构造函数标识;
-const:保证不会修改被复制的原对象;
-&:引用,避免拷贝时重复复制内存,提升效率;
-Person& p:传入同一个类的对象作为模板。
和普通构造的区别
普通构造:接收独立数据(字符串、数字)创建对象;
拷贝构造:接收另一个同类对象,复制全部属性。
补充关键知识点
编译器默认自带浅拷贝构造
如果你不手动写拷贝构造,C++ 会自动生成一个默认拷贝构造,逐成员赋值。
但如果类里有动态内存(int*、new 开辟的数组),默认浅拷贝会出现两个对象共用同一块堆内存,析构时重复 delete 报错,这时必须自己手写深拷贝构造。
本例不需要深拷贝的原因
string、普通 int 都是栈上基础类型,默认浅拷贝完全够用,手动写只是题目要求。
cpp
#include <iostream>
#include <cstring>
using namespace std;
//标准裸指针深拷贝模板,考试常考
class Person {
public:
char* name; // 姓名
int age; // 年龄
//姓名存在堆内存new char[]里
//裸指针,编译器默认浅拷贝只会复制地址,会造成同一块内存两次delete崩溃,必须手动写深拷贝构造
Person(const char* name,int age)
{
this->name = new char[strlen(name) + 1];
strcpy(this->name,name);
this->age = age;
}
//完整手动深拷贝
Person(const Person& p){ //拷贝构造函数
// 1. 先根据原字符串长度开辟全新堆内存
this->name = new char[strlen(p.name) + 1]; //设置name字符数组的长度
// 2. 把字符串内容复制到新内存
strcpy(this->name, p.name); //拷贝name数组
this->age = p.age;
}
//必须手动算长度、new数组、strcpy复制字符,手动实现深拷贝
//如果不写这段自定义拷贝构造,编译器默认浅拷贝只会复制name指针地址:
//p1、p2 共用同一块字符数组,程序结束析构时先后执行delete[] name,程序直接崩溃
void showPerson() {
cout << name << " " << age << endl;
}
~Person() {
if (name != nullptr) {
delete[] name;
name = nullptr;
}
}
};
int main() {
char name[100] = { 0 };
int age;
cin >> name;
cin >> age;
Person p1(name, age);
Person p2 = p1;
p2.showPerson();
return 0;
}
cpp
声明变量靠类型,& 是引用;运算放在变量前,& 取地址。
值传递(无 &):传参要复制对象 → 调用拷贝构造 → 无限递归崩溃;
引用传递(带 &):只是别名不复制 → 不会递归,安全。
Person(const Person p) 是值传递,传参时要把实参完整复制一份给形参 p;
复制同类对象,编译器就会调用拷贝构造函数,于是出现无限递归。
执行过程:
1.p1 是实参,要传给形参 Person p;
2.形参是值传递,不是引用,系统需要创建一个临时对象 p;
3.创建临时对象 p,就要用实参 p1 复制,触发拷贝构造函数;
4.进入拷贝构造函数,又要给形参 Person p 传参,再次复制,再次调用拷贝构造......
无限循环递归,栈溢出程序崩溃。