Почему оператор C ++ по модулю возвращает 0 для -1% str.size ()? - PullRequest
3 голосов
/ 19 апреля 2020

Я запутался, почему следующий код производит такой вывод:

#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
}

Почему z = 0? Это связано с кастингом?

Ответы [ 3 ]

3 голосов
/ 19 апреля 2020

Что здесь действительно происходит:

int z = i % s.size();

- это i, преобразуется в size_t, потому что другая сторона s.size() равна size_t. И в отличие от int, size_t не подписан; То есть значение интерпретируется как положительное число.

Проверьте вывод этой строки:

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

, чтобы увидеть, что стало -1.

2 голосов
/ 19 апреля 2020

Таким образом, сокращая ваш код до минимального примера, вы спрашиваете, почему это печатает 0:

#include <iostream>
#include <string>

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

Основная операция, о которой идет речь, заключается в следующем:

a % b

В соответствии со стандартом,

5,6 Мультипликативные операторы [expr.mul]

Операнды * и / должны иметь арифметику c или тип перечисления с незаданной областью; операнды% должны иметь целочисленный или незаданный тип перечисления. Обычные арифметические c преобразования выполняются над операндами и определяют тип результата .

Так что насчёт этих "обычных арифметических * c конверсии "? Это должно соединить типы двух операндов с общим типом до для выполнения фактической операции. Далее рассматриваются в порядке :

  • Если оба операнда являются целыми числами, целочисленное продвижение сначала выполняется для обоих операндов. Если после целочисленного преобразования операнды по-прежнему имеют разные типы, преобразование продолжается следующим образом:
    • Если один операнд имеет тип без знака T, ранг преобразования которого по крайней мере так же высок, как у другого операнда, тогда другой операнд преобразуется в тип T.
    • В противном случае один операнд имеет тип со знаком T, ранг преобразования которого выше, чем у типа другого операнда. Другой операнд преобразуется в тип T, только если тип T способен представлять все значения своего предыдущего типа.
    • В противном случае оба операнда преобразуются в тип без знака, соответствующий типу со знаком T.

Это лот для легализации того, что фактически говорит это:

  • У вас есть два операнда, signed int и std::string::size_type
  • Ранг std::string::size_type на больше , чем signed int
  • Следовательно, операнд signed int преобразуется в тип std::string:size_type до до запрашиваемой операции.

Таким образом, все, что осталось, - это преобразование, то есть есть еще одна легализация:

4.7 Интегральные преобразования [conv.integral]

Если тип назначения не имеет знака, полученное значение является наименьшим целым числом без знака, совпадающим с целым числом источника (по модулю 2 n , где n - число битов, используемых для представления типа без знака). [Примечание: в представлении дополнения до двух это преобразование является концептуальным, и в битовой комбинации нет изменений (если нет усечения). - конец примечания]

Это означает, что на 32-битной std::string::size_type платформе вы получите 2 32 -1 в качестве преобразованного значения из int (-1).

Что означает ...

4294967295 % 3

Что означает ... ноль . Если std::string::size_type 64-битный, то все выше остается тем же, за исключением окончательного вычисления, которое будет:

18446744073709551615 % 3
1 голос
/ 19 апреля 2020

@ GhaziMajdoub ответ правильный, но - почему бы вам не позволить компилятору сообщить вам, что происходит?

Давайте использовать флаги для включения подробных и подробных предупреждений 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();
      |             ^

и вот, что у вас есть: i преобразуется в long unsigned int, поэтому он больше не -1.

...