1.调用规则
默认情况下,C++至少会给一个类添加三个函数:
- 默认构造函数(无参,函数体为空)
- 默认析构函数(无参,函数体为空)
- 默认拷贝构造函数,对属性进行值拷贝
调用规则:
1.如果用户定义了有参构造函数,C++将不在提供默认无参构造函数,但是会提供默认拷贝构造函数。
如上图,我们给类A提供了一个有参的构造函数,所以此时,如果再去主函数定义一个无参的对象时,编译器就会提示“类A不存在默认构造函数”,这就说明如果自己定义了有参构造函数,那么就不会提供默认的无参构造函数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
#include<iostream>
using namespace std;
class A {
public:
int num;
A(int num) {
this->num = num;
}
};
int main()
{
A a(5);
A b(a);
cout << b.num;
return 0;
}
|
在类中我们只提供了一个有参的构造函数,我们在主函数中定义一个b对象,传的是对象a,那么它便会走拷贝构造的函数,如果能够输出b.num的值为5的话,则说明提供了默认的拷贝构造函数。
通过打印b的num,发现确实是5.那么就会提供默认的构造函数。
2.如果用户定义拷贝构造函数,c++不会提供其他构造函数
可以看到当我们仅在类A中写了一个拷贝构造函数时,再去主函数中定义一个无参的对象a时,就会报错,提示“类A不存在默认构造函数”。
2.深拷贝和浅拷贝问题
浅拷贝:就是简单的赋值操作
存在的问题:如果有指针指向堆区内存时,不同对象的指针成员指向的是同一块堆区内存。在对象进行释放时,该堆区会被释放两次。当一个对象修改堆区的内容时,另一个对象的内容也会随着改变。
深拷贝:申请同样大小的堆区内存,保证两个堆区的内容一样。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
#include<iostream>
using namespace std;
class A {
int num;
int* p;
public:
A() {
num = 0;
p = nullptr;
cout << "调用无参构造" << endl;
}
A(int a) {
num = a;
p = new int[num];
cout << "调用有参构造" << endl;
}
~A() {
if (p)delete[]p;
}
};
int main() {
A a(3);
A b = a;
return 0;
}
|
在类A中我们定义了一个指针变量,在有参构造函数中,我们在堆区开辟了一块空间。在主函数数中,定义对象b要走拷贝构造函数。下面我们来运行这段代码:
发现报错了,原因就是对象a和对象b的成员变量*p指向的是同一块堆区内存,再调用析构函数时,对象b先把这块堆区内存释放了,当对象a调用析构函数时,此时那块堆区内存已经不存在了,所以会出现访问内存失败,导致程序崩溃。
那么解决的办法就是重写拷贝构造函数,让他们指向不同的堆区区域。代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
#include<iostream>
using namespace std;
class A {
int num;
int* p;
public:
A() {
num = 0;
p = nullptr;
cout << "调用无参构造" << endl;
}
A(int a) {
num = a;
p = new int[num];
cout << "调用有参构造" << endl;
}
A(const A& other) {//万能引用,避免实参修改形参
num = other.num;
p = new int[num];//保证内存大小相同
for (int i = 0; i < num; i++) {
p[i] = other.p[i];//保证数据相同
}
cout << "调用拷贝构造" << endl;
}
~A() {
if (p)delete[]p;
cout << "调用析构函数" << endl;
}
};
int main() {
A a(3);
A b = a;
return 0;
}
|
通过再在堆区上开辟一块内存,让这块堆区内存上的内容和对象a的堆区内容一样就行,这就是深拷贝。
程序能正常运行。
3.string类的拷贝构造练习
可以通过下面这个自定义string类进一步加深对深拷贝的理解。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
#include<iostream>
using namespace std;
class String {
int size;
char* p;
public:
String() {
p = nullptr;
size = 0;
}
String(const char* p) {
size = strlen(p);
this->p = new char[size + 1];//\0,所以加一
strcpy_s(this->p, size + 1, p);
}
~String() {
if (p) delete[]p;
}
String(const String& other) {
this->size = other.size;
this->p = new char[size + 1];
strcpy_s(this->p, size + 1,other.p);
}
void print() {
cout << p<<endl;
}
};
int main() {
String str = "abcde";
String str2(str);
str2.print();
return 0;
}
|
|