很多情況下,一個操作可能返回一個值,也可能不返回值。比如在讀取到檔案的末尾時,你會希望沒有返回值:
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
不為nil
,error
指標也就不一定有效了。
在上面這些例子中,函式返回了一個“神奇”值來表示它其實沒有返回真正有用的值。這樣的“神奇”值被成為“哨兵值(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!");
}
複製程式碼
如果someString
為nil
,那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?