了解在C++中复制的实际运作方式,以及如何让它工作,如何避免它工作(避免在不想复制时复制,因为复制对性能有影响,有时只想读取对象或者修改现有对象)
当你把变量赋值给另一个变量时,都是在使用复制。如果是指针,则是复制指针的地址(一串数字,而不是指针指向的内容)
#include <iostream>
#include <string>
struct Vector2
{
float x, y;
};
int main()
{
int a1 = 2;
int b1 = a1;
//a,b是两个不同的变量,互不影响
b1 = 3;
std::cout << a1 << std::endl;//2
std::cout << b1 << std::endl;//2
Vector2 a = { 2,3 };
Vector2 b = a;
b.x = 5;
std::cout << a.x << std::endl;//2
std::cout << b.x << std::endl;//5
Vector2* aP = new Vector2();
//复制了指针,没有复制实际数据,复制的是地址
Vector2* bP = aP;
bP->x = 2;
std::cout << aP->x << std::endl;//2
std::cout << bP->x << std::endl;//2
bP++;//aP没有被影响到
std::cout << aP << std::endl;//000002A2946D9EC0
std::cout << bP << std::endl;//000002A2946D9EC8
std::cin.get();
return 0;
}除了引用以外,其他的给变量赋值,都是使用的复制
自己写一个基础的String类#
#include <iostream>
#include <string>
//写一个最原始的String类
class String
{
private:
char* m_Buffer;
unsigned int m_Size;
public:
//接收一个常量字符,初始化
String(const char* string)
{
//strlen 函数的作用是计算字符串的长度,它会从你提供的指针
// 地址开始扫描,直到遇到第一个空字符(终止符)\0 为止,
// 但计数结果不计入这个\0
m_Size = strlen(string);
//向堆申请m_Size字节的空间
m_Buffer = new char[m_Size+1];
/*for (int i = 0; i < m_Size; i++)
m_Buffer[i] = string[i];*/
//不使用上面的,而是使用简短的函数拷贝
//工作方式:它从 src 指向的地址开始,连续复制 count 个字节到 dest 指向的地址。
memcpy(m_Buffer, string, m_Size);
m_Buffer[m_Size] = '\0';
}
//让该函数在类外访问String私有成员
friend std::ostream& operator<<(std::ostream& stream, const String& string);
~String()
{
delete[] m_Buffer;
}
//测试,无意义
unsigned int& mytest()
{
return m_Size;
}
/*
返回引用意味着你拿到了内存中那个字符的“本体”,而不是它的副本。这样你不仅可以读它(char c = string[2]),还可以写它(string[2] = 'a')
*/
char& operator[](unsigned int index)
{
return m_Buffer[index];
}
};
//如果是成员函数,调用方式会变成 string << std::cout;(这看起来非常反人类)。
//为了实现 std::cout << string; 这种标准写法,左边的对象必须是 std::ostream。
// 因为我们没法修改 C++ 标准库的 ostream 类,所以只能在自己的类里写一个全局函数
// ,并把它设为“友元”。
std::ostream& operator<<(std::ostream& stream, const String& string)
{
//只有将该函数(operator<<定义为友元函数,才能在类外访问String私有成员
//std::cout 在处理 char* 时,它的内部逻辑大致如下:
//它拿到的只是一个内存地址。
//它并不知道你这个字符串到底有多长(它不读你的 m_Size 变量)。
//它的唯一指令是:“从这个地址开始打印,直到看到 \0(空终止符)为止。”
stream << string.m_Buffer;
return stream;
}
int main()
{
String string = "cherno";
std::cout << string << std::endl;
String second = string;
std::cout << second << std::endl;
second[2] = 'a';
//测试,无意义
//编译器眼中看到的不是一个函数调用,而是一个左值(lvalue),
// 也就是一个可以直接被赋值的内存位置。这行代码等同于直接操作:
// second.m_Size = 3;
second.mytest() = 3;
std::cout << second.mytest() << std::endl;//3
std::cout << string << std::endl;//charno
std::cout << second << std::endl;//charno
std::cin.get();
}按enter停止运行后提示出错

因为这里delete了两次m_Buffer
打上断点,发现 second和string 都是存储同一个内存地址
默认复制构造函数#
Class String
{
//......
//......
//如果没有给出复制构造函数,编译器会默认给出这样一个构造函数
String(const String& other)
:m_Buffer(other.m_Buffer), m_Size(other.m_Size)
{
//C++ 的权限控制是基于“类”(Class)的,而不是基于“对象”(Instance)。
//编译器认为:既然你已经身处 String 类的内部代码中,你就是“自己人”。作为一个 String 类,你理所当然知道如何处理另一个 String 类的内部数据。
//std::cout << other.m_Size;
}
//......
}简单的复制构造函数#
String(const String& other)
{
/*
memcpy(this, &other, sizeof(String)) 会把 other 对象内存里的所有内容(包括它的 vptr)原封不动地覆盖到 this 对象的内存上。
*/
memcpy(this, &other, sizeof(String));
}深层复制#
上面的String对象中,目前只有pointer和int。
浅拷贝没有拷贝指针指向的内容
//深度复制
String(const String& other)
:m_Size(other.m_Size)
{
m_Buffer = new char[m_Size + 1];
//把那个空字符也copy过来
memcpy(m_Buffer, other.m_Buffer, m_Size+1);
/*
深度复制的运行结果
cherno
cherno
3
cherno
charno
*/
}完整深度复制构造#
#include <iostream>
#include <string>
//写一个最原始的String类
class String
{
private:
char* m_Buffer;
unsigned int m_Size;
public:
//接收一个常量字符,初始化
String(const char* string)
{
//strlen 函数的作用是计算字符串的长度,它会从你提供的指针
// 地址开始扫描,直到遇到第一个空字符(终止符)\0 为止,
// 但计数结果不计入这个\0
m_Size = strlen(string);
//向堆申请m_Size字节的空间
m_Buffer = new char[m_Size + 1];
/*for (int i = 0; i < m_Size; i++)
m_Buffer[i] = string[i];*/
//不使用上面的,而是使用简短的函数拷贝
//工作方式:它从 src 指向的地址开始,连续复
// 制 count 个字节到 dest 指向的地址。
memcpy(m_Buffer, string, m_Size);
m_Buffer[m_Size] = '\0';
}
////如果没有给出复制构造函数,编译器会默认给出这样一个构造函数
//String(const String& other)
//{
// //C++ 的权限控制是基于“类”(Class)的,而不是基于“对象”(Instance)。
////编译器认为:既然你已经身处 String 类的内部代码中,你就是“自己人”。作为一个 String 类,你理所当然知道如何处理另一个 String 类的内部数据。
// //std::cout << other.m_Size;
//}
//String(const String& other)
//{
// /*
// memcpy(this, &other, sizeof(String)) 会把 other 对象内存里的所有内容(包括它的 vptr)原封不动地覆盖到 this 对象的内存上。
// */
// memcpy(this, &other, sizeof(String));
//}
//不允许复制
//String(const String& other) = delete;
//深度复制
String(const String& other)
:m_Size(other.m_Size)
{
std::cout << "Copied String!" << std::endl;
m_Buffer = new char[m_Size + 1];
//把那个空字符也copy过来
memcpy(m_Buffer, other.m_Buffer, m_Size+1);
/*
深度复制的运行结果
cherno
cherno
3
cherno
charno
*/
}
//让该函数在类外访问String私有成员
friend std::ostream& operator<<(std::ostream& stream, const String& string);
~String()
{
delete[] m_Buffer;
}
//测试,无意义
unsigned int& mytest()
{
return m_Size;
}
/*
返回引用意味着你拿到了内存中那个字符的“本体”,而不是它的副本。这样你不仅可以读它(char c = string[2]),还可以写它(string[2] = 'a')
*/
char& operator[](unsigned int index)
{
return m_Buffer[index];
}
};
//如果是成员函数,调用方式会变成 string << std::cout;(这看起来非常反人类)。
//为了实现 std::cout << string; 这种标准写法,左边的对象必须是 std::ostream。
// 因为我们没法修改 C++ 标准库的 ostream 类,所以只能在自己的类里写一个全局函数
// ,并把它设为“友元”。
std::ostream& operator<<(std::ostream& stream, const String& string)
{
//只有将该函数(operator<<定义为友元函数,才能在类外访问String私有成员
//std::cout 在处理 char* 时,它的内部逻辑大致如下:
//它拿到的只是一个内存地址。
//它并不知道你这个字符串到底有多长(它不读你的 m_Size 变量)。
//它的唯一指令是:“从这个地址开始打印,直到看到 \0(空终止符)为止。”
stream << string.m_Buffer;
return stream;
}
//修改为//void PrintString(String& string) 可以避免复制
void PrintString(String string)
{
std::cout << string << std::endl;
}
int main()
{
String string = "cherno";
std::cout << string << std::endl;
String second = string;
std::cout << second << std::endl;
second[2] = 'a';
//测试,无意义
//编译器眼中看到的不是一个函数调用,而是一个左值(lvalue),
// 也就是一个可以直接被赋值的内存位置。这行代码等同于直接操作:
// second.m_Size = 3;
second.mytest() = 3;
std::cout << second.mytest() << std::endl;//3
second.mytest() = 6;
std::cout << second << std::endl;
PrintString(string);//cherno
PrintString(second);//charno
std::cin.get();
}
/*
cherno
Copied String!
cherno
3
charno
Copied String!
cherno
Copied String!
charno
*/避免复制#
void PrintString(String& string)// 可以避免复制
{
std::cout << string << std::endl;
}
/*
cherno
Copied String!
cherno
3
charno
cherno
charno
*/