Cherno C++【41-45】

文章介绍了C++中的运算符重载,如何为自定义类型如Vector2定义加法和乘法运算,并展示了如何通过重载`+`和`*`运算符简化代码。此外,讨论了`this`关键字在成员函数中的作用,以及对象在内存中的生存期管理。文章还详细讲解了智能指针(unique_ptr、shared_ptr和weak_ptr)的概念和使用,以及如何选择合适的智能指针类型。最后,提到了复制与浅拷贝的区别,强调了深拷贝的重要性,并给出了拷贝构造函数的示例。

【41】运算符及其重载

运算符时我们使用给的一种符号,通常代替一个函数来执行一些事情。比如加减乘除、dereference运算符、箭头运算符、+=运算符、&运算符、左移运算符、new和delete、逗号、圆括号、方括号等等等等。

重载这个术语本质上是给运算符重载赋予新的含义,或者添加参数,或者创建,允许在程序中定义或更改运算符的行为。
运算符就是函数。运算符重载是一个非常有用的特性,但在Java等语言中不受支持,它在C#等语言中得到部分支持,C++给了我们完全的控制权。

不用运算符实现加、乘

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 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(1.1f, 1.1f);
	
	Vector2 result1 = position.Add(speed.Multiply(powerup)); //position+(speed*powerup),Java中只能这么用

	Vector2 result2 = position.+ speed * powerup;  //用运算符定义,运算符同样存在优先级

	std::cin.get();

加入运算符后

Vector2 operator+(const Vector2& other)const
	{
		return Add(other);
	}
int main() 
{
	Vector2 position(4.0f, 4.0f);
	Vector2 speed(0.5f, 1.5f);
	Vector2 powerup(1.1f, 1.1f);
	
	Vector2 result1 = position.Add(speed.Multiply(powerup)); //position+(speed*powerup),Java中只能这么用

	Vector2 result2 = position.+ speed * powerup;  //用运算符定义,运算符同样存在优先级

	std::cin.get();

左移运算符

std::ostream& operator<<(std::ostream& stream, const Vector2& other)  // 重载左移运算符
{
	stream << other.x << "," << other.y;
	return stream;
}

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

【42】this关键字

通过this可以访问成员函数,成员函数是一个属于某个类的函数或者方法,在方法内部可以引用this。
this是一个指向当前对象实例的指针,该方法属于这个对象实例。
链接:link

【43】对象生存期

内存以及对象是如何在栈上生存的,生存期对于基于栈的变量意味着什么。
每当我们在c++中进入一个作用域,我们是在push栈帧,它不一定非得是将数据push进一个栈帧。
栈上变量自动销毁,在很多方面都很有用,可以帮助我们自动化代码。比如类的作用域,比如智能指针unique_ptr,这是一个作用域指针,或者像作用域(scoped_lock)。

但最简单的例子可能是作用域指针,他基本上是一个类,它是一个指针的包装器,在构造时用堆分配指针,然后在析构时删除指针,所以我们可以自动化这个new和delete。

【44】智能指针

智能指针本质上是原始指针的包装。当你创建一个智能指针,它会调用new并为你分配内存,然后基于你使用的智能指针,这些内存会在某一时刻自动释放。

unique_ptr

他的构造函数时explicit,所以需要显式调用构造函数。作用域结束,Entity就会被自动摧毁。
unique_ptr 指针不能被复制,,因为只要作用域结束了,在堆上分配的内存也会被释放掉,因此在该指针在实现上强制地将拷贝构造函数和拷贝构造符都删除,避免错误的行为。
但是为了异常安全,一般我们使用std::make_unique

#include <memory>

class Entity
{
public:
	
	Entity()
	{
		std::cout << "created" << std::endl;
	}

	~Entity()
	{
		std::cout << "destoryed" << std::endl;
	}

	void Print() {}
};

int main() 
{
	{
		std::unique_ptr<Entity>  entity(new Entity());  

		//std::unique_ptr<Entity>  entity = std::make_unique<Entity>();    //为了异常安全,一般我们使用std::make_unique

		entity->Print();
	}

	std::cin.get();

}

share_ptr

和 unique_ptr 由于异常安全的原因而不推荐使用 new 的方式创建智能指针不同,因为 shared_ptr 需要分配另一块内存,称为控制块,用来存储引用计数,所以如果使用 new 的形式来创建一个对象再将其传给 shared_ptr 的构造函数,他就必须做两次内存分配。所以需要用make_share将他们组合在一起,这样更有效率。
share_ptr是可以复制的

	std::shared_ptr<Entity> shareEntity = std::make_shared<Entity>();
	std::shared_ptr<Entity> e0 = shareEntity;  //share_ptr可以复制

weak_ptr

和复制 shared_ptr 所做的一样,但当你把 shared_ptr 赋值给一个 weak_ptr,假如唯一的那个 shared_ptr 指针死去,内存就会被删除,因为赋值给 weak_ptr 并不会增加引用数量。例如你在排序一个集合,你不需要关注指针是否有效,你只需要存储一个他们的引用就好了。

std::weak_ptr<Entity> entity = std::make_weak<Entity>();

智能指针选择顺序

优先选择unique_ptr ,因为有一个较低的开销,如果需要分享,则选择share_ptr。

【45】复制与copy构造函数

复制普通变量与复制指针是不同的。
在栈中创建地对象,其数值存储在不同地内存地址,当赋值发生时,就是将值准确地拷贝到对方地内存那里。但在堆中创建的对象,我们所持有的变量是一个指针,这个指针存储着内存地址,并指向堆中的内存,当我们进行赋值时,实际上改变的是地址,也就是两个变量都将指向同一块内存了。

struct Vector2
{
	float x, y;
};

int main() 
{
	Vector2* a = new Vector2(); //在堆上创建指针
	Vector2* b = a; // a,b指向相同的内存地址
	b++; //只改变b,a指针是完整的
	b->x = 2; //访问内存地址,设置为某个值,同时影响a,b


	std::cin.get();

复制字符串

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]; //数组大小要+1,留给止位符
		memcpy(m_Buffer, string, m_Size); //复制函数memcpy[destination,source,size]
	}

	~String()
		{
			delete[] m_Buffer ;
		}
		
	//友元函数,声明一个友元函数
	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 = "cherno";
	std::cout << string << std::endl;

	std::cin.get();

}

浅拷贝:复制的是地址。如果其中一个调用了析构函数并且删除了堆上内存,当另一个在调用析构函数试图删除内存时,就会抛出异常,因为同一块内存不能被销毁两次。
浅拷贝是新增了一个指针指向内存,深拷贝是复制内存。

String string = "cherno";
String second = string; //浅拷贝,复制的是地址,即string和second指向同一段内存

深拷贝
想要实现深拷贝,希望被赋值的变量拥有自己的唯一的内存块,有自己的指针,就需要使用到拷贝构造函数,该构造函数会在你赋值复制时调用。

	//c++默认提供的拷贝构造函数
	//执行深copy必须使用的代码
	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); //从other对象复制

	}

建议总是通过const & 去传递对象!!!!

### 关于C++14的内容与The Cherno的教学资源 对于希望深入了解 **C++14** 的开发者来说,掌握其新增特性是非常重要的。这些特性包括但不限于 `auto` 类型推导增强、泛型 lambda 表达式以及返回类型后置语法等[^1]。 #### C++14 新增特性概述 以下是 C++14 中的一些重要改进: - **Generic Lambdas**: 支持在 Lambda 表达式中使用模板参数,从而实现更灵活的功能定义。 - **Return Type Deduction**: 编译器可以自动推断函数的返回类型,简化代码书写。 - **Variable Templates**: 提供了一种新的方式来创建基于类型的变量集合。 关于具体教程推荐方面,《Modern C++ Tutorial》一书提供了详尽讲解,适合那些已经熟悉传统 C++ 并想快速过渡到现代标准的人群。 另外,在视频教学领域,《The Cherno》是一个非常受欢迎的选择。他制作了一系列高质量编程课程,涵盖了游戏开发、图形学等多个方向,并且也有关于 C++ 不同版本的基础及高级主题介绍。虽然官方可能未单独设立专门针对某单一子集如仅限"C++14"部分的独立板块,但在他的基础至中级甚至更高阶系列里会穿插提及各个标准化更新要点及其实际应用场景演示。 下面是一段展示如何利用 C++14 特性的简单例子: ```cpp #include <iostream> #include <vector> int main() { auto add = [](auto a, auto b) { return a + b; }; // Generic lambda std::vector<int> numbers{1, 2, 3}; int sum = 0; for(auto num : numbers){ sum += num; } double average = static_cast<double>(sum)/numbers.size(); // Enhanced type inference with 'auto' std::cout << "Average: " << average << '\n'; } ``` 此程序片段展示了两个主要功能:一是通过通用Lambda表达式的灵活性;二是借助`auto`关键字让编译器自行决定合适的数据类别,减少显式声明带来的冗余感。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值