首先大家看下以下string类的实现是否有问题?
class String
{
char* _str;
public:
String(const char* str = "")
{
// 构造string类对象时,如果传递nullptr指针,认为程序非法,此处断言下
if(nullptr == str)
{
assert(false);
return;
}
_str = new char[strlen(str) + 1];
strcpy(_str, str);
}
~String() {
delete[] _str;
_str = nullptr;
}
};
// 测试
void TestString() {
String s1("hello bit!!!");
String s2(s1);
}
上述String类没有显式定义其拷贝构造函数与赋值运算符重载,此时编译器会合成默认的,当用s1构造s2时,编译器会调用默认的拷贝构造。最终导致的问题是,s1、s2共用同一块内存空间,在释放时同一块空间被释放多次而引起程序崩溃,这种拷贝方式,称为浅拷贝。
浅拷贝
浅拷贝:也称位拷贝,编译器只是将对象中的值拷贝过来。 如果 对象中管理资源,最后就会 导致多个对象共享同一份资源,当一个对象销毁时就会将该资源释放掉,而此时另一些对象不知道该资源已经被释放,以为还有效,所以当继续对资源进项操作时,就会发生发生了访问违规。
要解决浅拷贝问题,C++中引入了深拷贝。
深拷贝
如果一个类中涉及到资源的管理,其拷贝构造函数、赋值运算符重载以及析构函数必须要显式给出。一般情况都是按照深拷贝方式提供。
实现 String 类
class String
{
char* _str;
public:
String(const char* str = "")
{
// 构造string类对象时,如果传递nullptr指针,认为程序非法,此处断言下
if(nullptr == str)
{
assert(false);
return;
}
_str = new char[strlen(str) + 1];
strcpy(_str, str);
}
String(const String& s)
:_str(new char[strlen(s._str)+1])
{
strcpy(_str, s._str);
}
// const 是因为对 s 不需要修改,安全性更高
// 参数 & 是因为不需要传值拷贝、效率高
// 返回值 & 是为了连续赋值(效率高)
String& operator=(const String& s){
if(this != &s) // 检测是否自己给自己赋值
{
/*delete[] _str;
_str = new char[strlen(s._str)+1];
strcpy(_str, s._str);*/
// 开辟空间如果失败,不会破坏原本对象
char *temp = new char[strlen(s._str)+1];
strcpy(temp, s._str);
delete[] _str;
_str = temp;
}
return *this;
}
~String() {
if(_str) // 判断当前空间是否已经释放、置空
{
delete[] _str;
_str = nullptr;
}
}
};
测试代码:
void TestString() {
String s1("hello bit!!!");
String s2(s1);
}
int main(){
TestString();
return 0;
}
知识点习题:
- 以下代码是否完全正确,执行可能得到的结果是____。
class A{
int i;
};
class B{
A *p;
public:
B(){
p=new A;
}
~B(){
delete p;
}
};
void sayHello(B b){
}
int main(){
B b;
sayHello(b);
}
A. 程序正常运行
B. 程序编译错误
C. 程序崩溃
D. 程序死循环
正确答案:
C
答案解析:
形参传递析构指针两次
以下三种情况需要用到拷贝函数:
- 一个对象以值传递的方式传入函数体;
- 一个对象以值传递的方式从函数返回;
- 一个对象需要通过另外一个对象进行初始化。
本例中,在调用sayHello(b)时符合上面1. 的情况,因此会调用拷贝构造函数。由于类中未显式声明一个拷贝构造函数,编译器将会自动生成一个默认的拷贝构造函数,来完成对象间的位拷贝(或称浅拷贝),也就是把对象里的值完全复制给另一个对象,如ClassX = ClassY。这时,如果ClassY中有一个成员变量指针已经申请了内存,那ClassX中的那个成员变量也指向同一块内存。这就出现了问题:当ClassY把内存释放了(如:析构),这时ClassX内的指针就是野指针了,出现运行错误。(即sayHello()函数体结束析构了一次,main()函数结束又一次析构,而这时p已经是野指针了,因而出错。)
#include<iostream>
using namespace std;
class MyClass
{
public:
MyClass(int i = 0)
{
cout << i;
}
MyClass(const MyClass &x)
{
cout << 2;
}
MyClass &operator=(const MyClass &x)
{
cout << 3;
return *this;
}
~MyClass()
{
cout << 4;
}
};
int main()
{
MyClass obj1(1), obj2(2);
MyClass obj3 = obj1;
return 0;
}
运行时的输出结果是()
A. 11214444
B. 11314444
C. 122444
D. 123444
正确答案 : C
答案解析
C D 辨析:
关键是区分 浅/深拷贝操作 和 赋值操作:
- 没有重载=之前:
A a ;
A b;
a = b;
这里是赋值操作。
A a;
A b = a;
这里是浅拷贝操作。
- 重载 = 之后:
A a ;
A b;
a = b;
这里是深拷贝操作(当然这道题直接返回了,通常我们重载赋值运算符进行深拷贝操作)。
A a;
A b = a;
这里还是浅拷贝操作。
所以 MyClass obj3 = obj1; 调用的是拷贝构造函数。
如果写成 MyClass obj3; obj3 = obj1; 输出的结果就是 123444
如有不同见解,欢迎留言讨论~~