b站Cherno的课[45]-[48]
一、C++的复制与拷贝构造函数
拷贝:复制数据,复制内存
指针复制只是复制地址,两个指针所指向内容为同一个即可
#include <iostream>
#include <string>
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);
//m_Buffer[m_Size] = 0;
}
/*~String()
{
delete[] m_Buffer;
}
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);
}*/
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 = "wm";
std::cout << string << std::endl;
std::cin.get();
}
// 空终止字符
m_Buffer = new char[m_Size + 1];
memcpy(m_Buffer, string, m_Size + 1);
// 空终止字符
m_Buffer = new char[m_Size + 1];
memcpy(m_Buffer, string, m_Size);
m_Buffer[m_Size] = 0;
//浅拷贝 shallow copy
int main()
{
String string = "wm";
String second = string;
std::cout << string << std::endl;
std::cout << second << std::endl;
std::cin.get();
}
#include <iostream>
#include <string>
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];
}
/*
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);
}*/
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 = "wm";
String second = string;
second[2] = 'a';
std::cout << string << std::endl;
std::cout << second << std::endl;
std::cin.get();
}
崩溃,我们真正需要做的是,分配一个新的char数组,来存储复制的字符串
而我们现在做的是复制指针,两个字符串对象完全指向相同的内存缓冲区
也就是说,当我们想改变其中一个时,它同时改变了他们,因为它们指向同一个内存段。或者我们删除其中一个,会把两个都删除,因为指向同一个内存块
1、副本和视图
核心区别在于数据存储的独立性和内存管理的效率
副本是“数据的安全隔离区”,适合独立操作和备份。
视图是“数据的高效镜像”,适合实时处理和内存优化。
选择原则:
数据安全优先 → 副本
性能优先 → 视图
2、浅拷贝vs深拷贝(深度复制)
浅拷贝(Shallow Copy):不会去到指针的内容或者指针所指向的地方,也不会复制
深拷贝(Deep Copy):复制整个对象
核心区别在于对对象内部引用的处理方式
浅拷贝是“引用共享的快捷方式”,高效但需注意副作用。
深拷贝是“完全独立的克隆体”,安全但资源消耗高。
核心原则:根据数据结构的复杂性和修改需求选择合适的拷贝方式。
3、【拷贝构造函数】
构造函数
当你复制第二个字符串时,会被调用
当你把一个字符串赋值给一个对象时,这个对象也是一个字符串
当你试图创建一个新的变量并给它分配另一个变量时,它和你正在创建的变量有相同类型
//String(const String& other)
//{
// memcpy(m_Buffer, other.m_Buffer, m_Size + 1);
//}
String(const String& other)
: m_Buffer(other.m_Buffer),m_Size(other.m_Size)
{
}
#include <iostream>
#include <string>
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 = new char[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;
}
int main()
{
String string = "wm";
String second = string;
second[1] = 'a';
std::cout << string << std::endl;
std::cout << second << std::endl;
std::cin.get();
}
void PrintString(String string)
{
std::cout << string << std::endl;
}
int main()
{
String string = "wm";
String second = string;
second[1] = 'a';
/*std::cout << string << std::endl;
std::cout << second << std::endl;*/
PrintString(string);
PrintString(second);
std::cin.get();
}
String(const String& other)
: m_Size(other.m_Size)//浅拷贝
{//深拷贝
std::cout << "Copied String!" << std::endl;
m_Buffer = new char[m_Size + 1];
memcpy(m_Buffer, other.m_Buffer, m_Size + 1);
}
4、左值(LValue)和右值(RValue)
它们的核心区别在于内存身份和生命周期。
这两个概念对理解现代C++中的移动语义(Move Semantics)和完美转发(Perfect Forwarding)至关重要。
左值是“有身份的持久居民”,右值是“临时的过客”。
右值引用(T&&)和移动语义优化资源管理,减少拷贝开销。
关键原则:
优先用移动语义处理临时对象。
谨慎使用std::move和std::forward。
二、C++的箭头操作符 ->
箭头操作符(->) 是一个用于通过指针访问结构体或类成员的运算符。它简化了指针解引用和成员访问的组合操作。
pointer->member
(*pointer).member
箭头操作符是访问指针指向对象成员的快捷方式。
核心行为:ptr->member ⇨ (*ptr).member。
适用场景:
结构体/类指针访问成员
智能指针操作
自定义类的指针语义模拟(通过重载operator->)
#include <iostream>
#include <string>
class Entity
{
public:
void Print() const { std::cout << "Hello!" << std::endl; }
};
int main()
{
Entity e;
e.Print();
Entity* ptr = &e;
Entity& entity = *ptr;
//entity.Print();
//(*ptr).Print();
ptr->Print();
std::cin.get();
}
#include <iostream>
#include <string>
struct Vector3
{
float x, y, z;
};
int main()
{
int offset=(int)&((Vector3*)nullptr)->x;
std::cout << offset << std::endl;
std::cin.get();
}
我们分析一下这个代码得到信息,我们首先先定义了一个名字为Vector3的结构体。
在这个结构体中,它的成员是一个float类型的成员,它的成员名称有三个,分别是x、y、z。
然后我们再来看一下主要的main函数。
main函数中 首先定义一个int类型的整数变量名字叫offset ,然而offset是被初始化的,也就是说offset它是具有右边值的,那么等号左边的就是offset变量定义的的左边值。
那么offset初始化的值是怎么来的呢?这里有必要给大家说一下。
因为我们知道关键字空指针(nullptr = null pointer)的隐式性的隐含一个指针常量的整数地址零(0)的意思。
而且由于nullptr是一个指针类型,而且我们也知道指针内部是用地址偏移整数来表示的。
那么所以第一层括号里面的内容就是把空指针通过类型强制转换的方式,转换为指向这个Vector3结构体的指针类型。
第二层括号外面所包含的运算符,先暂时忽略不计,我们按照编译器的规则,先执行运算这个&地址运算符。
于是我们得到了指向名字为Vector3的结构体的指针地址。
然后我们又知道,实际上这个(->)成员访问运算符是个语法糖,实际上它的全部写法是(*pointer).member;
也就是说它使用逆向引用指针的运算符得到指针地址指向的值,那么这里是指名字为Vector3的结构体Instance(实例)或者内存地址。
那么经过这次运算之后,们就可以通过这个结构体的实力访问其中的float类型成员变量z。
最后再将这个变量z的值强制转换为int类型然后(一般情况下拷贝)赋值给offset。
因此,我们就得到名字为Vector3的结构体的各个成员的内存偏移量的输出了。
int offset=(int)&((Vector3*)nullptr)->y;
int offset=(int)&((Vector3*)nullptr)->z;
三、C++的动态数组(std::vector)
Vector(ArrayList),本质是一个动态数组而非向量
如何优化对vector类的使用
#include <iostream>
#include <string>
#include <vector>
struct Vertex
{
float x, y, z;
};
std::ostream& operator<<(std::ostream& stream, const Vertex& vertex)
{
stream << vertex.x << ", " << vertex.y << ", " << vertex.z;
return stream;
}
int main()
{
std::vector<Vertex> vertices;
//Vertex* vertices = new Vector[5];
vertices.push_back({ 1, 2, 3 });
vertices.push_back({ 4, 5, 6 });
for (int i = 0; i < vertices.size(); i++)
std::cout << vertices[i] << std::endl;
std::cin.get();
}
int main()
{
std::vector<Vertex> vertices;
vertices.push_back({ 1, 2, 3 });
vertices.push_back({ 4, 5, 6 });
for (int i = 0; i < vertices.size(); i++)
std::cout << vertices[i] << std::endl;
//范围for 增强for循环
for (Vertex v : vertices)
std::cout << v << std::endl;
std::cin.get();
}
顺序存储vs链式存储
顺序存储:
数据元素在内存中连续存放,通过物理位置的相邻表示逻辑关系(如数组)。
链式存储:
数据元素通过指针(或引用) 链接,在内存中非连续存放(如链表)。
顺序存储像“整齐排列的书架”,访问快但调整费力。
链式存储像“分散的笔记卡片”,增删灵活但查找慢。
实际应用中,常结合两者优势(如哈希表结合数组和链表)。
四、C++的stdvector使用优化
创建一个vector 开始push_back元素 向数组中添加元素
#include <iostream>
#include <string>
#include <vector>
struct Vertex
{
float x, y, z;
Vertex(float x, float y, float z)
: x(x), y(y), z(z)
{
}
// 拷贝构造函数
Vertex(const Vertex& vertex)
: x(vertex.x), y(vertex.y), z(vertex.z)
{
std::cout << "Copied!" << std::endl;
}
};
int main()
{
std::vector<Vertex> vertices;
vertices.push_back({ 1, 2, 3 });
vertices.push_back({ 4, 5, 6 });
std::cin.get();
}
int main()
{
std::vector<Vertex> vertices;
vertices.push_back(Vertex(1, 2, 3));
vertices.push_back(Vertex(4, 5, 6));
vertices.push_back(Vertex(7, 8, 9));
std::cin.get();
}
int main()
{
std::vector<Vertex> vertices;
vertices.reserve(3);
vertices.push_back(Vertex(1, 2, 3));
vertices.push_back(Vertex(4, 5, 6));
vertices.push_back(Vertex(7, 8, 9));
std::cin.get();
}
int main()
{
std::vector<Vertex> vertices;
vertices.reserve(3);
vertices.emplace_back(1, 2, 3);
vertices.emplace_back(4, 5, 6);
vertices.emplace_back(7, 8, 9);
std::cin.get();
}
emplace_back和push_back都是向容器(如std::vector)尾部添加元素的方法
优先用emplace_back:当需要直接通过参数构造对象时(尤其是构造开销大的对象)。
用push_back:当已有对象需要拷贝或移动,或代码兼容C++11之前的版本。
性能关键代码:emplace_back通常更高效,但需确保参数正确传递。