YouTube视频链接
C++的复制与拷贝构造函数
本文是ChernoP44视频的学习笔记。
看如下代码,a和b是两个独立的变量,它们有不同的内存地址,若将b=3则a仍然是2。在Vector2类中是同样的原理,c.x仍然会是2。若要在堆中使用new关键字来进行分配则复制了指针,e和f两个指针本质上有相同的值(内存地址),但是如果访问这个内存地址并设为某个值,则会同时影响e和f。
#include<iostream>
#include<string>
#include<memory>
struct Vector2
{
float x, y;
};
int main()
{
int a = 5;
int b = a;
Vector2 c = { 2,3 };
Vector2 d = c;
d.x = 5;
Vector2* e = new Vector2();
Vector2* f = e;
f->x = 2;
std::cin.get();
}
若使用C++原始特性写一个字符串类,使用标准的std::cout来打印字符串,所以重载左移字符串。将运算符的重载函数作为这个类的友元,就可以从函数中访问m_Buffer。
#include<iostream>
class String
{
private:
char* m_Buffer;
unsigned int m_Size;
public:
String(const char* string)
{
m_Size = strlen(string);
m_Buffer = new char[m_Size];
memcpy(m_Buffer, string, m_Size);
}
~String()
{
delete[] m_Buffer;
}
friend std::ostream& operator<<(std::ostream& stream, const String& string);//友元
};
std::ostream& operator<<(std::ostream& stream, const String& string)//重载
{
stream << string.m_Buffer;
return stream;
}
int main()
{
String string = "Cherno";
std::cout << string << std::endl;
std::cin.get();
}
按下F5运行代码,打印了Cherno但是伴随着很多随机字符,这是因为没有空终止字符。
所以要把空终止字符添加上,在分配缓冲区的时候将m_Size+1(也可以使用strcpy函数)。
也可以写m_Buffer[m_Size]=0,手动在最后添加自己的空终止。
浅拷贝
试着复制这个字符串叫它second,然后把它们打印出来。按F5可以看到打印了两次Cherno。
但如果按回车键,代码执行完cin.get()之后,代码就会崩溃。
当我们复制这个String时,C++自动为我们做的是将所有类成员变量复制到一个新的内存地址里面,这个新的内存地址包含了second字符串。现在内存中有两个String,因为它们直接进行复制,这种复制被称为浅拷贝,它所做的是复制这个指针内存中的两个String对象,它们有相同的char*的值,也就是相同的地址。这个m_Buffer的内存地址,对于这两个String对象来说是相同的,程序会崩溃。当我们到达作用域的尽头时,两个String都被销毁了,析构函数会被调用,执行两次delete[] m_Buffer,程序试图两次释放同一个内存块所以会崩溃。
在34行设置断点,发现string和second的m_Buffer地址一样。
若想要修改second字符串,访问第二个索引并把它赋值a,为了让[]操作符起作用,需要操作符重载。按下F5出现两个Charno,为什么会有两个同样的Charno?而且程序还是崩溃了。看起来我们复制了但没有完全复制。
深拷贝
我们需要做的是分配一个新的char数组来存储复制的字符串,而现在做的只是复制指针,两个字符串对象指向完全相同的内存缓冲区。若希望第二个字符串拥有自己的指针以拥有唯一的内存块,当修改或删除第二个字符串时不会触及第一个字符串。这里需要执行一种叫做深度复制(深拷贝)的东西,使用拷贝构造函数,拷贝构造函数是一个构造函数,当复制第二个字符串时它会被调用。当把一个字符串赋值给一个对象时,这个对象也是一个字符串。当试图创建一个新的变量并给它分配另一个变量时,它和正在创建的变量有相同的类型。复制这个变量也就是所谓的拷贝构造函数。
c++默认提供一个拷贝构造函数,拷贝构造函数的函数签名对同样的类对象的常引用const &。它的作用是内存复制,将other对象的内存浅层拷贝进这些成员变量。
String(const String& other)//拷贝构造函数
:m_Buffer(other.m_Buffer), m_Size(other.m_Size) {}
这样不行,因为不仅仅想复制指针还想复制指针所指向的内存。我们要做的是找到我们自己的拷贝构造函数进行深拷贝。
String(const String& other)
:m_Size(other.m_Size)
{
m_Buffer = new char[m_Size + 1];
memcpy(m_Buffer, other.m_Buffer, m_Size+1);
}
按F5运行代码发现有Cherno和Charno两个字。当我们改变第二个字符串时没有改变第一个字符串,按下回车键也不会崩溃。