C++ : 构造函数,拷贝构造函数,移动构造函数,拷贝赋值运算符,移动赋值运算符应用场景
??構造函數,拷貝構造函數,移動構造函數,拷貝賦值運算符,移動賦值運算符應用場景
#include <iostream> using namespace std;class ConstructTest{ public:ConstructTest(){cout<<"default Constructor\n";dim_=0;base_= nullptr;}; ~ConstructTest(){cout<<"Destructor:base "<<base_<<endl;if (base_ != nullptr){delete base_;}}ConstructTest(int dim){cout<<"Constructor with param"<<endl;dim_=dim;base_ = new int [dim];for (int i = 0; i < dim_; ++i) {*(base_ + i) = 0;}}ConstructTest (const ConstructTest & a){cout<<"copy Constructor"<<endl;dim_= a.dim_;base_ = new int [dim_];for (int i = 0; i < dim_; ++i) {*(base_ + i) = *(a.base_+i);}}ConstructTest& operator =(const ConstructTest & a){cout<<"copy assign "<<endl;dim_= a.dim_;base_ = new int [dim_];for (int i = 0; i < dim_; ++i) {*(base_ + i) = *(a.base_+i);}return *this;}ConstructTest& operator =( ConstructTest && a)noexcept{cout<<"moving copy assign "<<endl;//避免自己移動自己if ( this == &a )return *this;delete base_;dim_ = a.dim_;base_ = a.base_;a.base_ = nullptr;return *this;}ConstructTest (ConstructTest && a) noexcept{cout<<"moving copy Constructor"<<endl;dim_ = a.dim_;base_ = a.base_;a.base_ = nullptr;}public:int dim_;int * base_; }; ConstructTest square(ConstructTest para){ConstructTest ret(para.dim_);ret.base_ = new int [para.dim_];for (int i = 0; i < para.dim_; ++i) {*(ret.base_+i) = *(para.base_+i) * (*(para.base_+i));}return ret; }int main(){ConstructTest c1(3);ConstructTest c2(c1);ConstructTest c4 ;c4=c2;cout<<"------------------------\n";ConstructTest c5 ;c5=square(c4);ConstructTest c6 = std::move(c5);cout<<"------------------------\n";ConstructTest c7;c7=ConstructTest();ConstructTest c8 = square(c7);ConstructTest c9 = ConstructTest();cout<<"<<<<<<finish >>>>>>>>\n"; }C++ 和JAVA不一樣的是,C++ 區分了值類型和引用類型,不像JAVA一樣全是引用類型。創建對象 JAVA 用? <類名> 對象名= new ......? 而C++ 用 <類名> 對象名 就行 . 對于普通對象 用值傳遞的方式傳到形參,有一個隱式賦值的過程,此時調用的拷貝構造函數.以下的構造函數使用場景:
構造函數?: 創建對象時,給對象初始化時調用。
拷貝(復制)構造函數: 利用相同的類對象給新對象初始化.時調用.
拷貝賦值運算符 : 兩個舊對象之間的賦值。
所謂“移動”就是把a的內存資源挪為自用。
移動構造函數: 在創建對象時,用臨時對象初始化時調用。在返回值傳給返回值的副本時也會調用。
移動賦值運算符:?用臨時對象給舊對象賦值時調用。
?
我用的是CLION,在CMakeList.txt 加入如下代碼,來取消編譯器優化
add_compile_options(-fno-elide-constructors)在無編譯器優化的情況下,輸出結果:
Constructor with param copy Constructor default Constructor copy assign ------------------------ default Constructor copy Constructor Constructor with param moving copy Constructor Destructor:base 0 moving copy assign Destructor:base 0 Destructor:base 0x3e1da8 ------------------------ moving copy Constructor ------------------------ default Constructor default Constructor moving copy assign Destructor:base 0 copy Constructor Constructor with param moving copy Constructor Destructor:base 0 moving copy Constructor Destructor:base 0 Destructor:base 0x3e1da8 default Constructor moving copy Constructor Destructor:base 0 <<<<<<finish >>>>>>>> Destructor:base 0 Destructor:base 0x3e1918 Destructor:base 0 Destructor:base 0x3e1dd8 Destructor:base 0 Destructor:base 0x3e1d90 Destructor:base 0x3e1d78 Destructor:base 0x3e1d60Process finished with exit code 0在main函數第一到第四行中,展示了構造函數,拷貝構造,和拷貝賦值。
從第5行起:
cout<<"------------------------\n";ConstructTest c5 ;c5=square(c4);ConstructTest c6 = std::move(c5);cout<<"------------------------\n";首先用 默認構造函數創建對象c5,
在c5 = square(c4) 中, 首先把實參c4賦值給square的形參param,此時用的是拷貝構造函數。
在square函數體中,創建了局部對象ret,此時調用構造函數,
然后為返回值ret創建副本,此時調用移動構造函數,接著函數體結束,把形參param析構,
然后把原來ret的副本賦值給c5 ,對過程是對已經存在的對象c5進行賦值,所以調用移動賦值。
接著把ret 和ret的副本給析構。
利用 std::move() 手動把c5的內容移動給某個臨時引用,把臨時引用初始化給c6,調用移動構造函數.
cout<<"------------------------\n";ConstructTest c7;c7=ConstructTest();ConstructTest c8 = square(c7);ConstructTest c9 = ConstructTest();cout<<"<<<<<<finish >>>>>>>>\n";先默認構造函數創建c7對象。
用默認構造函數創建臨時對象,把臨時對象"移動"給c7;移動完后把臨時對象析構
在c8?= square(c7) 中, 首先把實參c7賦值給square的形參param,此時用的是拷貝構造函數。
在square函數體中,創建了局部對象ret,此時調用構造函數,
然后為返回值ret創建副本,此時調用移動構造函數,接著函數體結束,把形參param析構,
然后把原來ret的副本賦值給c8 ,對還未創建的對象的對象c8進行初始化,所以調用移動構造。
接著把ret 和ret的副本給析構。
創建臨時對象,并用給c9進行初始化,最后把臨時對象析構掉;
程序結束,析構所有變量。
編程思路
? ? 在類成員變量帶有指針的情況下,會面臨如何施放資源的難題。應為默認拷貝構造,拷貝賦值,移動構造、移動賦值只是單單的淺拷貝。即值復制 指針指向內存的地址。在淺拷貝后,就會有兩個對象中的成員指針指向同一處內存空間,如果在析構函數中對成員指針delete,最后就會面臨對同一處內存空間施放兩次。在C++中,如果訪問不屬于本程序中的內存就會出現“段錯誤”,即指針越界.?
? ? 在編程時,可以利用 boost::shared_ptr 來管理動態分配的內存,此時可以不用理睬資源施放問題。因為智能指針能夠對動態內存的引用計數歸零時自動清理內存空間。
? ?如果要用 普通指針來管理動態內存,那么就要考慮施放資源,淺拷貝與深拷貝的問題。如果想用深拷貝那么就要,寫好構造函數,拷貝構造函數,拷貝賦值運算符,移動構造,移動賦值運算符;
? 在構造函數中初始化所有成員變量包括指針;在拷貝構造和拷貝賦值中重新為指針分配內存空間,并把對應的內存值進行賦值;在移動構造和移動賦值中,把返回內容復制完后要把臨時對象指針內容重新賦空,達到不拷貝內存又不會施放兩次內存的目的,即所謂的"移動“.
總結
以上是生活随笔為你收集整理的C++ : 构造函数,拷贝构造函数,移动构造函数,拷贝赋值运算符,移动赋值运算符应用场景的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: C++ :Signal: SIGSEGV
- 下一篇: C++ 数据结构-图相关操作的算法思路