【C++11】——右值引用、移动语义

2023-10-31

目录

1. 基本概念

1.1 左值与左值引用

1.2 右值和右值引用

1.3 左值引用与右值引用

2. 右值引用实用场景和意义

2.1 左值引用的使用场景

2.2 左值引用的短板

2.3 右值引用和移动语义

2.3.1  移动构造

2.3.2 移动赋值

2.3.3 编译器做的优化

2.3.4 总结

2.4 右值引用引用左值

2.5 右值引用的其他场景(插入接口)

3. 完美转发

3.1 万能引用&&

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

3.3 完美转发的使用场景

1. 基本概念

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

1.1 左值与左值引用

左值:

左值是一个表示数据的表达式(如变量名或解引用的指针),有如下特性:

  1. 我们可以获取它的地址+可以对它赋值不一定能赋值,但一定能取地址);
  2. 左值可以出现赋值符号的左边,右值不能出现在赋值符号左边;
  3. 定义时const修饰符后的左值,不能给他赋值,但是可以取它的地址

左值引用:

  • 左值引用就是给左值的引用,给左值取别名
int main()
{
	// 以下的p、b、c、*p都是左值
	int* p = new int(0);
	int b = 1;
	const int c = 2;
	// 以下几个是对上面左值的左值引用
	int*& rp = p;
	int& rb = b;
	const int& rc = c;
	int& pvalue = *p;
	return 0;
}

1.2 右值和右值引用

右值:

右值也是一个表示数据的表达式,如临时变量字面常量、表达式返回值,函数返回值(这个不能是左值引用返回,要是传值返回)等等,有如下特性:

  1. 右值可以出现在赋值符号的右边,但是不能出现出现在赋值符号的左边,
  2. 右值不能取地址
  3. 综上左值和右值最大区别在于左值可以取地址,右值不可以取地址(因为右值是临时变量,没有实际被存储起来)。

补充:

C++里又把右值分为两类(纯右值和将亡值):

  1. 纯右值内置类型的对象):10、a + b……
  2. 将亡值自定义类型的对象):

          传值返回生成的拷贝:to_string(1234)、匿名对象:string("11111")、s1 + "hello"

右值引用:

  • 右值引用就是对右值的引用,给右值取别名。
int main()
{
	double x = 1.1, y = 2.2;
	// 以下几个都是常见的右值
	10;//字面常量
	x + y;//表达式返回值
	fmin(x, y);//函数返回值(传值返回)
 
	// 以下几个都是对右值的右值引用
	int&& rr1 = 10;
	double&& rr2 = x + y;
	double&& rr3 = fmin(x, y);
 
	/*
	这里编译会报错:error C2106: “=”: 左操作数必须为左值
	10 = 1; 
	x + y = 1; 
	fmin(x, y) = 1;
	*/
 
	/*
	这里编译会报错,右值不能取地址
	cout << &10 << endl;
	cout << &(x + y) << endl;
	cout << &fmin(x, y) << endl;
	*/
	return 0;
}
  • 右值是不能取地址的,但是给右值取别名后,会导致右值被存储到特定位置,且可以取到该位置的地址,也就是说例如:不能取字面量10的地址,但是rr1引用后,可以对rr1取地址,也可以修改rr1。如果不想rr1被修改,可以用const int&& rr1 去引用,这个了解一下实际中右值引用的使用场景并不在于此,这个特性也不重要。
int main()
{
	double x = 1.1, y = 2.2;
	int&& rr1 = 10;
	const double&& rr2 = x + y;
	rr1 = 20;
	rr2 = 5.5; // 报错
	return 0;
}

1.3 左值引用与右值引用

左值引用总结:

  1. 左值引用只能引用左值,不能引用右值;
  2. 但是const左值引用既可以应用左值,也可以引用右值;
int main()
{
	// 左值引用只能引用左值,不能引用右值。
	int a = 10;
	int& ra1 = a; // ra为a的别名
	//int& ra2 = 10; // 编译失败,因为10是右值
	// const左值引用既可引用左值,也可引用右值。
	const int& ra3 = 10;
	const int& ra4 = a;
	return 0;
}

右值引用总结:

  1. 右值引用只能引用右值,不能引用左值;
  2. 但是右值引用可以引用move以后的左值;
int main()
{
	// 右值引用只能引用右值,不能引用左值。
	int&& r1 = 10;
	int a = 10;
	/*
	error C2440: “初始化”: 无法从“int”转换为“int &&”
	message : 无法将左值绑定到右值引用
	int&& r2 = a;
	*/
	// 右值引用可以引用move以后的左值
	int&& r3 = move(a);
	return 0;
}

总结:

  1. 左值引用只能引用左值,不能引用右值;
  2. 但是const左值引用既可以引用左值,也可以引用右值
  3. 右值引用只能引用右值,不能引用左值;
  4. 但是右值引用可以引用move以后的左值

右值引用是通过移动构造和移动赋值来极大提高深拷贝的效率,详情见下文:

2. 右值引用实用场景和意义

2.1 左值引用的使用场景

左值引用解决的是拷贝构造引发的深拷贝而带来的开销过大、效率低的问题:

  • 左值引用做参数,防止传值传参引发的拷贝构造问题(导致效率低)
  • 左值引用做返回值,防止返回对象发生拷贝构造的操作(导致效率低)
void func1(cpp::string s)
{}
void func2(const cpp::string& s)
{}
int main()
{
	cpp::string s1("hello");
	func1(s1);//值传参
	func2(s1);//传引用传参
 
    // string operator+=(char ch) 传值返回存在深拷贝
    // string& operator+=(char ch) 传左值引用没有拷贝提高了效率
	s1 += 'a';//左值引用作为返回值
	return 0;
}

总结:

我们都清楚string类的+=运算符是左值引用作为返回值,这样做避免了传值返回引发的拷贝构造,而这样做的原因在于string类的拷贝构造为深拷贝,要经历开空间等操作,开销太大了,导致效率低,传值传参同样也是会发生拷贝构造(深拷贝)这个问题,为了避免如此之大的开销,使用左值引用可以很好的解决此问题,因为左值引用就是取别名,无开销,提高了效率。

2.2 左值引用的短板

 左值引用可以避免一些不必要的拷贝构造操作,但是并不是所有情况都是可以避免的:

  • 左值引用做参数,能够完全避免传参时不必要的拷贝操作;
  • 左值引用做返回值并不能完全避免函数返回对象时不必要的拷贝操作

当函数返回的是一个临时对象时,不能使用引用返回,因为临时对象出了函数作用域就销毁了只能使用传值返回,而传值返回难免会引发拷贝构造带来的深拷贝问题,但是无法避免,这就是左值引用的短板,示例:

namesapce cpp
{
	cpp::string to_string(int value)
	{
		bool flag = true;
		if (value < 0)
		{
			flag = false;
			value = 0 - value;
		}
		cpp::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;
	}
}

因为这里的to_string是传值返回,所以在调用to_string的时候一定会调用拷贝构造,而拷贝构造实现的又是一个深拷贝,效率低:

int main()
{
	cpp::string ret = cpp::to_string(1234);//string(const string& s) -- 深拷贝
	return 0;
}
  • 如果强硬的把上面的to_string实现成左值引用返回,那么又会出现一个问题,我str是临时对象,因为是左值引用返回,所以返回的是str的别名,把别名作为返回值再区拷贝构造ret对象,但是临时对象str出了作用域就调用析构函数销毁了,即使能够访问对象的值,但是空间已经不存在了,此时就发生了内存错误。(不能返回局部变量的引用!

综上所述,为了解决左值引用的短板,C++11引出了右值引用,但并不是简单的把右值引用作为返回值,要对string进行改造,详情见下文:

2.3 右值引用和移动语义

移动构造:

string拷贝构造的const左值引用会接收左值和右值,但是编译器遵循最匹配原则,如果我们单独增加一个右值引用版本的拷贝构造函数,使其只能接收右值,根据最匹配原则,遇到右值,传入右值引用版本的拷贝构造函数,遇到左值传入左值引用版本的拷贝构造函数,这样就能解决了左值引用带来的弊端,而上述单独增加的函数就是我们的移动构造!!!

移动赋值:

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

2.3.1  移动构造

为了解决左值引用的短板,我们需要在cpp::string中增加移动构造,移动构造的本质是将参数右值(将亡值)的资源窃取过来,占位已有,那么就不用做深拷贝了,所以它叫做移动构造,就是窃取别人的资源来构造自己。因为将亡值的特点就是很快就要被销毁了,在你销毁之前还不如把你的资源通过移动构造传给别人。

  • 该移动构造函数要做的就是调用swap函数将传入右值的资源窃取过来,为了能够更好的得知移动构造函数是否被调用,可以在该函数当中打印一条提示语句。
namespace cpp
{
	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;
	};
}
int main()
{
	cpp::string ret = cpp::to_string(1234);//转移将亡值的资源
	cpp::string s1("hello");
	cpp::string s2(s1);//深拷贝,左值拷贝时不会被资源转移
	cpp::string s3(move(s1));//转移将亡值的资源
	return 0;
}

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

  1. 在没有添加移动构造之前,拷贝构造采用的是const左值引用接收参数,所以无论左值还是右值都会被传进去,势必会引发一系列左值引用的短板
  2. 添加移动构造后,由于移动构造采用右值引用接收参数,只能接收右值
  3. 根据编译器的最匹配原则,左值传入左值引用的拷贝构造,右值传入右值引用的移动构造

2.3.2 移动赋值

移动赋值是一个赋值运算符重载函数,该函数的参数是右值引用类型的,移动赋值也是将传入右值的资源窃取过来,占为己有,这样就避免了深拷贝,所以它叫移动赋值,就是窃取别人的资源来赋值给自己的意思。

  • 在当前的string类中增加一个移动赋值函数,该函数要做的就是调用swap函数将传入右值的资源窃取过来,为了能够更好的得知移动赋值函数是否被调用,可以在该函数中打印一条提示语句。
namespace cpp
{
	class string
	{
	public:
		// 移动赋值
		string& operator=(string&& s)
		{
			cout << "string& operator=(string&& s) -- 移动赋值" << endl;
			swap(s);
			return *this;
		}
	private:
		char* _str;
		size_t _size;
		size_t _capacity;
	};
}
int main()
{
	cpp::string ret;//string(string&& s) -- 移动构造,资源转移
	ret = cpp::to_string(1234);//string& operator=(string&& s) -- 移动赋值,资源转换
	return 0;
}

来区分下移动赋值和operator=:

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

总结:

  • 这里运行后,我们看到调用了一次移动构造和一次移动赋值。因为如果是用一个已经存在的对象接收,编译器就没办法优化了。cpp::to_string函数中会先用str生成构造生成一个临时对象,但是我们可以看到,编译器很聪明的在这里把str识别成了右值,调用了移动构造。然后在把这个临时对象做为cpp::to_string函数调用的返回值赋值给ret1,这里调用的移动赋值。
  • 这里虽然调用两次函数,但都只是资源的移动,不需要进行深拷贝,大大提高了效率

2.3.3 编译器做的优化

int main()
{
	cpp::string s = cpp::to_string(1234);
	return 0;
}

1、先来看下没有移动构造编译器做的优化:

不优化:

  • 如果没有移动构造,那我们先前实现的to_string只能够传值返回,传值返回会先拷贝构造出一个临时对象,再用这个临时对象再拷贝构造我们接收返回值的对象。如图所示:

 优化:

  • C++11标准出来之前,也就是C++98的情况,本来应该是两次拷贝构造,但是编译器对其进行了优化,连续两次的拷贝构造函数最终被优化成一次,直接拿str拷贝构造s。

 2、再来看看有移动构造编译器做的优化:

不优化:

  • C++11出来后,我们假设它不优化,根据先前的了解,不优化的话,左值str会拷贝构造给一个临时对象,这个临时对象就是一个右值将亡值),随后进行移动构造,也就是先拷贝构造再移动构造:

 优化:

  • C++11这里编译器进行优化后,左值str会被优化成右值(通过move把左值变为右值),再移动构造给一个临时对象,此临时对象再移动构造给s,但是编译器还会再进行一次优化,把左值str识别出右值后直接移动构造给s。也就是只进行一次移动构造

 3、来看看编译器对移动赋值的处理:

  • 当我们不是用函数的返回值来构造一个对象,而是用一个之前已经定义出来的对象来接收函数的返回值,测试代码如下:
int main()
{
	cpp::string ret;
	ret = cpp::to_string(1234);
	return 0;
}

此时编译器会把左值str会被优化成右值(通过move把左值变为右值),再移动构造给一个临时对象,此临时对象再通过移动赋值传给之前已经定义出来的对象。

这里编译器并没有对这种情况进行优化,因为如果是用一个已经存在的对象接收,编译器就没办法优化了。cpp::to_string函数中会先用str生成构造生成一个临时对象,但是我们可以看到,编译器很聪明的在这里把str识别成了右值,调用了移动构造。然后在把这个临时对象做为cpp::to_string函数调用的返回值赋值给ret1,这里调用的移动赋值。

2.3.4 总结

  1. 左值引用的深拷贝 -- 拷贝构造 / 拷贝赋值
  2. 右值引用的深拷贝 -- 移动构造 / 移动赋值

C++11后STL中的容器都是增加了移动构造和移动赋值。

2.4 右值引用引用左值

move函数

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

template<class _Ty>
inline typename remove_reference<_Ty>::type&& move(_Ty&& _Arg) _NOEXCEPT
{
	// forward _Arg as movable
	return ((typename remove_reference<_Ty>::type&&)_Arg);
}

注意:

  • move函数中_Arg参数的类型不是右值引用,而是万能引用。万能引用跟右值引用的形式一样,但是右值引用需要是确定的类型。
  • 一个左值被move以后,它的资源可能就被转移给别人了,因此要慎用一个被move后的左值。

测试如下:

int main()
{
	cpp::string s1("hello world");
	// 这里s1是左值,调用的是拷贝构造
	cpp::string s2(s1);//string(const string& s) -- 深拷贝
	// 这里我们把s1 move处理以后, 会被当成右值,调用移动构造
	// 但是这里要注意,一般是不要这样用的,因为我们会发现s1的
	// 资源被转移给了s3,s1被置空了。
	cpp::string s3(std::move(s1));//string(string&& s) -- 移动构造
	return 0;
}

2.5 右值引用的其他场景(插入接口)

C++11后STL容器中的插入接口函数也增加了右值引用的版本:

 

注意:

  • C++98的时候,push_back函数只有const左值引用版本,所以这就会导致无论是左值还是右值都会被传入这个左值引用版本的push_back,势必会引发后续的深拷贝而带来的开销过大等问题。
  • C++11出来后,push_back函数增加了右值引用版本,如果传入push_back函数的是一个右值,那么在push_back函数构造节点时,这个右值就可以匹配到容器的移动构造函数进行资源的转移,这样就避免了深拷贝,提高了效率。
int main()
{
	list<cpp::string> lt;
	cpp::string s1("1111");
	// 这里调用的是拷贝构造
	lt.push_back(s1);//string(const string& s) -- 深拷贝
	// 下面调用都是移动构造5
	lt.push_back("2222");//string(string&& s) -- 移动构造
	lt.push_back(std::move(s1));//string(string&& s) -- 移动构造
	return 0;
}

上述代码中的插入第一个元素s1就会匹配到push_back的左值引用版本,在push_back函数内部就会调用string的拷贝构造函数进行深拷贝,而后面插入的两个元素时由于传入的是右值,因此会匹配到push_back的右值引用版本,此时在push_back函数内部就会调用string的移动构造函数进行资源的转移。

3. 完美转发

3.1 万能引用&&

&&应用在模板中时,不代表右值引用,而是万能引用,万能引用既能接收左值,也能接收右值。

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

万能引用的作用:

  1. 模板中的&&不代表右值引用,而是万能引用,其既能接收左值又能接收右值。
  2. 模板的万能引用只是提供了能够接收同时接收左值引用和右值引用的能力,
  3. 但是引用类型的唯一作用就是限制了接收的类型,后续使用中都退化成了左值

示例:

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

注意看上面的Fun函数我写了四个,分别是左值引用、const左值引用、右值引用、const右值引用。main函数中我把左值、右值、const左值、const右值均作为参数传入了函数模板PerfectForward里头,因为其参数类型是万能引用&&,所以既可以接收左值也可以接收右值,可是最终的测试结果却全为左值引用了:

  • 实际传入PerfectForward函数模板的左值和右值均匹配到了左值引用版本的Fun函数,而传入PerfectForward函数模板的const左值和const右值均匹配到了const左值引用版本的Fun函数。
  • 造成此现象的根本原因在于右值被引用后会导致右值被存储到特定位置,这时这个右值可以被取到地址,并且可以被修改,所以在PerfectForward函数中调用Func函数时会将t识别成左值。

这也就是万能引用限制了接收的类型,在后续使用中均退化成了左值,但是我们希望能够在传递过程中保持它的左值或者右值的属性, 就需要用我们下面学习的完美转发。

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

我们想要在传参的过程中保留对象的原生类型属性,就需要用到forward函数:

template<typename T>
void PerfectForward(T&& t)
{
    //完美转发
	Fun(std::forward<T>(t));
    //std::forward<T>(t)在传参的过程中保持了t的原生类型属性。
}

 完美转发后,左值、右值、左值引用、右值引用就可以被传入到理想状态下的函数接口了。

3.3 完美转发的使用场景

这里把先前模拟实现的list拖过来做测试案例,先前实现的list是没有对push_back函数和insert函数写一个右值引用版本的,所以这就会导致无论数据是左值还是右值都会传入左值引用的版本,势必在构建节点的时候引发深拷贝,测试代码如下:

int main()
{
	cpp::list<cpp::string> lt;
	cpp::string s1("1111");//右值
	lt.push_back(s1);//左值
	lt.push_back("2222");//右值
	lt.push_back(std::move(s1));//右值
}

为了避免深拷贝带来的开销过大,我们对push_back和insert函数单独写一个右值引用的版本,同样也要对构造函数写一个右值引用的版本,因为创建节点需要用到节点类的构造函数:

//节点类
template<class T>
struct list_node
{
	//……
	//右值引用节点类构造函数
	list_node(T&& val)
		:_next(nullptr)
		, _prev(nullptr)
		, _data(val)
	{}
};
template<class T>
class list
{
public:
	//……
	//右值引用版本的push_back
	void push_back(T&& xx)
	{
		insert(end(), xx);
	}
	//右值引用版本的insert
	iterator insert(iterator pos, T&& xx)
	{
		Node* newnode = new Node(xx);//创建新的结点
		Node* cur = pos._node; //迭代器pos处的结点指针
		Node* prev = cur->_prev;
		//prev newnode cur
		//链接prev和newnode
		prev->_next = newnode;
		newnode->_prev = prev;
		//链接newnode和cur
		newnode->_next = cur;
		cur->_prev = newnode;
		//返回新插入元素的迭代器位置
		return iterator(newnode);
	}
private:
	Node* _head;
}

虽然这里实现了右值引用版本,但是实际的运行结果依然是深拷贝的,和没写之前的运行结果一模一样,原因如下:

  • 根据先前的了解我们得知:&&应用在模板中时,不代表右值引用,而是万能引用,万能引用既能接收左值,也能接收右值。但是在后续的使用中,会把接收的类型全部退化成左值,既然退化成左值,那么自然会进入后续的深拷贝

此情况就是典型的完美转发的使用场景,解决办法如下:

  • 我们需要在传参的过程中保留对象的原生类型属性,就需要用到forward函数:
//右值引用节点类的构造函数
list_node(T&& val)
	:_next(nullptr)
	, _prev(nullptr)
	, _data(std::forward<T>(val))//完美转发
{}
//右值引用版本的push_back
void push_back(T&& xx)
{
	//完美转发
	insert(end(), std::forward<T>(xx));
}
//右值引用版本的insert
iterator insert(iterator pos, T&& xx)
{
	//完美转发
	Node* newnode = new Node(std::forward<T>(xx));
	//……
	return iterator(newnode);
}

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【C++11】——右值引用、移动语义 的相关文章

  • Accept() 是线程安全的吗?

    我目前正在用 C 语言为我正在做的课程编写一个简单的网络服务器 我们的一项要求是实现一个线程池来使用 pthread 处理连接 我知道我将如何粗略地执行此操作 在主线程中调用accept并将文件描述符传递给freee线程 但是我的朋友建议了
  • 如何从RichTextBox中获取显示的文本?

    如何获得显示的RichTextBox 中的文本 我的意思是 如果 RichTextBox 滚动到末尾 我只想接收那些对我来说可见的行 P S 获得第一个显示的字符串就足够了 您想使用 RichTextBox GetCharIndexFrom
  • 导出类时编译器错误

    我正在使用 Visual Studio 2013 但遇到了一个奇怪的问题 当我导出一个类时 它会抛出 尝试引用已删除的函数 错误 但是 当该类未导出时 它的行为会正确 让我举个例子 class Foo note the export cla
  • 如何在 Asp.net Gridview 列中添加复选框单击事件

    我在 asp 中有一个 gridview 其中我添加了第一列作为复选框列 现在我想选择此列并获取该行的 id 值 但我不知道该怎么做 这是我的 Aspx 代码
  • Paradox 表 - Oledb 异常:外部表不是预期的格式

    我正在使用 Oledb 从 Paradox 表中读取一些数据 我遇到的问题是 当我将代码复制到控制台应用程序时 代码可以工作 但在 WinForms 中却不行 两者都以 x86 进行调试 我实际上只是复制代码 在 WinForms 应用程序
  • 有没有办法使用 i387 fsqrt 指令获得正确的舍入?

    有没有办法使用 i387 fsqrt 指令获得正确的舍入 除了改变精确模式在 x87 控制字中 我知道这是可能的 但这不是一个合理的解决方案 因为它存在令人讨厌的重入型问题 如果 sqrt 操作中断 精度模式将出错 我正在处理的问题如下 x
  • 序列化和反序列化 Visual Studio 解决方案文件 - 或以编程方式编辑?

    我想以编程方式添加和删除项目 解决方案文件夹和其他项目 例如解决方案的资源文件 但我不确定最好的方法是什么 对于那些不知道的人 高度简化 解决方案文件 sln 通常如下所示 Microsoft Visual Studio Solution
  • 在 MATLAB 中创建共享库

    一位研究人员在 MATLAB 中创建了一个小型仿真 我们希望其他人也能使用它 我的计划是进行模拟 清理一些东西并将其变成一组函数 然后我打算将其编译成C库并使用SWIG https en wikipedia org wiki SWIG创建一
  • 默认值 C# 类 [重复]

    这个问题在这里已经有答案了 我在控制器中有一个函数 并且我收到表单的信息 我有这个代码 public Actionresult functionOne string a string b string c foo 我尝试将其转换为类似的类
  • 您可以在一个 Windows Azure 实例上部署多个 Web 应用程序吗?

    是否可以在一个 windows azure 小型计算实例中运行一堆 Web 应用程序 我正在考虑使用 Azure 作为放置一堆处于开发和非生产状态的项目 Web 应用程序 的地方 有些实际上已经被封存了 但我想在某个地方有一个活跃的实例 我
  • 在简单注入器中注册具有多个构造函数和字符串依赖项的类型

    我正在尝试弄清楚如何使用 Simple Injector 我在项目中使用了它 注册简单服务及其组件没有任何问题 但是 当组件具有两个以上实现接口的构造函数时 我想使用依赖注入器 public DAL IDAL private Logger
  • 错误左值需要作为赋值C++的左操作数

    整个程序基本上只允许用户移动光标 如果用户位于给定的坐标范围 2 2 内 则允许用户键入输入 我刚刚提供了一些我认为足以解决问题的代码 我不知道是什么导致了这个问题 你能解释一下为什么会发生吗 void goToXY int int 创建一
  • .NET JIT 编译的代码缓存在哪里?

    NET 程序首先被编译为 MSIL 代码 当它被执行时 JIT编译器会将其编译为本机机器代码 我想知道 这些JIT编译的机器代码存储在哪里 它只存储在进程的地址空间中吗 但由于程序的第二次启动比第一次快得多 我认为即使在执行完成后 该本机代
  • 使用(linq to sql)更新错误

    我有两个表 通过外键 CarrierID 绑定 Carrier CarrierID CarrierName CarrierID 1 CarrierName DHL CarrierID 2 CarrierName Fedex Vendor V
  • 如何使用收益返回和递归获得字母的每个组合?

    我有几个像这样的字符串列表 可能有几十个列表 1 A B C 2 1 2 3 3 D E F 这三个仅作为示例 用户可以从几十个具有不同数量元素的类似列表中进行选择 再举个例子 这对于用户来说也是一个完全有效的选择 25 empty 4 1
  • 如何访问窗口?

    我正在尝试使用其句柄访问特定窗口 即System IntPtr value Getting the process of Visual Studio program var process Process GetProcessesByNam
  • “int i=1,2,3”和“int i=(1,2,3)”之间的区别 - 使用逗号运算符的变量声明[重复]

    这个问题在这里已经有答案了 int i 1 2 3 int i 1 2 3 int i i 1 2 3 这些说法有什么区别 我无法找出任何具体原因 Statement 1 Result Compile error 运算符的优先级高于 运算符
  • #pragma pack(16) 和 #pragma pack(8) 的效果总是相同吗?

    我正在尝试使用来对齐数据成员 pragma pack n http msdn microsoft com en us library 2e70t5y1 28v vs 100 29 aspx 以下面为例 include
  • ASP.NET Core Razor Page 多路径路由

    我正在使用 ASP NET Core 2 0 Razor Pages 不是 MVC 构建系统 但在为页面添加多个路由时遇到问题 例如 所有页面都应该能够通过 abc com language 访问segment shop mypage 或
  • 如何将对象转换为传递给函数的类型?

    这不会编译 但我想做的只是将对象转换为传递给函数的 t public void My Func Object input Type t t object ab TypeDescriptor GetConverter t ConvertFro

随机推荐

  • VMware虚拟机上如何设置windows7虚拟系统与主机时间不同?

    每次打开虚拟机时 windows虚拟机的时间老是与主机时间相同 就很烦 如下设置虚拟机与主机时间不同步 在搜索栏输入 管理工具 2 打开管理工具 gt 服务 3 找到VMware Tools做如下设置 4 找到Windows Time做如下
  • AIX 软件安装与维护

    1 AIX 软件产品 2 软件包的构成 由一个或者多个package构成一个完整的LPP 一个LPP是一个完整的软件产品 这个软件包含与这个LPP相关的所有Package 一个软件包是由一组具有共同功能的文件集而组成的一个可单独安装的镜像
  • 异常的深入研究与分析(2)

    本文主要是关于异常的面试题目 出自前几年的迅雷 支付宝等名企的笔试题目 内容由金丝燕网原创编辑 转载请注明链接 题目一 考察异常类的继承结构 那个类是所有异常的基础类 A String B Error C Throwable D Runti
  • 铝电解电容的寿命计算(纹波电流法)手把手教你

    本文教你通过纹波电流预测铝电解电容的寿命 计算方法来自Nichicon 所以以Nichicon的电解电容为例 不同厂家的计算公式可能稍有不同 首先选一款铝电解电容为例 这里选择常见的LGN系列3000h 105 420V 470uF的电容
  • matlab找出二维矩阵中最大值的位置或者最小值的位置

    matlab寻找最大值或者最小值是通过max和min命令 对应二维矩阵寻找最大元素就是max max A 注意二维矩阵要写两个max 找对应位置用find函数 举个例子 gt gt A 1 2 3 4 5 6 A 1 2 3 4 5 6 g
  • android 为什么使用dp单位,它的真正优势在哪里?

    一些新手开发人员 不是很明白dp的真正好处 只是知道这样用 那么今天我用我的理解来解释下 有不对之处 欢迎指正 什么是dp dp是一种与像素密度无关的单位 那什么是像素密度呢 先来解释下像素密度 像素密度 就是所谓的dpi 每英寸像素的数量
  • 零基础入门语义分割-Task1 赛题理解

    1 3 数据标签 赛题为语义分割任务 因此具体的标签为图像像素类别 在赛题数据中像素属于2类 无建筑物和有建筑物 因此标签为有建筑物的像素 赛题原始图片为jpg格式 标签为RLE编码的字符串 RLE全称 run length encodin
  • Chrome OS 初体验

    今天Google放出了Chrome OS的源码 很快网上有人把它编译成功在虚拟机上跑了起来 有朋友第一时间把虚拟镜像下载了 于是搞来一份 立刻开始体验 安装过程就不多说了 可以参考谷奥上的Chromium OS 安装教程 我用的是VMWar
  • Vue中replace的用法

    Vue js V 啥也不说了 就是这么牛逼
  • ps 2019安装破解以及添加CUR和ICO插件

    下载最新版 2019版 的PS后 只能使用和登陆 挺麻烦的 就想着破解来着 在网上搜索了一大圈 终于解决了 使用之前的替换amtlib dll的方式不适用了 需要替换ps的启动程序 ps 64位的安装路径为C Program Files A
  • <van-field>使用方案

    1 官方示例
  • js += 含义(小知识)

    是连接复值 s 5 表示把s的值 5后复值给s若s是字符串 则是连接后赋值 s 5 就是将s字符串的值连接上字符串5后赋值给s s 5 若s为字符串 则同s 5 因为JS是弱变量 会自动转5为 5 与s s 5 语句同意 转载于 https
  • 《数据库系统内 幕》分布式系统

    分布式部分 章8 分布式系统抽象 章9 故障 无超时的故障检测器 phi增量故障检测器 章10 领导者选举 选举过程 选举规则 章11 以数据为中心的一致性模型 单操作一致性模型 客户为中心的一致性 会话模型 复制协议 基于主备份协议的复制
  • Linux国内环境下安装kubeadm、kubelet、kubectl

    因为项目需要用Kubernetes来改版底层的Docker 所以一边写完成现在的任务一边继续学习Kubernetes 现有的一些文档和Kubernetes中文社区的中文文档都没有很详细的初期安装部署环境的教程 所以建议直接去官网看文档 当然
  • 优秀网站源码、编程源码下载网站大集中

    1 51源码 http www 51aspx com 2 源码之家 http www codejia com 3 源码网 http www codepub com 4 虾客源码 http www xkxz com 5 多多源码 http w
  • 牛客SQL练习三

    21查找所有员工自入职以来的薪水涨幅情况 题目描述 查找所有员工自入职以来的薪水涨幅情况 给出员工编号emp no以及其对应的薪水涨幅growth 并按照growth进行升序 CREATE TABLE employees emp no in
  • Unity Vuforia(高通)AR全流程

    Unity Vuforia播放视频全过程 从Unity的安装到打包 一 安装Unity 1 点击安装地址下载Unity Hub 下载好之后点击安装 2 从Unity Hub进去安装Unity 要先注册登陆账号 安装完后是这样的 二 新建项目
  • Vue+Element-UI Table表头排序

    Vue Element UI Table表头排序 Vue代码 定义后台需要接受的排序属性 排序sortChange Vue代码
  • js控制输入框

    转自 http www pinlue com article 2019 11 2221 409812495947 html
  • 【C++11】——右值引用、移动语义

    目录 1 基本概念 1 1 左值与左值引用 1 2 右值和右值引用 1 3 左值引用与右值引用 2 右值引用实用场景和意义 2 1 左值引用的使用场景 2 2 左值引用的短板 2 3 右值引用和移动语义 2 3 1 移动构造 2 3 2 移