C Primer第五版筆記 過載運算子(三)

2021-08-22 19:20:30 字數 4377 閱讀 9564

過載、型別轉換與運算子:

我們可以通過定義對於類型別的型別轉換,通過定義型別轉換運算子可以做到這點,轉換建構函式和型別轉換運算子共同定義了類型別轉換,這樣的轉化有時也被稱為使用者自定義的型別轉換。

型別轉換運算子:

型別轉換運算子是類的一種特殊成員函式,它負責將乙個類型別的值轉換成其他型別,一般形式如下:

operatortype() const;

其中type表示某種型別。型別轉換運算子可以面向任意型別(除了void外)進行定義,只要該型別能作為函式的返回值,也就是說不能轉換成陣列或是函式型別,但允許被轉換成指標(包括陣列指標和函式指標)或是引用等型別。

型別轉換運算子沒有顯式的返回型別,也沒有形參,而且必須定義成類的成員函式,型別轉換運算子通常不應該改變待轉換物件的內容,因此被定義為const成員。

定義含有型別轉換運算子的類:

假設定義乙個表示0-255之間的整數的類:

class smallint

}operator

int() const

private:

std::size_t val;

};

以上的類中,建構函式就相當於將乙個int型別的值轉換成了smallint類型別的物件,而型別轉換運算子則將smallint物件轉換成了int型別的值。而且因為內建型別轉換的存在,可以將更多算術型別傳遞給smallint的建構函式,類似的,也能將smallint轉換成其他算術型別,例如:

smallint si = 3.14; //內建型別轉換將double轉換為int後再呼叫建構函式

si + 3.14; //型別轉換運算子將si轉換成int,內建型別轉換再將它轉換為double

因為型別轉換是隱式執行的,所以無法為其傳遞引數,因此在宣告的時候也不需要定義任何形參,同時,儘管型別轉換函式不負責制定返回型別,但實際上每個型別轉換函式都會返回乙個對應型別的值。

型別轉換運算子可能產生意外結果:

在實踐中,類通常很少提供型別轉換運算子,一種例外情況是:對於類來說,定義bool的型別轉換還是比較普遍的。

在早期的c++標準中,如果類想定義乙個想bool型別的轉換,會遇到乙個問題:bool是一種算術型別,那麼類型別在被轉換成bool型別後將能用在算術運算上,比如,假設在istream中定義了向bool型別的轉換,以下**仍能通過編譯:

int i = 42;

cin<< i;

該程式將輸出運算子用到了輸入流上,因為istream本身沒有定義<<,所以這段**本來應該產生錯誤,但是定義的型別轉換運算子將cin轉換成bool,而這個bool值緊接著被轉換成int型別。這樣一來,這行**相當於將乙個int型別的值(這裡應該是0或1)左移了42位。

顯式的型別轉換運算子:

為防止這樣的異常情況發生,在新標準中引入了顯式的型別轉換運算子:

class smallint

//其他定義與之前一樣

};

編譯器通常不會將乙個顯式的型別轉換運算子用於隱式轉換:

smallint si = 3;    //正確,建構函式不是隱式

si + 3; //錯誤,此處需要隱式的型別轉換,但是型別轉換運算子是顯式的

static_cast

(si) + 3; //正確,顯式的請求型別轉換

這種情況的例外就是,當表示式出現在以下位置時,顯式的型別轉換將被自動隱式執行:

1.if、while以及do的條件部分;

2.for語句中的條件部分;

3.邏輯非運算子(!)、邏輯或運算子(||)、邏輯與運算子(&&)的運算物件;

4.條件運算子(? :)的條件表示式。

轉換為bool:

當我們在條件中使用流物件時,都會使用io型別定義的operator bool。例如:

while(cin >> value)
while語句的條件執行輸入操作,它負責將資料讀入到value中,並返回cin。為了對條件求值,cin被istream operator bool隱式的執行轉換。如果cin的條件狀態是good,則函式返回為真,否則返回為假。

避免有二義性的轉換:

如果類中包含乙個或多個型別轉換,則必須確保在類型別和目標型別之間只存在唯一的一種轉換方式。

有兩種情況可能會造成二義性:第一種情況是兩個類提供相同的型別轉換。例如在a類中有將b類轉換為a類的建構函式,在b類中也有將b類轉換成a類的型別轉換運算子函式,例如:

struct b;

struct a;

struct b;

a a(const a&);

b b;

a c = a(b); //在這裡發生二義性錯誤,不知道應該是

//a(b::operator a()),還是

//a(a::a(const b&))

//不得不使用顯示來呼叫型別轉換運算子或建構函式

a a1 = a(b.operator a());

a a2 = a(a(b));

第二種是類定義了多個型別轉換規則,而這些轉換設計的型別本身可以通過其他型別聯絡在一起。最典型的例子是算術運算子,對某個給定的類來說,最好只定義最多乙個與算術型別有關的轉換規則。例如:

struct a;

void f(long

double);

a a1;

f(a1); //發生二義性錯誤,不知道是讓a1轉換成int還是double

long l;

a a2(l); //發生二義性錯誤,不知道該呼叫a(int)還是a(double)

這裡不論是long double還是long都不能被a精確匹配,而不論int還是double都需要呼叫內建的型別轉換,並且他們所需的轉換級別相同,編譯器無法分辨出優劣,產生了二義性,如果把long換成short,則會優先匹配a(int),因為把short提公升為int優於把short轉換為double的操作。

過載函式與轉換建構函式:

當我們呼叫過載的函式時,從多個型別轉換中進行選擇將變得更為複雜。因為當兩個或多個型別轉換都提供了同一種可行匹配時,這些型別轉換往往一樣好。例如:

struct a;

struct b;

//過載函式的引數為不同型別的類,但這兩個類都有同樣的轉換建構函式

void f(const a&);

void f(const b&);

f(10); //二義性錯誤,不知道是f(c(10))還是f(d(10))

f(c(10)); //通過顯式地構造正確型別消除二義性

過載函式與使用者定義的型別轉換:

當呼叫過載函式做型別轉換時,如果兩個或多個使用者定義的類中都有可行的型別轉換,也會出現二義性,比如:

struct a;

struct b;

void mapip2(const a&);

void mapip2(const b&);

mapip2(10); //二義性錯誤,不知道應該是mapip2(a(10))

//還是mapip2(b(double(10)))

即使10看起來和int是精確匹配的,但編譯器還是會將該呼叫標記為錯誤。

函式匹配和過載運算子:

過載運算子也是過載的函式,所以通用的函式匹配規則也同樣適用於判斷在給定的表示式中到底是使用內建運算子還是過載的運算子。當然這也有可能帶來二義性錯誤,比如:

class smallint //轉換目標為int的型別轉換

private:

size_t val;

};smallint s1,s2;

smallint s3 = s1 + s2; //正確,呼叫operator+

int i = s3 + 1; //二義性錯誤

第一條加法語句接受兩個smallint值並執行+運算子的過載版本。第二條語句具有二義性:因為可以將s3轉換成int,然後執行內建的整數相加;或者把1轉換成smallint,然後使用過載的+。

C Primer第五版筆記 過載運算子(一)

過載運算子是具有特殊名字的函式 由關鍵字operator加上要定義的運算符號組成,與其它函式相同,也有返回值 引數列表以及函式體。當乙個運算子函式是類的成員函式時,this指標繫結到左側運算物件。我能只能過載已有的大多數運算子,而無權發明新的運算符號 通常情況下也不過載逗號 取位址 邏輯與 邏輯或運...

C Primer第五版筆記 關聯容器

一 型別 關聯容器支援高效的關鍵字查詢和訪問,標準庫中兩個主要的關聯容器是map和set。map中的元素是鍵值對關鍵字表示索引。set中每個元素只包含乙個關鍵字,set支援高效的關鍵字查詢。關聯容器根據三個特性可以分為8種 1 set還是map 2 關鍵字是否可以重複,允許重複的容器名字中都包含單詞...

C Primer第五版筆記 動態陣列

allocator類 為了讓new分配乙個物件陣列,需要在型別名後跟方括號,括號中是分配物件的數目,該數目必須是整型,但不必是常量 new t 分配的記憶體並不是得到乙個陣列,而是得到乙個陣列元素型別的指標,因此不能對動態陣列呼叫begin和end 與普通陣列不同的是,普通陣列不能定義長度為0的陣列...