与有C++背景的人讨论,他们一部分人指出在有的地方应该尽可能使用有符号数,无符号类型 size_t
仅仅是历史的偶然,在今天绝不会这样定义.,我不同意这个观点,于是有了这篇文章。
本文地址:http://wuyudong.com/2016/08/27/2648.html,转载请注明源地址。
本文的内容仅仅在C中运行,有一些也可能扩展到C++与其他的语言,例如旧版的 Pascal 包含一个 cardinal
类型.
让我们实战一下,看看数组的索引使用什么类型,这里称为 T
。一个使用这样索引的典型代码如下:
for (T i = 0; i < array_size; ++i) { A[i] = something_nice; }
这里的第一个“问题”是比较操作 i < array_size
. 你的编译器可能不会出问题,如果 i
和 array_size
是不同的无符号类型。所有人都知道,索引和边界应该使用相同的类型,若是变量, array_size
应该这样定义:
T array_size = something;
如果不是变量而是表达式会怎样?表达式应该是相同的类型。现在问题出现了,在C中表达式的类型是一个微妙的主题。在一般情况下,你只能通过查找其所有组成的类型,才能找到一个表达式的具体类型。如果我们假设他们都是相同的宽度,
- 全部有符号,结果有符号
- 全部无符号,结果无符号
- 混合,结果无符号
(也许在一些历史平台上有点不同,但在过去的20年里应该没问题)
现在边界表达式通常包含无符号操作,理由很简单,size_t 是无符号类型:一个计算数组中元素个数的典型技巧:
sizeof A / sizeof A[0]
例如,对于上面的简单循环我们有:
for (T i = 0; i < sizeof A / sizeof A[0]; ++i) { A[i] = something_nice; }
T就应该是无符号类型
另一个关于索引的典型的就是判断他们是不是在边界内。通常下界是0,上界是一个值。一个更复杂的典型迭代的代码:
for (T i = start_value; in_bounds(i); i = update(i)) { A[i] = something_nice; }
怎样实现 in_bounds
? 如果 T
是符号类型的你可以这样做:
bool in_bounds(T i) { return i >= 0 && i < some_value; }
如果是无符号类型的,你可以更加简单的实现:
bool in_bounds(T i) { return i < some_value; }
这些都取决于 update(i)
, 甚至是 i 的增值。假设数组A至少有42个元素,下面的代码体现了 size_t
的魅力:
for (size_t i = 41; i < sizeof A / sizeof A[0]; --i) { A[i] = something_nice; }
这样的计算总是很好的定义的行为,无符号类型只是包裹在小于 0 或大于他们的最大值。没有陷阱,没有信号,也没有异常。
通常索引的无符号类型计算仅仅是计算,然而有符号类型,你往往需要考虑计算一些边界表达式和特殊情况的处理。所以使用size_t 无符号类型并不是历史的偶然,如果你想要事情简单这样做是必要的。和其他大多数其他现代编程语言相比,C是简单而直接的。
现在为什么要用size_t而不是无符号?因为它有宽度,你可能需要在你的平台上处理任何一种对象。例如,如果你使用一个狭窄宽度的无符号类型来为矩阵做复杂的索引计算将很容易溢出。
Comments