简体   繁体   English

为什么 C++ 模运算符为 -1 % str.size() 返回 0?

[英]Why does the C++ modulo operator return 0 for -1 % str.size()?

I'm confused why the following code produces this output:我很困惑为什么下面的代码会产生这个 output:

#include <iostream>
#include <string>

using namespace std;

int main()
{
    int i = -1;
    string s = "abc";
    int j = s.size();
    int x = 1 % 3;
    int y = i % j; 
    int z = i % s.size(); 
    cout << s.size() << endl; // 3
    cout << x << endl;        // 1
    cout << y << endl;        // -1
    cout << z << endl;        // 0
}

Why is z = 0?为什么z = 0? Does it have to do with casting?跟选角有关系吗?

What really happens here:这里真正发生了什么:

int z = i % s.size();

is i is converted to size_t because the other side s.size() is size_t . is i被转换为size_t因为另一边s.size()size_t And unlike int , size_t is unsigned;int不同, size_t是无符号的; That is to say the value is interpreted as a positive number.也就是说,该值被解释为正数。

Check the output of this line:检查这一行的output:

std::cout << (size_t)-1 << std::endl;

to see what -1 has become.看看 -1 变成了什么。

So, stripping down your code to a minimal example, you're asking why this prints 0 :因此,将您的代码简化为一个最小的示例,您会问为什么会打印0

#include <iostream>
#include <string>

int main()
{
    int a = -1;
    std::string::size_type b = 3; 
    int c = a % b;
    std::cout << c << '\n';
}

The primary operation in question here is this:这里有问题的主要操作是这样的:

a % b

Per the standard,按照标准,

5.6 Multiplicative operators [expr.mul] 5.6 乘法运算符 [expr.mul]

  1. The operands of * and / shall have arithmetic or unscoped enumeration type; * 和 / 的操作数应具有算术或非范围枚举类型; the operands of % shall have integral or unscoped enumeration type. % 的操作数应具有整数或无范围枚举类型。 The usual arithmetic conversions are performed on the operands and determine the type of the result .通常的算术转换在操作数上执行并确定结果的类型

So.. what about those "usual arithmetic conversions"?那么..那些“通常的算术转换”呢? This is to mate the types of the two operands to a common type prior to performing the actual operation.这是为了在执行实际操作之前将两个操作数的类型匹配为一个通用类型。 The following are considered in order :以下是按顺序考虑的

  • If both operands are integers, integer promotion is first performed on both operands.如果两个操作数都是整数,则首先对两个操作数执行 integer 提升。 If after integer promotion the operands still have different types, conversion continues as follows:如果 integer 提升后操作数仍然有不同的类型,则转换继续如下:
    • If one operand has an unsigned type T whose conversion rank is at least as high as that of the other operand's type, then the other operand is converted to type T.如果一个操作数的无符号类型 T 的转换等级至少与另一个操作数的类型一样高,则另一个操作数将转换为类型 T。
    • Otherwise, one operand has a signed type T whose conversion rank is higher than that of the other operand's type.否则,一个操作数的有符号类型 T 的转换等级高于另一操作数类型的转换等级。 The other operand is converted to type T only if type T is capable of representing all values of its previous type.只有当类型 T 能够表示其先前类型的所有值时,另一个操作数才会转换为类型 T。
    • Otherwise, both operands are converted to the unsigned type that corresponds to the signed type T.否则,两个操作数都转换为对应于有符号类型 T 的无符号类型。

That's a lot of legalize for what effectively says this:对于有效地说明这一点来说,这是很多合法化的:

  • You have two operands, a signed int and a std::string::size_type您有两个操作数,一个带signed int和一个std::string::size_type
  • The rank of std::string::size_type is greater than that of signed int std::string::size_type的秩大于signed int
  • Therefore, the signed int operand is converted to type std::string:size_type prior to the operation being requested.因此,有signed int操作数在请求操作之前被转换为std::string:size_type类型。

So all that is left is the conversion, to wit, there is one more piece of legalize:所以剩下的就是转换,也就是说,还有一个合法化:

4.7 Integral conversions [conv.integral] 4.7 积分转换[conv.integral]

  1. If the destination type is unsigned, the resulting value is the least unsigned integer congruent to the source integer (modulo 2 n where n is the number of bits used to represent the unsigned type).如果目标类型是无符号的,则结果值是与源 integer 一致的最小无符号 integer(模 2 n ,其中 n 是用于表示无符号类型的位数)。 [Note: In a two's complement representation, this conversion is conceptual and there is no change in the bit pattern (if there is no truncation). [注意:在二进制补码表示中,这种转换是概念性的,位模式没有变化(如果没有截断)。 —end note] ——尾注]

That means, on a 32-bit std::string::size_type platform, you're going to get 2 32 -1 as the converted value from int (-1).这意味着,在 32 位std::string::size_type平台上,您将获得 2 32 -1 作为来自int (-1) 的转换值。

Which means...意思是...

4294967295 % 3

Which is... zero .这是...... If std::string::size_type is 64-bits, then everything above stays the same, save for the final calculation, which would be:如果std::string::size_type是 64 位,那么上面的一切都保持不变,除了最后的计算,这将是:

18446744073709551615 % 3

@GhaziMajdoub's answer is correct, but - why don't you let the compiler tell you what's happening? @GhaziMajdoub 的回答是正确的,但是 - 你为什么不让编译器告诉你发生了什么?

Let's use the Flags to enable thorough and verbose g++ warnings ...让我们使用标志来启用彻底和详细的 g++ 警告......

$ g++ -pedantic -Wall -Wextra -Wcast-align -Wcast-qual -Wctor-dtor-privacy \ 
-Wdisabled-optimization -Wformat=2 -Winit-self -Wlogical-op -Wmissing-declarations \
-Wmissing-include-dirs -Wnoexcept -Wold-style-cast -Woverloaded-virtual \
-Wredundant-decls -Wshadow -Wsign-conversion -Wsign-promo -Wstrict-null-sentinel \
-Wstrict-overflow=5 -Wswitch-default -Wundef -Werror -Wno-unused -o a a.cpp
a.cpp: In function ‘int main()’:
a.cpp:12:13: error: conversion to ‘std::__cxx11::basic_string<char>::size_type’ {aka
‘long unsigned int’} from ‘int’ may change the sign of the result
[-Werror=sign-conversion]
   12 |     int z = i % s.size();
      |             ^
cc1plus: all warnings being treated as errors

a.cpp: In function ‘int main()’:
a.cpp:12:13: warning: conversion to ‘std::__cxx11::basic_string<char>::size_type’ 
{aka ‘long unsigned int’} from ‘int’ may change the sign of the result [-Wsign-
conversion]
   12 |     int z = i % s.size();
      |             ^

and there you have it: i is converted to long unsigned int , so it's no longer -1 .你有它: i被转换为long unsigned int ,所以它不再是-1

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM