一、拷贝构造函数调用时机
1.创建新对象时用已有的初始化
2.值传递的方式给函数参数传值
值传递的方法本质上会临时拷贝一个副本出来,即开辟一个新内存空间存储复制出来的内容,会调用拷贝构造函数,且形参更改不会影响实参。这里doWork函数中传入的doWork(p2),相当于是写了Person p1 = p2;
void doWork(Person p1) {
}
void test02() {
Person p2;
doWork(p2);
}
3.值方式返回局部对象(取决于编译器)
注意:vs2022版本不会进行拷贝,两个对象的地址都是一样的
函数调用结束后会释放空间,即原有的内部的p1空间会释放掉,但是由于我return p1,使得编译器会根据p1创建一个新的对象。也就是说,这么一串下来,就等于Person p2(doWork());,会调用拷贝构造函数。
Person doWork() {
Person p1;
return p1;
}
void test03() {
Person p2(doWork());
}
二、构造函数的调用规则
每写一个类,c++会自动编写默认构造/析构函数和拷贝构造函数。
- 若自行编写有参构造函数,则不再提供默认构造函数,调用会报错
- 若自行编写拷贝构造函数,则不再提供其他构造函数,调用会报错
注意,只编写拷贝构造函数,则c++不会提供默认构造函数,因此第一个对象会无法构造。
三、深拷贝与浅拷贝
深拷贝:在堆区重新申请空间,进行拷贝。
浅拷贝:简单的赋值操作。
1.浅拷贝
简单的赋值操作,类似于“=”,一旦涉及到堆区、指针等容易出错。
#include<bits/stdc++.h>
using namespace std;
class Person
{
public:
int age;
int* height;
public:
Person() {
cout << "默认构造函数" << endl;
}
Person(int n,int h) {
age = n;
height = new int(h);
cout << "有参构造函数" << endl;
}
Person(const Person& p) {
age = p.age;
height = p.height;
cout << "拷贝构造函数" << endl;
}
~Person() {
cout << "析构函数" << endl;
if (height != NULL) {
delete height;
height = NULL;
}
}
};
int main() {
Person p1(20,180);
Person p2(p1);
cout << "p1: " << *p1.height << endl;
cout << "p2: " << *p2.height << endl;
return 0;
}
如果利用编译器提供的拷贝构造函数,会做浅拷贝操作,把数据一个个拷贝过来,因此此时p1与p2时指向同一个堆区地址。堆区是公用的,来回两次释放了同一个空间,会报错。栈区先进后出,p2先释放了指针指向的空间,到p1再释放一次,重复释放就会出问题。
2.深拷贝
深拷贝就相当于再开辟一个堆区地址,使得p1与p2指向两个地址。
#include<bits/stdc++.h>
using namespace std;
class Person
{
public:
int age;
int* height;
public:
Person() {
cout << "默认构造函数" << endl;
}
Person(int n,int h) {
age = n;
height = new int(h);
cout << "有参构造函数" << endl;
}
Person(const Person& p) {
age = p.age;
//height = p.height; 编译器默认实现的本质就是这种写法
//深拷贝操作
height = new int(*p.height);
cout << "拷贝构造函数" << endl;
}
~Person() {
cout << "析构函数" << endl;
if (height != NULL) {
delete height;
height = NULL;
}
}
};
int main() {
Person p1(20,180);
Person p2(p1);
cout << "p1: " << *p1.height << endl;
cout << "p2: " << *p2.height << endl;
return 0;
}
这里由于在拷贝构造函数中使用了new函数,开辟了新的堆区空间,相当于从头到尾把p1的操作重新进行了一遍,因此不会导致错误。
四、初始化列表
看似更烦了,实则大有好处,可以对const的属性值进行更改了,牛逼克拉斯!还可以控制传入参数的数量,选择性进行初始化操作,多好!
#include<bits/stdc++.h>
using namespace std;
class Person
{
public:
int a;
int b;
int c;
const int d;
public:
//初始化列表
Person(int m,int n,int x,int y) :a(m), b(n), c(x),d(y)
{
cout << "初始化列表" << endl;
}
//传统赋值操作
Person(int m, int n, int x, int y) {
a = m;
b = n;
c = x;
d = y; //注意这里会出错,const不允许修改,但是初始化列表可以进行修改
}
~Person() {
cout << "析构函数" << endl;
}
};
int main() {
Person p1(1,2,3,4);
cout << p1.d << endl;
return 0;
}
五、类对象作为类成员
#include<bits/stdc++.h>
using namespace std;
class Ji {
public:
string Ji_Name;
Ji() {
cout << "Ji" << endl;
}
Ji(string name) {
Ji_Name = name;
cout << "Ji" << endl;
}
~Ji() {
cout << "Ji析构函数" << endl;
}
};
class Dan {
public:
string Dan_Name;
Ji a;
Dan(string name1 , string name2):Dan_Name(name1),a(name2)
{
cout << "Dan" << endl;
}
~Dan() {
cout << "Dan析构函数" << endl;
}
};
int main() {
Dan p("cc", "pp");
cout << "Dan的名字:" << p.Dan_Name << " Ji的名字:" << p.a.Ji_Name << endl;
return 0;
}
会先把子类成员构造出来,再构造自身。即例子中,Dan类中包含了Ji类,会先构造Ji,再构造Dan。而在析构时相反,先析构Dan,再析构Ji。就是先天下之忧而忧,后天下之乐而乐。哈哈没毛病!