1. 程式人生 > >從Qt談到C++(一):關鍵字explicit與隱式型別轉換

從Qt談到C++(一):關鍵字explicit與隱式型別轉換

提出疑問

當我們新建了一個Qt的widgets應用工程時。會自動生成一個框架,包含了幾個檔案。其中有個mainwindow.h的標頭檔案。就是你要操縱的UI主介面了。我們看看其中的一段程式碼:

class MainWindow : public QMainWindow
{
    Q_OBJECT//一個巨集,暫不考慮

public:
    explicit MainWindow(QWidget *parent = 0);
    ~MainWindow();

private:
    Ui::MainWindow *ui;
};

這段程式碼定義了一個新的類MainWindow,繼承自QMainWindow。我們可以看到在它的建構函式裡,前面有一個關鍵字 explicit 。相信大家都對沒有這個關鍵字的建構函式不陌生。那麼這個 explicit 是起到什麼作用的呢?

explicit研究

explicit是C++中的關鍵字,不是C語言中的。英文直譯是“明確的”、“顯式的”意思。出現這個關鍵字的原因,是在C++中有這樣規定的基礎上:當定義了只有一個引數的建構函式時,同時也定義了一種隱式的型別轉換。先看型別轉換。

型別轉換

C/C++中,有很多型別轉換。比如:
double a = 12.34;
int b = (int)a;
我們都知道這時b的值是12. 在變數前面加括號包裹的型別,就能實現顯式的型別轉換。這種叫做強制型別轉換。順便值得一提的是,C++中還支援這種強制型別轉換的例子:
double a = 12.34;
int b = int(a);
除此之外,還有一種轉換叫做 隱式型別轉換。
double a = 12.34;
int b = a;
同樣的,b的值也是12.雖然沒有顯式的轉換型別,但是編譯器會幫你自動轉換。同樣的,不僅是基本資料型別,自己定義的類和物件之間也存在這種轉換關係。

隱式轉換的場景

等於號與建構函式

比如你有一個類的物件A:
class A
{
public:
	A(int i)
	{
		a = i;
	}
	int getValue()
	{
		return a;
	};
private:
	int a;
};

你會發現,你在main函式中,使用下面的語句時是合法的:
A a;
a  = 10;
之所以類A的物件可以直接使用整型通過等於號來初始化,是因為這一語句呼叫了預設的單引數建構函式,這種建構函式又稱 型別轉換建構函式
。其效果等價於A temp(10); a(temp); 首先編譯器執行A temp(10);在棧中建立了一個臨時物件(假設叫做temp)。然後再呼叫物件a的拷貝初始化建構函式 a(temp) 給a初始化。然後臨時物件temp銷燬。這就是編譯器做的隱式轉換工作。你可以想到這樣的隱式操作的結果和直接顯示呼叫A a(10);的結果是一樣的,但是隱式轉換因為使用了拷貝建構函式所以在開銷上會更高一些。當然這基本資料型別,或許不明顯。如果一個複雜的物件,比如Qt的視窗。那麼開銷可想而知。
注意當你使用A a = 10;時並不會產生中間的臨時物件。而是直接把10作為引數傳遞給型別轉換建構函式。
又如當你使用如下語句會不通過:
A a = "123";
因為沒有引數為字串的單引數建構函式。知道了這個,你修改一下就能通過了。
class A
{
public:
	A(int i)
	{
		a = i;
	}
	A(char * c)
	{
		a=c[0];
	}
	int getValue()
	{
		return a;
	};
private:
	int a;
};

函式呼叫

我們再定義一個函式print 用來列印A物件的值。
void print(A a)
{
	cout<<a.getValue();
};
在main函式中:
void main()
{
	print(10);
}
這樣是可以編譯執行的。雖然我們並沒有建立一個類A的物件來傳給print 函式。但是編譯器預設會呼叫類A的單引數建構函式,創建出一個類A的物件出來。


加上explicit

上面可以看出編譯器會為你做一些,隱式的型別轉換工作。這或許會讓你感到方便,但是有時候卻會帶來麻煩。我們來做一個假設: 上面這個類A,只接受整型和字串型。所以你想傳遞一個字串 “2” 作為引數來構造一個新的物件。比如 A b = “2”; 然而,你卻寫錯了,雙引號寫成了單引號變成了 A b = ‘2’; 然而這樣並不會報錯。編譯器 會把 字元 ‘2’ 轉型成 整型 也就是它的ascll碼—— 50。為了避免這樣我們不希望的隱式轉換,我們可以加上explicit 關鍵字。
public:
	explicit A(int i)
	{
		a=i;
	}

這樣就能避免隱式的型別轉換了,當你誤寫成單引號的時候,就會報錯。這樣就只允許顯示的呼叫單引數構造函數了。如 A a(10); A b("123");  不僅如此,在加上explicit之後,print函式也會報錯了。因為編譯器不會主動呼叫explicit標識的構造器。這就需要你自己顯示的來呼叫了:
print(A(10));
當然了,是否應該禁止隱式轉換是沒有定論的,沒有一種放之四海皆準的標準,具體看你的情景需要了。
一般而言,顯示呼叫構造器,能避免一些麻煩,讓程式設計師手動來管理。很多人說C++難,因為很多東西對於程式設計師來說不是透明的,比如記憶體釋放什麼的,這個顯式呼叫也是需要程式設計師自己動手的。然而我感覺這正是C++的魅力所在,C++給了程式設計師幾乎等同於上帝的權力,所有一切都能自己掌控,還比如運算子過載的權力,甚至像Qt這樣可以自定義slot和signal關鍵字(其實是巨集),這在其他高階語言裡是不可想象的。當然了,語言這東西,是仁者見仁智者見智的。沒必要爭論優劣。我一直認為的是:沒有最優秀的語言,只有最合適的語言。程式語言本身沒有優劣之分,但是不同程式設計師對於不同語言確有好惡之別。

順便一提

explicit關鍵字只用在類內部的宣告中。在外部的實現部分不需要使用。

#include<iostream>
using namespace std;
class A
{
public:
	explicit A(int i);
	A(char * c)
	{
		a=c[0];
	}
	int getValue()
	{
		return a;
	};
private:
	int a;
};
A::A(int i)//無需再指明explicit
{
	a=i;
}
void print(A a)
{
	cout<<a.getValue();
};
void main()
{
	print(A(10));
}