【C++11】右值引用

news2024/11/24 10:49:00

右值引用是C++11中才被提出来的新概念,而以前的版本中也有引用,但是是指的左值引用。归根结底,左右值引用都是给对象取别名。


 1.区分左值和右值

 提起左值和右值很多小伙伴可能第一时间会有点小蒙圈,敲了好长时间代码了,对于这个概念可能有点蒙圈,其实模糊的说可以以=号为分界线,左边的叫左值,右边的叫右值。

1.1左右值特点 :

左值:是一个表示数据的表达式,如变量名或解引用的指针等

  1. 可以放在等号的左右边
  2. 左值可以修改
  3. 左值可以取地址
int main()
{
	int a = 10;
	int b = a;
	b = 10;
	const int c = 5;
	int* p = new int(0);
	//以上的a,b,c,*p都是左值
	cout << b << endl;
	cout << *p << endl;
	return 0;

}

右值:也是一个表示数据的表达式,如字母常量、表达式的返回值、函数的返回值(不能是左值引用返回)等。

  1. 右值不可以取地址
  2. 右值不可以直接修改
  3. 右值只能放在等号右边
  4. 右值往往是没有名称的
int main()
{
    int x,y=10;
//以下是常见的三种右值
    x+y       // 表达式返回值
    func(x,y)  //函数返回值
    5   //常量
}
  • 之所以右值无法被取地址是因为右值的本身是一个常量值或者是临时变量,这些常量值和临时变量并没有被储存起来,所以就没有他们的地址。

右值又被细分为纯右值和将亡值:

  • 纯右值: 就是指等号右边的常数,上式中的5
  • 将亡值:其实就是中间变量的过渡,过渡之后就消亡,可以细分两种:
  1. 函数的临时返回值:例如 int a = func(3); func(3)的返回值是右值,副本拷贝给a,然后消失。
  2. 表达式 像(x+y),其中(x+y)是右值。

 1.2左值引用和右值引用

左值引用

左值引用就是对左值的引用,给左值取别名,通过“&”来声明。

int main()
{
	int a = 10;
	int b = a;
	b = 10;
	const int c = 5;
	int* p = new int(0);
	//以上的a,b,c,*p都是左值

	int& ra=a;
    const int& rc = c;
    int*& rp=p;
    int& cpp=*p;
}

以上是几种常见的左值引用。

右值引用: 

右值引用就是对右值的引用,给右值取别名,通过“&&”来声明。

int main()
{
	double x = 4.1, y = 4.2;
	
	//以下几个都是常见的右值
	x + y;
	func(x, y);
    5

	//以下几个都是对右值的右值引用
	int&& rr1 = 5;
	double&& rr2 = x + y;
	double rr3 = func(x, y);
	return 0;
}
  • 右值是不能取地址的,但是给右值取别名后,会导致右值被存储到特定位置,这时这个右值可以被取到地址,并且可以被修改如果不想让被引用的右值被修改,可以用const修饰右值引用。
int main()
{
	double x = 4.1, y = 4.2;
	int&& rr1 = 5;
	const double&& rr2 = x + y;

	//修改右值
	rr1 = 10;
	rr2 = 20;
	return 0;
}

 左值引用能引用右值吗?或者右值引用能引用左值吗?

绝大多数情况下是不能的,因为左值是可以修改的而右值是不能修改的,所以涉及到权限放大问题,一般来说两者都不成立。但是要想左值引用来引用右值可以用const。

所以const左值引用既可以引用左值也可应引用右值。

同理,右值引用在绝大多数情况下不能引用左值,但是能引用move()中的左值。move()是C++11新增的函数,在后面我们会介绍。

 左右值引用总结:

  • 左值引用只能引用左值不能引用右值,但是能引用const修饰的右值。
  • 右值引用只能引用右值不能引用左值,但是能引用move后的左值。 

2.右值引用的提出

2.1引用的价值 

说起引用的价值不得不提起:提高效率,减少拷贝。 

 2.2左值引用能解决哪些问题?

  • 做参数:a、减少拷贝,提高效率,b、做输出型参数(这个左值引用几乎可以解决所有的问题)  。
  • 做返回值: a、减少拷贝,提高效率,b、引用返回,可以修改返回对象。(这个左值引用能解决大部分问题,但是有一些还无法解决,所以就提出了右值引用。
  • 如果函数返回的对象是一个局部变量,该变量出了函数作用域就被销毁了,这种情况下不能用左值引用作为返回值,只能以传值的方式返回,这就是左值引用的短板。

2.3 减少拷贝构造

我们以to_string函数为例,这个函数的返回对象是一个局部变量。此时就不能再使用引用返回了,只能用传值返回一次一次的传了。但是现在的编译器都会进行优化处理,所以一般进行一次拷贝构造而实际进行的是两次拷贝构造。

namespace cl
{
	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 += (x + '0');
		}
		if (flag == false)
		{
			str += '-';
		}
		std::reverse(str.begin(), str.end());
		return str;
	}
}

int main()
{
	int x = 10;
	string ret = tmp::to_string(-3456);
}

之所以不能用左值引用做返回值,就是因为to_string函数最后会被析构,这里在用传值引用就会出大问题。 

 有的编译器进行优化,就不再产生临时变量而是直接进行一次拷贝构造。

 但是并不是所有的代码都可以优化或者是有的情况必须要进行两次拷贝构造时就要用到右值引用了。

为了解决上述的问题c++11就提出了新的内容。


2.4右值引用和移动语句 

左值引用是直接加&来使用的,但是右值引用不是直接加&&使用的,而是有它自己的规则。

移动构造

为了更好的解决这个问题,又给移动语句新定义了两元大将:移动拷贝移动赋值

  • 我们以模拟实现的string来说明。
  • #define _CRT_SECURE_NO_WARNINGS 1
    #include<iostream>
    #include<string>
    #include<assert.h>
    using namespace std;
    
    namespace cl
    {
    	class string
    	{
    	public:
    		typedef char* iterator;
    		iterator begin()
    		{
    			return _str; //返回字符串中第一个字符的地址
    		}
    		iterator end()
    		{
    			return _str + _size; //返回字符串中最后一个字符的后一个字符的地址
    		}
    		//构造函数
    		string(const char* str = "")
    		{
    			_size = strlen(str); //初始时,字符串大小设置为字符串长度
    			_capacity = _size; //初始时,字符串容量设置为字符串长度
    			_str = new char[_capacity + 1]; //为存储字符串开辟空间(多开一个用于存放'\0')
    			strcpy(_str, str); //将C字符串拷贝到已开好的空间
    		}
    		//交换两个对象的数据
    		void swap(string& s)
    		{
    			//调用库里的swap
    			::swap(_str, s._str); //交换两个对象的C字符串
    			::swap(_size, s._size); //交换两个对象的大小
    			::swap(_capacity, s._capacity); //交换两个对象的容量
    		}
    		//拷贝构造函数(现代写法)
    		string(const string& s)
    			:_str(nullptr)
    			, _size(0)
    			, _capacity(0)
    		{
    			cout << "string(const string& s) -- 深拷贝" << endl;
    
    			string tmp(s._str); //调用构造函数,构造出一个C字符串为s._str的对象
    			swap(tmp); //交换这两个对象
    		}
    		//赋值运算符重载(现代写法)
    		string& operator=(const string& s)
    		{
    			cout << "string& operator=(const string& s) -- 深拷贝" << endl;
    
    			string tmp(s); //用s拷贝构造出对象tmp
    			swap(tmp); //交换这两个对象
    			return *this; //返回左值(支持连续赋值)
    		}
    		//析构函数
    		~string()
    		{
    			delete[] _str;  //释放_str指向的空间
    			_str = nullptr; //及时置空,防止非法访问
    			_size = 0;      //大小置0
    			_capacity = 0;  //容量置0
    		}
    		//[]运算符重载
    		char& operator[](size_t i)
    		{
    			assert(i < _size); //检测下标的合法性
    			return _str[i]; //返回对应字符
    		}
    		//改变容量,大小不变
    		void reserve(size_t n)
    		{
    			if (n > _capacity) //当n大于对象当前容量时才需执行操作
    			{
    				char* tmp = new char[n + 1]; //多开一个空间用于存放'\0'
    				strncpy(tmp, _str, _size + 1); //将对象原本的C字符串拷贝过来(包括'\0')
    				delete[] _str; //释放对象原本的空间
    				_str = tmp; //将新开辟的空间交给_str
    				_capacity = n; //容量跟着改变
    			}
    		}
    		//尾插字符
    		void push_back(char ch)
    		{
    			if (_size == _capacity) //判断是否需要增容
    			{
    				reserve(_capacity == 0 ? 4 : _capacity * 2); //将容量扩大为原来的两倍
    			}
    			_str[_size] = ch; //将字符尾插到字符串
    			_str[_size + 1] = '\0'; //字符串后面放上'\0'
    			_size++; //字符串的大小加一
    		}
    		//+=运算符重载
    		string& operator+=(char ch)
    		{
    			push_back(ch); //尾插字符串
    			return *this; //返回左值(支持连续+=)
    		}
    		//返回C类型的字符串
    		const char* c_str()const
    		{
    			return _str;
    		}
    	private:
    		char* _str;
    		size_t _size;
    		size_t _capacity;
    	};
    	
    }

移动构造:是一个右值引用构造函数是const修饰的左值引用,而移动构造的本质就是将参数右值的资源进行剽窃从而占为己有而避免深拷贝,而这个参数右值(将亡值)也将在不久后消失,所以也可以算是物尽其用了。

  • 这里我们在说一下右值到类型:
  1. 内置类型右值:纯右值
  2. 自定义类型右值:将亡值-----即将被消耗掉的值
namespace cl
{
	class string
	{
	public:
		//移动构造
		string(string&& s)
			:_str(nullptr)
			, _size(0)
			, _capacity(0)
		{
			cout << "string(string&& s) -- 移动构造--资源转移" << endl;
			swap(s);
		}
	private:
		char* _str;
		size_t _size;
		size_t _capacity;
	};
}

就单单看移动构造和拷贝构造的书写格式,拷贝构造把const去掉,把&换成&&j就ok了。

 对于左值来说,会调用拷贝构造,对于遇见的右值去调用移动构造,但是此时我们用move还会发生一些场景。

s1的所以东西全部都转移个s3了包括它本身的地址,这个就叫资源转移,这就叫专业。因为用到move函数时把s1当将亡值了,你都快没了,我继承你的家产,娶你的老婆没啥毛病吧,哈哈。 

移动构造和拷贝构造的区别:

  • 在没有增加移动构造之前,由于拷贝构造采用的是const左值引用接收参数,因此无论拷贝构造对象时传入的是左值还是右值,都会调用拷贝构造函数。
  • 增加移动构造之后,由于移动构造采用的是右值引用接收参数,因此如果拷贝构造对象时传入的是右值,那么就会调用移动构造函数(最匹配原则)。
  • string的拷贝构造函数做的是深拷贝,而移动构造函数中只需要调用swap函数进行资源的转移,因此调用移动构造的代价比调用拷贝构造的代价小。

给string类增加移动构造后,对于返回局部string对象的这类函数,在返回string对象时就会调用移动构造进行资源的移动,而不会再调用拷贝构造函数进行深拷贝了。

在上面已经说过了,编译器为了提高效率,会进行一系列的优化,例如将调用的两次深拷贝减少到一次。但是有点编译器缺不会优化。在C++11提出移动构造以后,移动构造也会从2次被优化为1次。

但如果我们不是用函数的返回值来构造一个对象,而是用一个之前已经定义出来的对象来接收函数的返回值,这时编译器就无法进行优化了。

这时当函数返回局部对象时,会先用这个局部对象拷贝构造出一个临时对象,然后再调用赋值运算符重载函数将这个临时对象赋值给接收函数返回值的对象。

  • 编译器并没有对这种情况进行优化,因此在C++11标准出来之前,对于深拷贝的类来说这里就会存在两次深拷贝,因为深拷贝的类的赋值运算符重载函数也需要以深拷贝的方式实现。
  • 但在深拷贝的类中引入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;
		}

移动赋值和原有operator=函数的区别:

  • 在没有增加移动赋值之前,由于原有operator=函数采用的是const左值引用接收参数,因此无论赋值时传入的是左值还是右值,都会调用原有的operator=函数。
  • 增加移动赋值之后,由于移动赋值采用的是右值引用接收参数,因此如果赋值时传入的是右值,那么就会调用移动赋值函数(最匹配原则)。
  • string原有的operator=函数做的是深拷贝,而移动赋值函数中只需要调用swap函数进行资源的转移,因此调用移动赋值的代价比调用原有operator=的代价小。

 在实现移动赋值函数之前,该代码的运行结果理论上应该是调用一次拷贝构造,再调用一次原有的operator=函数,但由于原有operator=函数实现时复用了拷贝构造函数,因此代码运行后的输出结果会多打印一次拷贝构造函数的调用,这是原有operator=函数内部调用的。

3.完美转发 

3.1万能引用&&

之所以叫万能引用就是&&代表的不再是右值引用而是既能用左值引用也能用右值引用的“万能充”

template<typename T>
void PerfectForward(T&& t)//万能引用
{
	//……
}

右值引用和万能引用的区别就是,右值引用需要是确定的类型,而万能引用是根据传入实参的类型进行推导,如果传入的实参是一个左值,那么这里的形参t就是左值引用,如果传入的实参是一个右值,那么这里的形参t就是右值引用。

万能引用也叫做引用折叠,如果说传进去的是个左值,那&&折叠成一个&,如果说传进去的是一个右值,那还是&&。

但是如果说原本的右值下一步变成左值后再被传参到万能引用中那他被当做左值传进去还是右值传进去还是右值传进去呢?

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

PerfectForward(0函数的参数类型是一个万能引用,而我们在PerfectForward函数中调用func函数就是希望调用PerfectForward函数时传入左值、右值、const左值、const右值,能够匹配到对应版本的Func函数。

为毛全部都是左值引用啊? 

实际调用PerfectForward函数时传入左值和右值,最终都匹配到了左值引用版本的Func函数,调用PerfectForward函数时传入const左值和const右值,最终都匹配到了const左值引用版本的Func函数。
根本原因就是:右值被引用后会导致右值被存储到特定位置,这时这个右值可以被取到地址,并且可以被修改,所以在PerfectForward函数中调用Func函数时会将t识别成左值。
也就是说,右值经过一次参数传递后其属性会退化成左值,如果想要在这个过程中保持右值的属性,就需要用到完美转发。

3.2完美转发

完美转发的保持属性 

要想在参数传递过程中保持其原有的属性,需要在传参时调用forward函数。

就比如上面的要想让右值不退化成左值,就可以用到forward函数。

template<class T>
void PerfectForward(T&& t)
{
	Func(std::forward<T>(t));
}

经过完美转发后,调用PerfectForward函数时传入的是右值就会匹配到右值引用版本的Func函数,传入的是const右值就会匹配到const右值引用版本的Func函数,这就是完美转发的价值。

 

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

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

相关文章

【Java基础】—— Java简介(超详细整理,适合新手入门)

​ “作者 久绊A” 专注记录自己所整理的Java、web、sql等&#xff0c;IT技术干货、学习经验、面试资料、刷题记录&#xff0c;以及遇到的问题和解决方案&#xff0c;记录自己成长的点滴。 写给小白看的入门级 Java 基本语法&#xff0c;需要掌握哪些知识点? Java涵盖的知识点…

1个 30多年程序员的生涯经验总结

有人说&#xff1a;一个人从1岁活到80岁很平凡&#xff0c;但如果从80岁倒着活&#xff0c;那么一半以上的人都可能不凡。 生活没有捷径&#xff0c;我们踩过的坑都成为了生活的经验&#xff0c;这些经验越早知道&#xff0c;你要走的弯路就会越少。 在我30多年的程序员生涯里…

部分iphone、安卓手机打开微信小程序不请求、白页问题

前言&#xff1a; 最近项目上发现用户测试小程序体验版打开一直白页&#xff0c;请求没反应&#xff0c;页面不渲染。开始以为是微信小程序某api问题&#xff0c;或者用户微信版本过低&#xff0c;或者用户网络不好&#xff0c;甚至考虑是不是服务器问题&#xff01;因为后端是…

vue3中如何使用JSX?

在绝大多数情况下&#xff0c;Vue 推荐使用模板<template>语法来创建应用。 在 Vue 3 的项目开发中&#xff0c;template 是 Vue 3 默认的写法。虽然 template 长得很像 HTML&#xff0c;但 Vue 其实会把 template 解析为 render 函数&#xff0c;之后&#xff0c;组件运…

【Java多线程】线程的优先级

线程的优先级等级 MAX_PRIORITY&#xff1a;10 MIN _PRIORITY&#xff1a;1 NORM_PRIORITY&#xff1a;5 涉及的方法 getPriority() &#xff1a;返回线程优先值 setPriority(int newPriority) &#xff1a;改变线程的优先级 例&#xff1a; 我们将分线程的优先级设置为…

Java设计模式-迭代器模式Iterator

介绍 根据GoF的定义&#xff0c;迭代器模式提供了一种顺序访问聚合对象的元素而不暴露其底层表示的方法。这是一种行为设计模式。 顾名思义&#xff0c;迭代器有助于以定义的方式遍历对象集合&#xff0c;这对客户端应用程序很有用。在迭代期间&#xff0c;客户端程序可以根据需…

SciPy 教程与安装

SciPy 教程SciPy 是一个开源的 Python 算法库和数学工具包。Scipy 是基于 Numpy 的科学计算库&#xff0c;用于数学、科学、工程学等领域&#xff0c;很多有一些高阶抽象和物理模型需要使用 Scipy。SciPy 包含的模块有最优化、线性代数、积分、插值、特殊函数、快速傅里叶变换、…

小场景解决大问题|明道云在京东方的落地实践

我是来自京东方集团京东方晶芯科技有限公司的季旭。很荣幸给各位分享我们京东方集团和明道云之间的合作情况。 关于京东方晶芯 在分享之前&#xff0c;我首先给各位介绍一下我们公司。京东方集团是1993年4月成立的&#xff0c;以半导体显示为核心技术&#xff0c;在物联网创新…

聚观早报 | 硅谷大数据龙头Palantir扩招;滴滴出行恢复新用户注册

今日要闻&#xff1a;硅谷大数据龙头Palantir扩招&#xff1b;美团无人机去年完成配送超10万单&#xff1b;滴滴出行恢复新用户注册&#xff1b;PS VR2将于2月22日全球同步上市&#xff1b;改款特斯拉 Model 3加州路测谍照曝光硅谷大数据龙头Palantir扩招 1 月 17 日消息&#…

【Javascript】面向对象编程,this,原型与原型链,类与实例

❤️ Author&#xff1a; 老九 ☕️ 个人博客&#xff1a;老九的CSDN博客 &#x1f64f; 个人名言&#xff1a;不可控之事 乐观面对 &#x1f60d; 系列专栏&#xff1a; 文章目录对象中的方法/thisthis使用bind函数原型原型链类与实例class对象中的方法/this 面向对象&#x…

【C语言】小王带您实现文件操作(简单图示讲解)

说到文件操作&#xff0c;大家会第一印象想到不就是电脑硬盘中创建文件&#xff0c;写入数据吗&#xff0c;键盘、鼠标就可以搞定&#xff0c;那么接下来我要告诉你的是C语言也可以实现文件操作哦&#xff01;&#xff01;&#xff01; 目录 前言 一、为什么要使用文件操作 …

模拟卷.C

1.分支_sine之舞 样例输入 3 样例输出 ((sin(1)+3)sin(1-sin(2))+2)sin(1-sin(2+sin(3)))+1 2.数组_和最大子序列 样例输入 5 3 -2 3 -5 4 样例输出 4 3.二维数组_星辰大海 样例输入 2 2 S. #T 2 RD DR 3 S.# .#. .T# 3 RL DDD DDRR 样例输出 I get …

文档管理系统采用电子签名的优势

DocuWare文档管理系统始终提供最高级的安全性&#xff0c;保护我们客户的机密文件和数据。 现在&#xff0c;我们与信任服务提供商ValidatedID 集成&#xff0c;电子签名又向前迈出了重要的一步。每当组织需要证明签名是真实的并确保文档未被更改时&#xff0c;都可以使用这些…

开源mybatis神器

什么是通用 Mapper&#xff1f; 它是一个可以方便的使用 Mybatis 进行单表的增删改查优秀开源产品。它使用拦截器来实现具体的执行 Sql&#xff0c;完全使用原生的 Mybatis 进行操作。在 Github 上标星 5.9K&#xff01; 为什么要用 Mapper&#xff1f; 它提供了所有单表的基…

《SQL基础》07. 约束

SQL-约束约束常见约束案例外键约束删除/更新行为约束 概念&#xff1a;约束是作用于表中字段上的规则&#xff0c;用于限制存储在表中的数据。 目的&#xff1a;保证数据库中数据的正确性、有效性和完整性。 分类&#xff1a; 约束描述关键字非空约束限制该字段的数据不能为n…

动态内存管理(2)

TIPS 1. scanf读取与空格&#xff1a; 我们都知道&#xff0c;scanf()在从输入缓冲区里面读取数据的时候&#xff0c;如果中间碰到了空格&#xff0c;那么就会直接停下来&#xff0c;而如果在最前面有个空格&#xff0c;直接无视空格。 2. scanf()读取与\n&#xff0c;如果是…

【论文精选】TPAMI2020 - PFENet_先验引导的特征富集网络_小样本语义分割

【论文精选】TPAMI2020 - PFENet_先验引导的特征富集网络_小样本语义分割 精选精析&#xff1a; 【论文原文】&#xff1a; Prior Guided Feature Enrichment Network for Few-Shot Segmentation (当前引用次数&#xff1a;184) 【论文代码】&#xff1a; https://github.co…

【爪洼岛冒险记】第5站:多图解,超详细讲解Java中的数组、二维数组--建议收藏

&#x1f331;博主简介&#xff1a;是瑶瑶子啦&#xff0c;一名大一计科生&#xff0c;目前在努力学习JavaSE。热爱写博客~正在努力成为一个厉害的开发程序媛&#xff01; &#x1f4dc;所属专栏&#xff1a;爪洼岛冒险记【从小白到大佬之路】 ✈往期博文回顾: 【爪洼岛冒险记】…

【GD32F427开发板试用】RT-THREAD标准版 移植使用

本篇文章来自极术社区与兆易创新组织的GD32F427开发板评测活动&#xff0c;更多开发板试用活动请关注极术社区网站。作者&#xff1a;打盹的消防车 前言&#xff1a; 无意在微信看到了GD做活动&#xff0c;想到了第一时间体验一下&#xff0c;搭配RT-THREAD&#xff0c;也很方…

【Java】【系列篇】【Spring源码解析】【三】【体系】【BeanDefinition体系】

整体结构图 1. BeanDefinition 用于保存 Bean 的相关信息&#xff0c;包括属性、构造方法参数、依赖的 Bean 名称及是否单例、延迟加载等&#xff0c; 它是实例化 Bean 的原材料&#xff0c;Spring 就是根据 BeanDefinition 中的信息实例化 Bean。 2. 我们获取对象的方式一般有…