拷贝是指将一个对象或者数据从一个地方复制到另一个地方,同时存在两个副本
拷贝一方面可以让代码按照预期运行,另一方不必要的拷贝会影响代码的运行效率。理解 C++中拷贝的工作原理对于编写高效的 C++代码是很有必要的
TIP💡
在读取或修改一个已经存在的对象时,应尽可能的避免拷贝,因为拷贝需要额外的时间开销
struct Vector
{
float x,y;
}
int main()
{
int a = 2;
int b = a; //b与a是独立的两个内存空间
b = 3; //a的值并不会受到影响
Vector a = {2,3};
Vector b = a; //b与a仍然是独立的两个内存空间
b.x = 5; //a的值不受影响
Vector* a = new Vector();
Vector* b = a; //b和a的"值"相同,即地址相同
//case1
b++; //a的“值”不变,即指针指向地址不变
//case2
b->x = 2; //因为a和b指向同一个地址,所以a和b同时变化
}除了引用以外,每次使用 = 都是在进行一次拷贝,指针会复制内存地址值,变量会复制内存中的值
下面通过实现一个字符串类
#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+1]; //为终止符留一个字节
memcpy(m_Buffer, string, m_Size);
m_Buffer[m_Size] = 0;
}
~String()
{
delete[] m_Buffer;
}
char& operator[] (unsigned int index)
{
return m_Buffer[index];
}
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 = "ZhangSan";
String second = string;
second[2] = 'e'; //两个字符串同时发生了变化
std::cout << string << std::endl;
}在程序结束时发生了崩溃,主要是字符串 second 在拷贝时,是将内部属性进行了拷贝,因此两者的 m_Buffer 指向的是同一片内存空间。在程序结束时,调用两者的析构函数对同一个内存空间调用了两次 delete,于是程序崩溃了
此外,修改 second 字符串导致 string 字符串的变化也同样说明了两者指向了同一个内存空间
因此这种类型的拷贝称之为浅拷贝
有时我们想使用独立于原对象的拷贝副本,那么需要进行深拷贝。深拷贝的方法有许多,例如 clone 函数或者自定义方法返回一个全新的对象。本文介绍一种使用拷贝构造函数的方法,当创建一个和源对象同类型的对象时会调用该方法,在 unique 中通过禁用该方法阻止对象拷贝
//c++默认拷贝构造函数(浅拷贝)
String(const String& other)
:m_Buffer(other.m_Buffer),m_Size(other.m_Size)
{
}
//禁用对象拷贝,例如unique_ptr
String(const String& other) = delete;
//自定义拷贝构造函数(深拷贝)
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);
}#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+1];
memcpy(m_Buffer, string, m_Size);
m_Buffer[m_Size] = 0;
}
String(const String& other)
:m_Size(other.m_Size)
{
m_Buffer=newchar[m_Size+1];
memcpy(m_Buffer,other.m_Buffer,m_Size+1);
}
~String()
{
delete[] m_Buffer;
}
char& operator[] (unsigned int index)
{
return m_Buffer[index];
}
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;
}
void printString(String string)
{
std::cout << string << std::endl;
}
int main() {
String string = "ZhangSan";
printString(string);
}在使用 printString 函数时会存在一次拷贝构造函数的调用, 每次拷贝对象都需要分配内存,复制内存以及释放内存,为了避免没必要的对象拷贝,只是单纯的使用原对象,建议使用引用传递对象
TIP💡
在多数情况下(除非复制会更快)参数对象建议使用引用传递,根据是否需要编辑对象决定
const关键字的使用。在函数内部可以进一步决定传入对象是否需要拷贝
void printString(const String& string)
{
//可自行决定是否拷贝
String copy = string;
std::cout << string << std::endl;
}