为什么将“score[11] = {};”和“grade”声明为“unsigned”,而不是“int”?

3

我是一名新手学习C++,正在尝试学习数组的概念。我在网上看到了这段代码片段。对于下面的示例代码,声明时是否有任何区别:

unsigned scores[11] = {};
unsigned grade; 

as:

int scores[11] = {};
int grade; 

我猜score[11] = {};grade被声明为unsigned肯定有原因,但是背后的原因是什么呢?

int main() {
    unsigned scores[11] = {};
    unsigned grade;
    while (cin >> grade) {
        if (0 <= grade <= 100) {
            ++scores[grade / 10];
        }
    }
    for (int i = 0; i < 11; i++) {
        cout << scores[i] << endl;
    }
}

5
请询问代码的原作者。以我的观点来看,在这里使用“unsigned”没有任何理由。 - MikeMB
2
@MikeMB 分数和成绩可以是负数吗?如果不行,那么它们应该是无符号的。特别是,看起来成绩被用作数组索引,所以它可能必须是正数。我的建议是,如果你知道负值没有意义,就把它们设置为无符号的。 - Colin Pitrat
3
@ColinPitrat,MikeMB 在这里是正确的。使用 unsigned 实际上会增加代码的复杂度。为了使其工作,应该使用 cin >> astring 将输入读入字符串,然后使用 strtoulstd::stoul 将字符串转换为无符号数据类型。这里可能更喜欢使用 strtoul,因为它可以处理退出条件而不抛出异常。 - user4581301
2
@user4581301:这个小例子是正确的,但在实际生活中并不是一个好的例子。在现实生活中,当用户输入-1或者无效输入时,你不想忽略它们。在现实生活中,你会有很多处理成绩的代码,你不想在所有函数中都检查>0。所以我更喜欢坚持变量的语义,并使用无符号类型,因为它是未来的保障。无论如何,检查<=100将捕获-1(但也许不能捕获一些大的、精心选择的负值)。 - Colin Pitrat
3
0 <= grade <= 100 可能不会产生你期望的结果。我记得有些编译器也会对这种错误发出警告,但我可能记错了... - anderas
显示剩余3条评论
5个回答

6
unsigned指的是变量不会保存负值(更准确地说-它不会关注符号)。很明显,scoresgrades都是无符号值(没有人考 -25 分)。因此,使用unsigned是很自然的选择。
但请注意:if (0 <= grade <= 100)是多余的。只需要if (grade <= 100)就足够了,因为不允许有负数值。
正如Blastfurnace所评论的那样,if (0 <= grade <= 100)甚至是错误的。如果你想这样写,应该写成:if (0 <= grade && grade <= 100)

1
你可能会提到表达式 0 <= grade <= 100 是无意义的。 - Blastfurnace
我确实做了。我有什么遗漏吗? - Humam Helfawi
1
那个表达式几乎肯定不会做 OP 认为它应该做的事情。这不是测试变量是否在两个值之间的方式。 - Blastfurnace
1
@Blastfurnace:这不是无意义的,只是一种奇怪的写法来表示“true”。 - Colin Pitrat
1
@ColinPitrat:我并没有说它不能编译或产生结果。它作为范围检查是无意义的,作为混淆的“true”也是无意义的。 - Blastfurnace

4

无符号变量

将变量声明为unsigned int而不是int有两个后果:

  • 它不能是负数。这为您提供了一个保证,即它永远不会是负数,因此在编写仅使用正整数的代码时,您不需要检查它并处理特殊情况。
  • 由于您具有有限的大小,因此它允许您表示更大的数字。在32位上,最大的unsigned int是4294967295(2^32-1),而最大的int是2147483647(2^31-1)

使用unsigned int的一个后果是算术运算将在unsigned int集合中进行。所以9 - 10 = 4294967295而不是-1,因为无法在unsigned int类型上编码负数。如果将它们与负的int进行比较,则还会遇到问题。

更多关于如何编码负整数的信息。

数组初始化

对于数组定义,如果只写:

unsigned int scores[11];

那么你有11个未初始化的无符号整型,这些变量的值可能不同于0。

如果你写:

unsigned int scores[11] = {};

所有的 int 均会被初始化为默认值 0。

请注意,如果您编写以下代码:

unsigned int scores[11] = { 1, 2 };

你将会得到第一个整数初始化为1,第二个初始化为2,所有其他的都为0。
你可以轻松地玩弄这些语法,以更好地理解它。
比较
关于代码:
if(0 <= grade <= 100)

正如评论中所述,这段代码并没有达到你期望的效果。事实上,它总是会被判定为真,因此会执行if语句中的代码。这意味着如果你输入一个20000分的成绩,你应该会遇到核心转储(core dump)问题。原因在于以下代码:

0 <= grade <= 100

等价于:

(0 <= grade) <= 100

第一部分要么是true(隐式转换为1),要么是false(隐式转换为0)。由于这两个值都小于100,所以第二个比较总是true


1
如果你想计算scores[0]scores[1]之间的差异怎么办?scores[0] - scores[1]应该是-1,而不是4294967295 - Oktalist

3
"无符号整数具有一些奇怪的属性,除非你有充分的理由,否则应该避免使用它们。获得1个额外的正数位或表达一个值不能为负的约束条件,都不是好的理由。 无符号整数实现算术运算UINT_MAX+1。相比之下,有符号整数的操作表示我们从学校里熟悉的自然算术。 溢出语义 无符号整数具有定义良好的溢出;有符号整数则没有:"
unsigned u = UINT_MAX;
u++; // u becomes 0
int i = INT_MAX;
i++; // undefined behaviour

这意味着,在测试期间可以捕获有符号整数溢出,而无符号溢出可能会默默地执行错误的操作。因此,只有在确定要合法化溢出时才使用unsigned
如果您有一个值不能为负数的约束条件,则需要一种方法来检测和拒绝负值;int非常适合这个任务。 unsigned将接受负值并将其静默地溢出为正值。 位移语义 对于不大于数据类型中位数的数量的unsigned位移,始终定义良好。在C++20之前,如果signed位移导致符号位中的1向左移动,它是未定义的,如果导致符号位中的1向右移动,则是实现定义的。自C++20以来,signed右移始终保留符号,但signed左移则不保留符号。因此,对于某些位操作,请使用unsigned混合符号操作 内置算术运算始终对相同类型的操作数进行操作。如果提供了不同类型的操作数,则“通常的算术转换”将它们强制转换为相同的类型,有时会产生令人惊讶的结果:
unsigned u = 42;
std::cout << (u * -1); // 4294967254
std::cout << std::boolalpha << (u >= -1); // false

什么是不同之处?

从一个无符号值中减去另一个无符号值会产生一个无符号结果,这意味着21之间的差值是4294967295

最大值加倍

int类型在表示数值时使用一个比特位来表示其符号。unsigned类型则将该比特位作为另一个数字比特位来使用。因此,通常情况下,int有31个数字比特位,而unsigned有32个。这个额外的比特位经常被认为是使用unsigned类型的原因之一。但是,如果31个比特位对于某个特定目的来说是不够的,则很可能32个比特位也不够用,你应该考虑64位或更多。

函数重载

intunsigned的隐式转换与从intdouble的转换具有相同的级别,因此以下示例是非法的:

void f(unsigned);
void f(double);
f(42); // error: ambiguous call to overloaded function

互操作性

许多API(包括标准库)使用unsigned类型,通常是出于错误的原因。与这些API交互时,使用unsigned是明智的选择,以避免混合符号运算。

附录

引用的代码片段包括表达式0 <= grade <= 100。它将首先评估0 <= grade,这始终为true,因为grade不能为负数。然后它将评估true <= 100,这始终为true,因为true转换为整数1,并且1 <= 100true


2
是的,这确实有所不同。在第一种情况下,您声明了一个由11个元素组成的类型为“unsigned int”的变量数组。在第二种情况下,您将它们声明为int。
当int为32位时,您可以具有以下范围内的值
-2,147,483,648到2,147,483,647 -普通int
0到4,294,967,295 - 无符号int
通常在不需要负数并且需要无符号给定的额外范围时,会将某些内容声明为无符号。在您的情况下,我假设通过声明变量为无符号,开发人员不接受负分数和等级。您基本上对通过命令行输入的0到10之间有多少个等级进行统计。因此,它看起来像是模拟学校评分系统的东西,因此您没有负面得分。但这是我阅读代码后的看法。
查看此文章,其中解释了无符号数据类型:what is the unsigned datatype?

2

如其名,有符号整数可以为负数,无符号整数则不能。如果我们用N位表示一个整数,则对于无符号整数,最小值为0,最大值为2^(N-1)。如果是N位的有符号整数,则它的取值范围为-2^(N-2)到2^(N-2)-1。这是因为我们需要1位来表示符号+/-。

例如:有符号3位整数(是的,这种东西是存在的)

000 = 0
001 = 1
010 = 2
011 = 3
100 = -4
101 = -3
110 = -2
111 = -1

对于未签名的数值,它仅代表[0,7]范围内的数值。在该示例中,最高有效位(MSB)表示负值。也就是说,所有MSB已设置的值都是负数。因此,在绝对值方面出现了一个似乎的位数丢失。

它的行为也如人们所期望的那样。如果将-1(111)增加1,则得到(1 000),但由于我们没有第四位,它只是“掉落”,留下了000。

对于从0减去1的情况也是一样。首先进行二进制补码运算。

 111 = twos_complement(001) 

将其加到000中得到111 = -1(从表中得出),这是人们可能预期的结果。当您将011(= 3)递增产生100(= -4)时会发生什么,可能不是人们所期望的,并且与我们的正常预期不一致。这些溢出在定点算术中很麻烦,必须加以处理。

还有一件值得指出的事情是,有符号整数可以比正数多取一个负值,这对舍入(例如使用整数表示固定点数)有影响,但我确信这在DSP或信号处理论坛上更好地涵盖了。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接