右值引用和移动语义 ---- c++11

news2024/9/24 5:22:14

文章目录:

    • 左值?左值引用?
    • 右值?右值引用?
    • 左值引用与右值引用比较
    • 右值引用的使用场景和意义
    • 左值引用的使用场景和意义
    • 右值引用和移动语义
    • 右值引用引用左值
    • 完美转发
    • 完美转发实际中的使用场景

在这里插入图片描述
c++ 是一种通用编程语言,旨在为资源受限的大型系统提高性能和效率。随着时间的推移,该语言得到了扩展和改进,并定期发布新的标准。其中的 c++11 标准引入了右值引用和移动语义等特性提供了语言的性能。

左值?左值引用?

在 c++ 中,左值(lvalue)是指可以标识一个内存位置的表达式(如:变量名或解引用的指针)或对象。左值是可以放在赋值运算符左边的表达式,可以对其取地址。定义时 const 修饰符后的左值,不能给它赋值,但是可以取其地址。

左值引用(lvalue reference)是指对左值的引用。它通过使用 & 符号来声明,并且只能绑定到左值。左值引用允许对绑定的左值进行修改,并且可以用作函数参数或返回类型。通过左值引用,可以创建一个别名,即对某个左值的引用,通过这个别名可以对原对象进行操作。

左值和左值引用示例:


int main()
{
	// 左值的示例
	int x = 7;               // x 是一个左值
	int* p = &x;             // p 是一个左值
	int arr[7];              // arr 是一个数组,也是左值
	int result = x + arr[0]; // x 和 arr[0] 都是左值

	// 左值引用的示例
	int y = 5;
	int& ref = y;            // ref 是 y 的左值引用,即对 y 的引用
	ref = 7;                 // 修改 ref 的值也会修改 y 的值
	int& ref2 = ref;         // ref2 是对 ref 的左值引用

	// const左值引用 -> 通过const左值引用只能读取z的值,不能修改它
	int z = 9;
	const int& ret = z;      // ref 是 z 的 const 左值引用,即对 z 的常量引用
	int m = ret;             // 通过 const 左值引用可以将 z 的值赋值给其它变量
	return 0;
}

左值引用在函数参数传递中的应用:

void swap(int& x, int& y)
{
	int tmp = x;
	x = y;
	y = tmp;
}

int main()
{
	int x = 2, y = 4;
	swap(x, y); // 传入x,y的左值引用
	cout << "x = " << x << "  y = " << y << endl;
	return 0;
}

右值?右值引用?

右值是一个临时的、即将被销毁的表达式或对象。如:字面量常量、临时对象、表达式返回值、函数返回值(这个不能是左值引用返回)等。右值可以出现在赋值符号的右边,但是不能出现在赋值符号的左边,且右值不能取地址。

右值引用是对右值的引用。它是 c++11 引入的一种新的引用类型,用于表示对右值的引用。右值引用通过 && 来进行声明。

右值和右值引用示例:

int main()
{
	// 右值的示例
	int a = 5;                                // 5 是一个右值
	int b = a + 7;                            // a + 7 是一个右值表达式
	std::string str = "rvalue";               // "rvalue" 是一个右值字符串

	// 右值引用示例
	int&& rref1 = 5;                          // rref1 是对右值 5 的右值引用
	int&& rref2 = a + 7;                      // rref2 是对右值 a + 7 的右值引用
	std::string&& rref3 = "rvalue reference"; // rref3 是对右值字符串字面常量的右值引用
	return 0;
}

说明:

  • 右值引用是一种新的引用类型,它可以绑定到右值,并且允许我们通过移动语义来避免不必要的内存分配和拷贝操作。与左值引用不同的是,右值引用并不会改变绑定对象的存储方式。因此,不能将右值转换为左值。
  • 常量右值引用可以保证被引用的对象不会被修改,但是不能保证该对象的生命周期。右值引用一个重要的应用是实现移动语义,它可以提高程序的性能和效率。

左值引用与右值引用比较

左值引用能否引用右值?

  • 左值引用不能引用右值,因为左值引用通常用于修改数据,而右值是临时的、不可修改的值。
  • 但是,const 左值引用即可引用左值,又可以引用右值,并确保对被引用对象的修改是禁止的。

const 左值引用既可引用也可引用右值的示例:

template<class T>
void func(const T& val)
{
	cout << val << endl;
}

int main()
{
	string s("lvalue");
	func(s);

	func("rvalue");

	// 左值引用只能引用左值,不能引用右值
	int a = 10;
	int& ra1 = a;  // ra1为a的别名
	// int& ra2 = 10; // 编译出错,因为左值不能引用右值

	// const左值引用既可以引用左值,又可以引用右值
	const int& ra3 = a;
	const int& ra4 = 10;
	return 0;
}

通过使用 const 左值引用,可以实现通用的函数接口,接受不同类型的参数(左值和右值),同时保证了参数的安全性和不可修改性。

右值引用能否引用左值?

  • 右值引用只能引用右值,不能引用左值。
  • 但是右值引用可以引用 move 之后的左值。

move 函数是 c++11 标准引入函数。它用于将左值转换为右值,从而允许将资源的所有权从一个对象转到另一个对象。示例:

#include<iostream>
#include<utility>
using namespace std;

int main()
{
	// 右值引用只能引用右值,不能引用左值
	int&& r1 = 10;

	// error C2440: “初始化”: 无法从“int”转换为“int &&”
	// message : 无法将左值绑定到右值引用
	int a = 10;
	int&& r2 = a; // error

	// 右值引用可以引用move之后的左值
	int&& r3 = std::move(a);
	return 0;
}

右值引用的使用场景和意义

虽然 const 左值引用既能接收左值,也能接收右值,但左值引用还是存在短板,c++11 中提出的右值引用就是用来解决左值引用的短板的。

为了能够理解这个问题,接下来我们使用一个深拷贝的类(简化的 string 类)。类中实现了一些基本的成员函数,拷贝构造和赋值,支持移动语义。如下:

#pragma once
#include<iostream>
#include<assert.h>
using namespace std;

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

		// 移动构造
		string(string&& s)
			:_str(nullptr)
			, _size(0)
			, _capacity(0)
		{
			cout << "string(string&& s) -- 资源转移" << endl;
			swap(s);
		}

		// 拷贝构造函数
		string(const string& s)
			:_str(nullptr)
			, _size(0)
			, _capacity(0)
		{
			cout << "string(const string& s) -- 深拷贝" << endl;

			string tmp(s._str);
			swap(tmp);
		}

		// 移动赋值
		string& operator=(string&& s)
		{
			cout << "string& operator=(string&& s) -- 资源转移" << endl;
			swap(s);

			return *this;
		}

		// 赋值运算符重载函数
		string& operator=(const string& s)
		{
			cout << "string& operator=(const string& s) -- 深拷贝" << endl;
			string tmp(s);
			swap(tmp);

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

接下来进行测试一下:

int main()
{
	hyr::string s1("lvalue");
	hyr::string s2(s1);       // string(const string& s) -- 深拷贝 -> 调用深拷贝函数进行拷贝构造

	hyr::string s3(move(s1)); // string(string && s) --资源转移  -> 调用移动构造函数构造s3对象
	
	return 0;
}

上面的两个函数(移动构造,移动赋值)是通过移动语义来提高对象的性能。通过移动资源而不是复制资源,可以避免不必要的内存拷贝和动态内存分配,从而提高程序的效率。移动构造函数和移动赋值运算符通常与临时对象和右值引用一起使用,可以有效地管理资源并减少资源的拷贝和销毁操作,从而提高性能。

左值引用的使用场景和意义

左值引用有以下使用场景:

1.参数传递

使用左值引用作为函数参数可以提高效率,避免了对象的拷贝。当函数需要修改传入的对象时,使用左值引用可以直接修改原始对象而不需要创建副本。如下:

void modifyValue(int& value)
{
	value = 7;
}

int main()
{
	int a = 3;
	modifyValue(a);
	return 0;
}

2.函数返回值

在某些场景下,函数返回一个对象时,可以使用左值引用来优化性能。如下:

// 拼接字符串,使用引用返回
string& concatenateStrings(string& str1, const string& str2)
{
	str1 += str2;
	return str1;
}

int main()
{
	string s1 = "concatenate";
	const string s2 = "string";
	string& result = concatenateStrings(s1, s2);
	return 0;
}

左值引用的主要使用场景是在函数参数传递返回值优化中,可以提高效率并避免不必要的对象拷贝。

左值引用的短板

当函数返回对象是一个局部变量,出了函数作用域就不存在了,这样的情况就不能使用左值引用返回,只能使用传值返回。

如下,当模拟实现一个 int 版本的 to_string 函数,该函数就不可以使用左值引用返回,因为 to_string 返回的是一个局部变量。这里只能使用传值返回,传值返回会导致至少一次拷贝构造(旧一点的编译器可能是2次拷贝构造):

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

		hyr::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 函数返回时,就一定会调用 string 的拷贝构造函数:

int main()
{
	hyr::string s = hyr::to_string(12345);
	return 0;
}

在这里插入图片描述

c++11 提出右值引用就是为了解决左值引用这个短板的。

右值引用和移动语义

右值引用和移动语义就是通过给模拟实现的 string 类增加移动构造和移动赋值来解决上述问题。

移动语义

移动构造(Move Constructor)是一种特殊的构造函数,它本质是将参数右值的资源窃取过来,占为己有,这样就避免了深拷贝,所以它叫做移动构造,即窃取别人的资源来构造自己。

通过移动构造,可以有效地利用已有对象的资源,而无需进行额外的数据复制。这对于管理动态分配的内存、大型数据结构或其它资源密集型对象都特别有用。移动构造的实现通常涉及将源对象的指针或句柄移交给目标对象,并将源对象的资源真正置为空或标记无效,以避免二次释放或资源泄漏。

在当前模拟实现的 string 类中新增一个移动构造函数,该函数要做的就是调用 swap 函数将传入的右值的数据窃取过来,string 类移动构造的代码如下:

namespace hyr
{
	class string
	{
	public:
		// s1.swap(s2)
		void swap(string& s)
		{
			::swap(_str, s._str);
			::swap(_size, s._size);
			::swap(_capacity, s._capacity);
		}

		// 移动构造
		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; 
	};
}

说明:

  • string 类中没有增加移动构造之前,因为拷贝构造采用的是 const 左值引用接收参数,因此无论拷贝构造对象时传入的左值还是右值,都会调用拷贝构造函数。
  • 当增加移动构造后,由于移动构造采用的是右值引用接收参数,因此,若拷贝构造对象时传入的是右值,那么就会调用移动构造函数。
  • string 的拷贝构造函数是深拷贝,而移动构造函数中调用 swap 函数进行了资源转移,因此调用移动构造的代价比调用拷贝构造的代价小。

给 string 类增加移动构造函数后,对于返回局部的 string 对象的函数,在返回 string 对象时就会调用移动构造进行资源的移动,而不需要调用拷贝构造函数,从而提高性能。如下所示:

在这里插入图片描述

虽然 to_string 函数中返回的局部 string 对象是一个左值,但是该 string 对象在当前函数调用结束之后就会被销毁,这种即将被销毁的值就叫做 “将亡值” 。“将亡值” 指的是即将销毁的对象,包括局部变量和匿名对象等。由于将亡值即将被销毁,因此可以将其资源转移给别的对象使用,而右值引用和移动构造函数正是用来实现这一目的的。编译器在识别将亡值时会将其识别为右值,从而匹配到参数类型和右值引用的移动构造函数,实现资源转移。

移动赋值

移动赋值运算符(Move Assignment Operator)是一个赋值运算符的重载函数,其参数为右值引用类型(&&),用于实现资源的移动赋值操作。移动赋值运算符的目的是将传入的右值对象的资源窃取过来,占为己有,避免进行深拷贝操作,从而提高效率,一种常见的实现方式是通过调用 swap 函数来实现资源的交换。

以下是在 hyr::string 类中增加移动赋值函数并使用 swap() 函数实现资源的转移示例:

namespace hyr {
    class string {
    public:
        // ... 其他成员函数的实现 ...

        // 移动赋值函数
        string& operator=(string&& s) {
            cout << "string::operator=(string&& other) - 资源转移" << endl;
            if (this != &s) {
                // 通过调用 swap() 函数将资源转移给当前对象
                swap(s);
            }
            return *this;
        }

        // 交换函数
        void swap(string& s)
        {
            ::swap(_str, s._str);
            ::swap(_size, s._size);
            ::swap(_capacity, s._capacity);
        }

        // ... 其他成员变量 ...

    private:
        char* _str;
        size_t _size;
        size_t _capacity;
    };
}

给 string 类增加移动赋值之后,使用一个已定义的 string 对象去接受 to_string 函数的返回值i,此时也不会存在深拷贝,如下:

在这里插入图片描述

这里,我们看到调用了一次移动构造和一次移动赋值。因为如果是一个已经存在的对象接收,编译器就没有办法优化了。hyr::string 函数中会先用 str(to_string 里面的内容) 构造生成一个临时对象。在这里,编译器将 str 识别为右值,调用了移动构造。然后将这个临时对象作为 hyr::to_string 函数调用的返回值赋值给 ret,这里调用的是移动赋值。

说明:若没有实现移动赋值函数,该代码的执行顺序应该是先调用一次拷贝构造,再调用一次原有的 operator= 函数,但由于原有的 operator= 函数实现时复用了拷贝构造函数,因此代码执行之后会多调用一次拷贝构造函数。

如下是没有实现移动赋值和移动构造的运行结果:

在这里插入图片描述

总结:增加了移动构造函数和移动赋值函数之后,可以通过移动语义实现资源的转移,避免了不必要的拷贝操作,减少动态内存分配的次数,并于现有的移动语义优化算法和容器进行配合,从而提高代码的效率和性能。这对于大型对象,资源管理类以及涉及频繁对象传递的情况非常重要。

STL中的容器也增加了移动构造和移动赋值:

string 类的移动构造函数:

在这里插入图片描述

string 类的移动赋值函数:

在这里插入图片描述

右值引用引用左值

按照语法,右值引用在声明时只能引用右值(临时对象、将亡值等),而不直接引用左值。但右值引用一定不能引用左值吗?然而,在某些情况下,确实需要使用右值引用去引用一个左值,以实现移动语义。为了实现该需求,c++11 引入了 std::move() 函数,位于头文件 <utility> 中。std::move()函数不搬移任何东西,唯一的功能就是将一个左值强制转换为右值引用,从而实现移动语义。

使用 std::move() 函数将一个左值转换为右值引用后,原始对象的状态将被标记为可移动的(moved from),即它的值可能已被修改或资源已被转换。因此,在使用 std::move() 后,原始对象的状态可能是未定义的,因此需要谨慎操作。

std::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 使用示例:

int main()
{
	hyr::string s1("Test uses an rvalue referencte to reference an lvalue");
	// 这里s1是左值,调用拷贝构造函数
	hyr::string s2(s1);

	// 这里将s1进行move之后,会被当作右值,调用移动构造函数
	// 需要注意,一般不是这样用的,因为我们会发现s1的资源被转移给了s3,s1被偷家了
	hyr::string s3(std::move(s1));
	return 0;
}

STL容器插入接口函数也增加了右值引用版本

c++11 标准中,STL 中的容器不仅增加了移动构造和移动赋值,STL容器的插入接口也增加了右值引用版本:

在这里插入图片描述

若 vector 容器中储存的是 string 对象,则调用 vector 的 push_back 接口插入数据时,会有以下几种插入方式:

int main()
{
	vector<hyr::string> vs;
	hyr::string s1("111");
	// 调用拷贝构造
	vs.push_back(s1);

	// 调用移动构造
	vs.push_back("222");
	vs.push_back(hyr::string("333"));
	vs.push_back(std::move(s1));
	return 0;
}

对于 c++11 之前的版本,vector 容器的 push_back 只有一个左值引用版本,因此在插入数据时,需要进行深拷贝。而在 c++11 标准中,vector 容器的 push_back 接口提供了右值引用版本,可以避免不必要的拷贝操作,提高了效率。当传入的数据是一个右值时,可以通过移动构造进行资源的转移,避免了深拷贝的开销。因此,对于 string 这种可以移动的类型,在插入元素时尽量使用右值引用版本的 push_back 接口,可以获得更好的性能。

完美转发

c++ 中的完美转发(Perfect Forwarding)是指在模板中将一个函数参数传递给另一个函数,并保留原始参数的左值/右值属性以及 const 和 volatile 属性的技术。完美转发通常用于实现泛型函数或类的转发机制,让原始的函数参数在传递过程中能够保持其值类别和常量属性,从而减少不必要的拷贝和副本,并提高代码的效率。

模板中的 && 不代表右值引用,而是万能引用,其既能接收左值又能接收右值:

template <class T>
void PrefectForward(T&& t)
{
	// ...
}

当模板函数的参数类型为 T&& 时,编译器会根据传入的实参的值类别(左值或右值)来确定 T 的类型。若传入的参数是一个左值,则 T 被推导为左值引用类型;若传入的参数是一个右值,则 T 被推导为右值引用类型。

下面代码重载了四个 func 函数,这四个函数的参数类型依次是:左值引用、const 左值引用、右值引用、const 右值引用。在 main 函数中调用 PerfectForward 函数,PerfectForward 函数用于再次调用 func 函数,如下所示:

void func(int& x) { cout << "左值引用" << endl; }
void func(const int& x) { cout << "const 左值引用" << endl; }
void func(int&& x) { cout << "右值引用" << endl; }
void func(const int&& x) { cout << "const 右值引用" << endl; }

// 模板中的 && 不代表右值引用,而是万能引用,其既能接收左值又能接收右值
// 模板中的万能引用只是提供了能够同时接收左值引用和右值引用的能力
// 但是引用类型的唯一作用就是限制了接收的类型,后续使用中都退化为了左值
// 我们希望能够在传递过程中保持它的左值或右值的属性,就需要使用完美转发
template<class T>
void PerfectForward(T&& t)
{
	func(t);
}

int main()
{
	int a = 10;
	PerfectForward(a);				// 左值
	PerfectForward(std::move(a));   // 右值

	const int b = 20;
	PerfectForward(b);		    	// const 左值
	PerfectForward(std::move(b));   // const 右值
	return 0;
}

运行结果如下:

在这里插入图片描述

我们希望传入的是什么类型的值就匹配对应的函数,运行的结果和预期不太一样。在上述代码中调用 PerfectForward 函数时传入的左值和右值,最后都匹配到了左值引用版本的 func 函数,PerfectForward 函数中传入的 const 左值和 const 右值,都匹配到了 const 左值引用版本的 func 函数。出现该现象的原因是:右值被引用之后会被储存到特定的位置,而这个储存位置在函数参数中是可以被取地址和修改的,因此实际上已经退化为左值了。

如果想要在传递右值时保持其右值属性,即保持它的临时性质和不能取地址的特定,就需要使用完美转发。完美转发可以将函数原封不动地转发给另一个函数,这样就可以保持参数的原有属性。通过使用 std::forward 函数,可以根据参数的类型来选择调用哪一个版本的函数,从而实现参数的完美转发。

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

// std::forward<T>(t); 在传参的过程中保持了t的原生类型属性
template<class T>
void PerfectForward(T&& t)
{
	func(std::forward<T>(t));
}

运行结果如下:

在这里插入图片描述

注意:在使用完美转发时,需要谨慎使用,避免出现悬垂引用(dangling reference)的问题。悬垂引用是指引用了一个已经被销毁的对象的引用,这回导致未定义的行为。

完美转发实际中的使用场景

下面模拟实现了一个简单的 list 类,类中分别提供了左值引用版本和右值引用版本的 push_back 和 insert 函数:

namespace hyr
{
	template<class T>
	struct ListNode
	{
		T _data;
		ListNode* _next = nullptr;
		ListNode* _prev = nullptr;
	};
	template<class T>
	class list
	{
		typedef ListNode<T> node;
	public:
		list()
		{
			_head = new node;
			_head->_next = _head;
			_head->_prev = _head;
		}
		
		// 左值引用版本
		void push_back(const T& x)
		{
			insert(_head, x);
		}
		
		// 右值引用版本
		void push_back(T&& x)
		{
			insert(_head, std::forward<T>(x)); 
		}
		
		// 左值引用版本
		void insert(node* pos, const T& x)
		{
			node* prev = pos->_prev;
			node* newnode = new node;
			newnode->_data = x;

			prev->_next = newnode;
			newnode->_prev = prev;
			newnode->_next = pos;
			pos->_prev = newnode;
		}
		
		// 右值引用版本
		void insert(node* pos, T&& x)
		{
			node* prev = pos->_prev;
			node* newnode = new node;
			newnode->_data = std::forward<T>(x);

			prev->_next = newnode;
			newnode->_prev = prev;
			newnode->_next = pos;
			pos->_prev = newnode;
		}
	private:
		node* _head; 
	};
}

下面定义了一个 list 对象,该对象中存储的数据是之前定义的 string 类,调用 push_back 函数向对象中插入数据,在函数中分别传入了左值和右值版本的数据,如下:

int main()
{
	hyr::list<hyr::string> lt;
	hyr::string s("111"); 
	lt.push_back(s);      // 调用左值引用版本的push_back

	lt.push_back("222");  // 调用右值引用版本的push_back
	return 0;
}

在调用右值引用版本的 push_back 函数时,会调用 string 类的移动赋值进行资源的移动,而为了保持原有的右值属性,需要在函数调用时使用完美转发,保持其右值属性不变。这样,在调用 insert 函数时,就可以调用右值引用版本的 insert 函数,从而在右值引用版本的 insert 函数中用右值给新结点赋值时也需要用到完美转发,保持右值属性不变,最终调用移动赋值函数进行资源的移动。即在 STL 库中,也会使用完美转发来保持右值的属性。

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

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

相关文章

个人器件库整理

样品本 包含如下&#xff1a; 电容器件&#xff1a; 元件值封装备注钽电容47uF 10V1206钽电容10uF 10V1206电容10uF 10% 10V0603X5R&#xff0c;CL10A106KP8NNNC 元件值封装备注100nF电容50V&#xff0c;10%0603 电阻器件&#xff1a; 元件值封装备注75 Ω \Omega Ω…

2023-06-05 stonedb-在派生表的场景查询为空无法传递默认值-问题分析

摘要: stonedb-在派生表的场景查询为空无法传递默认值-问题分析. 本文对该问题的成因, 相关功能的代码设计, 在下一步设计时如何应对这种问题, 做相关的分析。 https://stoneatom.yuque.com/staff-ft8n1u/lsztbl/rxlhws22n0f1otxn/edit#AqyB 相关ISSUE: https://github.com…

sql server 内存知识

SQL Server对服务器内存的使用策略是用多少内存就占用多少内存&#xff0c;只用在服务器内存不足时&#xff0c;才会释放一点占用的内存&#xff0c;至少释放多少&#xff0c;完全由sql server控制&#xff0c;所以SQL Server 服务器内存往往会占用很高。 SQL Server提供数据库…

华为OD机试真题 Java 实现【一种字符串压缩表示的解压】【2022Q4 100分】,附详细解题思路

一、题目描述 有一种简易压缩算法&#xff1a;针对全部由小写英文字母组成的字符串&#xff0c;将其中连续超过两个相同字母的部分压缩为连续个数加该字母&#xff0c;其他部分保持原样不变。例如&#xff1a;字符串“aaabbccccd”经过压缩成为字符串“3abb4cd”。 请您编写解…

基于深度学习的视频美颜SDK技术创新与应用案例分析

很多人在拍摄视频时会感到自己的皮肤不够好看&#xff0c;因此需要使用美颜功能。同时&#xff0c;视频美颜也是很多短视频App的核心功能之一。为了提供更加高效、准确的视频美颜功能&#xff0c;很多公司开始研发基于深度学习的视频美颜SDK技术。 与传统的图像处理技术相比&a…

kafka 安装快速入门

直接上干货&#xff0c;我们公司最近要进行消息推送指定软件kafka,直接走起。 1.下载 kafka 是apache的项目。下载地址&#xff1a;kafka.apache.org/ 点击download kafka 进入查看相关版本进行下载。 我这里用的版本比窘旧一点&#xff0c;公司技术一切求稳。 下载好安装包就已…

论文笔记:Normalizing Flows for Probabilistic Modeling and Inference

Abstract 正则流&#xff08;Normalizing flows&#xff09;提供了一种通用的机制来定义富有表达力的概率分布&#xff0c;只需要指定一个&#xff08;通常简单的&#xff09;基础分布和一系列可逆变换。 Intraduction 正则流通过将简单的密度通过一系列变换来产生更丰富、可…

怎么选择适合爬虫的代理IP,使用时需要注意什么

网络爬虫工作离不开代理服务器的支持&#xff0c;但并不是所有的代理服务器都适合爬虫工作。那么如何选择适合爬虫的代理服务器呢&#xff1f; 选择适合爬虫的代理服务器需要考虑以下几个方面&#xff1a; 1、代理服务器的稳定性&#xff1a;稳定可靠的代理服务器更能够保证爬虫…

JPEG压缩基本原理

JPEG算法的第一步是将图像分割成8X8的小块。 在计算机中&#xff0c;彩色图像最常见的表示方法是RGB格式&#xff0c;通过R(Red)、G(Green)A和(Blue)组合出各种颜色。 除此以外&#xff0c;还有一种表示彩色图像的方法&#xff0c;称为YUV格式。Y表示亮度&#xff0c;U和V表示…

【C++】一文带你吃透C++继承

&#x1f34e; 博客主页&#xff1a;&#x1f319;披星戴月的贾维斯 &#x1f34e; 欢迎关注&#xff1a;&#x1f44d;点赞&#x1f343;收藏&#x1f525;留言 &#x1f347;系列专栏&#xff1a;&#x1f319; C/C专栏 &#x1f319;那些看似波澜不惊的日复一日&#xff0c;…

Docker attach VS exec

我们知道&#xff0c;进入容器常用的两种方式为&#xff1a;docker exec ...、docker attach ...&#xff0c;那这两者有什么区别呢&#xff1f; 首先&#xff0c;运行一个测试容器&#xff0c;并在启动容器时运行相关指令&#xff0c;如下&#xff1a; docker run --name te…

JVM学习笔记一

程序计数器是一块儿较小的内存, 请你谈谈你对JVM的理解?java8虚拟机和之前的有什么变化更新?什么是OOM?什么是栈溢出(StackOverFlowError)?怎么分析JVM的常用调优参数?内存快照如何抓取?怎么分析Dump文件?谈谈JVM中类加载器你的认识?JVM的位置JVM的体系结构类加载器双…

科研热点|科研人专属身份证来了,国产ORCID ID启动!

2023年6月1日&#xff0c;国家自然科学基金委员会发布了《国家自然科学基金委员会关于推广和发布基础研究科研人员标识&#xff08;BRID&#xff09;有关工作安排的通告》&#xff0c;宣布从即日起&#xff0c;国家自然科学基金委员会&#xff08;以下简称自然科学基金委&#…

高完整性系统(4)Formal Logic (形式逻辑和 Alloy 简介)

文章目录 Story so far形式逻辑命题 proposition谓词 predicate连接词VariablesSet 集合Set operation 集合操作Set Relationship 集合关系Alloy Set alloy 的集合表示Quantification 量词Relations 关系案例Binary Relations 二元关系图Functions 函数Total v.s. Partial Func…

IO模型、select、poll、epoll

阻塞IO模型 阻塞IO是最通用的IO类型&#xff0c;使用这种模型进行数据接收的时候&#xff0c;在数据没有到之前程序会一直等待。例如&#xff0c;对于函数recvfrom(),内核会一直阻塞该请求直到有数据到来才返回。 非阻塞IO模型 当把套接字设置成非阻塞的IO,则对每次请求&…

Java网络开发(Tomcat)——遇到的 bug 汇总(持续更新)

目录 引出:bug::bug::bug:Tomcat开发的bug汇总项目启动就报错1.WebServlet()路径配置的问题2.由于之前的错误&#xff0c;Context[/day01]启动失败【困扰】3.启动过滤器异常---init方法 JSP使用相关报错1.后端传给jsp的数据&#xff0c;前端jsp不显示2.jsp的包没有导&#xff0…

6 vue

前端开发 1.前端开发 前端工程师“Front-End-Developer”源自于美国。大约从2005年开始正式的前端工程师角色被行业所认可&#xff0c;到了2010年&#xff0c;互联网开始全面进入移动时代&#xff0c;前端开发的工作越来越重要。 最初所有的开发工作都是由后端工程师完成的&…

‘jupyter‘ 不是内部或外部命令,也不是可运行的程序或批处理文件。

目录 0.问题背景环境介绍 1.解决步骤 2.测试步骤 0.问题背景环境介绍 1&#xff09;环境&#xff1a;windows64 2&#xff09;问题背景&#xff1a;在搭建jupyter notebook的过程中&#xff0c;想用windows的任务管理器启动jupyter notebook或者使用【jupyter notebook --…

降低成本,快速搭建企业帮助文档的方法盘点

企业帮助文档是企业为了解决客户疑问和提高客户满意度而制作的一种文档&#xff0c;通常包括产品的使用指南、故障排除、常见问题解答等内容。一个好的帮助文档可以帮助企业降低客服成本、提高客户满意度&#xff0c;进而提高产品销量和企业品牌形象。但是&#xff0c;有些企业…

基于html+css的图展示108

准备项目 项目开发工具 Visual Studio Code 1.44.2 版本: 1.44.2 提交: ff915844119ce9485abfe8aa9076ec76b5300ddd 日期: 2020-04-16T16:36:23.138Z Electron: 7.1.11 Chrome: 78.0.3904.130 Node.js: 12.8.1 V8: 7.8.279.23-electron.0 OS: Windows_NT x64 10.0.19044 项目…