【C++11】:右值引用移动语义完美转发

news2024/11/15 8:21:05

目录

  • 前言
  • 一,左值引用和右值引用
  • 二,左值引用与右值引用比较
  • 三,探索引用的底层
  • 四,右值引用使用场景和意义
    • 4.1 解决返回值问题
    • 4.2 STL容器插入接口的改变
  • 五,移动语义
  • 六,完美转发
    • 6.1 模板中的&& 万能引用
    • 6.2 forward 完美转发在传参的过程中保留对象原生类型属性

前言

传统的C++语法中就有引用的语法,而C++11中新增了的右值引用语法特性,所以从现在开始我们之前学习的引用就叫做左值引用。无论左值引用还是右值引用,都是给对象取别名

一,左值引用和右值引用

1.什么是左值?什么是左值引用?

左值是一个表示数据的表达式(如变量名或解引用的指针),我们可以获取它的地址 + 一般情况可以对它赋值,左值可以出现赋值符号的左边和右边。定义时const修饰符后的左值,不能给他赋值,但是可以取它的地址左值引用就是给左值的引用,给左值取别名

int main()
{
	// 以下的p、b、c、*p都是左值
	//左值:可以取地址
	int* p = new int(0);
	int b = 1;
	const int c = 2;
	*p = 10;
	string s("11111111");
	s[0];

	cout << &c << endl;
    cout << &s[0] << endl;
	cout << &s << endl;
	
	return 0;
}

2.什么是右值?什么是右值引用?

右值也是一个表示数据的表达式,如:字面常量、表达式返回值,函数返回值(这个不能是左值引用返回)等等右值可以出现在赋值符号的右边,但是不能出现出现在赋值符号的左边右值不能取地址右值引用就是对右值的引用,给右值取别名

右值又可以分为纯右值和将亡值
纯右值内置类型右值
将亡值类类型的右值,如匿名对象,类型转换过程中产生的临时对象。

int main()
{
	//右值:不能取地址
	double x = 1.1, y = 2.2;
	// 以下几个都是常见的右值:常量,临时对象,匿名对象
	10;
	x + y;
	fmin(x, y);
	string("2222222");

	//err
	//cout << &10 << endl;
	//cout << &(x + y) << endl;
	//cout << &(fmin(x + y));

	return 0;
}

二,左值引用与右值引用比较

左值引用总结
1.左值引用只能引用左值,不能引用右值
2.但是const左值引用既可引用左值,也可引用右值

代码示例1

int main()
{
	// 以下的p、b、c、*p都是左值
	int* p = new int(0);
	int b = 1;
	const int c = 2;
	*p = 10;
	string s("11111111");
	s[0];

	//左值引用给左值取别名
	int& r1 = b;
	int*& r2 = p;
	int& r3 = *p;
	string& r4 = s;

	//左值引用引用给右值取别名
	const int& rx1 = 10;
	const double& rx2 = x + y;
	const double& rx3 = fmin(x, y);
	const string&& rx4 = string("2222222");
	
	return 0;
}

代码示例2
比如在经常使用的容器中其实也有左值右值的身影

	//void push_back(const T& x) //这里加const -> 既可以传左值,也可以传右值
	string s1("3333333");
	vector<string> v;
	v.push_back(s1); //有名对象->传左值
	v.push_back(string("3333333")); //匿名对象->传右值
	v.push_back("3333333"); //单参数构造函数支持隐式类型转换,中间会产生临时变量

右值引用总结
1.右值引用只能右值,不能引用左值
2.但是右值引用可以move以后的左值

代码示例

int main()
{
	double x = 1.1, y = 2.2;
	// 以下几个都是常见的右值:常量,临时对象,匿名对象
	10;
	x + y;
	fmin(x, y);
	string("2222222");

	//右值引用给右值取别名
	int&& rr1 = 10;
	double&& rr2 = x + y;
	double&& rr3 = fmin(x, y);
	
	//右值引用引用给左值取别名
	int&& rrx1 = move(b);
	int*&& rrx2 = move(p);
	int&& rrx3 = move(*p);
	string&& rrx4 = move(s);
	
	return 0;
}

三,探索引用的底层

在语法层面有左值引用和右值引用的概念,那在底层它们的本质是什么呢?

int main()
{
	//当到汇编层时,就没有左值引用右值引用的概念了,只有指针
	int x = 0;
	int& r1 = x;

	int&& rr1 = x + 10;

	//move的本质:就是强制类型转换,只是为了通过语法层的检验而已
	//string&& rrx5 = (string&&)s;

	return 0;
}

转到反汇编查看
在这里插入图片描述

结论:到了汇编层时,就没有引用的概念了,它们的本质都是指针。而move的本质,其实就是强制类型转换而已,只是为了通过语法层的检查

四,右值引用使用场景和意义

4.1 解决返回值问题

引用的意义:减少拷贝,提高效率。
左值引用解决的场景:引用传参/引用传返回值
左值引用没有彻底解决的场景:传返回值

下面我们来看看左值引用的短板,右值引用是如何补齐这个短板的!接下来我们要用到以前自己模拟实现的 string 类来验证

下面的验证都要用 VS2019 才能观察到!!

namespace bit
{
	class string
	{
	public:
		typedef char* iterator;
		iterator begin()
		{
			return _str;
		}

		iterator end()
		{
			return _str + _size;
		}

		typedef const char* const_iterator;
		const_iterator begin()const
		{
			return _str;
		}

		const_iterator end()const
		{
			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)
		{
			::swap(_str, s._str);
			::swap(_size, s._size);
			::swap(_capacity, s._capacity);
		}

		// 拷贝构造
		string(const string& s)
			:_str(nullptr)
		{
			cout << "string(const string& s) -- 深拷贝" << endl;
			
			reserve(s._capacity);
			for (auto ch : s)
				push_back(ch);
		}

		// 赋值重载
		string& operator=(const string& s)
		{
			cout << "string& operator=(string s) -- 深拷贝" << endl;
			if (this != &s)
			{
				_str[0] = '\0';
				_size = 0;

				reserve(s._capacity);
				for (auto ch : s)
					push_back(ch);
			}
			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];
				if(_str)
				{
					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)
		{
			push_back(ch);
			return *this;
		}

		const char* c_str() const
		{
			return _str;
		}
	private:
		char* _str = nullptr;
		size_t _size = 0;
		size_t _capacity = 0; // 不包含最后做标识的\0
	};

	string to_string(int value)
	{
		bool flag = true;
		if (value < 0)
		{
			flag = false;
			value = 0 - value;
		}

		bit::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;
	}
}

左值引用的短板

但是当函数返回对象是一个局部变量,出了函数作用域就不存在了,就不能使用左值引用返回,只能传值返回例如:bit::string to_string(int value)函数中可以看到,这里只能使用传值返回,传值返回会导致至少1次拷贝构造(如果是一些旧一点的编译器可能是两次拷贝构造)。

图解如下

在这里插入图片描述

右值引用解决上述问题

在bit::string中增加移动构造,移动构造本质是将参数右值的资源窃取过来,占位已有,那么就不用做深拷贝了,所以它叫做移动构造,就是窃取别人的资源来构造自己

//移动构造
//临时创建的对象,用完就要消亡了
//深拷贝的类,移动构造才有意义
string(string&& s)
{
	cout << "string(string&& s) -- 移动拷贝" << endl;

	swap(s); // 直接转移资源
}

int main()
{
 	bit::string ret2 = bit::to_string(1234);
 	return 0;
}

再运行上面bit::to_string的两个调用,我们会发现,这里没有调用深拷贝的拷贝构造,而是调用了移动构造,移动构造中没有新开空间,拷贝数据,所以效率提高了

图解如下

在这里插入图片描述
不仅仅有移动构造,还有移动赋值

在bit::string类中增加移动赋值函数,再去调用bit::to_string(1234),不过这次是将bit::to_string(1234)返回的右值对象赋值给ret1对象,这时调用的是移动赋值。

//移动赋值
string& operator=(string&& s)
{
	cout << "string& operator=(string&& s) -- 移动赋值" << endl;
	swap(s);
	return *this;
}

int main()
{
	 bit::string ret1;
	 ret1 = bit::to_string(1234);
	 return 0;
}

图解如下

在这里插入图片描述

总结

从此以后,类似下面这种传值返回的场景随便用,因为浅拷贝的类没什么代价,深拷贝的类会走移动拷贝和移动赋值

T func()
{
	T ret;
	//……
	return ret;
}

4.2 STL容器插入接口的改变

int main()
{
	 list<bit::string> lt;
	 bit::string s1("111111111111111111");
	 // 这里调用的是拷贝构造
	 lt.push_back(s1);
	 
	 // 下面调用都是移动构造
	 lt.push_back("22222222222222222222222");
	 lt.push_back(std::move(s1));
	 
	 return 0;
}

在这里插入图片描述

五,移动语义

按照语法,右值引用只能引用右值,但右值引用一定不能引用左值吗?因为:有些场景下,可能真的需要用右值去引用左值实现移动语义。当需要用右值引用引用一个左值时,可以通过move函数将左值转化为右值。C++11中,std::move()函数位于 头文件中,该函数名字具有迷惑性,它并不搬移任何东西,唯一的功能就是将一个左值强制转化为右值引用,然后实现移动语义

右值在传递过程中的退化。要验证这句话,接下来我们还是要用到以前自己模拟实现的 list 类,和 string类 来验证

下面的是在 VS2022 下的运行结果

namespace bit
{
	template <class T>
	struct ListNode
	{
		ListNode<T>* _next;
		ListNode<T>* _prev;

		T _data;

		ListNode(const T& data = T())
			:_next(nullptr)
			,_prev(nullptr)
			,_data(data)
		{}

		ListNode(T&& data)
			:_next(nullptr)
			, _prev(nullptr)
			, _data(data)
		{}
	};

	template <class T,class Ref,class Ptr>
	struct ListIterator
	{
		typedef ListNode<T> Node;
		typedef ListIterator<T, Ref, Ptr> Self;//名字变得简短

		Node* _node;//定义一个节点指针

		ListIterator(Node* node)
			:_node(node)
		{}

		Self& operator++()
		{
			_node = _node->_next;
			return *this;
		}

		Self& operator--()
		{
			_node = _node->_prev;
			return *this;
		}

		//后置:返回之前的值
		Self operator++(int)
		{
			Self tmp(*this);
			_node = _node->_next;
			return tmp;
		}

		Self operator--(int)
		{
			Self tmp(*this);
			_node = _node->_prev;
			return tmp;
		}
	
		Ref operator*()
		{
			return _node->_data;
		}
	
		Ptr operator->()
		{
			return &_node->_data;
		}

		bool operator!=(const Self& it)
		{
			return _node != it._node;
		}

		bool operator==(const Self& it)
		{
			return _node == it._node;
		}
	};

	template <class T>
	class list
	{
		typedef ListNode<T> Node;
	public:
		typedef ListIterator<T, T&, T*> iterator;
		typedef ListIterator<T, const T&, const T*> const_iterator;

		iterator begin()
		{
			//iterator it(_head->_next);
			//return it;
			return iterator(_head->_next);//使用匿名对象
		}

		iterator end()
		{
			return iterator(_head);
		}

		const_iterator begin()const
		{
			return const_iterator(_head->_next);
		}

		const_iterator end()const
		{
			return const_iterator(_head);
		}

		//空初始化,申请哨兵位头节点
		void empty_init()
		{
			_head = new Node();
			_head->_next = _head;
			_head->_prev = _head;
		}

		list()
		{
			empty_init();
		}

		list(const list<T>& lt)
		{
			empty_init();

			//注意:使用范围for时加上const和&
			for (const auto& e : lt)
			{
				push_back(e);
			}
		}

		//赋值拷贝
		//lt1 = lt3
		list<T>& operator=( list<T> lt)
		{
			swap(_head, lt._head);

			return *this;
		}

		//析构:销毁整个链表
		~list()
		{
			clear();
			delete _head;
			_head = nullptr;
		}

		//清空当前数据 留头节点,其余节点释放
		void clear()
		{
			auto it = begin();
			while (it != end())
			{
				//返回删除节点的下一个节点的迭代器
				it = erase(it);
			}
		}

		//尾插:end的下一个位置
		void push_back(const T& x)
		{
			insert(end(), x);
		}

		//右值引用版本
		void push_back(T&& x)
		{
			insert(end(), x);
		}

		iterator insert(iterator pos, const T& x)
		{
			Node* cur = pos._node;//找到当前节点
			Node* newnode = new Node(x);//申请节点
			Node* prev = cur->_prev;//找到前一个节点

			//prev newnode cur 进行链接
			newnode->_next = cur;
			cur->_prev = newnode;
			prev->_next = newnode;
			newnode->_prev = prev;

			return iterator(newnode);
		}

		//右值引用版本
		iterator insert(iterator pos, T&& x)
		{
			Node* cur = pos._node;//找到当前节点
			Node* newnode = new Node(x);//申请节点
			Node* prev = cur->_prev;//找到前一个节点

			//prev newnode cur 进行链接
			newnode->_next = cur;
			cur->_prev = newnode;
			prev->_next = newnode;
			newnode->_prev = prev;

			return iterator(newnode);
		}

		iterator erase(iterator pos)
		{
			assert(pos != end());//防止删除头节点

			Node* cur = pos._node;//找到当前节点
			Node* prev = cur->_prev;//找到前一个节点
			Node* next = cur->_next;//找到后一个节点

			prev->_next = next;
			next->_prev = prev;

			delete cur;

			return iterator(next);
		}
	private:
		Node* _head;
	};
}

假设插入以下值,观察运行结果

int main()
{
	bit::list<bit::string> lt;
	bit::string s1("111111111111");
	lt.push_back(s1);
	
	lt.push_back(bit::string("222222222"));
	
	lt.push_back("222222222");

	lt.push_back(move(s1));

	return 0;
}

运行结果

在这里插入图片描述

疑问:在 list类中,我们已经实现了右值引用版本的插入函数和构造函数,在string类中也有移动构造,为什么传递右值,结果还是调用的深拷贝呢

解答:因为右值引用本身的属性是左值, 只有是左值,才能转移它的资源(才能在string类中swap转移资源)

在这里插入图片描述

也就是说,我们在main函数中push_back右值,在参数传递的过程中,push_back函数虽然匹配的是右值引用的那个,但是右值引用的那个参数本身是左值属性的,所以它进行下一步传递时又会去匹配那个左值引用的函数了,这就是所说的右值在传递过程中的退化

使用move再进行一层类型转换,把过程中的左值再转换成右值,就可以解决问题

在这里插入图片描述

运行结果

在这里插入图片描述

六,完美转发

6.1 模板中的&& 万能引用

(1) 下面代码的模板中的&&不代表右值引用,而是万能引用,其既能接收左值又能接收右值

(2) 模板的万能引用只是提供了能够接收同时接收左值引用和右值引用的能力,但是引用类型的唯一作用就是限制了接收的类型,后续使用中都退化成了左值,我们希望能够在传递过程中保持它的左值或者右值的属性, 就需要用我们下面学习的完美转发

template<typename T>
void PerfectForward(T&& t)
{
 	Fun(t);
}

6.2 forward 完美转发在传参的过程中保留对象原生类型属性

在下面的示例中,准备了各种形式的左值和右值,但是我们要在传参的过程中保留对象的原来的属性,就要加上forward

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(forward<T>(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;
}

运行结果
在这里插入图片描述

到这里,我们可以进行一下简单的总结

1.在传递右值的过程中,如果我们要保持该对象原来的属性,可以使用 move 直接强制,也可以使用 forward 进行完美转发

2.move和forward的区别
move用于我们确定知道是一个右值引用
forward用于在模板中,不知道是左值还是右值引用时

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

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

相关文章

【动态路由协议】RIP

一、前导知识 动态路由协议分类&#xff1a; 1.IGP(内部网关协议&#xff0c;位于同一个自治系统内) 1.1距离矢量路由协议 RIP&#xff08;距离矢量路由&#xff09; 1.2链路状态路由协议 OSPF&#xff08;开放式最短路径优先&#xff09; IS-IS&#xff08;中间系统到中间系统…

Xinstall新玩法:Web直接打开App,用户体验再升级!

在移动互联网时代&#xff0c;App已成为我们日常生活中不可或缺的一部分。然而&#xff0c;在App推广和运营过程中&#xff0c;许多开发者面临着从Web端引流到App的难题。这时&#xff0c;Xinstall作为国内专业的App全渠道统计服务商&#xff0c;提供了一种创新的解决方案——通…

【前端 21】Vue Router路由配置

Vue Router路由配置 在Vue.js项目中&#xff0c;Vue Router是官方提供的路由管理器&#xff0c;用于构建单页面应用&#xff08;SPA&#xff09;。它允许我们通过不同的URL访问不同的页面组件&#xff0c;而无需重新加载页面。下面&#xff0c;我将详细介绍如何在Vue项目中配置…

【研发日记】Matlab/Simulink技能解锁(十二)——Stateflow中的两种状态机嵌套对比

文章目录 前言 项目背景 两级状态机 函数状态机 分析和应用 总结 参考资料 前言 见《【研发日记】Matlab/Simulink技能解锁(七)——两种复数移相算法》 见《【研发日记】Matlab/Simulink技能解锁(八)——分布式仿真》 见《【研发日记】Matlab/Simulink技能解锁(九)——基…

将 HuggingFace 模型转换为 GGUF 及使用 ollama 运行 —— 以 Qwen2-0.5B 为例

前言 最近&#xff0c;阿里发布了Qwen2的系列模型&#xff0c;包括0.5B, 1.5B, 7B, 57B-A14B 和 72B&#xff0c;中英文效果都很好。 因为模型太新&#xff0c;目前还没有 GGUF 版本可以下载&#xff0c;于是转下GGUF&#xff0c;并分享转换教程。 什么是 GGUF&#xff1f; …

[VS Code扩展]写一个代码片段管理插件(一):介绍与界面搭建

文章目录 VS Code扩展机制项目搭建创建UI元素活动栏按钮主边栏视图主边栏工具栏按钮侧边栏右键菜单编辑器右键菜单 项目地址 [VS Code扩展]写一个代码片段管理插件&#xff08;一&#xff09;&#xff1a;介绍与界面搭建[VS Code扩展]写一个代码片段管理插件&#xff08;二&…

Io.net系统概述-核心功能;Io,net自动匹配资源与任务;两种令牌:IO和IOSD;

目录 Io.net 一、系统概述 二、核心功能 三、经济系统 四、产品与服务 五、团队与融资 六、市场前景与竞争优势 Io,net自动匹配资源与任务 一、动态资源分配机制 二、高级算法自动匹配资源与任务 三、用户界面与反馈机制 两种令牌:IO和IOSD。简单举例说明 $IO令牌…

2024四大硬盘数据恢复工具推荐!

不知道你有没有遇到过这种情况&#xff0c;电脑里的重要文件突然就不见了&#xff0c;可能是不小心删了&#xff0c;或者是硬盘出了点小问题。这时候&#xff0c;下面这几个好用的硬盘数据恢复工具就能帮你解决问题&#xff01; 一、福昕数据恢复 链接&#xff1a;www.pdf365…

【从英文中重建数字】python刷题记录

R2-字符串 目录 解简单方程法 线性代数法 ps: 就是从里面找出one,two,zero,---nine 想到哈希表,key代表单词&#xff0c;value代表0---9 用t表示单词&#xff0c;那不就是t在s中的查找问题了吗 但这样显然有些麻烦&#xff0c;在于t是不确定的,t需要遍历一遍keys()&…

万物分割(Segment Anything Model)C++模型推理部署

概述 SAM 是一种先进的人工智能模型&#xff0c;已经证明了在分割复杂和多样化图像方面具有优异的表现。该模型是计算机视觉和图像分割领域的一个重大突破。 SAM 的架构旨在处理各种图像分割任务&#xff0c;包括对象检测、实例分割和全景分割。这意味着该模型可以应用于各种用…

2024年Google Play上架指南:开发者账号与上包环境防关联

移动应用市场瞬息万变&#xff0c;成功上架Google Play商店无疑是每一位开发者的重要目标。然而&#xff0c;要确保应用程序顺利通过审核并获得持久的上架资格&#xff0c;开发者需要格外重视账号注册和上包环境管理这两个关键环节。 近年来&#xff0c;Google不断加强对开发者…

vtk2three之用three绘制vtk的Calculator公式

Calculator公式 vtk里面可以用这个过滤器filter&#xff0c;来绘制一个公式的点阵&#xff0c;想着其实可以把这个作为第一个切入点来把vtk里面的数据源引入到threejs里面&#xff0c;把threejs当作一个render&#xff0c;dataSource就是来自于这个vtk&#xff0c;下面先上一个…

字符串的模拟算法(思路+例题)

&#x1f44f;大家好&#xff01;我是和风coding&#xff0c;希望我的文章能给你带来帮助&#xff01; &#x1f525;如果感觉博主的文章还不错的话&#xff0c;请&#x1f44d;三连支持&#x1f44d;一下博主哦 &#x1f4dd;点击 我的主页 还可以看到和风的其他内容噢&#x…

华为的流程体系

缘由 2010年&#xff0c;华为销售额为1850亿元&#xff0c;其中国际市场占65%&#xff0c;净利润238亿元。当时&#xff0c;公司员工达11万人&#xff0c;公司处理合同达5万多个&#xff0c;290万个订单&#xff0c;大量的工作是手工处理&#xff0c;没有统一的流程支持&#…

《技术人求职之道》之面试准备篇:不打无准备之仗,优秀技术人的面试前准备

摘要 本文为求职者提供面试前的全面准备策略,旨在提升面试成功几率并减轻面试前的焦虑和不自信。文章首先强调准备求职资料的重要性,包括简历、寸照、学历证明等,并建议提前准备以避免入职时的尴尬。接着,讨论对应聘公司进行调研的必要性,包括了解公司业务和技术需求,以…

MySQL基础练习题19-查找拥有有效邮箱的用户

题目&#xff1a;查找具有有效电子邮件的用户 准备数据 分析数据 总结 题目&#xff1a;查找具有有效电子邮件的用户 一个有效的电子邮件具有前缀名称和域&#xff0c;其中&#xff1a; 前缀 名称是一个字符串&#xff0c;可以包含字母&#xff08;大写或小写&#xff09;&…

修改mac的音量能像windows系统那样给出音量反馈吗?

一、背景 windows有一些非常好的设计&#xff0c;比如拖动音量条的时候会有对应的音量大小的反馈。有时还能用来确定是视频没声音还是电脑坏了 在mac里怎么设置&#xff1f; 二、方法 首先点击菜单栏音量按钮->声音偏好设置…->勾选 “当更改音量时播放反馈”。 mac…

论文阅读:Mammoth: Building math generalist models through hybrid instruction tuning

Mammoth: Building math generalist models through hybrid instruction tuning https://arxiv.org/pdf/2309.05653 MAmmoTH&#xff1a;通过混合指令调优构建数学通才模型 摘要 我们介绍了MAmmoTH&#xff0c;一系列特别为通用数学问题解决而设计的开源大型语言模型&#…

书生大模型训练营 - 练习一

最近想了解一下大模型&#xff0c;查看了《2024大模型典型示范应用》文档&#xff0c;发现有公司使用的是书生大模型&#xff0c;正好发现他们有训练营&#xff0c;此文章记录的大模型作业。 一、各种链接 书生大模型官网&#xff1a;https://internlm.intern-ai.org.cn/ 进训…

Netty 必知必会(五)—— 核心组件

简单说下 Netty 中的重要组件&#xff1f;NIO中Channel的作用&#xff1f; 一、NIO 中三大核心组件 Buffer(缓冲区)。在NIO厍中&#xff0c;所有数据都是用缓冲区处理的。在读取数据时&#xff0c;它是直接读到缓冲区中的; 在写入数据时&#xff0c;写入到缓冲区中。任何时候访…