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。它的执行逻辑如下:

35成员变量初始化

普通的初始化方法#

#include <iostream>
#include <string>

class Entity
{
private :
	std::string m_Name;
public:
	Entity()
	{
		m_Name = "Unknown";
	}

	Entity(const std::string& name)
	{
		m_Name = name;
	}
	
	const std::string& GetName() const { return m_Name; }
};

int main()
{
	Entity e0;
	std::cout << e0.GetName() << std::endl;
	Entity e1("Cherno");
	std::cout << e1.GetName() << std::endl;
	std::cin.get();
}

初始化列表#

#include <iostream>
#include <string>

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

public:

	//如果不是用初始化列表,就会先调用成员变量的构造函数初始化给它默认值(这里是m_Name),然后在{}中再一次赋值
	//如果用了,那就直接使用初始化列表中的值初始化
	//第二种方式只调用了string(const char*),而第一种方式多调用了一次string()
	Entity()
		:m_Name("Unknown"),m_Score(0) //注意不论这里的书写顺序是啥
		//都是先初始化m_Score,再m_Name
	{

	}

	Entity(const std::string& name)
		: m_Name(name)
	{

	}

	const std::string& GetName() const { return m_Name; }
};

int main()
{
	Entity e0;
	std::cout << e0.GetName() << std::endl;
	Entity e1("Cherno");
	std::cout << e1.GetName() << std::endl;


	std::cin.get();
}

构造函数的“幕后”过程#

初始化阶段:以下只针对类对象,基本类型不会被自动初始化(除非是全局变量:全局变量会被清零),它们的值是内存中残留的随机垃圾值。

33-34const、mutable关键字

常量折叠、类型强转#

#include <iostream>

int main()
{
	//当你定义 const int MAX_AGE = 90; 时,编译器通常会进行常量折叠(Constant Folding)。这意味着在编译阶段,代码中所有出现 MAX_AGE 的地方都会被直接替换为数字 90;
	//所以最下面的 std::cout << MAX_AGE << std::endl; 打印90
	const  int MAX_AGE = 90;
	std::cout << MAX_AGE << std::endl;//90

	int* a = new int;

	*a = 2;
	std::cout << *a << std::endl;//2

	//如果不加 (int*)类型强转,提示 a value of type "const int *"cannot beassigned to an entity of type "int *"
	//报错原因:1. 如果你能直接把 const int* 赋值给 int*,那么你就可以通过 *a = 100; 来修改 MAX_AGE 的值。这违背了 const 的初衷。为了保证代码的健壮性,编译器禁止这种**“权限扩大”**的行为(从“只读”变成“可读写”)。
	//	2. 为什么加上(int*) 就不报错了?
	//	当你加上(int*) 时,你正在使用强制类型转换(C - style Cast)。
	//	这相当于你对编译器说:“我知道 MAX_AGE 是常数,我也知道我在把只读指针转成可读写指针,出了问题我负责,请闭嘴。” 编译器接收到了你的强制指令,于是停止报错。
	a = (int*)&MAX_AGE;
	std::cout << *a << std::endl;//90
	*a = 5;

	//如果MAX_AGE加了volatile关键字修饰,即	const volatile int MAX_AGE = 90;那
	//么不会发生常量折叠,这里应该打印的是5
	std::cout << MAX_AGE << std::endl;//90
	//MAX_AGE 对应的内存空间,通过指针非法修改成了 5
	std::cout << *a << std::endl;//5

	std::cin.get();
}

const的修饰位置#

变量中#

#include <iostream>

int main()
{
	const int MAX_AGE = 90;

	//不能修改指针指向的地址里的值(但可以修改指针指向)
	const int* a = new int;
	//int const* a = new int;//跟上一句一个意思,const都是在*前面
	//*a = 2;//报错

	//由于a已经是const int*了,
	//所以这里编译器没有像int* a时报错
	a = &MAX_AGE;

	std::cout << *a << std::endl;//90 

	//不能修改指针指向的地址里的值(但可以修改指针指向)
	int* const  b = new int;
	*b = 2; //可修改地址里的值
	//不可重新分配b指向其他地址
	//b = &MAX_AGE;//报错


	const int* const  c = new int;
	int const* const  d = new int;
	/*以下均编译不通过*/
	//*c = 2;
	//c = &MAX_AGE;
	//*d = 2;
	//d = &MAX_AGE;
	/*以上均编译不通过*/

	std::cin.get();
}

class中#

#include <iostream>

class Entity
{
private:
	int m_X, m_Y;
	static int s_X;
	int* m_XP;
public:

	//const: 在该方法中不允许修改成员变量
	//禁止修改成员变量: 在该函数内部,你不能对任何非 static 成员变量进行赋值操作。
	//禁止调用非 const 函数
	int GetX() const
	{
		//m_X = 3;
		s_X = 5;
		//test();//会报错
		return m_X;
	}

	//返回一个int指针,且该指针不能改变所存地址
	//指向的值,也不能指向其他地址
	const int* const GetXP() const
	{

		return m_XP;
	}

	void test() //const
	{

	}
};
int main()
{

	std::cin.get();
}

形参中#

#include <iostream>

class Entity
{
private:
	int m_X, m_Y;
	int m_X1;
	mutable int var;
public:

	int GetX1()  
	{
		return m_X1;
	}

	int GetX() const
	{
		//var是一个mutable变量,在const方法中可以修改它
		var = 3;
		return m_X;
	}

	void SetX(int x)
	{
		m_X = x;
	}
};

void PrintEntity1(const Entity* e)
{
	e = nullptr;//允许编译,允许指向其他
	//(*e).SetX(2);//报错,不允许修改值
	std::cout << (*e).GetX() << std::endl;
}

void PrintEntity2(const Entity& e)
{
	//e = nullptr;//不允许编译,因为是引用,不允许重新指向其他
	//e.SetX(2);//报错,不允许修改值
	std::cout << e.GetX() << std::endl;
	//不允许调用非const修饰的函数,下面会报错,
	//因为无法保证GetX1()是否修改了e,而e是const修饰的
	//std::cout << e.GetX1() << std::endl;
}

int main()
{

	std::cin.get();
}

mutable#

class成员变量#

#include <iostream>
#include <string>

class Entity
{
private:
	std::string m_Name;

	//允许标记为const的方法修改该变量
	mutable int m_DebugCount = 0;

public:

	//1.如果是 const std::string,则返回的是一个副本
	//返回一个引用
	//2.最后的const表示该函数内部不允许修改类的非static成员变量
	const std::string& GetName() const
	{
		//m_Name = "a";//非法
		m_DebugCount++;//编译通过

		//由于返回的是引用,所以这里返回了成员变
		//量 m_Name 的一个“地址”或“别名”
		return m_Name;
	}

	void MyTest()
	{

	}
};

int main()
{
	const Entity e;
	e.GetName();//调用const方法
	//e.MyTest();//不允许调用非const方法


	Entity e1;
	e1.GetName();//调用const方法
	e1.MyTest();//调用非const方法 

	std::cin.get();
}

lambda表达式中#

#include <iostream> 

int main()
{
	//lamda表达式

	int x = 1;
	int y = 8;
	const int z = 5;

	//按引用捕获所有变量
	auto f1 = [&]()
		{
			y = 3;//合法,和原变量一样
			//z = 5;//不合法,和原变量一样是const
			std::cout << "Hello" << std::endl;
		};

	//mutable 不能直接修饰普通的局部变量或全局变量,只能
	//是类成员变量
	//mutable int m2 = 0;//没这种写法,会报错

	//按值捕获所有变量
	auto f2 = [=]()
		{
			//y = 3;//不合法,默认是const
			std::cout << "Hello" << std::endl;
		};

	//按值捕获所有变量
	//默认情况下,Lambda 内部的 operator() 是 const 的,不允
	//许修改捕获的副本。加上 mutable 后,Lambda 内部就可以修改这些副本了。
	auto f2_1 = [=]() mutable
		{
			y = 3;
			std::cout << "Lambda 内部 y = " << y << std::endl;//3
		};

	std::cout << "外部原变量 y = " << y << std::endl;//8

	//按值捕获x变量
	auto f3 = [x]()
		{
			//y = 3;//不合法,默认是const
			std::cout << "Hello" << std::endl;
		};

	f1();
	f2();
	f3();
	std::cin.get();
}