天天看點

C++ Primer類設計者的工具

C++ Primer類設計者的工具

1 C++類中有四個不可或缺的部分,那就是構造函數、拷貝構造函數、指派操作符和析構函數。如果類中沒有定義這些函數,那麼編譯器将為類自動生成這些函數。當然,你也可以通過private控制政策限定不使用拷貝構造函數和指派操作符。

其中,拷貝構造函數、指派操作符和析構函數總稱為拷貝控制(copy control)。

當類中有指針類資料成員時,一般都需要自已實作類的拷貝控制。通常有兩種處理政策:一是定義值型類,每個類保留一份指針指向的對象的拷貝;另一種更常用的政策是使用智能指針(smart pointer),其通用技術是采用引用計數(reference count)來實作共享指針指向的對象。

我們知道C++中變量初始化有兩種形式:直接初始化和拷貝初始化。直接初始化将初始化放在圓括号中,而拷貝初始化使用=符号。對于内置類型,這兩者基本上沒有差別。但對于類類型,兩種方式是有差別的:直接初始化直接調用與實參比對的構造函數;而拷貝初始化總是調用拷貝構造函數,具體而言,就是拷貝初始化首先使用指定構造函數建立一個臨時對象,然後用拷貝構造函數将那個臨時對象拷貝到正在建立的對象。

支援拷貝初始化主要是為了與C的用法相容。當情況允許時,可以允許編譯器跳過拷貝構造函數直接建立對象,但編譯器沒有義務這樣做。注:事實上大多數編譯器都跳過了拷貝構造函數,因為這完全可以跳過。

我們知道可以用表示容量的單個參數來初始化容器,容器的這種構造方式使用了預設構造函數和拷貝構造函數。例如:

vector svec(5);

編譯器首先使用string的預設構造函數建立一個臨時值來初始化svec,然後使用拷貝構造函數将臨時對象拷貝到svec的每個元素。示例代碼如下

#include

#include

using namespace std;

class Test {

public:

Test() {

cout<<”Contructor”<<endl;

}

Test(const Test& t) {

cout<<”copy Contructor”<<endl;

}

};

int main() {

vector tvec(5);

return 0;

}

輸出結果為:(MinGW 2.05和VC6.0)

Contructor

copy Contructor

copy Contructor

copy Contructor

copy Contructor

copy Contructor

對于元素為類類型的數組,可以使用數組初始化清單來提供顯示元素初始化。此時,使用拷貝初始化來初始化每個元素。根據指定值建立适當類型的元素,然後用拷貝構造函數将該值拷貝到相應元素。當然,同前面一樣,是否跳過拷貝構造函數取決于編譯器(事實上,大多數編譯器跳過了這步)。

拷貝構造函數的形參是一個類類型引用(否則,參數本身就需要過拷貝構造函數了),但一般情況下,我們使用const修飾。并且,一般不應該設定為explicit。有時需要禁止拷貝類,例如,iostream類就不允許拷貝。這時,應當顯示聲明拷貝構造函數為private,此時可以不定義該函數。若聲明為private且進行了函數定義,則類的友元和成員仍然可以進行拷貝。

合成的拷貝構造函數:

如果我們沒有定義拷貝構造函數,則編譯器會自動生成一個,把這個自動生成的拷貝構造函數叫合成的拷貝構造函數(Synthesized Copy Constructor)。合成的拷貝構造函數執行逐個成員初始化,将新對象初始化為原對象的副本。如果成員是内置類型,則執行位拷貝;如果成員是類類型,則調用相應的拷貝構造函數;如果成員是數組類型,則分别對每個數組元素進制拷貝。

2 标準庫容器、string和shared_ptr類支援移動也支援拷貝,IO類和unique_ptr類可以移動但不能拷貝。

3 int i=42;

Int &r=i;

Int &&rr=I; //錯誤不能将一個右值引用綁定到一個左值上

Int &r2=I42; //錯誤,I42是一個右值

Const int &r3=I42;//錯誤,可以将const引用綁定到一個右值上

Int &&rr2=I42; 将rr2綁定到乘法結果上

雖然不能将一個右值引用直接綁定到一個左值上,但我們可以顯示将一個左值轉換對應右值引用類型。可以調用一個名為move的新标準庫函數來獲得綁定到左值上的右值引用。

Int &&rr3=std::move(rr2); 在調用move之後,我們不能對移後原對象的值做任何假設。

4 區分移動和拷貝的重載函數通常有一個版本接受一個const T&,而另一個版本接受一個T&&。

5 引用限定符:

class Foo{

Public:

Foo &operator=(const Foo&) &; //隻能向可修改的左值指派

};

Foo &Foo::operator=(const Foo &rhs) &

{

Return *this;

}

6 成員通路運算符:

Class StrBlobPtr{

Std::string & operator() const{

Auto p=check(curr, “dereference past end”);

Return (p)[curr];

}

Std::string operator->() const{

Return & this->operator();

}

}

7 重載的函數與function

我們不能(直接)将重載函數的名字存入function類型的對象中;

Int add(int i,int j){ return i+j;}

Sales_data add(const Sales_data &, const Sales_data&);

Map<string,function<int(int,int)>> binops;

Binops.insert({“+”,add}); //錯誤,不知是哪個add

Int (*fp)(int,int)=add;

Binops.insert({“+”,fp});//正确,fp指向一個正确的add版本

8 C++11新标準提供一種防止繼承發生的方法,在類名跟一個關鍵詞final

9 如果派生類(既内層作用域)的成員與基類(既外層作用域)的某個成員同名,派生類将在作用域内隐藏該基類成員。即使派生類成員和基類成員的形參清單不一緻,基類成員也仍然被隐藏;

Struct Base{

Int menfcn();

}

Struct Derived:Base{

Int memfcn(int);

}

Derived d;Base b;

b.memfcn();

d.memfcn(10);

d.memfcn(); //錯誤,參數清單為空的memfcn被隐藏了

d.Base::memfcn(); //正确,調用Base::memfcn

10 不存在從基類向派生類的隐式類型轉換:

Quote base;

Bulk_quote* bulkP=&base; //錯誤,不能将基類轉換成派生類

Bull_quote& bulkRef=base; //錯誤,不能将基類轉換成派生類

11 繼承關系類型之間的轉換規則:

從派生類向基類的類型轉換隻對指針或引用類型有效;

基類向派生類不存在隐式類型轉換;

和任何其他成員一樣,派生類向基類的類型轉換也可能會由于通路受限而變得不可行;

12 如果派生類的成員與基類的某個成員同名,則派生類将在其作用域内隐藏基類成員。即使派生類成員和基類成員的形參清單不一緻,基類成員仍然會被隐藏掉;

Struct Base{

Int memfcn();

}

Struct Derived:Base{

Int memfcn(int);

}

Derived d;Base b;

b.memfcn();

d.memfcn(10);

d.menfcn(); //錯誤,參數清單為空的memfcn被隐藏了

d.Base::memfcn();

13 函數模闆:template

Int compare(const T &v1,const T &v2){

}

Template T foo(T p){

T tmp=p;

Return tmp;

}