《C++ Primer Plus 6th.ed》讀書筆記之二:類型轉換初步

C語言的類型轉換

有過C語言基礎的讀者應該知道,C語言可以進行兩種不同的類型轉換:

  • 自動類型轉換
    char
    int
    short
    long
    float
    double
    其中以double的優先級最高,即所有轉換都朝向優先級高的方向進行,不會違反這個順序
    這是基本的類型轉換方法,其過程是隱式完成的,不需要程序猿人爲干預
  • 強制類型轉換
    這類轉換一般多見於嵌入式開發中,例如:
    float x = 32.333;
    int y = (int)x;	// 取變量x的整數部分,x = 32
    
    類似於上述例子的,違反自動類型轉換優先級的,稱爲強制類型轉換

C++ 完全繼承了C語言的類型轉換機制,而且在這個基礎上,派生出更多更強大的類型轉換方法

C++基本類型轉換的新語法.

C++對於基本類型的轉換,除了從C語言中繼承的以外,也出現了新的語法糖,以上面的截尾操作爲例:

float x = 32.333;
int y = int(x); // 語法風格上更接近python這樣的動態語言

這種寫法在各種腳本語言或者解釋型語言中較爲常見,歸根結底這是一種函數形式的轉換,即將int()作爲一個函數來調用;不過很可惜的是我並沒有在STL中找到用於基本數據類型的函數聲明,竊以爲這種形式是直接在編譯器中實現的
這種方法對於所有的基本數據類型都適用,只要給定typename()函數就行了;不過相應的,推薦使用這種新的語法做轉換,以便於和接下來介紹的轉換方法保持形式上的一致

C++自定義數據類型與基本類型的轉換

自定義數據類型主要是指class/struct,這二者在c++中並無本質區別——雖然這二位也算是基本數據類型但是不同的類之間,是沒辦法拿它們作爲一個數據類型來看待的
爲了敘述方便,這裏給出一個類,它接受浮點數並將該浮點數表示的小時數轉換爲x hours, y minutes這樣的格式:

class timeconv
{
private:
	int hours;		//	小時
	int minutes;	// 分鐘
public:
	timeconv() {} 		// 默認構造函數
	timeconv(double h)	// 轉換構造函數
	{
		hours = int(h);
		minutes = int((h - hours) * 60);
	}
	// 重載double() 轉換函數
	operator double() const { return hours + double(minutes) / 60.0f; }
	// 重載<<,將這個類嵌入到標準流中(省略實現)
	friend std::ostream& operator<<(std::ostream& os, const timeconv& s) const};
基本數據類型向自定義類型的轉換

可以看到,上面代碼中給出的類的聲明中包含一個接受double類型的構造函數。這意味着如果要創建一個timeconv類,以下幾種寫法是等價的:

timeconv mytc;
mytc = 2.0; // the 1st way
mytc = timeconv(2.0); 	// the 2nd way
mytc = (timeconv)2.0;	// the 3rd way

其中,第一種方式隱式的調用了在類聲明中定義的轉換構造函數,第二種和第三種方式都顯式的調用了轉換構造函數,而且第三種方式是繼承自C語言的風格,因此不太推薦;但是這三種方法在語義上是完全等價的,也都完成了由基本類型向自定義類型的轉換
觀察上面的例子,轉換構造函數至少應該擁有這樣的特點:

  • 是構造函數
  • 僅含有一個參數,且該參數不是自定義類型的const引用

然而事實上,這樣的函數也可以是轉換構造函數:

timeconv(int hours, int minutes = 0);

跟上面的代碼相比,不難發現,該函數雖然有兩個參數,但是其中一個參數有默認值;因此,轉換構造函數的特點應該是:

  • 是構造函數
  • 無默認值的函數參數有且只能有一個

這樣我們就藉助轉換構造函數完成了由基本類型向自定義類型的轉換

可能存在的轉換二義性

雖然在上一小節完成了預期的目標,但是這樣的轉換是有一定風險的,考慮以下代碼:

class timeconv
{
	// 其他內容省略
public:
	timeconv(long hours);
	timeconv(double hours);
}

uint8_t xx = 16u;
timecove nytc = xx;

可以看到,這裏給了兩個構造轉換函數,而且在最終構造類的時候,根據基本類型的自動轉換規則,uint8_t,也就是unsigned char類型,既可以轉換爲long,也可以轉換爲double——這樣就給編譯器出了個老大難的問題,到底應該調用哪個轉換構造函數?
一般這種現象被稱之爲轉換的二義性,這樣的代碼必然會被編譯器拒絕而報出編譯錯誤,在實際的工程中,應當注意可以被隱式調動的轉換構造函數的個數,使之不多於一個

小插曲:explicit關鍵字

剛纔提到了轉換的二義性,而且提出解決方法是讓可以被隱式調用的轉換構造函數不得多於一個——那麼在具體的工程中,真的不能定義多個轉換構造函數嗎?答案是能,這個時候就要請出expilicit關鍵字了
可以在不希望被隱式調用的轉換構造函數前加上該關鍵字,使得該轉換構造函數必須被顯式調用:

class timeconv
{
	// 省略其他內容
public:
	explicit timeconv(double hours);	// 不希望隱式調用該函數
}

timeconv mytc;
mytc = 16.00// 非法,未顯式調用轉換構造函數
mytc = timeconv(16.00); // 合法
mytc = (timeconv)16.00; // 合法
自定義類型向基本數據類型的轉換

做到了基本類型向自定義類型的轉換,自然也可以逆向該過程,實現自定義類型向基本類型的轉換,這個過程需要依賴轉換函數,而且該轉換屬於強制類型轉換,即不可能隱式調用轉換函數完成

轉換函數:由用戶定義的強制類型轉換

前面timeconv類的完整聲明中已經給出了一個定義好的double()轉換函數,這些轉換函數具有這樣的特點:

  • 聲明中無返回值類型,但是函數體中必須帶有return語句,且必須返回該轉換函數聲明的類型
  • 聲明中無函數參數,也不必使用void填充
  • 聲明中必須帶operator關鍵字
  • 函數名就是轉換後的數據類型
  • 必須聲明爲類成員函數

雖然講了一大堆注意事項,但是轉換函數的調用是非常簡單的:

timeconv mytc = timeconv(17.00);	// 定義一個待轉換的類

double xx1 = double(mytc);	// the 1st way
double xx2 = (double)mytc;	// the 2nd way

如同這段示例代碼展示的那樣,兩種書寫方式都是合法且易用的,它們完整繼承了C++的強制轉換語法,因此只需花費一點心思在轉換函數的聲明與定義上即可

結束

本文粗淺的討論了一下C++中基本的類型轉換,對於自定義類的相互轉換,例如static_castdynamic_cast等知識,將會在以後的文章中繼續發佈

發佈了17 篇原創文章 · 獲贊 20 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章