C++11-右值引用与移动语义

news2024/11/14 11:27:53

右值引用与移动语义

  • 一,右值引用概念
    • 右值引用简单例子
    • 左值引用与右值引用的比较
  • 二,右值引用的使用场景
    • 函数对于其内部局部对象的传值返回
    • insert,push等接口
    • 左值引用与右值引用总结
  • 三,完美转发
  • 四,新的类功能
    • 默认成员函数
    • default与delete关键字

一,右值引用概念

🚀在C++11之前,使用的引用都是左值引用,C++11后退出了右值引用,无论是左值引用还是右值引用本质都是起别名。
🚀什么是左值?什么是左值引用?
左值是一个表示数据的表达式(如变量命或者解引用的指针),我们可以获取它的地址,可以对他赋值,左值可以出现在赋值符号的左边也可以出现在赋值符号的右边,定义时const修饰符后的左值,不能给它赋值,但是可以取它的地址。
左值引用就是给左值的引用,给左值取别名。

🚀什么是右值?什么是右值引用?
右值是表示数据的表达式,如:字面量,表达式的返回值,函数的返回值(这里指的是传值返回),右值可以出现在赋值符号的右边,但是不能出现在赋值符号的左边,右值不能取地址,不能对其赋值。
右值引用是对右值的引用,给右值取别名。

右值引用简单例子

int Add(int x, int y) { return x + y; }
int main()
{
	int&& ref1 = 10; //10这个字面量就是一个右值
	int&& ref2 = 10 + 20; //表达式的返回值也是右值
	int&& ref3 = Add(30, 50); //传值返回的函数的返回值也是右值

	//下面的例子会报错,右值是不能出现在赋值符号的左边的
	10 = 1;
	Add(10, 20) = 80;
	return 0;
}

左值引用与右值引用的比较

🚀对于左值引用:
1,左值引用只能引用左值,不能引用右值。
2,const左值引用既能引用左值,也能引用右值。

int main()
{
	int x = 10;
	int y = 20;
	//左值引用引用左值
	int& ref1 = x;
	int& ref2 = y;
	//const左值引用引用左值
	const int& ref3 = x;
	const int& ref4 = y;
	//const左值引用引用右值
	const int& ref5 = x + y;
	const int& ref6 = 10;
	return 0;
}

🚀对于右值引用:
1,右值引用只能引用右值,不能引用左值。
2,右值引用可以引用move之后的左值。

int Add(int x, int y) { return x + y; }
int main()
{
	int x = 10;
	int y = 20;
	//右值引用引用右值
	int&& ref1 = 10;
	int&& ref2 = Add(x, y);
	//右值引用引用move以后的左值
	int&& ref3 = std::move(x);
	int&& ref4 = std::move(y);
	return 0;
}

二,右值引用的使用场景

🚀右值引用真正的意义是配合移动语义来减少资源的拷贝,在C++11之前,我们可以通过左值引用来减少资源的拷贝,例如:函数传参时尽量传引用,对于某个函数要返回的对象如果出了函数作用域不会销毁,那么尽量返回这个对象的引用进而减少拷贝。
🚀仍然还有些场景是不能通过左值引用解决的,例如函数对于局部对象的传值返回等等,对于这些场景在C++11之后,可以通过右值引用搭配移动语义来减少资源的拷贝。

🚀下面为了方便演示,模拟实现了一个string类。

namespace gy
{
	class string
	{
	public:
		typedef char* iterator;
		iterator begin()
		{
			return _str;
		}
		iterator end()
		{
			return _str + _size;
		}
		string(const char* str = "")
			:_size(strlen(str))
			, _capacity(_size)
		{
			//cout << "string(char* str)" << endl;
			_str = new char[_capacity + 1];
			strcpy(_str, str);
		}
		// s1.swap(s2)
		void swap(string& s)
		{
			std::swap(_str, s._str);
			std::swap(_size, s._size);
			std::swap(_capacity, s._capacity);
		}
		// 拷贝构造
		string(const string& s)
			:_str(nullptr)
		{
			cout << "string(const string& s) -- 深拷贝" << endl;
			string tmp(s._str);
			swap(tmp);
		}
		// 赋值重载
		string& operator=(const string& s)
		{
			cout << "string& operator=(string s) -- 深拷贝" << endl;
			string tmp(s);
			swap(tmp);
			return *this;
		}
		// 移动构造
		string(string&& s)
			:_str(nullptr)
			, _size(0)
			, _capacity(0)
		{
			cout << "string(string&& s) -- 移动构造" << endl;
			swap(s);
		}
		// 移动赋值
		string& operator=(string&& s)
		{
			cout << "string& operator=(string&& s) -- 移动赋值" << endl;
			swap(s);
			return *this;
		}
		~string()
		{
			delete[] _str;
			_str = nullptr;
		}
		char& operator[](size_t pos)
		{
			assert(pos < _size);
			return _str[pos];
		}
		void reserve(size_t n)
		{
			if (n > _capacity)
			{
				char* tmp = new char[n + 1];
				strcpy(tmp, _str);
				delete[] _str;
				_str = tmp;
				_capacity = n;
			}
		}
		void push_back(char ch)
		{
			if (_size >= _capacity)
			{
				size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;
				reserve(newcapacity);
			}
			_str[_size] = ch;
			++_size;
			_str[_size] = '\0';
		}
		//string operator+=(char ch)
		string& operator+=(char ch)
		{
			push_back(ch);
			return *this;
		}
		const char* c_str() const
		{
			return _str;
		}
	private:
		char* _str;
		size_t _size;
		size_t _capacity; // 不包含最后做标识的\0
	};

	string to_string(int value)
	{
		bool flag = true;
		if (value < 0)
		{
			flag = false;
			value = 0 - value;
		}
		string str;
		while (value > 0)
		{
			int x = value % 10;
			value /= 10;
			str += ('0' + x);
		}
		if (flag == false)
		{
			str += '-';
		}
		std::reverse(str.begin(), str.end());
		return str;
	}
}

函数对于其内部局部对象的传值返回

🚀将上面的移动构造和移动赋值注释掉,也就是C++11之前的场景。

gy::string str1 = gy::to_string(1000);

to_string这个函数就是传值返回的函数,对于传值返回的函数,返回的不是函数内的那个局部对象,而是对局部对象的拷贝(局部对象出了作用域就会被销毁),在编译器优化之前,应该先调用一次拷贝构造,使用str构造出一个临时对象,然后再调用一个拷贝构造,用这个临时对象对构造str1这个对象,但是编译器对于这种连续的拷贝构造,或者是连续的拷贝构造和构造,会有优化,将他们合二为一,对于上面这个例子就会被优化为一次拷贝构造,就是直接用str去拷贝构造出str1对象。

在这里插入图片描述

gy::string str2;
str2 = gy::to_string(1000);

对于赋值而言,首先会调用一次拷贝构造,用局部对象拷贝构造出一个临时对象,然后,会再调用一次拷贝的赋值用临时变量对str2进行拷贝赋值,目前的编译器对这种情况是没有做出优化的。

在这里插入图片描述
截图中出现了两次拷贝构造,是因为在实现拷贝赋值的时候复用了拷贝构造。
在这里插入图片描述
🚀C++11之后,有了移动赋值和移动构造。
🚀移动构造本质就是将参数右值的资源窃取过来,占为己有,那么就不会再做深拷贝了,所以它叫做移动构造,就是窃取别人的资源来构造自己。

// 移动构造
string(string&& s)
	:_str(nullptr)
	, _size(0)
	, _capacity(0)
{
	cout << "string(string&& s) -- 移动构造" << endl;
	swap(s);
}
// 移动赋值
string& operator=(string&& s)
{
	cout << "string& operator=(string&& s) -- 移动赋值" << endl;
	swap(s);
	return *this;
}
gy::string str1 = gy::to_string(1000);

在这里插入图片描述
string类中添加了移动构造,to_string函数返回的是一个右值,它即能匹配string类的拷贝构造,也能匹配移动构造,但是编译器会选择最匹配的那个,也就是这是会去调用移动构造。编译器优化之前,首先做的是用局部对象拷贝构造出一个临时对象,再用这个临时对象去移动构造出str1,编译器优化后会直接用str这个局部对象去移动构造str1。

🚀有些书籍上把右值分为:纯右值和将亡值,对于内置类型的字面量就是纯右值,而像to_string函数返回的临时对象就是将亡值,上面那个例子能优化为一个移动构造,就是编译器把str这个局部变量识别成了将亡值,直接用这个将亡值去移动构造出str1。

🚀可能有的人会有疑问?str这个局部对象出了作用域就会被销毁了,资源已经被释放了,怎么去移动构造出str1的呢?起始编译器会先调用移动构造去构造出str1,然后再调用str对象的析构函数。
第一步:
在这里插入图片描述
第二步:
在这里插入图片描述

🚀移动赋值的本质就是将自己的资源与参数右值的资源做交换,来减少深拷贝,右值对象析构时会将原本自己的资源释放掉。

gy::string str2;
str2 = gy::to_string(1000);

在这里插入图片描述
由于string类提供了参数为右值引用的赋值函数,所以对于上图中函数返回的临时对象会去调用移动赋值来对str2对象进行赋值。在编译器优化之前,首先将to_string函数内部的局部对象str拷贝构造出一个临时对象用于函数的返回,再用这个临时对象去移动赋值给str2对象,编译器优化之后会将str识别为一个将亡值,会直接用str对象的资源去移动构造出一个临时对象,再用这个临时对象去移动赋值给str2。

🚀其实对于函数内部局部对象传值返回的函数,如果函数内部的局部对象对应的类存在移动构造,编译器都会做一个优化就是,不再去调用拷贝构造去深拷贝出一个临时对象,而是直接将这个局部对象识别为将亡值,去移动构造出一个临时对象。

int main()
{
	gy::to_string(100);
	return 0;
}

在这里插入图片描述
在这里插入图片描述

🚀上面的例子不是能很好的体现出移动赋值的本质:是将参数右值的资源窃取过来,将自己的资源交还给右值,右值对象析构的时候会自动销毁之前属于自己的资源。

int main()
{
	gy::string str1("hello world");

	gy::string str = std::move(str1);
	return 0;
}

在这里插入图片描述
在这里插入图片描述

insert,push等接口

🚀在C++11之后,STL容器的insert接口或者push接口,都提供了参数为右值引用的版本,可以减少深拷贝。
在这里插入图片描述
在这里插入图片描述

int main()
{
	list<gy::string> lt;
	lt.push_back("hello world");
	return 0;
}

🚀在C++11之前,上面这种情况,首先将“hello world”调用string的构造函数来隐式类型转换为string类的对象,list中new一个结点在存储string类对象的时候,又会调用一次拷贝构造。
在这里插入图片描述
🚀在C++11之后,支持了push_back参数为右值的情况,“hello world”字符串隐式类型转换出的匿名对象就是一个右值,会去调用list的参数为右值引用的push_back,那么在list内部new一个新的结点去存储string类对象的时候,就会去利用这个匿名对象这个右值去移动构造list结点中的string对象。

在这里插入图片描述

左值引用与右值引用总结

🚀左值引用是直接减少了拷贝次数。
🚀右值引用是配合移动语义来减少拷贝次数。
🚀右值引用搭配移动语义针对的是存在深拷贝的自定义类型,因为对于自定义类型但不涉及深拷贝的类或者内置类型,就不会涉及到资源的窃取。

三,完美转发

🚀需要注意的是右值是不能取地址的,但是给右值取别名后,会导致右值存储在一个特定的位置,且可以取到该位置的地址。也就是说一个右值引用一旦引用了右值之后,其属性就会变成左值。

int main()
{
	int&& ref = 10;
	ref = 20;
	cout << ref << endl;
	return 0;
}

在这里插入图片描述
🚀这就会引发新的问题,对于一个右值可能要经过多层的传参采用到其右值的属性,但经过一次右值引用的传参后,在接下来的过程中这个右值的别名的属性就变成左值了。也就是说右值在随着参数的传递会丢失其右值的属性。

void Fun(int& x) { cout << "左值引用" << endl; }
void Fun(const int& x) { cout << "const 左值引用" << endl; }
void Fun(int&& x) { cout << "右值引用" << endl; }
void Fun(const int&& x) { cout << "const 右值引用" << endl; }
template<typename T>
void PerfectForward(T&& t)
{
	Fun(t);
}
int main()
{
	PerfectForward(10);           // 右值
	int a;
	PerfectForward(a);            // 左值
	PerfectForward(std::move(a)); // 右值
	const int b = 8;
	PerfectForward(b);      // const 左值
	PerfectForward(std::move(b)); // const 右值
	return 0;
}

🚀模板中的&&不再代表右值引用,它既能引用右值,也能引用左值,被称为万能引用。

在这里插入图片描述
🚀可以看到全是左值引用,这里就出现右值属性丢失的问题,可以使用完美转发来维持其右值的属性std::forward<T>()

void PerfectForward(T&& t)
{
	Fun(std::forward<T>(t));
}

在这里插入图片描述
可以看到这就达到了我们想要的结果。
🚀由此可见STL库中,为了支持参数为右值引用的insert或者push等函数,在其内部必定是存在大量的完美转发来维持右值的属性。下面我们来模拟实一下:

template<class T>
struct ListNode
{
	ListNode* _next = nullptr;
	ListNode* _prev = nullptr;
	T _data;
	struct ListNode(const T& t)
		:_data(t)
	{}
	struct ListNode(T&& t)
		:_data(std::forward<T>(t))
	{}
};
template<class T>
class List
{
	typedef ListNode<T> Node;
public:
	List()
	{
		_head = new Node(T());
		_head->_next = _head;
		_head->_prev = _head;
	}
	void PushBack(T&& x)
	{
		Insert(_head, std::forward<T>(x));
	}
	void PushFront(T&& x)
	{
		Insert(_head->_next, std::forward<T>(x));
	}
	void Insert(Node* pos, T&& x)
	{
		Node* prev = pos->_prev;
		Node* newnode = new Node(std::forward<T>(x));
		// prev newnode pos
		prev->_next = newnode;
		newnode->_prev = prev;
		newnode->_next = pos;
		pos->_prev = newnode;
	}
	void Insert(Node* pos, const T& x)
	{
		Node* prev = pos->_prev;
		Node* newnode = new Node(x);
		// prev newnode pos
		prev->_next = newnode;
		newnode->_prev = prev;
		newnode->_next = pos;
		pos->_prev = newnode;
	}
private:
	Node* _head;
};
int main()
{
	List<gy::string> lt;
	lt.PushBack("1111");
	lt.PushFront("2222");
	return 0;
}

在这里插入图片描述

四,新的类功能

默认成员函数

🚀在C++11之前,默认成员函数是六个:
1,构造函数
2,拷贝构造
3,赋值运算符重载
4,析构函数
5,取地址重载
6,const取地址重载
但是在C++11之后由于存在了移动构造与移动赋值,默认成员函数由原来的6个变成8个。
🚀针对移动构造和移动赋值有一些注意点如下:

  • 如果你没有自己实现移动构造函数,且没有实现析构函数,拷贝构造,赋值运算符重载,中的任意一个。那么编译器会自动生成一个默认的移动构造函数。默认生成的移动构造对内置类型会逐成员的按字节拷贝,对于自定义类型的成员,如果这个自定义类型的成员实现了移动构造,那么就去调用其移动构造,如果没有实现就调用其拷贝构造。
  • 如果没有实现移动赋值函数,且没有实现析构函数,拷贝构造,赋值运算符重载中的任意一个,那么编译器会自动生成一个移动赋值函数。默认生成的移动赋值函数,对于内置类型会逐成员的按字节拷贝,对于自定义类型的成员,如果这个成员实现了移动赋值那么就去调用其移动赋值,如果没有实现移动赋值那么就去调用其拷贝赋值。
  • 如果你提供了移动构造和移动赋值编译器就不会默认生成拷贝构造和拷贝赋值了。
class Person
{
public:
	Person(const char* name = "", int age = 0)
		:_name(name)
		, _age(age)
	{}
	/*Person(const Person& p)
		:_name(p._name)
		,_age(p._age)
	{}
	Person& operator=(const Person& p)
	{
		if(this != &p)
		{
		_name = p._name;
		_age = p._age;
		}
		return *this;
	}
	~Person()
	{}*/
private:
	gy::string _name;
	int _age;
};
int main()
{
	Person s1;
	Person s2 = s1;
	Person s3 = std::move(s1);
	Person s4;
	s4 = std::move(s2);
	return 0;
}

在这里插入图片描述
🚀上面的Person类中包含两个成员变量,一个是内置类型,一个是自定义类型并且自定类型的成员实现了西东构造和移动赋值,并且Person类没有自己实现析构函数,拷贝构造,拷贝赋值中的任意一种,所以编译器会默认生成移动构造和移动赋值。

default与delete关键字

🚀default关键字是让编译器强制生成某个默认成员函数,对于上面Person类的例子,如果实现了构造函数,拷贝赋值,析构函数中的任意一种,那么编译器就不会默认生成移动构造和移动赋值了。
在这里插入图片描述
但是,可以使用default关键字,让编译器强制生成移动构造和移动赋值。

Person(Person&& p) = default;
Person& operator=(Person&& p) = default;

在这里插入图片描述
🚀如果想限制某些默认成员函数的生成,在C++98中,通常将该成员函数设置为private,并且只提供声明。C++11后提供了delete关键字,只需要在函数声明后加上=delete即可,该语法就会提示编译器不生成对应函数的默认版本。

🚀delete关键字广泛的应用于某些不想被拷贝的类中,例如IO流类。

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/677643.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【数据分享】1929-2022年全球站点的逐月平均海平面压力数据(Shp\Excel\12000个站点)

气象数据是在各项研究中都经常使用的数据&#xff0c;气象指标包括气温、风速、降水、能见度等指标&#xff0c;说到气象数据&#xff0c;最详细的气象数据是具体到气象监测站点的数据&#xff01; 对于具体到监测站点的气象数据&#xff0c;之前我们分享过1929-2022年全球气象…

机器学习小结之决策树

文章目录 前言一、介绍1.1 原理1.2 流程1.3 信息熵&#xff0c;信息增益和基尼不纯度 二、构建决策树2.1 特征选择2.2 决策树生成2.3 剪枝 三、经典算法3.1 ID33.2 C4.53.3 CART 四、案例4.1 Iris 数据集 鸢尾花 分类4.2 基于决策树的英雄联盟游戏胜负预测 参考 前言 决策树(D…

GDPU 模电(电工) 作业答案

&#x1f351; 配套资源 &#x1f4da; 电子技术基础&#xff1a;电路与模拟电子[第2版] 提取码: 345r 第2章 电阻电路分析 第4章 正弦稳态电路分析 第6章 半导体器件 第7章 放大电路分析 第8章 负反馈放大电路 第9章 集成运算放大器及其应用 第11章 直流稳压电源 &#x1f6…

深度学习(10)之Roboflow 使用详解:数据集标注、训练 及 下载

Roboflow 使用详解&#xff1a;数据集标注、训练 及 下载 本文在 用YOLOv8推荐的Roboflow工具来训练自己的数据集 的基础上进行了修改 介绍如何从网站下载开源数据集详细介绍如何借助 roboflow 标注数据集并自动转换为可直接训练的数据格式 获取开源数据集 跳转找到开源分页…

浅谈Spring Cloud Gateway

网关:用户和微服务的桥梁 网关的核心是一组过滤器&#xff0c;按照先后顺序执行过滤操作。 Spring Cloud Gateway是基于webFlux框架实现&#xff0c;而webFlux框架底层则使用了高性能的Reactor模式通信框架的Netty Spring Cloud Gateway是Spring Cloud生态系统中的一个API网…

leetcode62. 不同路径(动态规划-java)

不同路径 leetcode62. 不同路径题目描述暴力递归代码演示 递归加缓存代码演示 动态规划代码演示 动态规划专题 leetcode62. 不同路径 来源&#xff1a;力扣&#xff08;LeetCode&#xff09; 链接&#xff1a;https://leetcode.cn/problems/unique-paths 题目描述 一个机器人位…

leetcode863. 二叉树中所有距离为 K 的结点(java)

二叉树中所有距离为 K 的结点 leetcode863. 二叉树中所有距离为 K 的结点题目描述 DFS 深度优先遍历代码演示 二叉树专题 leetcode863. 二叉树中所有距离为 K 的结点 来源&#xff1a;力扣&#xff08;LeetCode&#xff09; 链接&#xff1a;https://leetcode.cn/problems/all-…

英之剑法——第一境(通达人与物之境)

&#x1f31f;博主&#xff1a;命运之光 ☀️专栏&#xff1a;英之剑法&#x1f5e1; ❤️‍&#x1f525;专栏&#xff1a;英之试炼&#x1f525; ☀️博主的其他文章&#xff1a;点击进入博主的主页 &#x1f433; 开篇想说的话&#xff1a;开学就大三了&#xff0c;命运之光…

永磁同步电机在线参数辨识综述

优点 实时性&#xff1a;在线参数辨识能够在电机实际运行时进行参数估计&#xff0c;可以实时地获取电机的参数信息。这使得在线参数辨识更适用于需要实时调节和优化电机控制策略的应用场景。 动态适应性&#xff1a;在线参数辨识可以根据电机的实时工作状态和环境变化来动态地…

最“赚钱”编程语言出炉,惊到我了.....

Stack Overflow 发布了 2023 年开发者调查报告&#xff0c;据称共计超过 9 万名开发者参与了此次调查。 完整报告包含了受访开发者画像&#xff0c;以及关于开发技术、AI、职业、社区等方面的内容。本文主要介绍关于开发技术和 AI 的部分。 懒人目录&#xff1a; 最流行编程语…

[皮尔逊相关系数corrwith]使用案例:电影推荐系统

协同过滤算法用于发现用户与物品之间的相关性&#xff0c;主要有两种&#xff1a;基于用户的和基于物品的。 基于用户&#xff1a; 用户1购买了物品A、B、C、D&#xff0c;并给了好评&#xff1b;而用户2也买了A、B、C&#xff0c;那么认为用户1和用户2是同类型用户&#xff…

hello算法笔记之堆

堆&#xff1a; 一种满足特定条件的完全二叉树&#xff0c;可分为两种类型&#xff1a; 「大顶堆 Max Heap」&#xff0c;任意节点的值 ≥其子节点的值&#xff1b;「小顶堆 Min Heap」&#xff0c;任意节点的值 ≤ 其子节点的值&#xff1b; 将二叉树的根节点称为「堆顶」&a…

p5模型详解

1.研究动机 推荐系统种类繁多&#xff0c;user、item特征集合可以共享&#xff0c;特定架构特定任务使得各任务间无法迁移。 语言可以描述万物&#xff0c;可以作为推荐系统的中间桥梁&#xff0c;受到prompt学习的影响&#xff0c;本文提出了text-to-text框架&#xff0c;称…

【openGauss数据库配置运行参数】--略有小成

【openGauss数据库配置运行参数】--略有小成 &#x1f53b; 一、查看参数值&#x1f530; 1.1 使用SHOW命令查看&#x1f530; 1.2 使用pg_settings视图查看 &#x1f53b; 二、openGauss的六类GUC参数&#x1f53b; 三、重设运行参数&#x1f530; 1.3 重设参数的几种方式&…

5.0、Java_IO流 - IO流类的基本体系结构

5.0、Java_IO流 - IO流类的基本体系结构 Java 为我们提供了多种多样的 IO 流&#xff0c;我们可以根据不同的功能以及性能要求挑选合适的 IO 流&#xff1b; 下图为 Java 中 IO 流类的基本体系结构&#xff08;这里只列举出一些常用的类&#xff0c;详情可以参考 JDK API 文档&…

闭门造轮(LVGL_1)

参考的课程&#xff1a; 全志韦东山的课程&#xff1a;https://www.100ask.net/p/t_pc/course_pc_detail/column/p_61c5a317e4b0cca4a4e8b6f1?product_idp_61c5a317e4b0cca4a4e8b6f1 例程1_// 基础对象(lv_obj)&#xff0c;"Hello, LVGL!" void lvgl_demo(void) …

用平衡因子实现-AVL树

目录 AVL树的概念AVL树节点的定义AVL树的插入AVL树的旋转左单旋(parent->_bf 2 && cur->_bf 1)a,b,c当高度为0a,b,c当高度为1a,b,c当高度为2a,b,c当高度为...... 右单旋(parent->_bf -2 && cur->_bf -1)a,b,c当高度为0a,b,c当高度为1a,b,c当高…

UE5《Electric Dreams》项目PCG技术解析 之 基于关卡PCGSettings的工作流

程序化内容生成框架(Procedural Content Generation Framework,下文简称PCG)可谓是UE5.2版本中最令人瞩目的新技术之一&#xff0c;老王也是在第一时间就关注了这个技术&#xff0c;以前尝试过用Houdini来实现UE5中的程序化内容生成&#xff0c;感觉还是很蹩脚的&#xff0c;毕…

无线发射功率

无线电发射功率 无线电发射机输出的射频信号&#xff0c;通过馈线&#xff08;电缆&#xff09;输送到天线&#xff0c;由天线以电磁波形式辐射出去。电磁波到达接收地点后&#xff0c;由天线接收下来&#xff08;仅仅接收很小很小一部分功率&#xff09;&#xff0c;并通过馈…

Linux系统编程(再论execve)

文章目录 前言一、execve的第三个参数二、进程空间三、命令行参数规范四、optstring规则的扩展定义总结 前言 本篇文章我们继续来研究一下execve这个系统调用&#xff0c;上篇文章已经讲解了前两个参数的意义了&#xff0c;那么这篇文章就来讲解一下第三个参数的具体含义。 一…