1. C++11的发展历史

C++11是C++的第⼆个主要版本,并且是从C++98起的最重要更新。它引入了大量更改,标准化了既有实践,并改进了对C++程序员可用的抽象。在它最终由ISO在2011年8月12日采纳前,人们曾使用名称“C++0x”,因为它曾被期待在2010年之前发布。C++03与C++11期间花了8年时间,故而这 是迄今为止最长的版本间隔。从那时起,C++有规律地每3年更新⼀次。

2. 列表初始化

2.1 C++98传统的{}

C++98中⼀般数组和结构体可以用{}进行初始化

struct Point
{int _x;int _y;
};
int main()
{int array1[] = { 1, 2, 3, 4, 5 };int array2[5] = { 0 };Point p = { 1, 2 };return 0;
}

2.2C++11中的{}

  • 在C++11之后,为了统一初始化方式,试图实现一切对象皆可用{}初始化。{}初始化也称为列表初始化。
  • 内置类型支持{}初始化,自定义类型也支持。自定义类型的本质是类型转换,中间会产生临时对象,但经过优化后,最终会变成直接构造。
  • 在{}初始化的过程中,可以省略掉=
  • C++11引入列表初始化的本意是实现一种大统一的初始化方式。此外,在某些场景下,它带来了不少便利,例如在容器的pushinsert操作中构造多参数对象时,使用{}初始化会更加方便。
class Date
{
public:Date(int year = 1, int month = 1, int day = 1):_year(year), _month(month), _day(day){cout << "Date(int year, int month, int day)" << endl;}Date(const Date& d):_year(d._year), _month(d._month), _day(d._day){cout << "Date(const Date& d)" << endl;}
private:int _year;int _month;int _day;
};void PushBack(const Date& d)
{// ...
}int main()
{// C++11支持的// 内置类型支持int x1 = { 2 };// 运行一下,我们可以验证上面的理论,发现是没调用拷贝构造的// 本质都是由构造函数支持的隐式类型转换string str = "11111111";Date d0 = 2020;Date d1 = { 2025, 1, 1 };Date d2(2025, 1, 1);const Date& r1 = 2020;const Date& r2 = { 2025, 1, 1 };Date d3(2025, 1, 1);PushBack(d3);PushBack({ 2025, 1, 1 });PushBack(2020);PushBack({ 2020,12 });// 可以省略掉=Point p1{ 1, 2 };int x2{ 2 };Date d6{ 2024, 7, 25 };const Date& d7{ 2024, 7, 25 };// 不支持,只有{}初始化,才能省略=// Date d8 2025;return 0;
}

2.3C++11中的std::initializer_list

上面的初始化方式已经很方便,但是对象容器的初始化还是不太方便。比如一个vector对象,我想用N个值去构造初始化,那么我们需要实现很多个构造函数才能支持。

例如:

vector<int> v1 = {1,2,3};

vector<int> v2 = {1,2,3,4,5};

C++11库中提出了一个std::initializer_list的类。

auto il = {10, 20, 30};// il的类型是std::initializer_list<int>

这个类的本质是在底层开一个数组,将数据拷贝过来。std::initializer_list内部有两个指针,分别指向数组的开始和结束。

根据文档initializer_list,std::initializer_list支持迭代器遍历。

容器支持一个std::initializer_list的构造函数,也就支持任意多个值构成的初始化。STL中的容器支持使用{x1,x2,x3...}进行初始化,就是通过std::initializer_list的构造函数实现的。

#include<vector>
#include<map>
int main()
{auto il = { 10, 20, 30, 40, 50 };cout << typeid(il).name() << endl;//class std::initializer_list<int>cout << sizeof(il) << endl;//两个指针的大小16// 这⾥begin和end返回的值initializer_list对象中存的两个指针// 这两个指针的值跟i的地址跟接近,说明数组存在栈上int i = 0;cout << il.begin() << endl; //000000428B4FF048cout << il.end() << endl;   //000000428B4FF05Ccout << &i << endl;			//000000428B4FF074//{}列表中可以有任意多个值//这两个写法语义上还是有差别的,第⼀个v1是直接构造,//第⼆个v2是构造临时对象+临时对象拷贝v2 +优化为直接构造vector<int> v1({ 1,2,3,4,5 });vector<int> v2 = { 1,2,3,4,5 };const vector<int>& v3 = { 1,2,3,4,5 };// initializer_list版本的赋值⽀持v1 = { 10,20,30,40,50 };// 这里是pair对象的{}初始化和map的initializer_list构造结合到一起用了map<string, string> dict = { {"sort", "排序"}, {"string", "字符串"} };/*pair<string, string> kv1("sort", "排序");pair<string, string> kv2("string", "字符串");map<string, string> dict = { kv1, kv2 };*/return 0;
}

initializer_list模拟实现

以博主之前写的vector底层实现中模拟的initializer_list为例

vector(initializer_list<T> il) //il{1,2,3,4,5};:_start(nullptr), _finish(nullptr), _endofstorage(nullptr)
{reserve(il.size());for (auto& e : il){push_back(e);}
}

在构造函数中遍历initializer_list时可以使用迭代器遍历,也可以使用范围for遍历,因为范围for底层实际采用的就是迭代器方式遍历。

3. 右值引用和移动语义

C++98的C++语法中就有引用的语法,而C++11中新增了的右值引用语法特性,C++11之后我们之前学习的引用就叫做左值引用。无论左值引用还是右值引用,都是给对象取别名。

3.1 左值和右值

左值

左值是⼀个表示数据的表达式(如变量名或解引用的指针),⼀般是有持久状态,存储在内存中,我 们可以获取它的地址,左值可以出现赋值符号的左边,也可以出现在赋值符号右边。定义时const 修饰符后的左值,不能给他赋值,但是可以取它的地址。

int main()
{//以下的p、b、c、*p都是左值int* p = new int(0);int b = 1;const int c = 2;return 0;
}

右值

右值也是⼀个表示数据的表达式,要么是字面值常量、要么是表达式求值过程中创建的临时对象 等,右值可以出现在赋值符号的右边,但是不能出现出现在赋值符号的左边,右值不能取地址。

值得⼀提的是,左值的英文简写为lvalue,右值的英⽂简写为rvalue。传统认为它们分别是left value、right value 的缩写。现代C++中,lvalue被解释为loactorvalue的缩写,可意为存储在内存中、有明确存储地址可以取地址的对象,而rvalue被解释为readvalue,指的是那些可以提供数据值,但是不可以寻址,例如:临时变量,字面量常量,存储于寄存器中的变量等,也就是说左值和右值的核心区别就是能否取地址。

int main()
{double x = 1.1, y = 2.2;//以下几个都是常见的右值10;x + y;fmin(x, y);//错误示例(右值不能出现在赋值符号的左边)//10 = 1;//x + y = 1;//fmin(x, y) = 1;return 0;
}
  • 右值本质就是一个临时变量或常量值,比如代码中的10就是常量值,表达式x+y和函数fmin的返回值就是临时变量,这些都叫做右值。
  • 这些临时变量和常量值并没有被实际存储起来,这也就是为什么右值不能被取地址的原因,因为只有被存储起来后才有地址。
  • 但需要注意的是,这里说函数的返回值是右值,指的是传值返回的函数,因为传值返回的函数在返回对象时返回的是对象的拷贝,这个拷贝出来的对象就是一个临时变量
     

3.2左值引用 vs 右值引用

  • Type& r1 = x;         Type&& rr1 = y;
  • 第一个语句是左值引用,左值引用就是给左值取别名,第二个语句是右值引用,同样的道理,右值引用就是给右值取别名。
  • 左值引用不能直接引用右值,但是const左值引用可以引用右值。
  • 右值引用不能直接引用左值,但是右值引用可以引用move(左值)。
  • template <class T> typename remove_reference<T>::type&& move (T&& arg);
  • move是库里面的一个函数模板,本质内部是进行强制类型转换,当然它还涉及一些引用折叠的知识,这个我们后面会细讲。
  • 需要注意的是变量表达式都是左值属性,也就意味着一个右值被右值引用绑定后,右值引用变量表达式的属性是左值。
  • 从语法层面看,左值引用和右值引用都是取别名,不开空间。从汇编底层的角度看下面代码中r1和rr1的汇编层实现,底层都是用指针实现的,没什么区别。底层汇编等实现和上层语法表达的意义有时是背离的,所以不要混到一起理解,互相佐证,这样反而是陷入迷途。

传统的C++语法中就有引用的语法,而C++11中新增了右值引用的语法特性,为了进行区分,于是将C++11之前的引用就叫做左值引用。但是无论左值引用还是右值引用,本质都是给对象取别名。

左值引用

左值引用就是对左值的引用,给左值取别名,通过“&”来声明。比如:

int main()
{// 左值:可以取地址// 以下的p、b、c、*p、s、s[0]就是常见的左值int* p = new int(0);int b = 1;const int c = b;*p = 10;string s("111111");s[0] = 'x';cout << &c << endl;cout << (void*)&s[0] << endl;return 0;
}

右值引用

右值引用就是对右值的引用,给右值取别名,通过“&&”来声明。比如:

int main()
{// 右值:不能取地址double x = 1.1, y = 2.2;// 以下几个10、x + y、fmin(x, y)、string("11111")都是常见的右值10;x + y;fmin(x, y);string("11111");//以下几个都是对右值的右值引用int&& rr1 = 10;double&& rr2 = x + y;double rr3 = fmin(x, y);return 0;
}

   3.3 引用延延长命周期

右值引用可用于为临时对象延长生命周期,const的左值引用也能延长临时对象生命周期,但这些对象无法被修改。

class AA
{
public:AA(int a1, int a2):_a1(a1), _a2(a2){}~AA(){cout << "~AA()" << endl;}
private:int _a1 = 1;int _a2 = 1;
};int main()
{AA aa1(1, 1);const AA& rr1 = AA(2, 2);AA&& rr2 = AA(3, 3);cout << "----------------------" << endl;return 0;
}

rr1和rr2一个是左值引用临时变量,一个是右值引用临时对象,它们都延长了临时对象的生命周期,因为它们的析构都在横线后,不然应该是横线上有两个析构,横线下是aa1的析构

3.4 左值和右值的参数匹配

  • 在C++98中,我们实现一个const左值引用作为参数的函数,那么实参传递左值和右值都可以匹配。
  • C++11以后,分别重载左值引用、const左值引用、右值引用作为形参的f函数,那么实参是左值会匹配f(左值引用),实参是const左值会匹配f(const左值引用),实参是右值会匹配f(右值引用)。
  • 右值引用变量在用于表达式时属性是左值,这个设计这里会感觉跟怪,下一小节我们讲右值引用的使用场景时,就能体会这样设计的价值了。
void f(int& x)
{std::cout << "左值引用重载 f(" << x << ")\n";
}void f(const int& x)
{std::cout << "到 const 的左值引用重载 f(" << x << ")\n";
}void f(int&& x)
{std::cout << "右值引用重载 f(" << x << ")\n";
}int main()
{int i = 1;const int ci = 2;f(i); // 调用 f(int&)f(ci); // 调用 f(const int&)f(3); // 调用 f(int&&),如果没有 f(int&&) 重载则会调用 f(const int&)f(std::move(i)); // 调用 f(int&&)int& y = i;f(y);int&& x = 1;f(x); // 调用 f(int& x),右值引用变量在用于表达式时属性是左值f(std::move(x)); // 调用 f(int&& x)return 0;
}

3.5右值引用和移动语义的使用场景

3.5.1左值引用主要使用场景回顾

左值引用主要使用场景是在函数中左值引用传参和左值引用传返回值时减少拷贝,同时还可以修改实参和修改返回对象的价值。左值引用已经解决大多数场景的拷贝效率问题,但是有些场景不能使用传左值引用返回,如addStrings和generate函数,C++98中的解决方案只能是被迫使用输出型参数解决。那么C++11以后这里可以使用右值引用做返回值解决吗?显然是不可能的,因为这里的本质是返回对象是一个局部对象,函数结束这个对象就析构销毁了,右值引用返回也无法改变对象已经析构销毁的事实。

class Solution {
public://  传值返回需要拷贝string addStrings(string num1, string num2) {string str;int end1 = num1.size() - 1, end2 = num2.size() - 1;// 进位int next = 0;while (end1 >= 0 || end2 >= 0){int val1 = end1 >= 0 ? num1[end1--] - '0' : 0;int val2 = end2 >= 0 ? num2[end2--] - '0' : 0;int ret = val1 + val2 + next;next = ret / 10;ret = ret % 10;str += ('0' + ret);}if (next == 1)str += '1';reverse(str.begin(), str.end());return str;}};
class Solution {
public://这⾥的传值返回拷⻉代价就太⼤了vector<vector<int>> generate(int numRows) {vector<vector<int>> vv(numRows);for (int i = 0; i < numRows; ++i){vv[i].resize(i + 1, 1);}for (int i = 2; i < numRows; ++i){for (int j = 1; j < i; ++j){vv[i][j] = vv[i - 1][j] + vv[i - 1][j - 1];}}return vv;}
};

C++11提出右值引用就是为了解决左值引用的这个短板的,但解决方式并不是简单的将右值引用作为函数的返回值。

3.5.2 移动构造和移动赋值

  • 移动构造函数是一种构造函数,类似于拷贝构造函数,移动构造函数要求第一个参数是该类类型的引用,但不同的是要求这个参数是右值引用。如果还有其他参数,额外的参数必须有缺省值。
  • 移动赋值是一个赋值运算符的重载,它与拷贝赋值构成函数重载。类似于拷贝赋值函数,移动赋值函数要求第一个参数是该类类型的引用,但不同的是要求这个参数是右值引用。
  • 对于像string/vector这样需要进行深拷贝的类,或者包含需要进行深拷贝的成员变量的类,移动构造和移动赋值才有意义。因为移动构造和移动赋值的第一个参数都是右值引用的类型,它们的本质是要"窃取"引用的右值对象的资源,而不是像拷贝构造和拷贝赋值那样去拷贝资源,从而提高效率。下面的bit::string样例实现了移动构造和移动赋值,我们需要结合具体场景来理解。

右值引用和移动语句解决上述问题的方式就是,给当前模拟实现的string类增加移动构造和移动赋值方法。

移动构造

移动构造是一个构造函数,该构造函数的参数是右值引用类型的,移动构造本质就是将传入右值的资源窃取过来,占为己有,这样就避免了进行深拷贝,所以它叫做移动构造,就是窃取别人的资源来构造自己的意思。

在当前的string类中增加一个移动构造函数,该函数要做的就是调用swap函数将传入右值的资源窃取过来,为了能够更好的得知移动构造函数是否被调用,可以在该函数当中打印一条提示语句。

代码如下:
 


namespace lzg
{class string{public:typedef char* iterator;typedef const char* const_iterator;string(const char* str = ""):_size(strlen(str)), _capacity(_size){cout << "string(char* str)-构造" << endl;_str = new char[_capacity + 1];strcpy(_str, str);}void swap(string& s){::swap(_str, s._str);::swap(_size, s._size);::swap(_capacity, s._capacity);}string(const string& s):_str(nullptr){cout << "string(const string& s) -- 拷贝构造" << endl;reserve(s._capacity);for (auto ch : s){push_back(ch);}}string& operator=(const string& s){cout << "string& operator=(const string& s) -- 拷贝赋值" <<endl;if (this != &s){_str[0] = '\0';_size = 0;reserve(s._capacity);for (auto ch : s){push_back(ch);}}return *this;}// 移动构造string(string&& s){cout << "string(string&& s) -- 移动构造" << endl;swap(s);}//移动赋值string& operator=(string&& s){cout << "string& operator=(string&& s) -- 移动赋值" << endl;swap(s);return *this;}~string(){cout << "~string() -- 析构" << endl;delete[] _str;_str = nullptr;}private:char* _str = nullptr;size_t _size = 0;size_t _capacity = 0;};
}

移动构造和拷贝构造的区别:

  • 在没有增加移动构造之前,由于拷贝构造采用的是const左值引用接收参数,因此无论拷贝构造对象时传入的是左值还是右值,都会调用拷贝构造函数。
  • 增加移动构造之后,由于移动构造采用的是右值引用接收参数,因此如果拷贝构造对象时传入的是右值,那么就会调用移动构造函数(最匹配原则)。
  • string的拷贝构造函数做的是深拷贝,而移动构造函数中只需要调用swap函数进行资源的转移,因此调用移动构造的代价比调用拷贝构造的代价小。
     

移动构造和拷贝构造

int main()
{lzg::string s1("xxxxx");// 拷贝构造lzg::string s2 = s1;// 构造+移动构造lzg::string s3 = lzg::string("yyyyy");cout << "******************************" << endl;return 0;
}

s1是构造,s2是通过s1拷贝构造而来,lzg::string("yyyyy")是匿名构造属于将亡值(右值),来移动构造s3,匿名对象的生命周期在下一行就析构了,所以在*横线之上。之下的三个析构分别是s3、s2、s1(后声明先析构)。后来编译器会进行优化->构造+移动构造优化成构造

3.5.3右值引用和移动语义解决传值返回问题

namespace lzg
{string addStrings(string num1, string num2){string str;int end1 = num1.size() - 1, end2 = num2.size() - 1;int next = 0;while (end1 >= 0 || end2 >= 0){int val1 = end1 >= 0 ? num1[end1--] - '0' : 0;int val2 = end2 >= 0 ? num2[end2--] - '0' : 0;int ret = val1 + val2 + next;next = ret / 10;ret = ret % 10;str += ('0' + ret);}if (next == 1)str += '1';reverse(str.begin(), str.end());cout << "******************************" << endl;return str;}
}int main()
{lzg::string ret;//...ret = lzg::addStrings("11111", "2222");cout << ret.c_str() << endl;return 0;
}

注释掉移动构造和移动赋值后。由于vs编译器优化的十分厉害,这里我们在linux环境下关闭构造优化进和RAO行调试,可以看到

*下面的拷贝构造和拷贝赋值是针对临时对象的像下面的图示一样

在vs编译器优化的场景下连续步骤中的拷贝合⼆为⼀变为⼀次拷贝构造,也就是不创建中间的临时变量,把str拷贝赋值给ret

需要注意的是在vs2019的release和vs2022的debug和release,下面代码优化为非常恐怖,会直接 将str对象的构造,str拷贝构造临时对象,临时对象拷贝构造ret对象,合三为⼀,变为直接构造。 变为直接构造。要理解这个优化要结合局部对象生命周期和栈帧的角度理解

3.5.4 右值引用和移动语义在传参中的提效

  • 查看STL文档我们发现,C++11以后容器的push和insert系列的接口都增加了右值引用版本。
  • 当实参是一个左值时,容器内部继续调用拷贝构造进行拷贝,将对象拷贝到容器空间中的对象。
  • 当实参是一个右值时,容器内部则调用移动构造,将右值对象的资源转移到容器空间中的对象上。
  • 把我们之前模拟实现的bit::list拷贝过来,支持右值引用参数版本的push_back和insert。
  • 其实这里还有一个emplace系列的接口,但是这个涉及可变参数模板,我们需要把可变参数模板讲解以后再讲解emplace系列的接口。
 // void push_back (const value_type& val);// void push_back (value_type&& val);// iterator insert (const_iterator position, value_type&& val);// iterator insert (const_iterator position, const value_type& val);
int main()
{std::list<bit::string> lt;bit::string s1("111111111111111111111");lt.push_back(s1);cout << "*************************" << endl;lt.push_back(bit::string("22222222222222222222222222222"));cout << "*************************" << endl;lt.push_back("3333333333333333333333333333");cout << "*************************" << endl;lt.push_back(move(s1));cout << "*************************" << endl;return 0;
}运⾏结果:string(char* str)
string(const string& s) -- 
拷⻉构造
*************************
string(char* str)
string(string&& s) -- 
移动构造
~string() -- 
析构
*************************
string(char* str)
string(string&& s) -- 
移动构造
~string() -- 
析构
*************************
string(string&& s) -- 
移动构造
*************************
~string() -- 
析构
~string() -- 
析构
~string() -- 
析构
~string() -- 
析构
~string() -- 
析构

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

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【一】Django框架版本介绍

【一】Django框架版本介绍 【一】Django框架版本 ● Django 是一个高级的Python Web框架&#xff0c;由荷兰人Armin Ronacher创建。 ● 随着版本的迭代和功能的不断优化&#xff0c;Django在处理异步请求方面也有了显著的进步。 【1】Django1.x ● 默认不支持异步 ● Django 1.…

git 大文件上传不了的 问题

你 还是在 cmd 里执行&#xff0c;Select-String 是 PowerShell 的命令&#xff0c;cmd 不认识。 请务必按下面的步骤 切换到 PowerShell 再运行。✅ 1. 打开 PowerShell&#xff08;不要再用 cmd&#xff09;最简单&#xff1a; 在资源管理器里进入 D:\linShiWenjian\my-react…

【FIX】go运行报错“missing go.sum entry for module providing package”解决方案

&#x1f527; ​核心解决方案​**运行 go mod tidy**​ ​作用​&#xff1a;自动同步 go.mod和 go.sum文件&#xff0c;添加缺失依赖并移除无用条目。 go mod tidy​适用场景​&#xff1a;90% 的校验和缺失问题可通过此命令解决。 ​注意​&#xff1a;若项目含私有仓库&…

【实操教学】ArcGIS 如何进行定义坐标系

一、坐标系定义的方式创建数据时可直接完成坐标系定义&#xff1b;针对已创建的数据集&#xff08;涵盖要素类、要素数据集及栅格数据集&#xff09;&#xff0c;则可通过以下这种方式定义&#xff1a;工具箱工具调用&#xff1a;使用 ArcGIS 工具箱中的 “定义投影&#xff08…

如何使用Windows自带的PnPUtil命令来禁用/停用和启用硬件设备

我来详细讲解一下如何使用 Windows 自带的 PnPUtil 命令来禁用&#xff08;停用&#xff09; 和启用硬件设备。 PnPUtil (即插即用实用工具) 是一个功能强大的命令行工具&#xff0c;主要用于安装、卸载、枚举和修改驱动程序包。对于硬件的启用和禁用&#xff0c;它通过操作设…

鸿蒙Next媒体展示组件实战:Video与动态布局全解析

今天我们来深入探讨HarmonyOS Next中几种核心媒体展示组件的使用方法&#xff0c;通过实际代码示例展示如何打造丰富的多媒体体验。HarmonyOS Next为开发者提供了一套强大而灵活的媒体展示组件&#xff0c;使开发者能够轻松实现视频播放、动态布局适应、全屏切换等常见多媒体功…

复现RoboDK机器人校准功能(以Staubli TX2‑90L / TX200机械臂为测试对象,实测精度接近原厂)

本算法复现了 RoboDK 的机器人校准功能&#xff1a;在训练集的理论校准后精度与 RoboDK 一致&#xff0c;在测试集的实测精度接近 Staubli 原厂。 参考&#xff1a;RoboDK 机器人校准功能&#xff08;https://robodk.com.cn/cn/robot-calibration&#xff09; 特性 支持 SDH 参…

Vue常用指令和生命周期

Vue 是基于 MVVM模型的前端 JavaScript 框架。Vue 核心是数据驱动视图&#xff0c;通过响应式数据实现视图自动更新。<template><div>{{ message }}</div><button click"changeMsg">修改内容</button> </template><script se…

深度学习周报(8.25~8.31)

目录 摘要 Abstract 1 RNN学习意义 2 RNN基础知识 2.1 核心思想 2.2 传播 2.3 优缺点 2.4 变体结构与应用场景 3 RNN结构代码示例 4 总结 摘要 本周主要学习了循环神经网络的学习意义与基础知识&#xff0c;重点了解了RNN循环连接的核心思想、前向传播与反向传播过程…

借助 LAMBDA 公式,实现单元格区域高效转换

新特性介绍 “转换单元格&#xff08;Transform&#xff09;” 功能允许用户将自定义的单参数 LAMBDA 公式应用于选中的单元格区域。用户可选择公式参数的作用域 —— 按单元格、按行、按列或按整个区域。 转换完成后&#xff0c;源单元格区域会被清空&#xff0c;转换后的区…

LeetCode 01背包 494. 目标和

494. 目标和给你一个非负整数数组 nums 和一个整数 target 。 向数组中的每个整数前添加 ‘’ 或 ‘-’ &#xff0c;然后串联起所有整数&#xff0c;可以构造一个 表达式 &#xff1a; 例如&#xff0c;nums [2, 1] &#xff0c;可以在 2 之前添加 ‘’ &#xff0c;在 1 之前…

Dify 1.8.0 全网首发,预告发布

距离Dify 1.7.2过去两周了 Dify 1.8.0 又跟大伙见面了&#xff01; 1.8.0&#xff0c;属于主版本号不变、但第二位数字更新的“阶段性大更”&#xff0c;意味着功能上的显著优化和体验上的重要升级。 根据官方的Github日志&#xff0c;这一版本将继续聚焦三大核心方向&#x…

基于LangChain框架搭建AI问答系统(附源码)

AI问答系统1. 背景知识2. 问答系统流程3. 知识问答系统相关组件3.1 文档加载器3.2 文档切割器3.3 嵌入模型包装器3.4 向量存储库3.5 模型包装器3.6 链组件4. 问答系统演示4.1 问答程序4.2 演示大模型回答效果5.问答系统代码1. 背景知识 在人工智能技术飞速发展的今天&#xff…

【Python】QT(PySide2、PyQt5):Qt Designer,VS Code使用designer,可能的报错

Qt designer&#xff1a;可直接在designer界面&#xff0c;使用拖拽的方式设计需要的界面&#xff0c;可设定部分属性。安装Pyside2后&#xff0c;designer默认在python安装目录的Lib/sit_packages/PySide2文件夹中。designer使用&#xff1a;① 双击打开designer.exe&#xff…

前端常见安全问题 + 防御方法 + 面试回答

目录 XSS&#xff08;跨站脚本攻击&#xff09;CSRF&#xff08;跨站请求伪造&#xff09;SQL 注入文件上传漏洞其他前端常见安全问题面试常见问答 1. XSS&#xff08;跨站脚本攻击&#xff09; 定义 XSS&#xff08;Cross-Site Scripting&#xff09;是一种 通过注入恶意脚…

jxWebUI--下拉选择框

下拉选择框提供了预先定义好的选项&#xff0c;用户只能在这些选项中选择输入。 combobox 定义格式 combobox 控件名 属性列表 ;属性 bind 类型&#xff1a;string 缺省值&#xff1a; 输入控件所绑定的变量名。当给输入控件bind了一个变量名后【bindbind_var_name】&#xff0…

大模型时代:用Redis构建百亿级向量数据库方

大模型时代&#xff1a;用Redis构建百亿级向量数据库方案第一章&#xff1a;大模型时代的向量数据库挑战1.1 大模型时代的特征与需求1.2 向量数据库的核心价值1.3 百亿级向量的技术挑战第二章&#xff1a;Redis作为向量数据库的优势2.1 Redis的核心优势2.2 Redis向量搜索模块&a…

jsqlparser(六):TablesNamesFinder 深度解析与 SQL 格式化实现

在数据库应用开发中&#xff0c;SQL语句的解析和处理是一项常见而重要的任务。本文将深入探讨 JSQLParser 中的 TablesNamesFinder 类&#xff0c;分析其核心原理、与 AST 访问接口&#xff08;CCJSqlParserVisitor &#xff09;的关系、使用场景&#xff0c;并通过实际代码示例…

Python训练营打卡Day49-神经网络调参指南

知识点回顾&#xff1a;随机种子内参的初始化神经网络调参指南 参数的分类调参的顺序各部分参数的调整心得 作业&#xff1a;对于day41的简单cnn&#xff0c;看看是否可以借助调参指南进一步提高精度。 随机种子 import torch import torch.nn as nn# 定义简单的线性模型&…

Elasticsearch 常用任务管理命令及实战应用

常用任务管理命令 列出所有任务 curl -X GET "http://<es_host>:<es_port>/_tasks?detailedtrue&pretty" -H Content-Type: application/json获取特定类型的任务 curl -X GET "http://<es_host>:<es_port>/_tasks?actions<act…