C++11 右值引用、移動語義、完美轉發、萬能引用

C++11 右值引用、移動語義、完美轉發、引用折疊、萬能引用

轉自:http://c.biancheng.net/

C++中的左值和右值

右值引用可以從字面意思上理解,指的是以引用傳遞(而非值傳遞)的方式使用 C++ 右值。關于 C++ 引用,已經在《C++引用》專題給大家做了詳細的講解,這里不再重復贅述。接下來重點給大家介紹什么是 C++ 右值。

在 C++ 或者 C 語言中,一個表達式(可以是字面量、變量、對象、函數的返回值等)根據其使用場景不同,分為左值表達式和右值表達式。確切的說 C++ 中左值和右值的概念是從 C 語言繼承過來的。

值得一提的是,左值的英文簡寫為“lvalue”,右值的英文簡寫為“rvalue”。很多人認為它們分別是"left value"、“right value” 的縮寫,其實不然。lvalue 是“loactor value”的縮寫,可意為存儲在內存中、有明確存儲地址(可尋址)的數據,而 rvalue 譯為 “read value”,指的是那些可以提供數據值的數據(不一定可以尋址,例如存儲于寄存器中的數據)。

通常情況下,判斷某個表達式是左值還是右值,最常用的有以下 2 種方法。

  1. 可位于賦值號(=)左側的表達式就是左值;反之,只能位于賦值號右側的表達式就是右值。舉個例子:
int a = 5;
5 = a; //錯誤,5 不能為左值

其中,變量 a 就是一個左值,而字面量 5 就是一個右值。值得一提的是,C++ 中的左值也可以當做右值使用,例如:

int b = 10; // b 是一個左值
a = b; // a、b 都是左值,只不過將 b 可以當做右值使用
  1. 有名稱的、可以獲取到存儲地址的表達式即為左值;反之則是右值。

以上面定義的變量 a、b 為例,a 和 b 是變量名,且通過 &a 和 &b 可以獲得他們的存儲地址,因此 a 和 b 都是左值;反之,字面量 5、10,它們既沒有名稱,也無法獲取其存儲地址(字面量通常存儲在寄存器中,或者和代碼存儲在一起),因此 5、10 都是右值。

注意,以上 2 種判定方法只適用于大部分場景。由于本節主要講解右值引用,因此這里適可而止,不再對 C++ 左值和右值做深度剖析,感興趣的讀者可自行研究。

更詳細的左右值的相關概念可參考:

在這里插入圖片描述

C++右值引用

前面提到,其實 C++98/03 標準中就有引用,使用 “&” 表示。但此種引用方式有一個缺陷,即正常情況下只能操作 C++ 中的左值,無法對右值添加引用。舉個例子:

int num = 10;int &b = num; //正確int &c = 10; //錯誤

如上所示,編譯器允許我們為 num 左值建立一個引用,但不可以為 10 這個右值建立引用。因此,C++98/03 標準中的引用又稱為左值引用。

注意,雖然 C++98/03 標準不支持為右值建立非常量左值引用,但允許使用常量左值引用操作右值。也就是說,常量左值引用既可以操作左值,也可以操作右值,例如:

int num = 10;const int &b = num;const int &c = 10;

我們知道,右值往往是沒有名稱的,因此要使用它只能借助引用的方式。這就產生一個問題,實際開發中我們可能需要對右值進行修改(實現移動語義時就需要),顯然左值引用的方式是行不通的。

為此,C++11 標準新引入了另一種引用方式,稱為右值引用,用 “&&” 表示。

話說,C++標準委員會在選定右值引用符號時,既希望能選用現有 C++ 內部已有的符號,還不能與 C++ 98 /03 標準產生沖突,最終選定了 2 個 ‘&’ 表示右值引用。

需要注意的,和聲明左值引用一樣,右值引用也必須立即進行初始化操作,且只能使用右值進行初始化,比如:

int num = 10;//int && a = num;  //右值引用不能初始化為左值int && a = 10;

和常量左值引用不同的是,右值引用還可以對右值進行修改。例如:

int && a = 10;a = 100;cout << a << endl;

程序輸出結果為 100。

另外值得一提的是,C++ 語法上是支持定義常量右值引用的,例如:

const int&& a = 10;//編譯器不會報錯

但這種定義出來的右值引用并無實際用處。一方面,右值引用主要用于移動語義和完美轉發,其中前者需要有修改右值的權限;其次,常量右值引用的作用就是引用一個不可修改的右值,這項工作完全可以交給常量左值引用完成。

學到這里,一些讀者可能無法記清楚左值引用和右值引用各自可以引用左值還是右值,這里給大家一張表格,方便大家記憶:

引用類型使用場景
非常量左值常量左值非常量右值常量右值
非常量左值引用YNNN
常量左值引用YYYY常用于類中構建拷貝構造函數
非常量右值引用NNYN移動語義、完美轉發
常量右值引用NNYY無實際用途

表中,Y 表示支持,N 表示不支持。

其實,C++11 標準中對右值做了更細致的劃分,分別稱為純右值(Pure value,簡稱 pvalue)和將亡值(eXpiring value,簡稱 xvalue )。其中純右值就是 C++98/03 標準中的右值(本節中已經做了大篇幅的講解),而將亡值則指的是和右值引用相關的表達式(比如某函數返回的 T && 類型的表達式)。對于純右值和將亡值,都屬于右值,讀者知道即可,不必深究。

拷貝構造函數與深拷貝

在 C++ 11 標準之前(C++ 98/03 標準中),如果想用其它對象初始化一個同類的新對象,只能借助類中的復制(拷貝)構造函數。通過《C++拷貝構造函數》一節的學習我們知道,拷貝構造函數的實現原理很簡單,就是為新對象復制一份和其它對象一模一樣的數據。

需要注意的是,當類中擁有指針類型的成員變量時,拷貝構造函數中需要以深拷貝(而非淺拷貝)的方式復制該指針成員。有關深拷貝和淺拷貝以及它們的區別,讀者可閱讀《C++深拷貝和淺拷貝》一文做詳細了解。

舉個例子:

#include <iostream>
using namespace std;class demo{
public:demo():num(new int(0)){cout<<"construct!"<<endl;}//拷貝構造函數demo(const demo &d):num(new int(*d.num)){cout<<"copy construct!"<<endl;}~demo(){cout<<"class destruct!"<<endl;}
private:int *num;
};demo get_demo(){return demo();
}int main(){demo a = get_demo();return 0;
}

如上所示,我們為 demo 類自定義了一個拷貝構造函數。該函數在拷貝 d.num 指針成員時,必須采用深拷貝的方式,即拷貝該指針成員本身的同時,還要拷貝指針指向的內存資源。否則一旦多個對象中的指針成員指向同一塊堆空間,這些對象析構時就會對該空間釋放多次,這是不允許的。

可以看到,程序中定義了一個可返回 demo 對象的 get_demo() 函數,用于在 main() 主函數中初始化 a 對象,其整個初始化的流程包含以下幾個階段:

  1. 執行 get_demo() 函數內部的 demo() 語句,即調用 demo 類的默認構造函數生成一個匿名對象;
  2. 執行 return demo() 語句,會調用拷貝構造函數復制一份之前生成的匿名對象,并將其作為 get_demo() 函數的返回值(函數體執行完畢之前,匿名對象會被析構銷毀);
  3. 執行 a = get_demo() 語句,再調用一次拷貝構造函數,將之前拷貝得到的臨時對象復制給 a(此行代碼執行完畢,get_demo() 函數返回的對象會被析構);
  4. 程序執行結束前,會自行調用 demo 類的析構函數銷毀 a。

注意,目前多數編譯器都會對程序中發生的拷貝操作進行優化,因此如果我們使用 VS 2017、codeblocks 等這些編譯器運行此程序時,看到的往往是優化后的輸出結果:

construct!
class destruct!

而同樣的程序,如果在 Linux 上使用g++ demo.cpp -fno-elide-constructors命令運行(其中 demo.cpp 是程序文件的名稱),就可以看到完整的輸出結果:

construct!        <-- 執行 demo()
copy construct!    <-- 執行 return demo()
class destruct!     <-- 銷毀 demo() 產生的匿名對象
copy construct!    <-- 執行 a = get_demo()
class destruct!     <-- 銷毀 get_demo() 返回的臨時對象
class destruct!     <-- 銷毀 a

如上所示,利用拷貝構造函數實現對 a 對象的初始化,底層實際上進行了 2 次拷貝(而且是深拷貝)操作。當然,對于僅申請少量堆空間的臨時對象來說,深拷貝的執行效率依舊可以接受,但如果臨時對象中的指針成員申請了大量的堆空間,那么 2 次深拷貝操作勢必會影響 a 對象初始化的執行效率。

事實上,此問題一直存留在以 C++ 98/03 標準編寫的 C++ 程序中。由于臨時變量的產生、銷毀以及發生的拷貝操作本身就是很隱晦的(編譯器對這些過程做了專門的優化),且并不會影響程序的正確性,因此很少進入程序員的視野。

那么當類中包含指針類型的成員變量,使用其它對象來初始化同類對象時,怎樣才能避免深拷貝導致的效率問題呢?C++11 標準引入了解決方案,該標準中引入了右值引用的語法,借助它可以實現移動語義。

移動構造函數、移動語義及其實現

所謂移動語義,指的就是以移動而非深拷貝的方式初始化含有指針成員的類對象。簡單的理解,移動語義指的就是將其他對象(通常是臨時對象)擁有的內存資源“移為已用”。

以前面程序中的 demo 類為例,該類的成員都包含一個整形的指針成員,其默認指向的是容納一個整形變量的堆空間。當使用 get_demo() 函數返回的臨時對象初始化 a 時,我們只需要將臨時對象的 num 指針直接淺拷貝給 a.num,然后修改該臨時對象中 num 指針的指向(通常另其指向 NULL),這樣就完成了 a.num 的初始化。

事實上,對于程序執行過程中產生的臨時對象,往往只用于傳遞數據(沒有其它的用處),并且會很快會被銷毀。因此在使用臨時對象初始化新對象時,我們可以將其包含的指針成員指向的內存資源直接移給新對象所有,無需再新拷貝一份,這大大提高了初始化的執行效率。

例如,下面程序對 demo 類進行了修改:

#include <iostream>
using namespace std;
class demo{
public:demo():num(new int(0)){cout<<"construct!"<<endl;}demo(const demo &d):num(new int(*d.num)){cout<<"copy construct!"<<endl;}//添加移動構造函數demo(demo &&d):num(d.num){d.num = NULL;cout<<"move construct!"<<endl;}~demo(){cout<<"class destruct!"<<endl;}
private:int *num;
};
demo get_demo(){return demo();
}
int main(){demo a = get_demo();return 0;
}

可以看到,在之前 demo 類的基礎上,我們又手動為其添加了一個構造函數。和其它構造函數不同,此構造函數使用右值引用形式的參數,又稱為移動構造函數。并且在此構造函數中,num 指針變量采用的是淺拷貝的復制方式,同時在函數內部重置了 d.num,有效避免了“同一塊對空間被釋放多次”情況的發生。

在 Linux 系統中使用g++ demo.cpp -o demo.exe -std=c++0x -fno-elide-constructors命令執行此程序,輸出結果為:

construct!
move construct!
class destruct!
move construct!
class destruct!
class destruct!\

通過執行結果我們不難得知,當為 demo 類添加移動構造函數之后,使用臨時對象初始化 a 對象過程中產生的 2 次拷貝操作,都轉由移動構造函數完成。

我們知道,非 const 右值引用只能操作右值,程序執行結果中產生的臨時對象(例如函數返回值、lambda 表達式等)既無名稱也無法獲取其存儲地址,所以屬于右值。當類中同時包含拷貝構造函數和移動構造函數時,如果使用臨時對象初始化當前類的對象,編譯器會優先調用移動構造函數來完成此操作。只有當類中沒有合適的移動構造函數時,編譯器才會退而求其次,調用拷貝構造函數。

在實際開發中,通常在類中自定義移動構造函數的同時,會再為其自定義一個適當的拷貝構造函數,由此當用戶利用右值初始化類對象時,會調用移動構造函數;使用左值(非右值)初始化類對象時,會調用拷貝構造函數。

讀者可能會問,如果使用左值初始化同類對象,但也想調用移動構造函數完成,有沒有辦法可以實現呢?

默認情況下,左值初始化同類對象只能通過拷貝構造函數完成,如果想調用移動構造函數,則必須使用右值進行初始化。C++11 標準中為了滿足用戶使用左值初始化同類對象時也通過移動構造函數完成的需求,新引入了 std::move() 函數,它可以將左值強制轉換成對應的右值,由此便可以使用移動構造函數。

std::move()

通過上節內容,我們知道,C++11 標準中借助右值引用可以為指定類添加移動構造函數,這樣當使用該類的右值對象(可以理解為臨時對象)初始化同類對象時,編譯器會優先選擇移動構造函數。

注意,移動構造函數的調用時機是:用同類的右值對象初始化新對象。那么,用當前類的左值對象(有名稱,能獲取其存儲地址的實例對象)初始化同類對象時,是否就無法調用移動構造函數了呢?當然不是,C++11 標準中已經給出了解決方案,即調用 move() 函數。

move 本意為 “移動”,但該函數并不能移動任何數據,它的功能很簡單,就是將某個左值強制轉化為右值。

基于 move() 函數特殊的功能,其常用于實現移動語義。

move() 函數的用法也很簡單,其語法格式如下:

move( arg )

其中,arg 表示指定的左值對象。該函數會返回 arg 對象的右值形式。

【例 1】move() 函數的基礎應用。

#include <iostream>
using namespace std;
class movedemo{
public:movedemo():num(new int(0)){cout<<"construct!"<<endl;}//拷貝構造函數movedemo(const movedemo &d):num(new int(*d.num)){cout<<"copy construct!"<<endl;}//移動構造函數movedemo(movedemo &&d):num(d.num){d.num = NULL;cout<<"move construct!"<<endl;}
public:     //這里應該是 private,使用 public 是為了更方便說明問題int *num;
};
int main(){movedemo demo;cout << "demo2:\n";movedemo demo2 = demo;//cout << *demo2.num << endl;   //可以執行cout << "demo3:\n";movedemo demo3 = std::move(demo);//此時 demo.num = NULL,因此下面代碼會報運行時錯誤//cout << *demo.num << endl;return 0;
}

程序執行結果為:

construct!
demo2:
copy construct!
demo3:
move construct!

通過觀察程序的輸出結果,以及對比 demo2 和 demo3 初始化操作不難得知,demo 對象作為左值,直接用于初始化 demo2 對象,其底層調用的是拷貝構造函數;而通過調用 move() 函數可以得到 demo 對象的右值形式,用其初始化 demo3 對象,編譯器會優先調用移動構造函數。

注意,調用拷貝構造函數,并不影響 demo 對象,但如果調用移動構造函數,由于函數內部會重置 demo.num 指針的指向為 NULL,所以程序中第 30 行代碼會導致程序運行時發生錯誤。

【例 2】靈活使用 move() 函數。

#include <iostream>
using namespace std;
class first {
public:first() :num(new int(0)) {cout << "construct!" << endl;}//移動構造函數first(first &&d) :num(d.num) {d.num = NULL;cout << "first move construct!" << endl;}
public:    //這里應該是 private,使用 public 是為了更方便說明問題int *num;
};
class second {
public:second() :fir() {}//用 first 類的移動構造函數初始化 firsecond(second && sec) :fir(move(sec.fir)) {cout << "second move construct" << endl;}
public:    //這里也應該是 private,使用 public 是為了更方便說明問題first fir;
};
int main() {second oth;second oth2 = move(oth);//cout << *oth.fir.num << endl;   //程序報運行時錯誤return 0;
}

程序執行結果為:

construct!
first move construct!
second move construct

程序中分別構建了 first 和 second 這 2 個類,其中 second 類中包含一個 first 類對象。如果讀者仔細觀察不難發現,程序中使用了 2 此 move() 函數:

  • 程序第 31 行:由于 oth 為左值,如果想調用移動構造函數為 oth2 初始化,需先利用 move() 函數生成一個 oth 的右值版本;
  • 程序第 22 行:oth 對象內部還包含一個 first 類對象,對于 oth.fir 來說,其也是一個左值,所以在初始化 oth.fir 時,還需要再調用一次 move() 函數。

C++11完美轉發及實現方法詳解

C++11 標準為 C++ 引入右值引用語法的同時,還解決了一個 C++ 98/03 標準長期存在的短板,即使用簡單的方式即可在函數模板中實現參數的完美轉發。那么,什么是完美轉發?它為什么是 C++98/03 標準存在的一個短板?C++11 標準又是如何為 C++ 彌補這一短板的?別急,本節將就這些問題給讀者做一一講解。

首先解釋一下什么是完美轉發,它指的是函數模板可以將自己的參數“完美”地轉發給內部調用的其它函數。所謂完美,即不僅能準確地轉發參數的值,還能保證被轉發參數的左、右值屬性不變。

舉個例子:

template<typename T>
void function(T t) {otherdef(t);
}

如上所示,function() 函數模板中調用了 otherdef() 函數。在此基礎上,完美轉發指的是:如果 function() 函數接收到的參數 t 為左值,那么該函數傳遞給 otherdef() 的參數 t 也是左值;反之如果 function() 函數接收到的參數 t 為右值,那么傳遞給 otherdef() 函數的參數 t 也必須為右值。

顯然,function() 函數模板并沒有實現完美轉發。一方面,參數 t 為非引用類型,這意味著在調用 function() 函數時,實參將值傳遞給形參的過程就需要額外進行一次拷貝操作;另一方面,無論調用 function() 函數模板時傳遞給參數 t 的是左值還是右值,對于函數內部的參數 t 來說,它有自己的名稱,也可以獲取它的存儲地址,因此它永遠都是左值,也就是說,傳遞給 otherdef() 函數的參數 t 永遠都是左值。總之,無論從那個角度看,function() 函數的定義都不“完美”。

讀者可能會問,完美轉發這樣嚴苛的參數傳遞機制,很常用嗎?C++98/03 標準中幾乎不會用到,但 C++11 標準為 C++ 引入了右值引用和移動語義,因此很多場景中是否實現完美轉發,直接決定了該參數的傳遞過程使用的是拷貝語義(調用拷貝構造函數)還是移動語義(調用移動構造函數)。

事實上,C++98/03 標準下的 C++ 也可以實現完美轉發,只是實現方式比較笨拙。通過前面的學習我們知道,C++ 98/03 標準中只有左值引用,并且可以細分為非 const 引用和 const 引用。其中,使用非 const 引用作為函數模板參數時,只能接收左值,無法接收右值;而 const 左值引用既可以接收左值,也可以接收右值,但考慮到其 const 屬性,除非被調用函數的參數也是 const 屬性,否則將無法直接傳遞。

這也就意味著,單獨使用任何一種引用形式,可以實現轉發,但無法保證完美。因此如果使用 C++ 98/03 標準下的 C++ 語言,我們可以采用函數模板重載的方式實現完美轉發,例如:

#include <iostream>
using namespace std;
//重載被調用函數,查看完美轉發的效果
void otherdef(int & t) {cout << "lvalue\n";
}
void otherdef(const int & t) {cout << "rvalue\n";
}
//重載函數模板,分別接收左值和右值
//接收右值參數
template <typename T>
void function(const T& t) {otherdef(t);
}
//接收左值參數
template <typename T>
void function(T& t) {otherdef(t);
}
int main()
{function(5);//5 是右值int  x = 1;function(x);//x 是左值return 0;
}

程序執行結果為:

rvalue
lvalue

從輸出結果中可以看到,對于右值 5 來說,它實際調用的參數類型為 const T& 的函數模板,由于 t 為 const 類型,所以 otherdef() 函數實際調用的也是參數用 const 修飾的函數,所以輸出“rvalue”;對于左值 x 來說,2 個重載模板函數都適用,C++編譯器會選擇最適合的參數類型為 T& 的函數模板,進而 therdef() 函數實際調用的是參數類型為非 const 的函數,輸出“lvalue”。

顯然,使用重載的模板函數實現完美轉發也是有弊端的,此實現方式僅適用于模板函數僅有少量參數的情況,否則就需要編寫大量的重載函數模板,造成代碼的冗余。為了方便用戶更快速地實現完美轉發,C++ 11 標準中允許在函數模板中使用右值引用來實現完美轉發。

C++11 標準中規定,通常情況下右值引用形式的參數只能接收右值,不能接收左值。但對于函數模板中使用右值引用語法定義的參數來說,它不再遵守這一規定,既可以接收右值,也可以接收左值(此時的右值引用又被稱為“萬能引用”)。

If a variable or parameter is declared to have type T&& for some deduced type T, that variable or parameter is a universal reference.

如果一個變量或者參數被聲明為T&&,其中T是被推導的類型,那這個變量或者參數就是一個universal reference

仍以 function() 函數為例,在 C++11 標準中實現完美轉發,只需要編寫如下一個模板函數即可:

template <typename T>
void function(T&& t) {otherdef(t);
}

此模板函數的參數 t 既可以接收左值,也可以接收右值。但僅僅使用右值引用作為函數模板的參數是遠遠不夠的,還有一個問題繼續解決,即如果調用 function() 函數時為其傳遞一個左值引用或者右值引用的實參,如下所示:

int n = 10;
int & num = n;
function(num); // T 為 int&
int && num2 = 11;
function(num2); // T 為 int &&

其中,由 function(num) 實例化的函數底層就變成了 function(int & & t),同樣由 function(num2) 實例化的函數底層則變成了 function(int && && t)。要知道,C++98/03 標準是不支持這種用法的,而 C++ 11標準為了更好地實現完美轉發,特意為其指定了新的類型匹配規則,又稱為引用折疊規則(假設用 A 表示實際傳遞參數的類型):

  • 當實參為左值或者左值引用(A&)時,函數模板中 T&& 將轉變為 A&(A& && = A&);
  • 當實參為右值或者右值引用(A&&)時,函數模板中 T&& 將轉變為 A&&(A&& && = A&&)。

讀者只需要知道,在實現完美轉發時,只要函數模板的參數類型為 T&&,則 C++ 可以自行準確地判定出實際傳入的實參是左值還是右值。

通過將函數模板的形參類型設置為 T&&,我們可以很好地解決接收左、右值的問題。但除此之外,還需要解決一個問題,即無論傳入的形參是左值還是右值,對于函數模板內部來說,形參既有名稱又能尋址,因此它都是左值。那么如何才能將函數模板接收到的形參連同其左、右值屬性,一起傳遞給被調用的函數呢?

C++11 標準的開發者已經幫我們想好的解決方案,該新標準還引入了一個模板函數 forward<T>(),我們只需要調用該函數,就可以很方便地解決此問題。仍以 function 模板函數為例,如下演示了該函數模板的用法:

#include <iostream>
using namespace std;
//重載被調用函數,查看完美轉發的效果
void otherdef(int & t) {cout << "lvalue\n";
}
void otherdef(const int & t) {cout << "rvalue\n";
}
//實現完美轉發的函數模板
template <typename T>
void function(T&& t) {otherdef(forward<T>(t));
}
int main()
{function(5);int  x = 1;function(x);return 0;
}

程序執行結果為:

rvalue
lvalue

注意程序中第 12~16 行,此 function() 模板函數才是實現完美轉發的最終版本。可以看到,forward() 函數模板用于修飾被調用函數中需要維持參數左、右值屬性的參數。

總的來說,在定義模板函數時,我們采用右值引用的語法格式定義參數類型,由此該函數既可以接收外界傳入的左值,也可以接收右值;其次,還需要使用 C++11 標準庫提供的 forward() 模板函數修飾被調用函數中需要維持左、右值屬性的參數。由此即可輕松實現函數模板中參數的完美轉發。

Ref:

http://c.biancheng.net/view/7829.html

http://c.biancheng.net/view/7847.html

http://c.biancheng.net/view/7863.html

http://c.biancheng.net/view/7868.html

https://cloud.tencent.com/developer/article/1561681

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/532555.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/532555.shtml
英文地址,請注明出處:http://en.pswp.cn/news/532555.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

C++11 std::function, std::bind, std::ref, std::cref

C11 std::function, std::bind, std::ref, std::cref 轉自&#xff1a;http://www.jellythink.com/ std::function 看看這段代碼 先來看看下面這兩行代碼&#xff1a; std::function<void(EventKeyboard::KeyCode, Event*)> onKeyPressed; std::function<void(Ev…

Java安全(一) : java類 | 反射

給個關注&#xff1f;寶兒&#xff01; 給個關注&#xff1f;寶兒&#xff01; 給個關注&#xff1f;寶兒&#xff01; 1.java基礎 Java平臺共分為三個主要版本Java SE&#xff08;Java Platform, Standard Edition&#xff0c;Java平臺標準版&#xff09;、Java EE&#xff0…

LeetCode-287 尋找重復數 二分法

LeetCode-287 尋找重復數 二分法 287. 尋找重復數 給定一個包含 n 1 個整數的數組 nums &#xff0c;其數字都在 1 到 n 之間&#xff08;包括 1 和 n&#xff09;&#xff0c;可知至少存在一個重復的整數。 假設 nums 只有 一個重復的整數 &#xff0c;找出 這個重復的數 。…

對某公司一次弱口令到存儲型xss挖掘

轉自我的奇安信攻防社區文章:https://forum.butian.net/share/885 免責聲明: 滲透過程為授權測試,所有漏洞均以提交相關平臺,博客目的只為分享挖掘思路和知識傳播** 涉及知識: xss注入及xss注入繞過 挖掘過程: 某次針對某目標信息搜集無意發現某工程公司的項目招標平臺 …

C++11新特性選講 語言部分 侯捷

C11新特性選講 語言部分 侯捷 本課程分為兩個部分&#xff1a;語言的部分和標準庫的部分。只談新特性&#xff0c;并且是選講。 本文為語言部分筆記。 語言 Variadic Templatesmove semanticsautoRange-based for loopInitializer listLambdas… 標準庫 type_traitsunodered…

java安全(二):JDBC|sql注入|預編譯

給個關注&#xff1f;寶兒&#xff01; 給個關注&#xff1f;寶兒&#xff01; 給個關注&#xff1f;寶兒&#xff01; 1 JDBC基礎 JDBC(Java Database Connectivity)是Java提供對數據庫進行連接、操作的標準API。Java自身并不會去實現對數據庫的連接、查詢、更新等操作而是通…

java安全(三)RMI

給個關注&#xff1f;寶兒&#xff01; 給個關注&#xff1f;寶兒&#xff01; 給個關注&#xff1f;寶兒&#xff01; 1.RMI 是什么 RMI(Remote Method Invocation)即Java遠程方法調用&#xff0c;RMI用于構建分布式應用程序&#xff0c;RMI實現了Java程序之間跨JVM的遠程通信…

LeetCode-726 原子的數量 遞歸

LeetCode-726 原子的數量 遞歸 題目鏈接&#xff1a;LeetCode-726 原子的數量 給你一個字符串化學式 formula &#xff0c;返回 每種原子的數量 。 原子總是以一個大寫字母開始&#xff0c;接著跟隨 0 個或任意個小寫字母&#xff0c;表示原子的名字。 如果數量大于 1&#xf…

java安全(四) JNDI

給個關注&#xff1f;寶兒&#xff01; 給個關注&#xff1f;寶兒&#xff01; 給個關注&#xff1f;寶兒&#xff01; 1.JNDI JNDI(Java Naming and Directory Interface)是Java提供的Java 命名和目錄接口。通過調用JNDI的API應用程序可以定位資源和其他程序對象。JNDI是Java…

二叉樹的層序遍歷和前中后序遍歷代碼 迭代/遞歸

前中后序遍歷&#xff08;DFS&#xff09; 首先我們要明確前中后序遍歷的順序&#xff1a; 前序&#xff1a;中左右中序&#xff1a;左中右后序&#xff1a;左右中 前中后序遍歷的遞歸代碼和迭代代碼分別有各自的框架&#xff0c;然后根據遍歷順序調整記錄元素的位置即可。 …

java安全(五)java反序列化

給個關注&#xff1f;寶兒&#xff01; 給個關注&#xff1f;寶兒&#xff01; 給個關注&#xff1f;寶兒&#xff01; 1. 序列化 在調用RMI時,發現接收發送數據都是反序列化數據. 例如JSON和XML等語言,在網絡上傳遞信息,都會用到一些格式化數據,大多數處理方法中&#xff0c…

git merge和rebase的區別與選擇

git merge和rebase的區別與選擇 轉自&#xff1a;https://github.com/geeeeeeeeek/git-recipes/wiki/5.1-%E4%BB%A3%E7%A0%81%E5%90%88%E5%B9%B6%EF%BC%9AMerge%E3%80%81Rebase-%E7%9A%84%E9%80%89%E6%8B%A9#merge BY 童仲毅&#xff08;geeeeeeeeekgithub&#xff09; 這是一篇…

java安全(六)java反序列化2,ysoserial調試

給個關注&#xff1f;寶兒&#xff01; 給個關注&#xff1f;寶兒&#xff01; 給個關注&#xff1f;寶兒&#xff01; ysoserial 下載地址&#xff1a;https://github.com/angelwhu/ysoserial ysoserial可以讓?戶根據??選擇的利?鏈&#xff0c;?成反序列化利?數據&…

C++面試常見問題一

C面試常見問題一 轉自&#xff1a;https://oldpan.me/archives/c-interview-answer-1 原作者&#xff1a;[oldpan][https://oldpan.me/] 前言 這里收集市面上所有的關于算法和開發崗最容易遇到的關于C方面的問題&#xff0c;問題信息來自互聯網以及牛客網的C面試題目匯總。答題…

java安全(七) 反序列化3 CC利用鏈 TransformedMap版

給個關注&#xff1f;寶兒&#xff01; 給個關注&#xff1f;寶兒&#xff01; 給個關注&#xff1f;寶兒&#xff01; 目錄圖解代碼demo涉及的接口與類&#xff1a;TransformedMapTransformerConstantTransformerInvokerTransformerChainedTransformerdome理解總結&#xff1a…

C++編譯時多態和運行時多態

C編譯時多態和運行時多態 作者&#xff1a;melonstreet 出處&#xff1a;https://www.cnblogs.com/QG-whz/p/5132745.html 本文版權歸作者和博客園共有&#xff0c;歡迎轉載&#xff0c;但未經作者同意必須保留此段聲明&#xff0c;且在文章頁面明顯位置給出原文連接&#xff0…

java安全(八)TransformedMap構造POC

給個關注&#xff1f;寶兒&#xff01; 給個關注&#xff1f;寶兒&#xff01; 給個關注&#xff1f;寶兒&#xff01; 上一篇構造了一個了commons-collections的demo 【傳送門】 package test.org.vulhub.Ser;import org.apache.commons.collections.Transformer; import org…

Pytorch Tutorial 使用torch.autograd進行自動微分

Pytorch Tutorial 使用torch.autograd進行自動微分 本文翻譯自 PyTorch 官網教程。 原文&#xff1a;https://pytorch.org/tutorials/beginner/basics/autogradqs_tutorial.html#optional-reading-tensor-gradients-and-jacobian-products 在訓練神經網絡時&#xff0c;最常使用…

TVM:編譯深度學習模型快速上手教程

TVM&#xff1a;編譯深度學習模型快速上手教程 本文將展示如何使用 Relay python 前端構建一個神經網絡&#xff0c;并使用 TVM 為 Nvidia GPU 生成一個運行時庫。 注意我們需要再構建 TVM 時啟用了 cuda 和 llvm。 TVM支持的硬件后端總覽 在本教程中&#xff0c;我們使用 cu…

TVM:設計與架構

TVM&#xff1a;設計與架構 本文檔適用于想要了解 TVM 架構和/或積極開發項目的開發人員。頁面組織如下&#xff1a; 示例編譯流程概述了 TVM 將模型的高層描述轉換為可部署模塊所采取的步驟。要開始使用&#xff0c;請先閱讀本節。 邏輯架構組件部分描述了邏輯組件。后面的部…