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

news2024/11/16 21:36:06

目录

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

 

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

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

相关文章

开源盛行:为什么学习国产达梦数据库?

开源盛行&#xff1a;为什么学习国产达梦数据库&#xff1f; 武汉达梦数据库股份有限公司成立于2000年&#xff0c;是国内领先的数据库产品开发服务商&#xff0c;国内数据库基础软件产业发展的关键推动者。公司为客户提供各类数据库软件及集群软件、云计算与大数据等一系列数据…

【数据结构】·顺序表函数实现·赶紧学起来呀

&#x1f490; &#x1f338; &#x1f337; &#x1f340; &#x1f339; &#x1f33b; &#x1f33a; &#x1f341; &#x1f343; &#x1f342; &#x1f33f; &#x1f344;&#x1f35d; &#x1f35b; &#x1f364; &#x1f4c3;个人主页 &#xff1a;阿然成长日记 …

element-ui使用动态渲染下拉选择框el-select已经选择的下拉框的值不可以重复选择让其disabled

调接口拿到下拉框数据的数据的时候将其disabled全为true 但是如果编辑的时候就需要与详情接口对比&#xff0c;如果有id一致就将disabled为true if (res.code 0) {if (this.dialogtitle "新增合同") {res.data.map((v) > {v.nameUnitVoList.forEach((item) >…

九耶:产品经理面试题 ——请说说工作中你主导的产品上线后你会关注哪些数据指标?

【面试官考察的点】 实际的产品工作中需要通过数据驱动业务&#xff1a;产品经理了解用户对不同功能的使用情况&#xff0c;行为特征和使用反馈&#xff0c;这样可以为产品的改进提供很好的方向。面试官想考察你是否具备数据分析的意识和能力。 【满分回答】 在产品上线后&am…

百度开发者平台API地理编码,根据地址获取经纬度

地理编码 | 百度地图API SDK (baidu.com) 原始csv # encoding:utf-8 import requests import csv import json # 接口地址 url "https://api.map.baidu.com/geocoding/v3"# 此处填写你在控制台-应用管理-创建应用后获取的AK ak "XXXXXXX"# 创建CSV文件并…

vue 实现拖拽效果

实现方式&#xff1a;使用自定义指令可以实现多个面板拖拽互不影响 1.自定义指令 js directives: {// 拖拽drag(el) {el.onmousedown function (e) {let x e.pageX - el.offsetLeftlet y e.pageY - el.offsetTopdocument.onmousemove function (e) {el.style.left e.pag…

掌握Python的X篇_12_如何使用VS Code调试Python程序

本篇将会介绍如何使用VS Code调试Python程序。 文章目录 1. 什么是调试2. 断点3. 如何启动调试4. 监视窗口5. 单步 1. 什么是调试 我们可以利用VS Code对Python代码进行调试。所谓调试&#xff0c;大家可以理解成有能力将程序进行 “慢动作播放”让我们有机会看到程序一步一步…

一文2500字详解如何使用mock.js实现接口测试的自动化

这篇文章主要为大家介绍了如何使用mock.js实现接口测试的自动化详解&#xff0c;有需要的朋友可以借鉴参考下&#xff0c;希望能够有所帮助&#xff0c;祝大家多多进步&#xff0c;早日升职加薪 Mock.js 基础用法介绍 Mock.js是一个常用于生成随机数据和拦截Ajax请求的JavaScr…

linux基础学习

1.day1 1、修改虚拟机的网络&#xff1b; sudo vim /etc/netplan/*.yaml sudo netplan apply 2.day2 1、VIM配置&#xff1b; 2、安装SSH&#xff0c;调用putty接入终端&#xff1b; 3、shell命令&#xff1b; *&#xff1a;匹配任意长度的字符 &#xff1f;&#xff1a;匹…

关于前端框架vue2升级为vue3的相关说明

一些框架需要升级 当前&#xff08;202306&#xff09; Vue 的最新稳定版本是 v3.3.4。Vue 框架升级为最新的3.0版本&#xff0c;涉及的相关依赖变更有&#xff1a; 前提条件&#xff1a;已安装 16.0 或更高版本的Node.js&#xff08;摘&#xff09; 必须的变更&#xff1a;核…

CAN总线开发必看! 如何使用CANlib检测CAN帧溢出情况? Kvaser三招帮你轻松解决

从1980年代&#xff0c;Kvaser就开始CAN产品的研发&#xff0c;在相关产品开发领域有近40多年的经验&#xff0c;对CAN和相关总线技术有着非常深入的研究。广州智维电子科技是KVASER的中国引进者&#xff0c;我们会不定期分享一些有趣的发现和特定情况的技术处理。 在开发严重…

美国过境签证申请也要面签吗?

随着人们出国旅行的增加&#xff0c;美国过境签证成为了一个热门话题。对于许多人来说&#xff0c;了解美国过境签证的流程和要求非常重要。在这篇文章中&#xff0c;知识人网小编将介绍美国过境签证是否需要面签&#xff0c;以及相关的注意事项。 首先&#xff0c;让我们来了解…

Redis7学习笔记01

一、redis7实战教程简洁 1、大纲&#xff1a; ①、适合对象&#xff0c;从小白到熟手&#xff0c;一套全包圆 ②、Redis专题-大厂面试题&#xff0c;含100道 ③、Redis专题-真实需求生产真实案例 ④、Redis7新特性 2、小白篇高阶篇&#xff1a; 3、大厂面试题&#xff1a…

web场景-静态资源规则与定制化

一、SpringBoot默认静态资源路径 1.1 静态资源放在类路径下: /static/public/resources/META-INF/resources 1.2 浏览器访问&#xff1a; 当前项目根路径/ 静态资源名 eg&#xff1a;如上图&#xff0c;要想访问这四张图片&#xff0c;访问路径依次为&#xff1a;localhos…

1400*C. Computer Game

Example input 6 15 5 3 2 15 5 4 3 15 5 2 1 15 5 5 1 16 7 5 2 20 5 7 3 output 4 -1 5 2 0 1 解析&#xff1a; k个电&#xff0c; 第一种为 k>a 时&#xff0c;只玩游戏 k-a; 第二种&#xff0c;k>b,一边玩一边充电 k-b 问完成n轮游戏的情况下&#xff0c;优先第…

被逼无奈在小公司熬了2年,现在我终于进了腾讯测试岗...

其实两年前校招的时候就往腾讯投了一次简历&#xff0c;结果很明显凉了&#xff0c;随后这个理想就被暂时放下了&#xff0c;但是这个种子一直埋在心里&#xff0c;想着总有一天会再次挑战的。 其实这两年除了工作以外&#xff0c;其余时间基本上都在学习&#xff0c;打磨自己…

基于OpenCV solvePnP函数估计头部姿势

人脸识别 文章目录 人脸识别一、姿势估计概述1、概述2、姿态估计3、在数学上表示相机运动4、姿势估计需要什么5、姿势估计算法6、Levenberg-Marquardt 优化 二、solvePnP函数1、函数原型2、参数详解 三、OpenCV源码1、源码路径 四、效果图像示例参考链接 一、姿势估计概述 1、…

vue中通过JavaScript实现web端鼠标横向滑动触控板滑动效果-demo

JavaScript实现web端鼠标横向滑动&触控板滑动效果 支持鼠标拖动滑动&触控板滑动效果 web端实现滑动&#xff0c;就是对鼠标按下、鼠标松开、鼠标移动事件进行监听 效果图 代码 结构代码 <template><div class"swiper"><div class"co…

财报解读:谷歌成功绝地反击?厮杀尚未真正开始!

在经历了一轮激烈的攻防战之后&#xff0c;谷歌、微软同一天发布了财报&#xff0c;从数据来看&#xff0c;谷歌成功抵御了微软携OpenAI发起的挑战&#xff0c;业绩表现全面超预期&#xff0c;而微软的业绩虽然整体也超预期&#xff0c;但相比其四面出击的高调则黯淡了许多。 巨…

GoogleLeNet V2 V3 —— Batch Normalization

文章目录 Batch Normalizationinternal covariate shift激活层的作用BN执行的位置数据白化网络中的BN层训练过程 BN的实验效果MNIST与GoogleLeNet V1比较 GoogleLeNet出来之后&#xff0c;Google在这个基础上又演进了几个版本&#xff0c;一般来说是说有4个版本&#xff0c;之前…