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

本文主要是介绍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) – 移动拷贝

这篇关于23.右值引用_c++11(左值引用的使用场景、右值引用的使用场景、左值引用和右值引用的对比、移动构造、移动赋值、右值引用完美转发)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/970537

相关文章

如何使用Docker部署FTP和Nginx并通过HTTP访问FTP里的文件

《如何使用Docker部署FTP和Nginx并通过HTTP访问FTP里的文件》本文介绍了如何使用Docker部署FTP服务器和Nginx,并通过HTTP访问FTP中的文件,通过将FTP数据目录挂载到N... 目录docker部署FTP和Nginx并通过HTTP访问FTP里的文件1. 部署 FTP 服务器 (

MySQL 日期时间格式化函数 DATE_FORMAT() 的使用示例详解

《MySQL日期时间格式化函数DATE_FORMAT()的使用示例详解》`DATE_FORMAT()`是MySQL中用于格式化日期时间的函数,本文详细介绍了其语法、格式化字符串的含义以及常见日期... 目录一、DATE_FORMAT()语法二、格式化字符串详解三、常见日期时间格式组合四、业务场景五、总结一、

Python中配置文件的全面解析与使用

《Python中配置文件的全面解析与使用》在Python开发中,配置文件扮演着举足轻重的角色,它们允许开发者在不修改代码的情况下调整应用程序的行为,下面我们就来看看常见Python配置文件格式的使用吧... 目录一、INI配置文件二、YAML配置文件三、jsON配置文件四、TOML配置文件五、XML配置文件

Go使用pprof进行CPU,内存和阻塞情况分析

《Go使用pprof进行CPU,内存和阻塞情况分析》Go语言提供了强大的pprof工具,用于分析CPU、内存、Goroutine阻塞等性能问题,帮助开发者优化程序,提高运行效率,下面我们就来深入了解下... 目录1. pprof 介绍2. 快速上手:启用 pprof3. CPU Profiling:分析 C

C++实现回文串判断的两种高效方法

《C++实现回文串判断的两种高效方法》文章介绍了两种判断回文串的方法:解法一通过创建新字符串来处理,解法二在原字符串上直接筛选判断,两种方法都使用了双指针法,文中通过代码示例讲解的非常详细,需要的朋友... 目录一、问题描述示例二、解法一:将字母数字连接到新的 string思路代码实现代码解释复杂度分析三、

MySQL InnoDB引擎ibdata文件损坏/删除后使用frm和ibd文件恢复数据

《MySQLInnoDB引擎ibdata文件损坏/删除后使用frm和ibd文件恢复数据》mysql的ibdata文件被误删、被恶意修改,没有从库和备份数据的情况下的数据恢复,不能保证数据库所有表数据... 参考:mysql Innodb表空间卸载、迁移、装载的使用方法注意!此方法只适用于innodb_fi

Python中conda虚拟环境创建及使用小结

《Python中conda虚拟环境创建及使用小结》本文主要介绍了Python中conda虚拟环境创建及使用小结,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们... 目录0.前言1.Miniconda安装2.conda本地基本操作3.创建conda虚拟环境4.激活c

Spring中@Lazy注解的使用技巧与实例解析

《Spring中@Lazy注解的使用技巧与实例解析》@Lazy注解在Spring框架中用于延迟Bean的初始化,优化应用启动性能,它不仅适用于@Bean和@Component,还可以用于注入点,通过将... 目录一、@Lazy注解的作用(一)延迟Bean的初始化(二)与@Autowired结合使用二、实例解

SpringBoot使用Jasypt对YML文件配置内容加密的方法(数据库密码加密)

《SpringBoot使用Jasypt对YML文件配置内容加密的方法(数据库密码加密)》本文介绍了如何在SpringBoot项目中使用Jasypt对application.yml文件中的敏感信息(如数... 目录SpringBoot使用Jasypt对YML文件配置内容进行加密(例:数据库密码加密)前言一、J

MySQL表锁、页面锁和行锁的作用及其优缺点对比分析

《MySQL表锁、页面锁和行锁的作用及其优缺点对比分析》MySQL中的表锁、页面锁和行锁各有特点,适用于不同的场景,表锁锁定整个表,适用于批量操作和MyISAM存储引擎,页面锁锁定数据页,适用于旧版本... 目录1. 表锁(Table Lock)2. 页面锁(Page Lock)3. 行锁(Row Lock