国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 編程 > C++ > 正文

c++教程(十九: Special members)

2019-11-11 01:51:08
字體:
來源:轉載
供稿:網友

特殊成員函數是在某些情況下被隱式定義為類的成員函數,下面6種:

這里寫圖片描述

下面對每一個進行介紹

默認構造函數

默認構造函數就是一個對象被聲明以后而沒有初始化元素的時候會被調用。

如果一個類沒有默認的構造函數,編譯器就假設這個類有一個默認的構造函數。因此聲明一個類可以像下面這樣:

class Example { public: int total; void accumulate (int x) { total += x; }};

這個例子中編譯器默認有一個構造函數。因此這個類可以簡單的表示為不帶參數的形式:

Example ex;

但是假如一個類一旦擁有了構造函數,不管這個構造函數帶不帶參數,那么編譯器將不會在產生默認的構造函數,新的類聲明中也不允許這個類不帶參數,例如:

class Example2 { public: int total; Example2 (int initial_value) : total(initial_value) { }; void accumulate (int x) { total += x; };};

這里,可以聲明一個帶參數int類型的類構造函數,因此下面的一個聲明是對的:

Example2 ex (100); // ok: calls constructor

但是下面這樣不對:

Example2 ex; // not valid: no default constructor

上面這一個將無效,因為在類的聲明中是需要一個參數的構造函數來代替了沒有參數的默認構造函數。

因此如果這個類的實體沒有被帶參數的構造函數所構造,那么默認的構造函數就會在類中被聲明。例如:

// classes and default constructors#include <iostream>#include <string>using namespace std;class Example3 { string data; public: Example3 (const string& str) : data(str) {} Example3() {} const string& content() const {return data;}};int main () { Example3 foo; Example3 bar ("Example"); cout << "bar's content: " << bar.content() << '/n'; return 0;}

在這里,Example3 有一個默認的構造函數(沒有參數)用空模塊定義:

Example3() {}

這就可以讓類Example3以不帶參數的構造函數出現(如同前面的foo函數例子)。通常情況下,默認的構造函數是隱式定義的,所有的類都沒有其他構造函數,因此不需要顯式定義。但在這種情況下,Example3 有另一個構造函數: Example3 (const string& str);

當不管有什么其他的構造函數被聲明時,那么編譯器就不會自動提供默認的構造函數了。

析構函數

析構函數完成構造函數相反的功能:他們負責必要的清理需要結束的一些類。我們在前面的章節中定義的類沒有分配任何資源,因此并沒有真正需要任何清理。

但是現在,想象一下最后一個例子中的類如果動態分配內存來存儲它作為數據成員的字符串,在這種情況下,如果有一個函數在這個類生命結束時自動調用來清除這個內存將是非常有用的。要做到這一點,我們使用一個析構函數。析構函數是一個默認構造函數非常相似的成員函數:它不需要參數,沒有返回,不是void。它還使用類的名字作為自己的名字,但前面有一個符號標志(~):

// destructors#include <iostream>#include <string>using namespace std;class Example4 { string* ptr; public: // constructors: Example4() : ptr(new string) {} Example4 (const string& str) : ptr(new string(str)) {} // destructor: ~Example4 () {delete ptr;} // access content: const string& content() const {return *ptr;}};int main () { Example4 foo; Example4 bar ("Example"); cout << "bar's content: " << bar.content() << '/n'; return 0;}

這個例子中,Example4 分配一個內存個string,這塊內存稍后會被析構函數釋放掉。

析構函數會在這個類使用周期結束的時候執行。 這個例子中foo和bar就是在main函數結束的時候執行。

構造函數復制

當一個對象通過一個它自己類型的被命名對象作為參數時,它的構造函數就會被以構造函數的一個副本被復制。

copy 構造函數的第一個參數是它的類本身的類型引用(可能是const),它可以用一個參數調用該類型。例如,一個類MyClass,復制構造函數可以像下面的形式:

MyClass::MyClass (const MyClass&);

如果一個類沒有定義copy 或者move構造函數,那么則提供了一個隱式copy 構造函數。此copy 構造函數只執行其自己的成員的副本。例如,對于一個類:

class MyClass { public: int a, b; string c;};

這里隱式copy 構造函數會自動生成,這個假設這個函數 執行一個shallow copy,大致相當于:

MyClass::MyClass(const MyClass& x) : a(x.a), b(x.b), c(x.c) {}

這個默認的copy構造函數可以滿足許多類的需要。但shallow copy復制該類本身的成員,這或許不是我們所期望的像我們上面定義 Example4的類那樣,因為它包含指針,它操作了內存。對于那個類,進行shallow copy意味著指針的值被復制,而不是內容本身;這里意味著物體(復制的和原件的)將共享一個字符串對象(他們都會指向同一個對象),并在一些點(結構上)對象會嘗試刪除相同的內存塊,可能導致程序崩潰的運行。這可以通過定義執行以下deep copy來代替shallow copy解決:

// copy constructor: deep copy#include <iostream>#include <string>using namespace std;class Example5 { string* ptr; public: Example5 (const string& str) : ptr(new string(str)) {} ~Example5 () {delete ptr;} // copy constructor: Example5 (const Example5& x) : ptr(new string(x.content())) {} // access content: const string& content() const {return *ptr;}};int main () { Example5 foo ("Example"); Example5 bar = foo; cout << "bar's content: " << bar.content() << '/n'; return 0;}

該 deep copy函數為一個新的字符串分配存儲執行的深拷貝,該存儲被初始化為原始對象的一個副本。在這種方式中,兩個對象(復制和原始)都有不同的副本存儲在不同的位置。

復制賦值

對象不僅在結構上可以被復制,當它們被初始化時,它們也可以被復制到任何賦值操作上。看下面的不同例子:

MyClass foo;MyClass bar (foo); // object initialization: copy constructor calledMyClass baz = foo; // object initialization: copy constructor calledfoo = bar; // object already initialized: copy assignment called

注意,baz 使用等號初始化,但這不是一個賦值操作!(盡管它可能看起來像一個):對象的聲明不是一個賦值操作,它是另一個的語法調用單個參數的構造函數。

foo的分配是一個賦值操作。在這里沒有對象被聲明,但這個操作是在現有對象上執行。

復制賦值操作符是運算符的重載,它以參數的值或引用本身作為參數。返回值通常是*this指針(雖然這不是必需的)。例如,一個類myclass,副copy操作可能具有以下特點:

MyClass& Operator= (const MyClass&);

復制賦值操作符也是一個特殊的函數,也被定義為隱式,如果一個類沒有自定義的copy ,也沒有move 賦值(也沒有move構造函數),那么將會自動隱式定義。

但是,隱版的淺拷貝適用于很多類,而不只是類的對象為指針的處理存儲,比如example5例子。在這種情況下,不僅類會導致刪除指向對象的雙倍風險,而且任務在賦值之前通過不刪除對象所指向的對象而創建內存泄漏。可以使用刪除前一個對象的副本分配來解決這些問題,并執行一個深度拷貝:

Example5& operator= (const Example5& x) { delete ptr; // delete currently pointed string ptr = new string (x.content()); // allocate space for new string, and copy return *this;}

更好的操作是,因為它的string成員不是常量,可以重新利用相同的string對象:

Example5& operator= (const Example5& x) { *ptr = x.content(); return *this;}

構造函數的移動與賦值

和復制一樣,移動也使用一個對象的值來設置到另一個對象上。但是,不像復制,移動的內容實際上是從一個對象(源)轉移到另一個對象上(目的地):源對象會丟失所有的內容,到達目標對象上。只有當源對象的值是一個未命名的對象時才會發生這種情況。

未命名的對象是臨時性的對象,因此沒有被命名。未命名對象的典型例子是函數或類型轉換的返回值。

使用一個臨時對象的值,如對象初始化為另一個對象或賦值,不需要拷貝:對象永遠不會被用于其他任何東西,因此,它的值可以被移動到目標對象。這些情況視為移動構造函數和移動任務分配:

當使用一個未命名的臨時對象來初始化一個對象時,調用這個移動構造函數。同樣,當一個對象被賦值給一個未命名的臨時的值時,調用這個移動賦值:

MyClass fn(); // function returning a MyClass objectMyClass foo; // default constructorMyClass bar = foo; // copy constructorMyClass baz = fn(); // move constructorfoo = bar; // copy assignmentbaz = MyClass(); // move assignment

無論是fn的返回值還是MyClass類返回值都是無名的臨時量。在這些情況下,沒有必要復制,因為未命名的對象是非常短暫的,可以通過其他對象獲取,當這是一個更有效的操作。

移動構造函數和移動賦值是需要類本身作為一個參數的成員:

MyClass (MyClass&&); // move-constructorMyClass& operator= (MyClass&&); // move-assignment

右值引用指定由以下兩個符號類型(&&)。作為一個參數,右值引用符合這種類型的臨時參數。

移動的概念是管理他們使用的內存、如分配新的和刪除存儲的對象的最有用的對象。在這樣的對象中,復制和移動是真的不同的操作:

從A到B的復制意味著新的內存被分配給B,然后一個被復制到這個新的內存分配給B的整個內容。 從A到B的移動意味著已經分配給A的內存被轉移到B沒有分配任何新的存儲。它涉及簡單的指針復制。 例如:

// move constructor/assignment#include <iostream>#include <string>using namespace std;class Example6 { string* ptr; public: Example6 (const string& str) : ptr(new string(str)) {} ~Example6 () {delete ptr;} // move constructor Example6 (Example6&& x) : ptr(x.ptr) {x.ptr=nullptr;} // move assignment Example6& operator= (Example6&& x) { delete ptr; ptr = x.ptr; x.ptr=nullptr; return *this; } // access content: const string& content() const {return *ptr;} // addition: Example6 operator+(const Example6& rhs) { return Example6(content()+rhs.content()); }};int main () { Example6 foo ("Exam"); Example6 bar = Example6("ple"); // move-construction foo = foo + bar; // move-assignment cout << "foo's content: " << foo.content() << '/n'; return 0;}

編譯器已經優化了許多情況,一般情況下需要一個移動構造的被稱為Return Value Optimization。最值得注意的是,當函數返回的值用于初始化一個對象時,在這種情況下,移動構造函數實際上可能永遠不會被調用。

注意即使用于任何函數參數的類型右值引用,它也比移動構造函數使用的少。右值引用是棘手的,不必要的情況下使用錯誤很難跟蹤。

隱式成員

上面描述的六個特別成員函數是在某些情況下在類上隱式聲明的成員:

這里寫圖片描述

注意,在同一情況下,不是所有的特殊成員函數都是隱式定義的。這主要是由于C的結構和早期的C++版本的向后兼容性,事實上,包括一些過時的例子。幸運的是,每個類都可以顯式地選擇這些成員中存在的默認定義或通過使用關鍵字默認和刪除刪除,語法之一是:

function_declaration = default;function_declaration = delete;

例如:

// default and delete implicit members#include <iostream>using namespace std;class Rectangle { int width, height; public: Rectangle (int x, int y) : width(x), height(y) {} Rectangle() = default; Rectangle (const Rectangle& other) = delete; int area() {return width*height;}};int main () { Rectangle foo; Rectangle bar (10,20); cout << "bar's area: " << bar.area() << '/n'; return 0;}

在這里,Rectangle 可以有兩個int參數或默認構造構造(不帶參數)。它不能從另一個Rectangle 對象復制構建,因為這個函數已經被刪除了。因此,假設示例是的最后一個對象,下面的語句將無效:

Rectangle baz (foo);

然而,它可以通過將其復制構造函數定義為顯式有效的:

Rectangle::Rectangle (const Rectangle& other) = default;

這基本上相當于:

Rectangle::Rectangle (const Rectangle& other) : width(other.width), height(other.height) {}

請注意,默認關鍵字不定義一個成員函數等于默認構造函數(即,默認構造函數意味著沒有參數的構造函數),但等于將隱式定義的構造函數,如果沒有刪除。

在一般情況下,為和未來的兼容性,需要明確定義一個復制/移動構造函數,或一個復制/移動分配,但不是兩個都鼓勵指定刪除或默認的其他他們沒有明確定義的特殊成員的功能。


發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表

圖片精選

主站蜘蛛池模板: 定日县| 河池市| 巩义市| 卓资县| 秦皇岛市| 富顺县| 广水市| 永州市| 武陟县| 临澧县| 安西县| 双牌县| 南投县| 宁海县| 大足县| 城步| 青浦区| 宣武区| 西宁市| 杭锦旗| 张家川| 柯坪县| 邢台市| 阿城市| 疏勒县| 永新县| 卓资县| 全南县| 黄梅县| 安康市| 东乡族自治县| 汤原县| 郧西县| 晋州市| 常熟市| 望奎县| 内江市| 弥渡县| 高雄县| 包头市| 南投县|