44_1使用memcpy的注意事项

毁掉多态:篡改虚函数表指针 (vptr)#

#include <iostream>
#include <cstring>

class Entity {
public:
    virtual void SayName() { std::cout << "I am Entity" << std::endl; }
};

class Player : public Entity {
public:
    void SayName() override { std::cout << "I am Player" << std::endl; }
};

void Print(Entity* e) {
    e->SayName(); // 强制程序通过虚表查找
}

int main() {
    Entity base;
    Player p1;

    std::cout << "Before memcpy: ";
    Print(&p1); // 应该输出 I am Player

	//base复制给p1,但是p1的虚函数表却指向的是Entity的
    // 暴力覆盖!把 base 的内存(包含 Entity 的 vptr)强行塞进 p1
    std::memcpy(&p1, &base, sizeof(Entity));

    std::cout << "After memcpy:  ";
    Print(&p1); // 这次,它一定会输出 I am Entity!

    std::cin.get();
}

内存错位#

#include <iostream>
#include <cstring>

struct BaseA {
    int a = 111;
};

struct BaseB {
    int b = 222;
};

// Child 同时拥有 a 和 b,继承顺序决定内存顺序,这里先BaseA-->BaseB-->自己的成员
//在内存里,Child 对象是一块连续的砖,布局如下(假设每个 int 占 4 字节):
// 字节偏移  内容  属于谁
// 0    int a(111)  BaseA的地盘
// 4    int b(222)  BaseB的地盘8int c(3)Child 的地盘
struct Child : public BaseA, public BaseB {
    int c = 333;
};

int main() {
    Child p;
    BaseB sourceB;
    sourceB.b = 999; // 我们准备了一个新的 B,想把它拷进 p 里

    std::cout << "--- 拷贝前 ---" << std::endl;
    std::cout << "p.a = " << p.a << " (BaseA部分)" << std::endl;
    std::cout << "p.b = " << p.b << " (BaseB部分)" << std::endl;

    // --- 致命操作 ---
    // 程序员的意图:把 sourceB 的数据拷贝给 p
    // 程序员认为:p 既然继承了 BaseB,那我就直接拷过去
    std::memcpy(&p, &sourceB, sizeof(BaseB));

    std::cout << "\n--- 运行 memcpy(&p, &sourceB, ...) 后 ---" << std::endl;

    // 错位发生了!
    // 1. p.a 被改成了 999。因为 memcpy 从 p 的开头(BaseA的位置)开始写。
    // 2. p.b 依然是 222。因为它在内存后面,memcpy 根本没写到它。

    std::cout << "p.a = " << p.a << " (被错误覆盖了!)" << std::endl;
    std::cout << "p.b = " << p.b << " (完全没被拷进去!)" << std::endl;

    std::cin.get();

    return 0;
}

44复制和复制构造函数

了解在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;
}

除了引用以外,其他的给变量赋值,都是使用的复制

43智能指针

智能指针,是真实原始指针被包装。使用智能指针时,将调用new并分配给你内存,这块内存在某些时候会自动释放

unique pointer 唯一指针#

在一个作用域内,同一个原始指针只能被一个 unique_ptr 管理。它禁止拷贝,这意味着你不能通过赋值来增加它的引用,只能使用 std::move() 将所有权从一个指针转移给另一个。

转让所有权-附加知识点#

#include <iostream>
#include <memory>
class Entity {};
int main() {
    // 1. 创建第一个 unique_ptr
    std::unique_ptr<Entity> entity = std::make_unique<Entity>();

    // 2. 使用 std::move 转移所有权
    // 此时,entity 原有的内存所有权交给了 e2
    std::unique_ptr<Entity> e2 = std::move(entity);

    // 3. 检查状态
    if (entity == nullptr) {
        std::cout << "entity 现在是空的 (nullptr)" << std::endl;
    }

    if (e2 != nullptr) {
        std::cout << "e2 现在拥有 Entity 的所有权" << std::endl;
    }

    std::cin.get();
    return 0;
} // e2 在这里离开作用域,Entity 被自动销毁

示例-主要知识点#

#include <iostream>  
#include <string>
#include <memory>

class Entity
{
public:

	//constructor-构造函数
	Entity()
	{
		std::cout << "Created Entity!" << std::endl;
	}


	//destructor-析构函数
	~Entity()
	{
		std::cout << "Destroyed Entity!" << std::endl;
	}

	void Print()
	{
		std::cout << "hello" << std::endl;
	}

};

int main()
{
	std::cout << "start--" << std::endl;
	{
		//不允许,禁止隐式转换
		//std::unique_ptr<Entity> entity =new Entity();
		
		//允许,直接调用构造函数,但是不建议
		//如果创建完原始对象后发生了异常,没有来得及将它放到智能指针对象
		//会导致不释放指针指向内存,导致内存泄漏
		//std::unique_ptr<Entity> entity(new Entity());
		
		//不会有这问题,因为std::make_unique保证了创建对象和将其
		// 放入智能指针是一个原子操作==,中间不会被其他逻辑插入,因
		// 此它是异常安全的。
		std::unique_ptr<Entity> entity = std::make_unique<Entity>();

		entity->Print();

		std::unique_ptr<Entity> e2 = std::move(entity);//允许
		//std::unique_ptr<Entity> e3 = entity;//不允许,编译报错
		/*查看源码
		* 复制构造函数和赋值操作符被删除
			unique_ptr(const unique_ptr&)            = delete;
			unique_ptr& operator=(const unique_ptr&) = delete;		
		*/

	}
	std::cout << "end--" << std::endl;
	std::cin.get();
}
/*
start--
Created Entity!
hello
Destroyed Entity!
end--
*/

不允许std::unique_ptr<Entity> entity =new Entity(); 因为禁止隐式转换

42对象生命周期

栈中对象的生命周期

范围#

#

#include <iostream>  
#include <string>

class Entity
{
public:

	//constructor-构造函数
	Entity()
	{
		std::cout << "Created Entity!" << std::endl;
	}


	//destructor-析构函数
	~Entity()
	{
		std::cout << "Destroyed Entity!" << std::endl;
	}

};

int main()
{
	std::cout << "start--" << std::endl;
	{
		Entity e;
	}
	std::cout << "end--" << std::endl;
	std::cin.get();
}
/*
start--
Created Entity!
Destroyed Entity!
end--
*/

超出范围则自动销毁

#

#include <iostream>  
#include <string>

class Entity
{
public:

	//constructor-构造函数
	Entity()
	{
		std::cout << "Created Entity!" << std::endl;
	}


	//destructor-析构函数
	~Entity()
	{
		std::cout << "Destroyed Entity!" << std::endl;
	}

};

int main()
{
	std::cout << "start--" << std::endl;
	{
		Entity* e=new Entity();
	}
	std::cout << "end--" << std::endl;
	std::cin.get();
}
/*
start--
Created Entity!
end--
*/

返回栈内存的指针及解决办法#

#include <iostream>  
#include <string>

class Entity
{
public:

	//constructor-构造函数
	Entity()
	{
		std::cout << "Created Entity!" << std::endl;
	}


	//destructor-析构函数
	~Entity()
	{
		std::cout << "Destroyed Entity!" << std::endl;
	}

};

int* CreateArrayWrong()
{
	//在栈上声明一个数组
	//第一个元素为6,剩余49个为0
	int array[50] = { 6 };
	//返回指向该数组(栈内存)的指针
	return array;
}


int* CreateArray1()
{
	//在栈上声明一个数组
	//第一个元素为6,剩余49个为0
	int* array = new int[50] { 6 };
	//返回指向该数组(栈内存)的指针
	return array;
}
void CreateArray2(int*)
{
	//填充数组
}

int main()
{
	//错误,这里得到的数据是不可信的,
	//极有可能被其他代码拿来使用
	int* a = CreateArrayWrong();

	//方法1
	int* a1 = CreateArray1();
	//方法2
	int array[50];
	CreateArray2(array);

	std::cin.get();
}

智能指针#

这是一个包装类,构造在栈中使用指针,分配堆内存;在包装类销毁时删除指针

41this关键字

this: 是一个指针,指向当前对象实例

#include <iostream> 

class Entity;//必要的,视频没有加上
void PrintEntity(Entity* e);
void PrintEntity2(const Entity& e);

class Entity
{
public:
	int x, y;

	Entity(int x, int y)
	{
		//这里会报错,this是指针常量
		//this = nullptr;
		
		//this是指针常量
		//这里如果不加const不会报错,
		//视频教程中必须加否则报错,不知道为啥
		Entity* const e = this; 
		
		//或者//(*e).x = x;
		e->x = x;

		this->x = x;
		this->y = y;

		PrintEntity(this);
		PrintEntity2(*this);
		delete e;
 	}

	int GetX() const
	{
		//没有const修饰会报错
		//Entity* e = this;
		const Entity* e = this;
		return x;
	}
};


void PrintEntity(Entity* e)
{
	//Print
}
void PrintEntity2(const Entity& e)
{
	//Print
}

int main()
{

	std::cin.get();
}

40运算符和运算符重载

重载运算符+#

方法1#

#include <iostream>
#include <string>   

struct Vector2
{
	float x, y;

	Vector2(float x, float y)
		:x(x), y(y) {
	}

	Vector2 Add(const Vector2& other) const
	{
		return Vector2(x + other.x, y + other.y);
	}

	Vector2 operator+(const Vector2& other) const
	{
		return Add(other);
	}

	Vector2 Multiply(const Vector2& other) const
	{
		return Vector2(x * other.x, y * other.y);
	}

};

int main()
{
	Vector2 position(4.0f, 4.0f);
	Vector2 speed(0.5f, 1.5f);
	Vector2 powerup(0.5f, 1.5f);

	Vector2 result1 = position.Add(speed.Multiply(powerup));
	Vector2 result2 = position + speed;// *powerup;

	std::cin.get();
}

方法2#

#include <iostream>
#include <string>   

struct Vector2
{
	float x, y;

	Vector2(float x, float y)
		:x(x), y(y) {
	}

	Vector2 Add(const Vector2& other) const
	{
		//this是指向自身的一个指针
		//return *this + other;

		//或者
		return operator+(other);
	}

	Vector2 operator+(const Vector2& other) const
	{
		return Vector2(x + other.x, y + other.y);
	}

	Vector2 Multiply(const Vector2& other) const
	{
		return Vector2(x * other.x, y * other.y);
	}

};

int main()
{
	Vector2 position(4.0f, 4.0f);
	Vector2 speed(0.5f, 1.5f);
	Vector2 powerup(0.5f, 1.5f);

	Vector2 result1 = position.Add(speed.Multiply(powerup));
	Vector2 result2 = position + speed;// *powerup;



	std::cin.get();
}

重载运算符 *==#

#include <iostream>
#include <string>   

struct Vector2
{
	float x, y;

	Vector2(float x, float y)
		:x(x), y(y) {
	}

	Vector2 Add(const Vector2& other) const
	{
		return Vector2(x + other.x, y + other.y);
	}

	Vector2 operator+(const Vector2& other) const
	{
		return Add(other);
	}

	Vector2 Multiply(const Vector2& other) const
	{
		return Vector2(x * other.x, y * other.y);
	}

	Vector2 operator*(const Vector2& other) const
	{
		return Multiply(other);
	}

	bool operator==(const Vector2& other) const
	{
		return x == other.x && y == other.y;
	}

	bool operator!=(const Vector2& other) const
	{
		//return !(*this == other);
		//或者
		return !operator==(other);
	}

};

std::ostream& operator<<(std::ostream& stream, const Vector2& other)
{
	stream << other.x << "," << other.y;
	return stream;
}

int main()
{
	Vector2 position(4.0f, 4.0f);
	Vector2 speed(0.5f, 1.5f);
	Vector2 powerup(0.5f, 1.5f);

	Vector2 result1 = position.Add(speed.Multiply(powerup));
	Vector2 result2 = position + speed * powerup; 

	std::cout << result1 << std::endl;
	std::cout << result2 << std::endl;

	if (result1 == result2)
		std::cout << "---equals---" << std::endl;

	std::cin.get();
}

运算符重载本质上就是函数。虽然你可以自定义运算符的行为(即函数体里写什么),但你不能改变 C++ 语言定义的运算符规则,包括:

39显式转换和隐式转换

隐式转换表示你没有明确告诉他你要这么做

#include <iostream>
#include <string>  

class Entity
{
private:
	std::string m_Name;
	int m_Age;

public:

	//加上explicit则拒绝隐式转换
	/*explicit*/ Entity(const std::string& name)
		:m_Name(name), m_Age(-1) {
	}

	/*explicit*/ Entity(int age)
		:m_Name("Unknown"), m_Age(age) {
	}

};

void PrintEntity(const Entity& entity)
{

}

int main()
{
	//直接初始化,直接寻找匹配的构造函数,不属于隐式转换
	Entity a("Cherno");
	Entity b(22);

	//违反了“最多只能进行一次用户自定义转换”的原则
	//先从const char[7]转换为std::string
	//然后从std::string转换为Entity
	//Entity a1 = "Cherno";
	//PrintEntity("Cherno");

	//隐式转换
	Entity a1 = std::string("Cherno");
	Entity b2 = 22;
	//建议这样书写简单明了
	//Entity b3(22);

	//隐式转换
	PrintEntity(22);
	PrintEntity(std::string("Cherno"));

	//只进行一次转换,把 const char[7]转换为std::string
	//这里没有涉及到把xx转换为Entity,所以即使
	// explicit Entity(const std::string& name)也
	//能编译通过
	PrintEntity(Entity("Cherno"));

	//强制显式转换
	Entity a3 = (Entity)"Cherno";
	//不转换而是直接调用构造函数
	Entity a3 = Entity("Cherno");

	std::cin.get();
}

38new关键字

常见用法#

  • new 指定数据类型,数据类型包括类、原始类型、数组,根据数据类型确定必要的大小,以字节为单位
  • new int,4字节。向操作系统找到连续的4字节大小的内存,然后返回指向该内存地址的指针
  • 有一个空闲列表维护具体可用字节的地址,不需要一个个地址扫描是否可用
#include <iostream>
#include <string> 

//类型别名 (Type Alias) 声明。
using String = std::string;

class Entity
{
private:
	String m_Name;
public:
	Entity() :m_Name("Unknown") {}
	Entity(const String& name) :m_Name(name) {}

	const String& GetName() const { return m_Name; }
};

int main()
{
	int a = 2;
	int* b = new int;
	//连续的50个元素(200个字节)
	int* b_arr = new int[50];

	Entity* e1 = new Entity;
	//和上面那句一个意思
	Entity* e2 = new Entity();

	
	//为了在执行 delete[] e_arr 时知道要调用多少次析构函数,
	// 编译器通常会在分配的内存块头部额外存储一个计数
	// 器(Cookie),记录数组的长度。
	//e_arr指向内存块的大小:50 * sizeof(Entity) + sizeof(size_t)
	//size_t是 C++ 中专门用来表示内存大小和索引的类型:64位系统中是8字节,
	//32位系统中是4字节
	//连续的50个Entity
	Entity* e_arr = new Entity[50];

	std::cin.get();
}

补充:newdeletedelete[] 其实是一个运算符

37创建对象

两种方式,取决于我们在内存的哪个位置创建对象(堆heap或栈stack)

C++ 有一条基本准则:任何不同的对象在内存中都必须有唯一的地址。所以如果创建了两个对象class Empty {};Empty e1;Empty e2;,只有他们都有1字节大小的情况下,e1和e2才会有不同的内存地址

  • 栈上创建的对象,有自动的生命周期,由他们的作用域决定的,离开作用域则消失
  • 堆上创建的对象,会一直在,直到你手动释放它

栈上创建对象#

#include <iostream>
#include <string> 

//类型别名 (Type Alias) 声明。
using String = std::string;

// using 声明,使用的时候只能用string(只起到了省略std::的作用)
//using std::string; 

/*
- 如果你把"using String = ... 还是 using std::string;"写
在 .h (头文件) 的全局作用域里,都会产生“污染”:
- 任何 #include 你这个头文件的人,都会被强制接受你的命名习惯。
如果别人也定义了一个 String,代码就会崩溃(冲突)。
*/

class Entity
{
private:
	String m_Name;
public:
	Entity() :m_Name("Unknown") {}
	Entity(const String& name) :m_Name(name) {}

	const String& GetName() const { return m_Name; }
};

void Function()
{
	int a = 2;
	Entity entity = Entity("Cherno");
}

int main()
{
	Function();
	Entity* e;
	{
		//在栈上创建对象(这里会默认调用默认构造函数)
		Entity entity3("Cherno");
		e = &entity3;
		std::cout << e->GetName() << std::endl;
	}

	//在栈上创建对象(这里会默认调用默认构造函数)
	Entity entity;
	std::cout << entity.GetName() << std::endl;
	//在栈上创建对象
	Entity entity1("Cherno1");
	std::cout << entity1.GetName() << std::endl;
	//在栈上创建对象
	Entity entity2 = Entity("Cherno2");
	std::cout << entity2.GetName() << std::endl;

	std::cin.get();
}

e = &entity3;加断点,并走到下一步

36三元运算符

if-else的语法糖

初试#

#include <iostream>
#include <string>

static int s_Level = 12;
static int s_Speed = 2;

int main()
{

	if (s_Level > 5)
		s_Speed = 10;
	else
	{
		s_Speed = 5;
	}

	s_Speed = s_Level > 5 ? 10 : 5;
	//嵌套
	// 
	//s_Level > 5 && s_Level < 100 这个会先被连接在一起判断
	s_Speed = s_Level > 5 && s_Level < 100 ? s_Level > 10 ? 15 : 10 : 5;

	//如果>5的情况下,如果还>10,则15,如果不大于10则10;否则(不
	//>5),则5
	s_Speed = s_Level > 5 ? s_Level > 10 ? 15 : 10 : 5;


	//方式1:这里没有多构造空对象其实和编译器有关,后面会说到
	std::string rank = s_Level > 10 ? "Master" : "Beginer";

	//方式2:这种声明方式,还会比上面额外多构造一个空字符串对象
	std::string otherRank;
	if (s_Level > 10)
		otherRank = "Master";
	else
		otherRank = "Beginer";

	std::cout << s_Speed << std::endl; //15

	std::cin.get();
}

解释一下方式2#

编译器并不会先创建一个临时的 std::string 之后再拷贝给 rank。它的执行逻辑如下: