很多情况下,一个操作可能返回一个值,也可能不返回值。比如在读取到文件的末尾时,你会希望没有返回值:
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?