C++11基礎——— 右值引用和移動語義

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…