【C++11】右值引用 | 移动构造赋值 | 万能引用 | 完美转发

文章目录

  • 一、引言
  • 二、左值和右值
      • 什么是左值
      • 什么是右值
  • 三、左值引用和右值引用
      • 左值引用
      • 右值引用
      • 左值引用与右值引用的比较
  • 四、右值引用的使用场景和意义
      • 左值引用的使用场景
      • 左值引用的短板
      • 用右值引用和移动语义解决上述问题
          • 移动构造
          • 移动赋值
      • 右值引用引用左值 - std::move()
      • STL容器的接口函数更新了右值引用的版本
  • 五、完美转发
      • 模板中的“&&”是万能引用
      • std::forward()实现完美转发
      • 完美转发的使用场景


一、引言

传统的C++语法中就有引用的语法,而C++11中新增了的右值引用语法特性,所以从现在开始,为了与右值引用(rvalue reference)区分开来,我们可以称之为左值引用 (lvalue reference)无论左值引用还是右值引用,都是给对象取别名



二、左值和右值

在了解右值引用之前,有必要先区分左值和右值。C++的表达式要么是右值( rvalue ,读作“ are-value ”),要么就是左值( lvalue ,读作 "ell-value ” )。

这两个名词是从 C 语言继承过来的,原本是为了帮助记忆:
左值可以位于赋值语句的左侧,右值则不能。

什么是左值

左值是一个表示数据的表达式(如变量名或解引用的指针/迭代器)

  • 我们可以获取左值的地址且可以对左值赋值。
  • 左值可以出现赋值符号的左边,而右值不能出现在赋值符号左边。
  • 定义被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;
}

右值引用

右值引用就是对右值的引用,给右值取别名。

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);
	
	// 特例:可以将一个const左值引用绑定到一个右值上
	const int& cref = 10;
	
	// 下面三个编译会报错:error C2106: “=”: 左操作数必须为左值
	10 = 1;
	x + y = 1;
	fmin(x, y) = 1;
	return 0;
}

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

左值引用总结:

  1. 左值引用只能引用左值,不能引用右值。
    int a = 10;
    int& ra1 = a;  // 正确,ra为a的别名
    int& ra2 = 10; // 编译失败,因为10是右值
    
  2. 但是const左值引用既可引用左值,也可引用右值。
    const int& ra3 = 10; // 正确,ra3引用了右值10
    const int& ra4 = a;  // 正确,ra4引用了左值a
    

右值引用总结:

  1. 右值引用只能右值,不能引用左值。
    // 右值引用只能右值,不能引用左值。
    int&& r1 = 10; // 正确,10绑定到一个右值引用
    
    int a = 10;
    // error C2440: “初始化”: 无法从“int”转换为“int &&”
    int&& r2 = a; // 错误,无法将左值a绑定到右值引用r2
    
  2. 但是右值引用可以引用 std::move() 后的左值。
    int&& r3 = std::move(a); //正确,右值引用可以引用move以后的左值
    


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

前面我们可以看到左值引用既可以引用左值和又可以引用右值:

const int& cref = 10;

那为什么C++11还要提出右值引用呢?是不是化蛇添足呢?下面我们来看看左值引用的短板,右值引用是如何补齐这个短板的!下面是我们模拟实现的 std::string 类:

#pragma once
#include <cstring>
#include <cassert>
#include <iostream>
namespace chen
{
	class string
	{
	public:
		// 迭代器
		typedef char* iterator;
		iterator begin()
		{
			return _str;
		}
		iterator end()
		{
			return _str + _size;
		}

		// 默认构造
		string(const char* str = "")
			:_size(strlen(str))
			, _capacity(_size)
		{
			//std::cout << "string(char* str)" << std::endl;
			_str = new char[_capacity + 1];
			strcpy(_str, str);
		}

		void swap(string& s)
		{
			std::swap(_str, s._str);
			std::swap(_size, s._size);
			std::swap(_capacity, s._capacity);
		}

		// 拷贝构造
		string(const string& s)
			:_str(nullptr)
		{
			std::cout << "string(const string& s) -- 深拷贝" << std::endl;
			string tmp(s._str);
			swap(tmp);
		}

		// 赋值重载
		string& operator=(const string& s)
		{
			std::cout << "string& operator=(string s) -- 深拷贝" << std::endl;
			string tmp(s);
			swap(tmp);
			return *this;
		}

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

		// 移动赋值
		string& operator=(string&& s)
		{
			std::cout << "string& operator=(string&& s) -- 移动语义" << std::endl;
			swap(s);
			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) 传左值引用没有拷贝提高了效率
		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
	};
}

左值引用的使用场景

左值引用做参数和做返回值都可以提高效率,减少了深拷贝:

void func1(chen::string s)
{}

void func2(const chen::string& s)
{}

int main()
{
	chen::string s1("hello world");
	// func1和func2的调用我们可以看到左值引用做参数减少了拷贝,提高效率的使用场景和价值
	func1(s1);
	func2(s1);
	// string operator+=(char ch) 传值返回存在深拷贝
	// string& operator+=(char ch) 传左值引用没有拷贝提高了效率
	s1 += '!';
	return 0;
}

因为我们模拟实现是string类的拷贝构造函数当中打印了提示语句,因此运行代码后通过程序运行结果就知道,值传参时调用了string的拷贝构造函数:请添加图片描述

因为string的+=运算符重载函数是左值引用返回的,因此在返回+=后的对象时不会调用拷贝构造函数,但如果将+=运算符重载函数改为传值返回,那么重新运行代码后你就会发现多了一次拷贝构造函数的调用。

我们都知道string的拷贝是深拷贝,深拷贝的代价是比较高的,我们应该尽量避免不必要的深拷贝操作,因此这里左值引用起到的作用还是很明显的。

左值引用的短板

是当函数返回对象是一个函数作用域内的局部变量,它出了函数作用域就会被销毁,就不能使用左值引用返回,只能传值返回。
例如:bit::string to_string(int value) 函数中可以看到,这里只能使用传值返回:

namespace chen
{
	chen::string to_string(int value)
	{
		bool flag = true;
		if (value < 0)
		{
			flag = false;
			value = 0 - value;
		}
		chen::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()
{
	chen::string s = chen::to_string(1234);
	return 0;
}

传值返回会导致至少1次拷贝构造:
请添加图片描述

如果是一些旧一点的编译器可能是两次拷贝构造:
请添加图片描述

对于vs2022,有可能只调用一次构造函数,没错,是构造函数,极致的优化:
请添加图片描述

因为即使编译器支持 C++11,也不能确保一定会调用移动构造函数。具体调用的是拷贝构造函数还是移动构造函数,取决于编译器对于返回对象优化的实现和对移动语义的判断。

用右值引用和移动语义解决上述问题

移动构造

string中增加移动构造移动构造本质是将参数右值的资源窃取过来,占为已有,那么就不
用做深拷贝了,所以它叫做移动构造,就是“窃取”别人的资源来构造自己

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

可以这样鼓励编译器使用移动构造函数:

// chen::string s1 = chen::to_string(1234); // 由于编译器的优化,这里调用的可能是构造函数
chen::string s2 = std::move(chen::to_string(1234)); // 显式move一下来通知编译器调用移动构造,来构造s2

//运行结果:
// string(char* str) -- 构造函数
// string(string&& s) -- 移动语义
移动赋值

string类中增加移动赋值函数,再去调用to_string(1234),不过这次是将
to_string(1234)返回的右值对象赋值给ret1对象,这时调用的是移动构造。

// 移动赋值
string& operator=(string&& s)
{
	std::cout << "string& operator=(string&& s) -- 移动语义" << std::endl;
	swap(s);
	return *this;
}
chen::string s;
s = chen::to_string(1234);

//运行结果:
// string(char* str) -- 构造函数
// string(char* str) -- 构造函数
// string& operator=(string&& s) -- 移动语义

右值引用引用左值 - std::move()

按照语法,右值引用只能引用右值,但右值引用一定不能引用左值吗?因为:有些场景下,可能真的需要用右值去引用左值实现移动语义。当需要用右值引用引用一个左值时,可以通过std::move()函数将左值转化为右值。

C++11中,std::move 的定义位于头文件 <utility> 中,其定义如下:

template <class _Ty>
constexpr remove_reference_t<_Ty>&& move(_Ty&& _Arg) noexcept 
{
    return static_cast<remove_reference_t<_Ty>&&>(_Arg);
}

几点说明:

  • 这里的 remove_reference_t 是一个辅助模板,用于去除传入类型的引用。move 函数接受一个通用引用 T&&(即右值引用),并返回一个右值引用 T&&
  • move函数中_Arg参数的类型不是右值引用,而是万能引用。万能引用跟右值引用的形式一样,但是右值引用需要是确定的类型。
  • 一个左值被move以后,它的资源可能就被转移给别人了,因此要避免使用一个被move后的左值
  • move函数的名字具有迷惑性,它并不搬移任何东西,唯一的功能就是将一个左值强制转化为右值引用,然后实现移动语义。

STL容器的接口函数更新了右值引用的版本

请添加图片描述


请添加图片描述

请添加图片描述

如果list容器当中存储的是string对象,那么在调用push_back向list容器中插入元素时,可能会有如下几种插入方式:

void push_back (value_type&& val);
int main()
{
	list<chen::string> lt;
	bit::string s1("1111");
	// 这里调用的是拷贝构造
	lt.push_back(s1);
	// 下面调用都是移动构造
	lt.push_back("2222");
	lt.push_back(std::move(s1));
	return 0;
}
//运行结果:
// string(const string& s) -- 深拷贝
// string(string&& s) -- 移动语义
// string(string&& s) -- 移动语义

请添加图片描述



五、完美转发

模板中的“&&”是万能引用

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

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

右值引用和万能引用的区别是:

  • 右值引用需要是确定的类型,而万能引用是根据传入实参的类型进行推导,如果传入的实参是一个左值,那么这里的形参t就是左值引用,如果传入的实参是一个右值,那么这里的形参t就是右值引用。
  • 换句话说:右值引用的类型在声明时就已经确定,而通用引用的类型是根据传入的实参类型进行推导的:
    int&& rvalue_ref = 42;  // 右值引用,类型是 int&&
    

万能引用因此更加灵活,可以接受各种值类别的参数。

下面重载了四个Func函数,这四个Func函数的参数类型分别是左值引用、const左值引用、右值引用和const右值引用。在主函数中调用PerfectForward函数时分别传入左值、右值、const左值和const右值,在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(move(a)); //右值
	
	const int b = 20;
	PerfectForward(b);       //const 左值
	PerfectForward(move(b)); //const 右值
	
	return 0;
}

但实际调用PerfectForward()函数时传入左值和右值,最终都匹配到了左值引用版本的Func()函数,调用PerfectForward()函数时传入const左值const右值,最终都匹配到了const左值引用版本的Func函数,如下:
请添加图片描述

根本原因就是,编译器选择调用左值引用版本的Func(),通常希望对传递的对象进行修改,而将右值引用看作左值引用可以确保安全的修改,所以在PerfectForward函数中调用Func()函数时会将t识别成左值。

[!Quote] 举个简单的例子:

#include \<iostream>

void Func(int&& x) 
{
	// 在函数内部,x 被当作左值引用
	x += 10;
	std::cout << "Inside Func: " << x << std::endl;
}

int main() 
{
	int a = 5;

	// 将右值引用传递给函数
	Func(std::move(a));

	// 在这里,a 可能被移动了,但在函数外部,a 仍然是左值
	std::cout << "Outside Func: " << a << std::endl;

	return 0;
}

结果:请添加图片描述

在这个例子中,std::move(a) 将左值 a 转换为右值引用,并传递给 Func 函数。在函数内部,x 被当作左值引用,但我们仍然可以对它进行修改。在函数外部,a 仍然是左值,但在传递给函数时可能已经发生了移动。

总结:右值经过一次参数传递后其属性会退化成左值,如果想要在这个过程中保持右值的属性,就需要用到完美转发

std::forward()实现完美转发

想要在传参的过程中保留对象原生类型属性,可以使用std::forward(),比如:

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

经过完美转发后,调用PerfectForward函数时传入的是右值就会匹配到右值引用版本的Func函数,传入的是const右值就会匹配到const右值引用版本的Func函数:
请添加图片描述

完美转发的使用场景

下面模拟实现了一个简化版的list类,类当中分别提供了左值引用版本和右值引用版本的push_backinsert函数。

namespace chen
{
	template<class T>
	struct ListNode
	{
		ListNode* _next = nullptr;
		ListNode* _prev = nullptr;
		T _data;
	};

	template<class T>
	class List
	{
		typedef ListNode<T> Node;
	public:
		List()
		{
			_head = new Node;
			_head->_next = _head;
			_head->_prev = _head;
		}
		//左值引用版本的pushback
		void PushBack(const T& x)
		{
			Insert(_head, x);
		}
		//右值引用版本的pushback
		void PushBack(T&& x)
		{
			//Insert(_head, x);
			Insert(_head, std::forward<T>(x)); // 关键位置1
		}
		void PushFront(T&& x)
		{
			//Insert(_head->_next, x);
			Insert(_head->_next, std::forward<T>(x)); // 关键位置2
		}
		void Insert(Node* pos, T&& x)
		{
			Node* prev = pos->_prev;
			Node* newnode = new Node;
			newnode->_data = std::forward<T>(x); // 关键位置3
			// prev newnode pos
			prev->_next = newnode;
			newnode->_prev = prev;
			newnode->_next = pos;
			pos->_prev = newnode;
		}
		void Insert(Node* pos, const T& x)
		{
			Node* prev = pos->_prev;
			Node* newnode = new Node;
			newnode->_data = x; // 关键位置4
			// prev newnode pos
			prev->_next = newnode;
			newnode->_prev = prev;
			newnode->_next = pos;
			pos->_prev = newnode;
		}
	private:
		Node* _head;
	};
}

下面定义一个list对象,list容器中存储的就是之前模拟实现的string类,这里分别传入左值和右值来调用不同版本的push_back。

int main()
{
	chen::List<chen::string> lt;
	chen::string s("1111");
	lt.PushBack(s);      // 调用左值引用版本的push_back

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

请添加图片描述

只要想保持右值的属性,在每次右值传参时都需要用std::forward进行完美转发,实际STL库中也是通过完美转发来保持右值属性的。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

spring boot学习第十二篇:mybatis框架中调用存储过程控制事务性

1、MySQL方面&#xff0c;已经准备好了存储过程&#xff0c;参考&#xff1a;MYSQL存储过程&#xff08;含入参、出参&#xff09;-CSDN博客 2、pom.xml文件内容如下&#xff1a; <?xml version"1.0" encoding"UTF-8"?> <project xmlns"…

3.4-媒资管理之视频处理+xx-job分布式任务

文章目录 媒资管理6 视频处理6.1 需求6.1.1 总体需求6.7.3 FFmpeg 的基本使用6.7.4 视频处理工具类 6.2 分布式任务处理6.2.1 什么是分布式任务调度6.2.2 XXL-JOB介绍6.2.3 搭建XXL-JOB6.2.3.1 调度中心6.2.3.2 执行器6.2.3.3 执行任务 6.2.4 分片广播 6.3 技术方案6.3.1 作业分…

Optimism为 CQT提供价值 20 万美元的生态系统资助,以表彰其支持

Covalent Network&#xff08;CQT&#xff09; 是 Web3 生态系统中关键的“数据可用性”层&#xff0c;在与 Optimism Collective 多年的合作中取得了骄人的成果。Covalent Network&#xff08;CQT&#xff09;对于 Optimism 跨链数据的增长产生了直接的影响&#xff0c;而这一…

Java并发基础:Deque接口和Queue接口的区别?

核心概念 Deque&#xff08;double ended queue&#xff0c;双端队列&#xff09;和Queue&#xff08;队列&#xff09;都是Java集合框架中的接口&#xff0c;它们用于处理元素的排队和出队&#xff0c;但是它们之间存在一些重要的区别&#xff0c;如下&#xff1a; 1、Queue…

C语言——oj刷题——调整数组使奇数全部都位于偶数前面

题目&#xff1a; 输入一个整数数组&#xff0c;实现一个函数&#xff0c;来调整该数组中数字的顺序使得数组中所有的奇数位于数组的前半部分&#xff0c;所有偶数位于数组的后半部分。 一、实现方法&#xff1a; 当我们需要对一个整数数组进行调整&#xff0c;使得奇数位于数…

Git详细讲解

文章目录 一、Git相关概念二、本地分支中文件的添加 、提交2.1 文件状态2.2 创建Git仓库2.2.1 git init2.2.2 git clone 2.3 添加操作(git add)2.4 提交操作&#xff08;git commit&#xff09;2.5 撤销操作2.5.1 撤销 add操作2.5.2 撤销 commit操作2.5.3 覆盖上一次的commit操…

机器学习系列——(十八)K-means聚类

引言 在众多机器学习技术中&#xff0c;K-means聚类以其简洁高效著称&#xff0c;成为了数据分析师和算法工程师手中的利器。无论是在市场细分、社交网络分析&#xff0c;还是图像处理等领域&#xff0c;K-means都扮演着至关重要的角色。本文旨在深入解析K-means聚类的原理、实…

EF Core 模型优先——根据类对象创建数据表

需要的nuget包&#xff1a; Microsoft.EntityframeworkCore.SqlServer &#xff08;根据自己的数据库类型选择对应的nuget包&#xff09; Microsoft.EntityframeworkCore.Tools Microsoft.VisualStudio.Web.CodeGeneration.Design 说明&#xff1a; &#xff08;1&#xf…

排序算法---插入排序

原创不易&#xff0c;转载请注明出处。欢迎点赞收藏~ 插入排序是一种简单直观的排序算法&#xff0c;它的基本思想是将待排序的元素分为已排序和未排序两部分&#xff0c;每次从未排序部分中选择一个元素插入到已排序部分的合适位置&#xff0c;直到所有元素都插入到已排序部分…

考研数据结构笔记(3)

顺序表存储结构 存储结构顺序结构定义基本操作的实现静态分配问题 动态分配代码功能 顺序表的特点: 顺序表小结顺序表的插入删除插入删除小结 顺序表的查找按位查找按值查找小结 存储结构 顺序结构 定义 线性表是具有相同数据类型的n(n>0)个数据元素的有限序列(每个数据元素…

大数据 - Spark系列《五》- Spark常用算子

Spark系列文章&#xff1a; 大数据 - Spark系列《一》- 从Hadoop到Spark&#xff1a;大数据计算引擎的演进-CSDN博客 大数据 - Spark系列《二》- 关于Spark在Idea中的一些常用配置-CSDN博客 大数据 - Spark系列《三》- 加载各种数据源创建RDD-CSDN博客 大数据 - Spark系列《…

vue3 之 组合式API—模版引用

模版引用的概念 通过ref标识获取真实的dom对象或者组件实例对象 如何使用&#xff08;以获取dom为例 组件同理&#xff09; 1️⃣调用ref函数生成一个ref对象 2️⃣通过ref标识绑定ref对象到标签 dom中使用 父组件中可以看到打印出来proxy里面只有一个属性&#xff0c;其他…

Vue中 常用的修饰符有哪些

Vue是一款建立在JavaScript框架上的开源前端库&#xff0c;已经成为当今前端开发人员最喜爱的选择之一。它的简洁语法和强大的功能使得开发者可以轻松地构建交互性的网页应用程序。在Vue中&#xff0c;修饰符是一个重要的概念&#xff0c;它们可以帮助我们更好地控制和定制DOM元…

wyh的迷宫

涉及知识点&#xff1a;求迷宫能否到达终点的&#xff0c;而不是求路径数的&#xff0c;用bfs时可以不用重置状态数组&#xff08;回溯&#xff09;。 题目描述 给你一个n*m的迷宫&#xff0c;这个迷宫中有以下几个标识&#xff1a; s代表起点 t代表终点 x代表障碍物 .代…

【多模态】27、Vary | 通过扩充图像词汇来提升多模态模型在细粒度感知任务(OCR等)上的效果

文章目录 一、背景二、方法2.1 生成 new vision vocabulary2.1.1 new vocabulary network2.1.2 Data engine in the generating phrase2.1.3 输入的格式 2.2 扩大 vision vocabulary2.2.1 Vary-base 的结构2.2.2 Data engine2.2.3 对话格式 三、效果3.1 数据集3.2 图像细粒度感…

数据库管理-第147期 最强Oracle监控EMCC深入使用-04(20240207)

数据库管理147期 2024-02-07 数据库管理-第147期 最强Oracle监控EMCC深入使用-04&#xff08;20240207&#xff09;1 发现Exadata2 Exadata监控计算节点&#xff1a;存储节点RoCE交换机管理交换机PDU 总结 数据库管理-第147期 最强Oracle监控EMCC深入使用-04&#xff08;202402…

网工内推 | 物流、航空业信息安全工程师,CISP认证优先,带薪年假

01 盛辉物流 招聘岗位&#xff1a;网络安全工程师 职责描述: 1、对机房内的网络、系统进行安全扫描和安全防护&#xff0c;上报安全评估报告、日常安全作业计划报告&#xff1b; 2、负责防火墙等安全设备、漏洞扫描工具的维护管理和使用&#xff0c;负责日常安全运维工作及安…

JavaScript相关(一)——作用域

本篇将从JS的执行上下文开始&#xff0c;去理解&#xff1a;变量提升、 栈式调用、作用域和闭包。 参考&#xff1a; 浏览器工作原理与实践 JS执行上下文 执行上下文是 JavaScript 执行一段代码时的运行环境&#xff0c;比如调用一个函数&#xff0c;就会生成这个函数的执行…

【MySQL】_JDBC编程

目录 1. JDBC原理 2. 导入JDBC驱动包 3. 编写JDBC代码实现Insert 3.1 创建并初始化一个数据源 3.2 和数据库服务器建立连接 3.3 构造SQL语句 3.4 执行SQL语句 3.5 释放必要的资源 4. JDBC代码的优化 4.1 从控制台输入 4.2 避免SQL注入的SQL语句 5. 编写JDBC代码实现…

同步和异步、阻塞与非阻塞

一、同步和异步的概念 首先同步和异步是访问数据的机制 同步&#xff1a;同步一般指主动请求并等待IO操作完成的方式异步&#xff1a;主动请求数据后便可以继续处理其它任务&#xff0c;随后等待IO操作完毕的通知 两者的区别&#xff1a;同步会一行一行执行代码&#xff0c;而…