Нужно ли явно обрабатывать отрицательные числа или ноль при суммировании квадратов?


220

У меня недавно был тест в моем классе. Одной из проблем было следующее:

Учитывая число n , напишите функцию на C / C ++, которая возвращает сумму цифр квадрата числа . (Важно следующее) Диапазон из п является [- (10 ^ 7), 10 ^ 7]. Пример: если n = 123, ваша функция должна вернуть 14 (1 ^ 2 + 2 ^ 2 + 3 ^ 2 = 14).

Это функция, которую я написал:

int sum_of_digits_squared(int n) 
{
    int s = 0, c;

    while (n) {
        c = n % 10;
        s += (c * c);
        n /= 10;
    }

    return s;
}

Смотрел прямо на меня. Итак, теперь тест вернулся, и я обнаружил, что учитель не дал мне все пункты по причине, которую я не понимаю. По его словам, чтобы моя функция была завершена, я должен был добавить следующую деталь:

int sum_of_digits_squared(int n) 
 {
    int s = 0, c;

    if (n == 0) {      //
        return 0;      //
    }                  //
                       // THIS APPARENTLY SHOULD'VE 
    if (n < 0) {       // BEEN IN THE FUNCTION FOR IT
        n = n * (-1);  // TO BE CORRECT
    }                  //

    while (n) {
        c = n % 10;
        s += (c * c);
        n /= 10;
    }

    return s;
}

Аргументом для этого является то, что число n находится в диапазоне [- (10 ^ 7), 10 ^ 7], поэтому оно может быть отрицательным числом. Но я не вижу, где происходит сбой моей собственной версии функции. Если я правильно понимаю, значение while(n)есть while(n != 0), а не while (n > 0) так, поэтому в моей версии функции число n не может не войти в цикл. Это будет работать точно так же.

Затем я попробовал обе версии этой функции на своем компьютере дома, и я получил точно такие же ответы на все примеры, которые я пробовал. Итак, sum_of_digits_squared(-123)равно sum_of_digits_squared(123)(что опять равно 14) (даже без детали, которую я, очевидно, должен был добавить). Действительно, если я попытаюсь напечатать на экране цифры числа (от наименьшего до наибольшего значения), в 123случае, если я получу, 3 2 1и в -123случае, если получу -3 -2 -1(что на самом деле довольно интересно). Но в этой проблеме это не имеет значения, так как мы возводим цифры в квадрат.

Так кто же не прав?

РЕДАКТИРОВАТЬ : мой плохой, я забыл указать и не знал, что это важно. Версия C, используемая в нашем классе и тестах, должна быть C99 или новее . Поэтому я предполагаю (прочитав комментарии), что моя версия получит правильный ответ в любом случае.


120
n = n * (-1)это нелепый способ писать n = -n; Только академик мог бы подумать об этом. Не говоря уже о добавлении лишних скобок.
user207421

32
Напишите серию модульных тестов, чтобы проверить, соответствует ли данная реализация спецификации. Если есть (функциональная) проблема с фрагментом кода, должна быть возможность написать тест, который демонстрирует неверный результат при конкретном вводе.
Карл

94
Интересно, что «сумма цифр квадрата числа» может быть интерпретирована тремя (3) совершенно разными способами. (Если число 123, возможные интерпретации дают 18, 14 и 36.)
Андреас Рейбранд

23
@ilkkachu: "сумма цифр числа в квадрате". Итак, «число в квадрате» явно равно 123 ^ 2 = 15129, поэтому «сумма цифр в квадрате» - это «сумма цифр 15129», которая, очевидно, равна 1 + 5 + 1 + 2 + 9. = 18.
Андреас Рейбранд

15
n = n * (-1)? Wut ??? Вот что ищет ваш проф: n = -n. Язык Си имеет унарный оператор минус.
Kaz

Ответы:


245

Подводя итог дискуссии, которая просачивалась в комментариях:

  • Нет веских причин для проверки заранее n == 0. while(n)Тест будет обрабатывать этот случай прекрасно.
  • Вероятно, ваш учитель все еще привык к более ранним временам, когда результат %с отрицательными операндами определялся по-другому. На некоторых старых системах (включая, в частности, ранний Unix на PDP-11, где Деннис Ритчи первоначально разрабатывал C), результат всегдаa % b был в диапазоне , что означает, что -123% 10 было 7. На такой системе тест заранее будет необходимо.[0 .. b-1]n < 0

Но вторая пуля относится только к более ранним временам. В текущих версиях стандартов C и C ++ целочисленное деление определено так, что оно усекается до 0, поэтому получается, что n % 10гарантированно даст вам (возможно, отрицательную) последнюю цифру, nдаже когда nона отрицательная.

Итак, ответ на вопрос «В чем смысл while(n)является «Точно так же , как и while(n != 0)» , и ответ на «Будет ли этот код будет работать должным образом для негатива, а также положительным nявляется «Да, под любым современным, стандартами соответствующим компилятора.» Ответ на вопрос «Тогда почему инструктор отметил это?» возможно, они не знают о существенном переопределении языка, которое произошло с C в 1999 году и с C ++ в 2010 году или около того.


39
«Нет веских причин для предварительного тестирования на n == 0» - технически это правильно. Но, учитывая, что мы говорим о профессоре в преподавательской среде, они могут ценить ясность по сравнению с краткостью гораздо больше, чем мы. Добавление дополнительного теста, по n == 0крайней мере, сразу же и совершенно очевидно для любого читателя, что происходит в этом случае. Без этого читатель должен убедиться, что цикл действительно пропущен, а sвозвращаемое значение по умолчанию является правильным.
ilkkachu

22
Кроме того, профессор может захотеть узнать, что студент знает и задумался о том, почему и как функция ведет себя с нулевым вводом (то есть, что она не возвращает правильное значение случайно ). Возможно, они встречались со студентами, которые не понимают, что произойдет в этом случае, что цикл может выполняться ноль раз и т. Д. Когда вы имеете дело с учебной обстановкой, лучше всего быть предельно ясным в отношении любых предположений и угловых случаев. ..
ilkkachu

36
@ilkkachu Если это так, то учитель должен раздать задание, которое требует, чтобы такой тест работал правильно.
19

38
@ilkkachu Хорошо, я понимаю вашу точку зрения в общем случае, потому что я очень ценю ясность по поводу краткости - и в значительной степени все время, не обязательно только в педагогической обстановке. Но с учетом вышесказанного иногда краткость - это ясность, и если вы можете устроить так, чтобы основной код обрабатывал как общий случай, так и крайний случай (ы), не загромождая код (и анализ покрытия) специальными случаями для крайних случаев Это прекрасная вещь! Я думаю, что это то, что нужно ценить даже на уровне новичка.
Стив Саммит

49
@ilkkachu По этому аргументу вы, конечно же, должны добавить тесты для n = 1 и так далее. В этом нет ничего особенного n=0. Введение ненужных веток и усложнений не делает код более легким, он усложняет задачу, поскольку теперь вам нужно не только показать, что общий алгоритм корректен, вы также должны подумать обо всех особых случаях отдельно.
Во

107

Ваш код в порядке

Вы абсолютно правы, а ваш учитель не прав. Нет абсолютно никакой причины добавлять эту дополнительную сложность, поскольку это никак не влияет на результат. Это даже вносит ошибку. (См. ниже)

Во-первых, отдельная проверка, если nноль, очевидно, совершенно не нужна, и это очень легко реализовать. Если честно, я на самом деле подвергаю сомнению вашу компетентность учителя, если у него есть возражения по этому поводу. Но я думаю, что каждый может время от времени пукнуть. Тем не менее, я ДЕЙСТВИТЕЛЬНО думаю, что это while(n)следует изменить, while(n != 0)потому что это добавляет немного дополнительной ясности даже без затрат на дополнительную строку. Это незначительная вещь, хотя.

Второй более понятен, но он все еще не прав.

Вот что говорит стандарт C11 6.5.5.p6 :

Если частное a / b представимо, выражение (a / b) * b + a% b должно быть равно a; в противном случае поведение a / b и a% b не определено.

Сноска говорит это:

Это часто называют «усечением до нуля».

Усечение до нуля означает, что абсолютное значение для a/bравно абсолютному значению (-a)/bдля всех aи b, что, в свою очередь, означает, что ваш код в порядке.

Модуло - легкая математика, но может быть нелогичным

Тем не менее, ваш учитель имеет смысл, что вы должны быть осторожны, потому что факт, что вы возводите в квадрат результат, на самом деле имеет решающее значение. Вычисление в a%bсоответствии с приведенным выше определением - простая математика, но она может пойти вразрез с вашей интуицией. Для умножения и деления результат является положительным, если операнды имеют знак равенства. Но когда дело доходит до модуля, результат имеет тот же знак, что и первый операнд. Второй операнд не влияет на знак вообще. Например, 7%3==1но (-7)%(-3)==(-1).

Вот фрагмент, демонстрирующий это:

$ cat > main.c 
#include <stdio.h>

void f(int a, int b) 
{
    printf("a: %2d b: %2d a/b: %2d a\%b: %2d (a%b)^2: %2d (a/b)*b+a%b==a: %5s\n",
           a, b ,a/b, a%b, (a%b)*(a%b), (a/b)*b+a%b == a ? "true" : "false");
}

int main(void)
{
    int a=7, b=3;
    f(a,b);
    f(-a,b);
    f(a,-b);
    f(-a,-b);
}

$ gcc main.c -Wall -Wextra -pedantic -std=c99

$ ./a.out
a:  7 b:  3 a/b:  2 a%b:  1 (a%b)^2:  1 (a/b)*b+a%b==a:  true
a: -7 b:  3 a/b: -2 a%b: -1 (a%b)^2:  1 (a/b)*b+a%b==a:  true
a:  7 b: -3 a/b: -2 a%b:  1 (a%b)^2:  1 (a/b)*b+a%b==a:  true
a: -7 b: -3 a/b:  2 a%b: -1 (a%b)^2:  1 (a/b)*b+a%b==a:  true

Итак, по иронии судьбы, ваш учитель доказал свою точку зрения, ошибаясь.

Код вашего учителя имеет недостатки

Да, это действительно так. Если вход - INT_MINИ, архитектура - это дополнение к двум, И битовая комбинация, в которой бит знака равен 1, а все биты значения равны 0, НЕ является значением ловушки (использование дополнения к двум без значений ловушек очень распространено), тогда код вашего учителя приведет к неопределенному поведению на линии n = n * (-1). Ваш код - если даже немного - лучше, чем его. И если учесть небольшую ошибку, сделав код ненужным сложным и получив абсолютно нулевое значение, я бы сказал, что ваш код НАМНОГО лучше.

Другими словами, в компиляциях, где INT_MIN = -32768 (даже если результирующая функция не может получить ввод <-32768 или> 32767), допустимый ввод -32768 вызывает неопределенное поведение, потому что результат - (- - 32768i16) не может быть выражено как 16-битное целое число. (На самом деле, -32768, вероятно, не приведет к неправильному результату, потому что - (- - 32768i16) обычно оценивается как -32768i16, и ваша программа корректно обрабатывает отрицательные числа.) (SHRT_MIN может быть -32768 или -32767, в зависимости от компилятора.)

Но ваш учитель прямо заявил, что nможет быть в диапазоне [-10 ^ 7; 10 ^ 7]. 16-битное целое число слишком мало; вам придется использовать [как минимум] 32-разрядное целое число. Использование intможет показаться , чтобы сделать свой код сейф, за исключением того, что intэто не обязательно 32-разрядное целое число. Если вы компилируете для 16-битной архитектуры, оба ваших фрагмента кода имеют недостатки. Но ваш код все еще намного лучше, потому что этот сценарий повторно вводит ошибку с INT_MINупомянутой выше в его версии. Чтобы избежать этого, вы можете написать longвместо int, что является 32-разрядным целым числом в любой архитектуре. longГарантируется, что A может содержать любое значение в диапазоне [-2147483647; 2147483647]. Стандарт С11 5.2.4.2.1 LONG_MIN часто-2147483648но максимум (да, максимум, это отрицательное число) допустимое значение LONG_MINявляется 2147483647.

Какие изменения я бы сделал в вашем коде?

Ваш код в порядке, так что это на самом деле не жалобы. Это больше похоже на то, что если мне действительно нужно что-то сказать о вашем коде, есть некоторые мелочи, которые могут сделать его чуть-чуть понятнее.

  • Имена переменных могут быть немного лучше, но это короткая функция, которую легко понять, так что это не имеет большого значения.
  • Вы можете изменить условие с nна n!=0. Семантически это на 100% эквивалентно, но делает его немного понятнее.
  • Переместите объявление c(которое я переименовал digit) в цикл while, поскольку оно используется только там.
  • Измените тип аргумента так, чтобы longон мог обрабатывать весь входной набор.
int sum_of_digits_squared(long n) 
{
    long sum = 0;

    while (n != 0) {
        int digit = n % 10;
        sum += (digit * digit);
        n /= 10;
    }

    return sum;
}

На самом деле, это может немного вводить в заблуждение, потому что - как упоминалось выше - переменная digitможет принимать отрицательное значение, но сама по себе цифра никогда не бывает ни положительной, ни отрицательной. Есть несколько способов обойти это, но это ДЕЙСТВИТЕЛЬНО придирчиво, и мне было бы наплевать на такие мелкие детали. Особенно отдельная функция для последней цифры заходит слишком далеко. По иронии судьбы, это одна из вещей, которую код ваших учителей на самом деле решает.

  • Перейдите sum += (digit * digit)на sum += ((n%10)*(n%10))и пропустите переменную digitполностью.
  • Измените знак, digitесли отрицательный. Но я бы настоятельно рекомендовал не делать код более сложным, просто чтобы иметь смысл имя переменной. Это ОЧЕНЬ сильный запах кода.
  • Создайте отдельную функцию, которая извлекает последнюю цифру. int last_digit(long n) { int digit=n%10; if (digit>=0) return digit; else return -digit; }Это полезно, если вы хотите использовать эту функцию где-то еще.
  • Просто назовите его так, cкак вы это делаете изначально. Это имя переменной не дает никакой полезной информации, но, с другой стороны, оно также не вводит в заблуждение.

Но, честно говоря, на этом этапе вы должны перейти к более важной работе. :)


19
Если входные данные INT_MINи архитектура использует два дополнения (что очень распространено), то код вашего учителя будет давать неопределенное поведение. Уч. Это оставит след. ;-)
Эндрю Хенле

5
Следует отметить, что в дополнение к (a/b)*b + a%b ≡ aкоду ФП также учитывается тот факт, что /округляется до нуля, и это (-c)*(-c) ≡ c*c. Он мог бы утверждать , что дополнительные проверки оправданы , несмотря на стандарт гарантирует , что все, потому что это достаточно неочевидным. (Конечно, можно с таким же успехом утверждать, что должен быть комментарий, связывающий соответствующие стандартные разделы, но рекомендации по стилю различаются.)
leftaroundabout

7
@MartinRosenau Вы говорите "может". Вы уверены, что это на самом деле происходит, или это разрешено стандартом, или что-то в этом роде, или вы просто спекулируете?
Klutt

6
@MartinRosenau: Хорошо, но использование этих переключателей сделало бы его уже не Си. У GCC / clang нет переключателей, которые нарушают целочисленное деление на любом ISA, о котором я знаю. Даже если игнорирование знакового бита может дать ускорение, используя нормальное мультипликативное обратное значение для константных делителей. (Но все знакомые мне ISA, в которых есть инструкция аппаратного деления, реализуют ее по принципу C99, усекая до нуля, поэтому операторы C %и C /могут компилироваться только в idivx86, sdivARM и т. Д. Тем не менее, это не связано с более быстрое
Питер Кордес

5
@ TonyK AFIK, так обычно решают, но по стандарту это UB.
klutt

20

Мне не совсем нравится ни ваша версия, ни ваша учительница. Версия вашего учителя делает дополнительные тесты, которые вы правильно указали, не нужны. Оператор мода C не является правильным математическим модом: мод с отрицательным числом 10 даст отрицательный результат (правильный математический модуль всегда неотрицателен). Но так как вы все равно возводите в квадрат, без разницы.

Но это далеко не очевидно, поэтому я бы добавил к вашему коду не проверки вашего учителя, а большой комментарий, объясняющий, почему он работает. Например:

/ * ПРИМЕЧАНИЕ: это работает для отрицательных значений, потому что модуль получает квадрат * /


9
C %лучше всего называть остатком , потому что это так, даже для подписанных типов.
Питер Кордес

15
Квадрат важен, но я думаю, что это очевидная часть. На что следует обратить внимание, это то, что (например) -7 % 10 будет на самом деле,-7 а не 3.
Джейкоб Райл

5
«Правильный математический модуль» ничего не значит. Правильный термин - «евклидово по модулю» (обратите внимание на суффикс!), И это действительно то, чем не является %оператор Си .
Ян Худек

Мне нравится этот ответ, потому что он решает вопрос о нескольких способах интерпретации по модулю. Никогда не оставляйте такую ​​вещь на волю случая / толкования. Это не код гольф.
Харпер - Восстановить Монику

1
«правильный математический модуль всегда неотрицателен» - не совсем. Результатом операции по модулю является класс эквивалентности , но обычно результат рассматривается как наименьшее неотрицательное число, принадлежащее этому классу.
Klutt

10

ПРИМЕЧАНИЕ. Поскольку я писал этот ответ, вы пояснили, что используете C. Большая часть моего ответа касается C ++. Однако, поскольку в вашем заголовке по-прежнему есть C ++, а вопрос все еще помечен как C ++, я все равно решил ответить, если это все еще полезно для других людей, тем более что большинство ответов, которые я видел до сих пор, в основном неудовлетворительные.

В современном C ++ (примечание: я действительно не знаю, где стоит C), ваш профессор, кажется, ошибается в обоих случаях.

Во-первых, эта часть прямо здесь:

if (n == 0) {
        return 0;
}

В C ++ это в основном то же самое, что и :

if (!n) {
        return 0;
}

Это означает, что ваше время эквивалентно чему-то вроде этого:

while(n != 0) {
    // some implementation
}

Это означает, что поскольку вы просто выходите из своего if, если while не будет выполняться в любом случае, на самом деле нет причины помещать это if здесь, поскольку то, что вы делаете после цикла и в if, в любом случае эквивалентны. Хотя я должен сказать, что по какой-то причине они были другими, вам нужно иметь это, если.

Так что на самом деле, это если утверждение не особенно полезно, если я не ошибаюсь.

Вторая часть, где вещи становятся волосатыми:

if (n < 0) {
    n = n * (-1);
}  

Суть проблемы заключается в том, что вывод модуля отрицательного числа выводит.

В современном C ++ это, кажется, в основном четко определено :

Двоичный / оператор дает частное, а бинарный оператор% - остаток от деления первого выражения на второе. Если второй операнд / или% равен нулю, поведение не определено. Для целочисленных операндов оператор / дает алгебраический фактор с любой отброшенной дробной частью; если частное a / b представимо в типе результата, (a / b) * b + a% b равно a.

И позже:

Если оба операнда неотрицательны, то остаток неотрицателен; если нет, знак остатка определяется реализацией.

Как правильно указывает автор цитируемого ответа, важная часть этого уравнения здесь:

(a / b) * b + a% b

Если взять пример вашего случая, вы получите что-то вроде этого:

-13/ 10 = -1 (integer truncation)
-1 * 10 = -10
-13 - (-10) = -13 + 10 = -3 

Единственный улов - последняя строка:

Если оба операнда неотрицательны, то остаток неотрицателен; если нет, знак остатка определяется реализацией.

Это означает, что в таком случае только знак, по- видимому, определяется реализацией. Это не должно быть проблемой в вашем случае, потому что вы все равно возводите это значение в квадрат.

Тем не менее, имейте в виду, что это не обязательно относится к более ранним версиям C ++ или C99. Если это то, что использует ваш профессор, возможно, именно поэтому.


РЕДАКТИРОВАТЬ: Нет, я ошибаюсь. Это похоже на случай с C99 или позже :

C99 требует, чтобы при представлении a / b:

(a / b) * b + a% b должно равняться a

И еще одно место :

Когда целые числа делятся и деление неточно, если оба операнда положительны, результат оператора / является наибольшим целым числом, меньшим алгебраического отношения, а результат оператора% положителен. Если любой из операндов отрицателен, то, является ли результат оператора / наибольшим целым числом, меньшим, чем алгебраическое частное, или наименьшим целым числом, большим, чем алгебраическое частное, определяется реализацией, как и знак результата оператора%. Если частное a / b представимо, выражение (a / b) * b + a% b должно быть равно a.

Указывает ли ANSI C или ISO C, что должно быть -5% 10?

Так что да. Даже в C99 это, кажется, не влияет на вас. Уравнение то же самое.


1
Части, которые вы цитировали, не поддерживают этот ответ. «знак остатка определяется реализацией» не означает, что (-1)%10может произвести -1или 1; это означает, что он может произвести -1или 9, и в последнем случае (-1)/10произведет -1и код OP никогда не завершится.
тушеное мясо

Не могли бы вы указать источник для этого? Мне очень трудно поверить (-1) / 10 это -1. Это должно быть 0. Кроме того, (-1)% 10 = 9, кажется, нарушает основное уравнение.
Чипстер

1
@ Чипстер, начни с того (a/b)*b + a%b == a, потом давай a=-1; b=10, давай (-1/10)*10 + (-1)%10 == -1. Теперь, если значение -1/10действительно округляется в меньшую сторону (в сторону -inf), то у нас есть (-1/10)*10 == -10, и вам нужно, чтобы (-1)%10 == 9первое уравнение совпадало. Как и в других состояниях ответов , это не так, как это работает в рамках текущего стандарта (ов), но это то, как он работал. Дело не в знаке остатка как такового, а в том, как округляется деление и каким должен быть остаток , чтобы удовлетворить уравнению.
ilkkachu

1
@Chipster Источник - фрагменты, которые вы цитировали. Отметим, что (-1)*10+9=-1так выбор (-1)/10=-1и (-1)%10=9не нарушает определяющее уравнение. С другой стороны, выбор (-1)%10=1не может удовлетворить основное уравнение, независимо от того, как (-1)/10он выбран; нет qтакого целого числа , что q*10+1=-1.
тушеное мясо

8

Как уже отмечали другие, специальная обработка для n == 0 бессмысленна, поскольку для каждого серьезного программиста на C очевидно, что "while (n)" делает свою работу.

Поведение при n <0 не так очевидно, поэтому я бы предпочел увидеть эти две строки кода:

if (n < 0) 
    n = -n;

или хотя бы комментарий:

// don't worry, works for n < 0 as well

Честно говоря, в какое время вы начали считать, что n может быть отрицательным? При написании кода или при чтении замечаний вашего учителя?


1
Независимо от того, является ли N отрицательным, N в квадрате будет положительным. Итак, зачем убирать знак в первую очередь? -3 * -3 = 9; 3 * 3 = 9. Или математика изменилась за 30 с лишним лет с тех пор, как я это узнал?
Меровекс

2
@CB Честно говоря, я даже не заметил, что n могло быть отрицательным, когда я писал тест, но когда он вернулся, у меня было ощущение, что цикл while не будет пропущен, даже если число отрицательное. Я провел несколько тестов на своем компьютере, и это подтвердило мой скептицизм. После этого я разместил этот вопрос. Так что нет, я не думал так глубоко при написании кода.
user010517720

5

Это напоминает мне о задании, которое мне не удалось

Еще в 90-х. Лектор рассказывал о циклах, и, короче говоря, нашим заданием было написать функцию, которая возвращала бы число цифр для любого заданного целого числа> 0.

Так, например, количество цифр в 321будет 3.

Хотя в присваивании просто сказано написать функцию, которая возвращает количество цифр, ожидалось, что мы будем использовать цикл, который делится на 10, пока ... вы не получите его, как описано в лекции .

Но использование циклов не было явно указано, поэтому я: took the log, stripped away the decimals, added 1 и был впоследствии подвергнут критике перед всем классом.

Дело в том, что целью задания было проверить наше понимание того, что мы узнали во время лекций . Из лекции, которую я получил, я узнал, что учитель информатики был немного придурком (но, может быть, подонком с планом?)


В вашей ситуации:

написать функцию на C / C ++, которая возвращает сумму цифр числа в квадрате

Я бы определенно дал два ответа:

  • правильный ответ (сначала возводите квадрат в число) и
  • неправильный ответ в соответствии с примером, просто чтобы держать его счастливым ;-)

5
А также третий, возводящий в квадрат сумму цифр?
Kriss

@kriss - да, я не такой умный :-(
SlowLearner

1
У меня также была моя доля слишком расплывчатых оценок в студенческое время. Учитель хотел немного манипулировать библиотекой, но он был удивлен моим кодом и сказал, что он не работает. Мне пришлось указать ему, что он никогда не определял порядковый номер в своем задании, и он выбрал младший бит как бит 0, а я сделал другой выбор. Единственная раздражающая часть в том, что он должен был выяснить, где разница, и я не сказал ему.
Kriss

1

Обычно в заданиях не все оценки ставятся только потому, что код работает. Вы также получаете оценки за простоту чтения, эффективность и элегантность решения. Эти вещи не всегда взаимоисключающие.

Одна вещь, которую я не могу понять, это «использовать значимые имена переменных» .

В вашем примере это не имеет большого значения, но если вы работаете над проектом с миллионами строк, читаемость кода становится очень важной.

Еще одна вещь, которую я склонен видеть в C-коде, это люди, которые пытаются выглядеть умно. Вместо использования while (n! = 0) я покажу всем, насколько я умен, написав while (n), потому что это означает то же самое. Что ж, это происходит в вашем компиляторе, но, как вы и предполагали, более старая версия вашего учителя не реализовала его так же.

Типичным примером является обращение к индексу в массиве, в то же время увеличивая его; Numbers [i ++] = iPrime;

Теперь следующий программист, который работает над кодом, должен знать, увеличивается ли я до или после назначения, просто чтобы кто-то мог похвастаться.

Мегабайт дискового пространства дешевле, чем рулон туалетной бумаги, иди для ясности, а не пытаясь сэкономить место, твои коллеги-программисты будут счастливее.


2
Я программировал на C только несколько раз, и я знаю, что ++iувеличивается до оценки, а i++затем увеличивается. while(n)это также общая языковая особенность. Основываясь на такой логике, я видел много подобного кода if (foo == TRUE). Я согласен re: имена переменных, хотя.
алан ocallaghan

3
Как правило, это неплохой совет, но избегать базовых языковых возможностей (которые люди неизбежно встретят в любом случае) для целей защитного программирования является излишним. Короткий, понятный код часто более удобен для чтения. Мы не говорим о сумасшедших однострочниках Perl или Bash, а просто о базовых языковых возможностях.
алан ocallaghan

1
Не уверен, для чего были даны отрицательные ответы на этот ответ. Для меня все изложенное здесь является правдой и важно, и хороший совет для каждого разработчика. Особенно в части программирования умных задниц, хотя while(n)это и не самый плохой пример для этого (мне «больше нравится» if(strcmp(one, two)))
Кай Хуппманн,

1
И, кроме того, вы действительно не должны позволять кому-либо, кто не знает разницы между i++и ++iизменять код C, который должен использоваться в производстве.
Klutt

2
@PaulMcCarthy Мы оба за читаемость кода. Но мы не согласны с тем, что это значит. Кроме того, это не объективно. То, что легко читать одному человеку, может быть трудно другому. Личность и базовые знания сильно влияют на это. Мое главное, что вы не получите максимальную читабельность, слепо следуя некоторым правилам.
klutt

0

Я бы не стал спорить о том, лучше ли оригинальное или современное определение «%», но любой, кто пишет два оператора return в такую ​​короткую функцию, вообще не должен учить программированию на С. Дополнительный возврат - это оператор goto, и мы не используем goto в C. Более того, код без проверки на ноль будет иметь тот же результат, дополнительный возврат затруднит чтение.


4
«Дополнительный возврат - это оператор goto, и мы не используем goto в C.» - это сочетание очень широкого обобщения и очень далёкого натяжения.
SS Anne

1
«Мы» определенно используем goto в C. В этом нет ничего плохого.
Klutt

1
Кроме того, нет ничего плохого в такой функции, какint findChar(char *str, char c) { if(!str) return -1; int i=0; while(str[i]) { if(str[i] == c) return i; i++; } return -1; }
klutt

1
@PeterKrassoi Я не защищаю трудный для чтения код, но я видел множество примеров, когда код выглядит как полный беспорядок, просто чтобы избежать простого перехода или дополнительного оператора возврата. Вот код с соответствующим использованием goto. Я призываю вас удалить операторы goto, одновременно упрощая чтение и ведение кода: pastebin.com/aNaGb66Q
klutt

1
@PeterKrassoi Пожалуйста, также покажите свою версию этого: pastebin.com/qBmR78KA
klutt

0

Постановка задачи сбивает с толку, но числовой пример проясняет значение суммы цифр квадрата числа . Вот улучшенная версия:

Напишите функцию в общем подмножестве C и C ++, которая принимает целое число nв диапазоне [-10 7 , 10 7 ] и возвращает сумму квадратов цифр своего представления в базе 10. Пример: если nесть 123, ваша функция должен вернуться 14(1 2 + 2 2 + 3 2 = 14).

Функция, которую вы написали, хороша за исключением двух деталей:

  • Аргумент должен иметь тип, longчтобы соответствовать всем значениям в указанном диапазоне, поскольку longстандарт C гарантирует, что тип должен иметь как минимум 31 бит значения, следовательно, диапазон, достаточный для представления всех значений в [-10 7 , 10 7 ] . (Обратите внимание, что тип intдостаточно для возвращаемого типа, максимальное значение которого равно 568.)
  • Поведение %отрицательных операндов не является интуитивно понятным, и его спецификация варьировалась между стандартом C99 и предыдущими выпусками. Вы должны документально подтвердить, почему ваш подход действителен даже для отрицательных входных данных.

Вот модифицированная версия:

int sum_of_digits_squared(long n) {
    int s = 0;

    while (n != 0) {
        /* Since integer division is defined to truncate toward 0 in C99 and C++98 and later,
           the remainder of this division is positive for positive `n`
           and negative for negative `n`, and its absolute value is the last digit
           of the representation of `n` in base 10.
           Squaring this value yields the expected result for both positive and negative `c`.
           dividing `n` by 10 effectively drops the last digit in both cases.
           The loop will not be entered for `n == 0`, producing the correct result `s = 0`.
         */
        int c = n % 10;
        s += c * c;
        n /= 10;
    }
    return s;
}

Ответ учителя имеет несколько недостатков:

  • Тип intможет иметь недостаточный диапазон значений.
  • нет необходимости в особом случае значения 0.
  • отрицание отрицательных значений не является необходимым и может иметь неопределенное поведение для n = INT_MIN.

Учитывая дополнительные ограничения в постановке задачи (C99 и диапазон значений для n), только первый недостаток является проблемой. Дополнительный код все еще дает правильные ответы.

Вы должны получить хорошую оценку в этом тесте, но объяснение требуется в письменном тесте, чтобы показать ваше понимание проблем отрицательного n, в противном случае учитель может предположить, что вы не знали и просто повезло. На устном экзамене вы получили бы вопрос, и ваш ответ прибил бы его.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.