第四章——可選型別(哨兵值)

bestswifter發表於2017-12-27

很多情況下,一個操作可能返回一個值,也可能不返回值。比如在讀取到檔案的末尾時,你會希望沒有返回值:

int ch;
while ((ch = getchar()) != EOF) {
printf("Read character %c\n", ch);
}
printf("Reached end-of-file\n");
複製程式碼

EOF通過#define被預定義為-1。所以當檔案中還有內容時,getchar會返回內容,如果訪問到了檔案末尾,getchar就會返回-1。

有時候沒有返回值也表示“未找到”。比如這段C++的程式碼:

auto vec = {1,2,3}
auto iterator = std::find(vec.begin(), vec.end(), someValue);
if (iterator != vec.end()) {
std::cout << "vec contains " << *iterator << std::endl;
}
複製程式碼

你可以用一個迭代器與vec.end()比較,如果相等表示已經迭代到了容器的末端。但你一定不會用它來獲取某個值。find方法用vec.end()表示容器中沒有這個值。

還有些時候,我們根本無法獲得返回值,因為在函式執行的過程中發生了比較嚴重的錯誤。我們都知道空指標的例子,這段很普通的Java程式碼就很有可能丟擲一個NullPointerException異常:

int i = Integer.getInteger("123");
複製程式碼

在這段程式碼中我們理解錯了getInteger()方法的用法[1],導致它返回了null。當null被轉換成int型別時,Java丟擲了NullPointerException異常。

再看一個OC中的例子:

[[NSString alloc] initWithContentsOfURL:url
encoding:NSUTF8StringEncoding
error:&error];
複製程式碼

這時的NSString可能為nil,於是我們需要檢查(也只有此時需要檢查)error指標。如果NSString不為nilerror指標也就不一定有效了。

在上面這些例子中,函式返回了一個“神奇”值來表示它其實沒有返回真正有用的值。這樣的“神奇”值被成為“哨兵值(sentinel value)”。

但這種解決方案可能會帶來一些問題。返回的結果看上去像是一個真正的值。比如-1也是一個有效的整數而你不會把它列印出來。v.end()也是迭代器但你如果試圖使用它,你無法確定會得到什麼值。而且每個人都希望在Java丟擲NullPointerException異常時能夠列印當前的呼叫堆疊。

所以哨兵值很容易導致錯誤。你有可能忘記了檢查它,而是錯誤的使用了一個哨兵值。他們還要求實現對問題有一定了解,比如C++的end迭代器。很多時候你需要去查一查文件才能確定怎麼使用。而且我們沒有辦法讓函式告訴呼叫者它不能失敗。比如呼叫一個函式返回的是指標,那這個指標就不能為nil。但一般不通過查閱文件,我們無法區分,何況有些時候文件有可能會出錯。

在OC中,向nil物件傳送訊息是安全的。如果根據方法簽名返回的是物件,那麼nil物件的方法會返回nil,如果方法簽名返回的是結構體,那麼nil物件的方法會返回結構體的零值。但是,考慮下面這段程式碼:

NSString *someString = ...;
if ([someString rangeOfString:@"swift"].location != NSNotFound) {
NSLog(@"Someone mentioned swift!");
}
複製程式碼

如果someStringnil,那rangeOfString: message方法會返回一個NSRange結構體的零值,也就是說.location為0,而NSNotFound被定義為NSIntegerMax。因此if語句也會執行。這顯然不合理,因為nil字串不包含"swift"。

Null引用帶來了太多令人頭疼的問題。Tony Hoare曾在1965年把之稱為“帶來幾十億美元損失的錯誤”。他批評道:

當時我為一個物件導向的語言(ALGOL W)設計第一個綜合性的引用型別系統,我的本意是通過編譯器的自動檢查,確保所有的引用都是絕對安全的。但我沒能抵擋住新增一個null引用的誘惑,僅僅是因為它非常容易實現。這導致了無數的錯誤和系統崩潰,在過去的四十年中可能導致了數十億美元的損失。

#譯者注

[1]:應該用parseInt()方法。getInteger()方法用於獲取某個系統屬性。具體解釋參考:

Why does int num = Integer.getInteger(“123”) throw NullPointerException?

相關文章