23.右值引用_c++11(左值引用的使用场景、右值引用的使用场景、左值引用和右值引用的对比、移动构造、移动赋值、右值引用完美转发)

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

4.右值引用

4.1 左值引用和右值引用

什么是左值?什么是左值引用?
左值是一个表示数据的表达式(如变量名或解引用的指针),我们可以获取它的地址并且可以对它赋值,左值可以出现赋值符号的左边(也可以出现在赋值符号的右边),右值不能出现在赋值符号左边(只可以出现在符号的右边)。定义时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
#include<iostream>// 一个函数模板
template<class T>
T fmin(T  x, T y)
{if (x < y){return x;}return y;	
}int main()
{double x = 1.1, y = 2.2;// 以下几个都是常见的右值// 字面常量:10;// 表达式的返回值 x + y;// 函数返回值 fmin(x, y);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;return 0;
}
  • 案例2
int main()
{// 左值引用只能引用左值,不能引用右值。int a = 10;// ra1为a的别名,a是左值int& ra1 = a;  // 编译失败,因为10是右值,右值是不可以被左值引用的//int& ra2 = 10;   // const修饰的左值引用,既可引用左值,也可引用右值。const int& ra3 = 10;  const int& ra4 = a;	  // 右值引用只能右值,不能引用左值。int&& r1 = 10;// error C2440: “初始化”: 无法从“int”转换为“int &&”// message : 无法将左值绑定到右值引用int a = 10;// int&& r2 = a; // 报错// move()左值之后,编译器会将其识别为右值// 右值引用可以引用move()以后的左值int&& r3 = std::move(a);return 0;
}
  • 案例3

需要注意的是右值是不能取地址的,但是给右值取别名后,会导致右值被存储到特定位置,且可以取到该位置的地址,也就是说例如:不能取字面量10的地址,但是rr1引用后,可以对rr1取地址,也可以修改rr1。如果不想rr1被修改,可以用const int&& rr1 去引用,这个了解一下实际中右值引用的使用场景并不在于此,这个特性也不重要。

int main()
{double x = 1.1, y = 2.2;// 右值引用之后,会导致右值被存储到特定位置(此时我们就可以对rr1进行修改,不过修改的是特定位置的变量,此时我们可以认为rr1就是一个左值),且可以取到该位置的地址int&& rr1 = 10;//  const修饰rr2之后,则rr2不可以被修改了const double&& rr2 = x + y;rr1 = 20;// rr2 = 5.5;  // 报错return 0;
}

4.2左值引用与右值引用比较

左值引用总结:

  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;// error C2440: “初始化”: 无法从“int”转换为“int &&”// message : 无法将左值绑定到右值引用int a = 10;int&& r2 = a;// 右值引用可以引用move以后的左值int&& r3 = std::move(a);return 0;
}

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

左值引用的意义是什么?

1.函数传参或者是函数传返回值,使用左值引用,可以减少参数的拷贝
但是左值引用并没有完全解决问题,例如以下场景

  • 场景1
// 场景1:左值引用可以解决函数传参,减少参数的拷贝
// const引用,既可以接收左值,也可以接收右值
template<class T>
void func1(const T& x)
{}int main()
{// v1为左值vector<int> v1(10, 0);func1(v1);// vector<int>(10,0) 是一个匿名对象,也就是一个右值(属于将亡值)// 出了当前这一行,这个匿名对象就会被释放func1(vector<int>(10, 0));return 0;
}
  • 场景2:
// 场景2:函数传返回值,
// x的声明周期是直到main()返回,才会被销毁
// 因此我们才可以使用左值引用返回,如果x出func2()就被销毁,那么是不可以使用左值引用返回的
template<class T>
const T& func2(const T& x)
{// ...假设中间还做了许多操作return x;
}int main()
{// v1为左值vector<int> v1(10, 0);func2(v1);return 0;
}
  • 场景3
// 场景三:左值引用尚未解决的问题场景
// 当ret出func3()的函数作用于就会被销毁,那么我们是不可以使用左值引用返回的(这是因为引用的变量已经被销毁了)
// 因此,此时ret返回时,就会产生临时变量,就会增加参数ret的拷贝
// 右值引用的价值之一:就是补齐这个最后一块短板,传值返回的拷贝问题
template<class T>
T func3(const T& x)
{T ret;// ...return ret;
}int main()
{// v1为左值vector<int> v1(10, 0);func3(v1);return 0;
}
  • 场景4
// 但是其实也可以使用左值引用来解决场景三的问题
// 就是使用输出型参数,但是这样使用起来是很别扭的
// 假设ret的类型就是int
// 使用了输出型参数就不需要进行返回了
template<class T>
void func4(const T& x, int& ret)
{// ...//return ret;
}int main()
{// v1为左值vector<int> v1(10, 0);int ret = 10;// ret是一个输出型参数func4(v1,ret);return 0;
}

右值引用是怎样解决左值引用的短板的?

// 首先,我们先来看下面代码运行时,其底层的拷贝原理
#include<iostream>
#include<assert.h>
using namespace std;namespace qwy
{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(const string& s){cout << "string(const string& s) -- 深拷贝" << endl;string tmp(s._str);swap(tmp);}// 赋值重载string& operator=(const string& s){cout << "string& operator=(string s) -- 深拷贝" << endl;string tmp(s);swap(tmp);return *this;}~string(){delete[] _str;_str = nullptr;}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){push_back(ch);return *this;}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变为正数value = 0 - value;}qwy::string str;while (value > 0){// 获取value的个位int x = value % 10;  // 获取value的十位及以上value /= 10;str += ('0' + x); // 将x转化为对应的ascll值,并放入str}if (flag == false){str += '-';}// 逆转string对象中字符串的顺序std::reverse(str.begin(), str.end());return str;}
}
场景一
int main()
{// 场景1:// to_string()的返回值str,会先将其拷贝给一个临时变量,再将临时变量拷贝给ret// 但是编译器会自动对其进行优化,将两次拷贝简化为一次拷贝// 如下图所示qwy::string ret = qwy::to_string(-1234);return 0;
}
  • 打印结果为:string(const string& s) – 深拷贝
  • 通过打印结果我们可知,只调用了一次深拷贝,符合我们预期的结果

image-20230415141547347

场景二
// 场景2:
int main()
{// 对于场景二:编译器不敢将两次深拷贝优化为一次深拷贝// 优化的规定一般为:传参或传返回值过程中,存在连续的构造、拷贝构造、就会被优化// 但是具体是取决于编译器的// 由于编译器对于如下这种情况是不进行优化的,// 因此to_string()的返回值str会先拷贝给临时变量,再由临时变量赋值给ret,但是赋值重载的过程中,会调用拷贝函数qwy::string ret;// 在上面创建string对象ret和下面对ret进行赋值之间,可能对ret进行了其他操作,因此编译器不敢进行优化ret = qwy::to_string(-1234);return 0;
}

注:编译器没有进行优化

第一步:拷贝给临时变量,调用了一次拷贝构造,由临时变量赋值给ret,调用了赋值重载,赋值重载的函数内部调用了一次拷贝构造

打印结果为:

string(const string& s) – 深拷贝

string& operator=(string s) – 深拷贝

string(const string& s) – 深拷贝

4.4移动构造和移动赋值

// 移动构造
// 所谓的移动构造就是将string对象的右值引用s与将要构造的对象进行资源交换
// 这样是不需要在移动构造内部创建新的对象
string(string&& s)
{cout << "string(const string& s) -- 移动拷贝" << endl;swap(s);
}// 移动赋值
string& operator=(string&& s)
{cout << "string& operator=(string s) -- 移动赋值" << endl;swap(s);return *this;
}		   
#include<iostream>
#include<assert.h>
using namespace std;namespace qwy
{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(const string& s){cout << "string(const string& s) -- 深拷贝" << endl;string tmp(s._str);swap(tmp);}// 赋值重载string& operator=(const string& s){cout << "string& operator=(string s) -- 深拷贝" << endl;string tmp(s);swap(tmp);return *this;}// 移动构造// 所谓的移动构造就是将s与将要构造的对象进行资源交换string(string&& s){cout << "string(const string& s) -- 移动拷贝" << endl;swap(s);}// 移动赋值string& operator=(string&& s){cout << "string& operator=(string s) -- 移动赋值" << endl;swap(s);return *this;}~string(){delete[] _str;_str = nullptr;}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){push_back(ch);return *this;}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变为正数value = 0 - value;}qwy::string str;while (value > 0){int x = value % 10;  // 获取value的个位value /= 10; // 获取value的十位及以上str += ('0' + x); // 将x转化为对应的ascll值,并放入str}if (flag == false){str += '-';}std::reverse(str.begin(), str.end());return str;}
}
场景一:右值引用解决了拷贝构造问题(只是对于右值传参拷贝的问题)
//  使用移动拷贝进一步提升构造的效率(移动构造的效率的大于拷贝构造的)
//  这就是右值引用解决了拷贝构造问题(只是对于右值传参拷贝的问题)
int main()
{qwy::string s1("hello world");// 用s1来构造s2// s1是左值,因此编译器会默认调用左值引用传参的拷贝构造qwy::string s2(s1);// 左值被move()之后,编译器会将其看作是右值// move(s1)是右值,编译器会默认调用右值引用传参的移动构造// 如果没有右值引用传参的移动构造,move(s1)也是可以被左值引用传参的拷贝构造调用qwy::string s3(move(s1));return 0;
}

打印结果为:
string(const string& s) – 深拷贝
string(const string& s) – 移动拷贝

image-20240506135630271

场景二:利用右值引用解决赋值问题
int main()
{// 根据优化的规则:传参或传返回值过程中,存在连续的构造、拷贝构造、就会被优化// 因此对于如下的情况,编译器并不会进行优化// 1.编译器会将to_string()的返回值默认识别为右值,并将其移动拷贝给临时变量,// 2.再将临时变量移动赋值给retqwy::string ret;ret = qwy::to_string(-1234);return 0;
}

打印结果:

string(const string& s) – 移动拷贝

string& operator=(string s) – 移动赋值

场景三:
int main()
{list<qwy::string> lt;qwy::string s1("111111");// s1是左值,调用拷贝构造lt.push_back(s1);// "222222" 是右值(字面常量),调用移动拷贝// 如果我们没有实现移动拷贝,那么就会调用拷贝构造lt.push_back(qwy::string("222222"));// "333333" 会先构造一个string的对象,再调用移动拷贝// 如果我们没有实现移动拷贝,那么就会调用拷贝构造lt.push_back("333333");return 0;
}

打印结果
string(const string& s) – 深拷贝
string(const string& s) – 移动拷贝
string(const string& s) – 移动拷贝

总结

右值引用和左值引用减少拷贝的原理不太一样

1.左值引用是取别名,直接起作用。

2.右值引用是间接起作用,实现移动构造和移动赋值,在拷贝的场景中,如果是右值(将亡值),转义资源

4,5右值引用引用左值及其一些更深入的使用场景分析

  • 场景1:
#include<iostream>
using namespace std;void func1(int& x) 
{ cout << "void func1(int& x)" << endl; 
}void func2(int&& x) 
{ cout << "void func2(int&& x)" << endl; 
}int main()
{int x = 1;// 左值调用对应的func1()func1(x);// 右值调用对应的func2()func2(2);return 0;
}
// 打印结果为:
// void func1(int& x)
// void func2(int&& x)
  • 场景二
void func2(int&& x) 
{ cout << "void func2(int&& x)" << endl; 
}int main()
{int x = 1;func2(2);func1(x); // 此处会报错,右值引用无法引用左值return 0;
}   
  • 模板中的&&万能引用
// 使用模板中的&&万能引用就可以解决场景二中:左值不可以被右值引用的问题了// 模板的右值引用我们可以认为是万能引用,
// 既可以引用右值,也可以引用左值
template<typename T>
void PerfectForward(T&& t)
{// t是否可以被加加,详情请看下图// t++;
}int main()
{perfectforward(10);           // 右值int a;perfectforward(a);            // 左值perfectforward(std::move(a)); // 右值// 且万能引用既可以引用const的左值,也可以引用const的右值const int b = 8;PerfectForward(b);		      // const 左值PerfectForward(std::move(b)); // const 右值return 0;
}    

image-20230415225029477

  • 万能引用存在的问题
// 万能引用还存在如下的问题:
#include<iostream>
using namespace std;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)
{// T&& t  不论是对左值引用,还是对右值引用,t本身都是一个左值// 因此调用Fun()函数,始终会调用参数为左值引用的Fun()函数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;
}
//  打印结果为:
// 左值引用
// 左值引用
// 左值引用
// const 左值引用
// const 左值引用
  • 解决万能引用存在的问题
// 使用完美转发来解决上述的问题
// 具体如下:
#include<iostream>
using namespace std;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)
{// 完美转发,保持t的本源属性,如果t是对左值的引用,那么就保留t为左值的属性,反之保留t为右值的属性Fun(std::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;
}
// 打印结果为:
// 右值引用
// 左值引用
// 右值引用
// const 左值引用
// const 右值引用

对list类的改造(移动构造、移动赋值)(内部使用了完美转发)

// 关于list的封装
namespace qwy
{// 链表节点的类模板template<class T>struct list_node{list_node* _next;list_node* _prev;T _data;list_node(const T& x):_next(nullptr), _prev(nullptr), _data(x){}// 此处右值传参之后,x是左值,为了需要保证x的原生类型属性,因此我们需要使用完美转发// 来保证x的原生类型属性(此处使用了完美转发)list_node(T&& x):_next(nullptr), _prev(nullptr), _data(std::forward<T>(x)){}};// 迭代器的类模板template<class T, class Ref, class Ptr>struct __list_iterator{typedef list_node<T> node;typedef __list_iterator<T, Ref, Ptr> Self;node* _pnode;__list_iterator(node* p):_pnode(p){}Ptr operator->(){return &_pnode->_data;}Ref operator*(){return _pnode->_data;}Self& operator++(){_pnode = _pnode->_next;return *this;}Self operator++(int){Self tmp(*this);_pnode = _pnode->_next;return tmp;}Self& operator--(){_pnode = _pnode->_prev;return *this;}Self operator--(int){Self tmp(*this);_pnode = _pnode->_prev;return tmp;}bool operator!=(const Self& it) const{return _pnode != it._pnode;}bool operator==(const Self& it) const{return _pnode == it._pnode;}};template<class T>class list{typedef list_node<T> node;public:typedef __list_iterator<T, T&, T*> iterator;iterator begin(){return iterator(_head->_next);}iterator end(){return iterator(_head);}void empty_initialize(){_head = new node(T());_head->_next = _head;_head->_prev = _head;_size = 0;}list(){empty_initialize();}void swap(list<T>& lt){std::swap(_head, lt._head);std::swap(_size, lt._size);}// 拷贝构造list(const list<T>& lt){empty_initialize();list<T> tmp(lt.begin(), lt.end());swap(tmp);}size_t size() const{return _size;}bool empty() const{return _size == 0;}~list(){clear();delete _head;_head = nullptr;}void clear(){iterator it = begin();while (it != end()){it = erase(it);}}// 左值引用的push_backvoid push_back(const T& x){insert(end(), x);}// 右值引用的push_backvoid push_back(T&& x){// 此处右值传参之后,x是左值,为了需要保证x的原生类型属性,因此我们需要使用完美转发// 来保证x的原生类型属性(此处,使用了完美转发)insert(end(), std::forward<T>(x));}// 左值引用的insertiterator insert(iterator pos, const T& x){node* newnode = new node(x);node* cur = pos._pnode;node* prev = cur->_prev;prev->_next = newnode;newnode->_prev = prev;newnode->_next = cur;cur->_prev = newnode;++_size;return iterator(newnode);}// 右值引用的insertiterator insert(iterator pos, T&& x){// 此处右值传参之后,x是左值,为了需要保证x的原生类型属性,因此我们需要使用完美转发// 来保证x的原生类型属性(此处使用了完美转发)node* newnode = new node(std::forward<T>(x));node* cur = pos._pnode;node* prev = cur->_prev;prev->_next = newnode;newnode->_prev = prev;newnode->_next = cur;cur->_prev = newnode;++_size;return iterator(newnode);}iterator erase(iterator pos){assert(pos != end());node* prev = pos._pnode->_prev;node* next = pos._pnode->_next;prev->_next = next;next->_prev = prev;delete pos._pnode;--_size;return iterator(next);}private:node* _head;size_t _size;};
}

image-20230416102036977

使用list::push_back
int main()
{qwy::list<qwy::string> lt;qwy::string s1("111111");lt.push_back(s1);lt.push_back(qwy::string("222222"));lt.push_back("333333");return 0;
}

打印结果为
string(const string& s) – 移动拷贝
string(const string& s) – 深拷贝
string(const string& s) – 移动拷贝
string(const string& s) – 移动拷贝

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

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

相关文章

2024年常用的几款透明加密软件大全

文件透明加密软件是一种特殊类型的加密工具&#xff0c;它们能够在后台自动加密和解密文件&#xff0c;对用户来说&#xff0c;加密和解密是透明的&#xff0c;不需要额外的操作。以下是几种常见的文件透明加密软件&#xff1a; 1、Ping32&#xff1a; Ping32透明加密软件还具…

4000定制网站,因为没有案例,客户走了

接到一个要做企业站点的客户&#xff0c;属于定制开发&#xff0c;预算4000看起来是不是还行的一个订单&#xff1f; 接单第一步&#xff1a;筛客户 从客户询盘的那一刻开始就要围绕核心要素&#xff1a;预算和工期&#xff0c;凡是不符合预期的一律放掉就好了&#xff0c;没必…

Python | Leetcode Python题解之第74题搜索二维矩阵

题目&#xff1a; 题解&#xff1a; class Solution:def searchMatrix(self, matrix: List[List[int]], target: int) -> bool:row,col len(matrix),len(matrix[0])row_l,row_r 0,row-1while row_l < row_r:m (row_lrow_r)//2if target < matrix[m][0]:row_r m-1…

[已解决]oneforAll ImportError: cannot import name ‘sre_parse‘ from ‘re‘

在使用 oneforall中&#xff0c;配置环境时出现了这个报错&#xff1a; 实际上是因为高版本python中re模块没有了sre_parse模块&#xff0c;可以修改exrex.py 代码&#xff0c;直接导入sre_parse模块&#xff0c;如下&#xff1a;

Pspice for TI学习

Pspice for TI中PSpice Part Search空白解决方法 配置环境变量 Cad_PSpice_TI_Regr_Srvr https://software-dl.ti.com/pspice/S009 重新安装2023版的Pspice Pspice安装链接 打开新安装的软件即可发现PSpice Part Search可以正常使用了 VSIN各参赛的含义 VOFF直流偏置VAMPL…

重生奇迹MU获取宝石方法

1、商城&#xff1a;商场分为钻石商城和绑钻商城&#xff0c;钻石是直接充值的&#xff0c;绑钻是系统赠送的&#xff0c;两个地方所出售的道具都不一样&#xff0c;绑钻不能在钻石商城中购买。钻石商城中有各个等级的宝石出售&#xff0c;越高级的钻石越贵&#xff0c;不建议平…

网络安全之ACL

ACL&#xff1a;访问控制列表——控制列表&#xff08;策略列表&#xff09;&#xff0c;是一个控制工具。 功能&#xff1a;&#xff01;、定义感兴趣路由&#xff08;控制层面&#xff09;。2、定义感兴趣流量&#xff08;数据层面&#xff09;。 例如&#xff1a; 假设在该…

Python 中 “yield“ 的不同行为

在我们使用Python编译过程中&#xff0c;yield 关键字用于定义生成器函数&#xff0c;它的作用是将函数变成一个生成器&#xff0c;可以迭代产生值。yield 的行为在不同的情况下会有不同的效果和用途。 1、问题背景 在 Python 中&#xff0c;“yield” 是一种生成器&#xff0…

武汉星起航:亚马逊欧洲站:丰富商品与卓越服务铸就高客单价典范

亚马逊&#xff0c;作为全球最大的电商平台之一&#xff0c;其欧洲站在全球电商市场中占据着举足轻重的地位。其中&#xff0c;亚马逊欧洲站的人均客单价更是高居世界前列&#xff0c;这背后究竟隐藏着哪些原因呢&#xff1f; 首先&#xff0c;亚马逊具有丰富且高质量的商品品…

设计模式:命令模式

文章目录 一、什么是命令模式二、命令模式结构三、命令模式实现步骤四、命令模式应用场景 一、什么是命令模式 它允许将请求封装为对象&#xff0c;一个请求对应于一个命令&#xff0c;将发出命令的责任和执行命令的责任分割开。每一个命令都是一个操作&#xff1a;请求的一方…

【@ohos.events.emitter (Emitter)】

ohos.events.emitter (Emitter) 本模块提供了在同一进程不同线程间&#xff0c;或同一进程同一线程内&#xff0c;发送和处理事件的能力&#xff0c;包括持续订阅事件、单次订阅事件、取消订阅事件&#xff0c;以及发送事件到事件队列的能力。 说明&#xff1a; 本模块首批接…

zlib编译后静态库调用时遇到的无法解析的外部符号问题

编译zlib的静态库后引用到项目中使用&#xff0c;发现报下面的链接错误&#xff1a; error LNK2019: 无法解析的外部符号 _zlibVersion error LNK2019: 无法解析的外部符号 _deflateEnd error LNK2019: 无法解析的外部符号 _deflate error LNK2019: 无法解析的外部符号 _deflat…

什么牌子充电宝质量耐用且性价比高、充电宝性价比高品牌合集

在现代社会&#xff0c;手机几乎成了我们生活中不可或缺的一部分。无论是工作、学习还是娱乐&#xff0c;我们都离不开手机的陪伴。然而&#xff0c;随着手机使用频率的增加&#xff0c;电量焦虑也逐渐成为了我们共同面临的问题。出门时&#xff0c;我们可能会忘记带钱包&#…

已解决-你需要trustedinstaller权限才能执行此操作

欢迎关注我👆,收藏下次不迷路┗|`O′|┛ 嗷~~ 目录 一.前言 二.解决方法 一.前言 我想删除C:\Windows\System32\drivers文件夹下面的内容,可是报错: 怎么办呢? 二.解决方法 右键要删除的文件

OpenCV 入门(六) —— Android 下的人脸识别

OpenCV 入门系列&#xff1a; OpenCV 入门&#xff08;一&#xff09;—— OpenCV 基础 OpenCV 入门&#xff08;二&#xff09;—— 车牌定位 OpenCV 入门&#xff08;三&#xff09;—— 车牌筛选 OpenCV 入门&#xff08;四&#xff09;—— 车牌号识别 OpenCV 入门&#xf…

二手手机店需要用专业erp软件进行管理吗?

2024年在科技发展迅速的今天&#xff0c;手机批发和零售行业正迎来前所未有的革新。为了在这个变革中抓住机遇&#xff0c;提升竞争力&#xff0c;二手手机店需要寻找一种全面、高效、智能的管理工具。基于这个原因&#xff0c;超机商城为众多二手手机店商家量身打造的一款二手…

水电站泄洪安全声光预警广播系统建设方案

一、水电站泄洪安全声光预警广播系统建设方案背景 水电站建成运行以后&#xff0c;会使河道水文情势发生改变&#xff0c;为了加强水电站工程安全管理&#xff0c;保证水库泄洪放水工作安全有序进行&#xff0c;保护下游河道沿岸人民群众生命和财产安全&#xff0c;根据《中华…

DevSecOps 是什么?你知道吗?

使用工具自动进行安全检查和扫描。这些工具包括静态应用程序安全测试 (SAST)、动态应用程序安全测试 (DAST) 和依赖性扫描。 什么是 DevSecOps&#xff1f; DevSecOps 是 DevOps 实践的自然演进&#xff0c;其重点是将安全集成到软件开发和部署流程中。 DevSecOps 一词代表了…

elk + filebeat 8.4.3 收集nginx日志(docker部署)

ELK filebeat docker部署 一、 elasticsearch部署1、运行elasticsearch临时配置容器2、拷贝文件目录到本地3、检查elasticsearch.yml4、删除之前elastic&#xff0c;运行正式容器5、docker logs记录启动日志 二、部署kibana1、运行kibana临时配置容器2、docker拷贝配置文件到本…

MySQL —— 数据类型

一、数值类型 以上表格整理了用来表示数值类型的数据类型&#xff0c;其中&#xff0c;接下来将介绍和展示其中几个类型的使用和各种细节 1.tinyint 越界测试&#xff1a;建立一个包含tinyint类型的表格&#xff0c;插入各中数据去查看结果&#xff0c;并且尝试插入边界数据和…