欢迎光临
我们一直在努力

C++学习日记 Day5:构造函数/析构函数+深拷贝&浅拷贝+初始化列表

## 今天学了什么

今天学习C++对象的初始化和清理:构造函数/析构函数、深拷贝与浅拷贝、初始化列表。

## 构造函数

作用:主要作用在于创建对象时,为对象成员属性赋值,由编译器自动调用,无需手动调用。

语法:类名(){}

  • 不需要返回值,不写void;
  • 函数名与类名相同;
  • 可以有参数,可以发生重载;
  • 程序在调用对象时会自动调用构造函数,无需手动调用,且只会调用一次。

### 构造函数的分类和调用

两种分类:

  • 参数:有参构造、无参构造(默认构造);
  • 类型:普通构造、拷贝构造;

三种调用方式:

括号法:

Person p;  //默认

Person p1(10);  //Person (int a) 有参

Person p2(p1);  // 拷贝

*注:调用默认构造时,不要写Person p()。原因:编译器会认为是一个函数声明,不会认为在创建对象。

显示法:

Person p3=Person (10);  //有参           Person (10); //匿名对象

Person p4=Person (p3);  //拷贝

匿名对象特点:当前行执行结束后,系统会立即回收匿名对象。

*注:不要利用拷贝构造函数初始化匿名对象,例: Person (p3); 原因:编译器会认为是对象的声明,等价于 Person p3; 

隐式转换法:

  • Person p5=10;  //有参 == Person p5=Person (10); 
  • Person p6=p5;  //拷贝

### 拷贝构造函数调用时机

①使用一个已经创建完毕的对象来初始化一个新对象:

Person p;

Person p1(p);

②值传递的方式给函数参数传值:

void test(Person p) {…}

…   

Person p;

test(p);   …

③以值方式返回局部对象:

Person test() {

Person p;

return p; }

*注:Release模式下直接 return p 会触发NRVO(具名返回值优化),跳过拷贝,直接在外接收变量p1的内存空间原地构造对象;Debug模式下,无优化,会拷贝。

### 构造函数调用规则

  • 默认情况下,编译器至少给一个类添加3个构造函数:构造函数(空实现)、析构函数(空实现)、拷贝函数(值拷贝)。
  • 写了有参构造,编译器不再提供默认构造函数,但会提供拷贝构造。
  • 如果自己写了拷贝构造,编译器不再提供其他函数。

### 深拷贝&浅拷贝

浅拷贝:简单的赋值拷贝操作;

深拷贝:在堆区重新申请空间,进行拷贝操作。

调试验证:打开visual studio2022,创建一个Person类,在堆区创建一个身高变量,不写拷贝构造,让编译器自动生成:

class Person
{
public:

Person()
{
cout << "Person的默认构造函数" << endl;
}

Person(int age,int height)
{
cout << "Person的有参构造函数" << endl;
m_height =new int(height);
m_age = age;
}

~Person()
{
if (m_height != nullptr)
{
delete m_height;
m_height = nullptr;
}
cout << "Person的析构函数" << endl;
}

int m_age;
int* m_height;
};

void test03()
{
Person p(18, 160);
cout << "年龄: " << p.m_age << " 身高: " << *p.m_height << endl;

Person p1(p);
cout << "年龄: " << p.m_age << " 身高: " << *p1.m_height << endl;
}

int main()
{
test03();
return 0;
}

运行报错,在构造函数下一个断点,逐步调试,发现p.m_height和p1.m_height的地址一模一样,由此可见,编译器提供的拷贝构造函数属于浅拷贝,导致两个指针指向同一个堆区内存,导致在调用析构时,堆区内存被重复释放,程序报错。解决方法,在堆区开辟独立空间存放拷贝地址,重写拷贝构造:

Person(const Person& p)
{
cout << "Person的拷贝构造函数" << endl;
m_age = p.m_age;
m_height=new int(*p.m_height);
}

错误逻辑思考:在析构里面已经有if判断m_height是否为空,为什么还会报错?

原因:p.m_height,p1.m_height存的只是值传递拷贝过来的地址,只是一个数字,即使p1执行析构释放了对应的堆内存,p.m_height的值不会被改变,仍然是之前存的地址。所以当p执行析构时,会产生错误,重复释放。

### 初始化列表

适用于:const常量成员、引用成员。

语法:构造函数():属性1(值1),属性2(值2)…{}

例:Person(int age, int height): m_height(new int(height)),m_age(age)
{
   …
}

## 析构函数

作用:主要作用在于对象销毁前系统自动调用,执行清理工作。

语法:~类名(){}

  • 不需要返回值,不写void;
  • 函数名与类名相同;
  • 没有参数,不可以发生重载;
  • 程序在调用对象时会自动调用,无需手动调用。

*注:构造和析构必须有,如果自己不写,编译器会提供默认的构造/析构。

## 补充

** NULL与nullptr的区别 **

NULL:宏,等价整数0,重载可能会选错函数。(只是数字0,重载会匹配int)

nullptr:C++11关键字,专门用于空指针,不会和int混淆。

** 栈对象和堆对象的构造析构顺序 **

①栈:

Person p1;

Person p2;

构造:p1-p2

析构:p2-p1     先进后出。

②堆:

Person *p1=new Person();

Person *p2=new Person();

delete p1;

delete p2;

构造:p1-p2

析构:取决于delete顺序

③混合(栈里面保存指向堆对象的指针):

Person *p=new Person();

没有写delete,只会释放栈上p的地址,不会释放堆区的,内存泄漏。

delete作用:获取指针p存的地址,找到对应堆对象,调用*p的析构,释放对应内存。

赞(0)
未经允许不得转载:171主机测评 » C++学习日记 Day5:构造函数/析构函数+深拷贝&浅拷贝+初始化列表
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址