У меня недавно был тест в моем классе. Одной из проблем было следующее:
Учитывая число 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 или новее . Поэтому я предполагаю (прочитав комментарии), что моя версия получит правильный ответ в любом случае.
n = n * (-1)
это нелепый способ писатьn = -n
; Только академик мог бы подумать об этом. Не говоря уже о добавлении лишних скобок.n = n * (-1)
? Wut ??? Вот что ищет ваш проф: n = -n. Язык Си имеет унарный оператор минус.Ответы:
Подводя итог дискуссии, которая просачивалась в комментариях:
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 году или около того.источник
n == 0
крайней мере, сразу же и совершенно очевидно для любого читателя, что происходит в этом случае. Без этого читатель должен убедиться, что цикл действительно пропущен, аs
возвращаемое значение по умолчанию является правильным.n=0
. Введение ненужных веток и усложнений не делает код более легким, он усложняет задачу, поскольку теперь вам нужно не только показать, что общий алгоритм корректен, вы также должны подумать обо всех особых случаях отдельно.Ваш код в порядке
Вы абсолютно правы, а ваш учитель не прав. Нет абсолютно никакой причины добавлять эту дополнительную сложность, поскольку это никак не влияет на результат. Это даже вносит ошибку. (См. ниже)
Во-первых, отдельная проверка, если
n
ноль, очевидно, совершенно не нужна, и это очень легко реализовать. Если честно, я на самом деле подвергаю сомнению вашу компетентность учителя, если у него есть возражения по этому поводу. Но я думаю, что каждый может время от времени пукнуть. Тем не менее, я ДЕЙСТВИТЕЛЬНО думаю, что этоwhile(n)
следует изменить,while(n != 0)
потому что это добавляет немного дополнительной ясности даже без затрат на дополнительную строку. Это незначительная вещь, хотя.Второй более понятен, но он все еще не прав.
Вот что говорит стандарт C11 6.5.5.p6 :
Сноска говорит это:
Усечение до нуля означает, что абсолютное значение для
a/b
равно абсолютному значению(-a)/b
для всехa
иb
, что, в свою очередь, означает, что ваш код в порядке.Модуло - легкая математика, но может быть нелогичным
Тем не менее, ваш учитель имеет смысл, что вы должны быть осторожны, потому что факт, что вы возводите в квадрат результат, на самом деле имеет решающее значение. Вычисление в
a%b
соответствии с приведенным выше определением - простая математика, но она может пойти вразрез с вашей интуицией. Для умножения и деления результат является положительным, если операнды имеют знак равенства. Но когда дело доходит до модуля, результат имеет тот же знак, что и первый операнд. Второй операнд не влияет на знак вообще. Например,7%3==1
но(-7)%(-3)==(-1)
.Вот фрагмент, демонстрирующий это:
Итак, по иронии судьбы, ваш учитель доказал свою точку зрения, ошибаясь.
Код вашего учителя имеет недостатки
Да, это действительно так. Если вход -
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.1LONG_MIN
часто-2147483648
но максимум (да, максимум, это отрицательное число) допустимое значениеLONG_MIN
является2147483647
.Какие изменения я бы сделал в вашем коде?
Ваш код в порядке, так что это на самом деле не жалобы. Это больше похоже на то, что если мне действительно нужно что-то сказать о вашем коде, есть некоторые мелочи, которые могут сделать его чуть-чуть понятнее.
n
наn!=0
. Семантически это на 100% эквивалентно, но делает его немного понятнее.c
(которое я переименовалdigit
) в цикл while, поскольку оно используется только там.long
он мог обрабатывать весь входной набор.На самом деле, это может немного вводить в заблуждение, потому что - как упоминалось выше - переменная
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
как вы это делаете изначально. Это имя переменной не дает никакой полезной информации, но, с другой стороны, оно также не вводит в заблуждение.Но, честно говоря, на этом этапе вы должны перейти к более важной работе. :)
источник
INT_MIN
и архитектура использует два дополнения (что очень распространено), то код вашего учителя будет давать неопределенное поведение. Уч. Это оставит след. ;-)(a/b)*b + a%b ≡ a
коду ФП также учитывается тот факт, что/
округляется до нуля, и это(-c)*(-c) ≡ c*c
. Он мог бы утверждать , что дополнительные проверки оправданы , несмотря на стандарт гарантирует , что все, потому что это достаточно неочевидным. (Конечно, можно с таким же успехом утверждать, что должен быть комментарий, связывающий соответствующие стандартные разделы, но рекомендации по стилю различаются.)%
и C/
могут компилироваться только вidiv
x86,sdiv
ARM и т. Д. Тем не менее, это не связано с более быстроеМне не совсем нравится ни ваша версия, ни ваша учительница. Версия вашего учителя делает дополнительные тесты, которые вы правильно указали, не нужны. Оператор мода C не является правильным математическим модом: мод с отрицательным числом 10 даст отрицательный результат (правильный математический модуль всегда неотрицателен). Но так как вы все равно возводите в квадрат, без разницы.
Но это далеко не очевидно, поэтому я бы добавил к вашему коду не проверки вашего учителя, а большой комментарий, объясняющий, почему он работает. Например:
/ * ПРИМЕЧАНИЕ: это работает для отрицательных значений, потому что модуль получает квадрат * /
источник
%
лучше всего называть остатком , потому что это так, даже для подписанных типов.-7 % 10
будет на самом деле,-7
а не 3.%
оператор Си .ПРИМЕЧАНИЕ. Поскольку я писал этот ответ, вы пояснили, что используете C. Большая часть моего ответа касается C ++. Однако, поскольку в вашем заголовке по-прежнему есть C ++, а вопрос все еще помечен как C ++, я все равно решил ответить, если это все еще полезно для других людей, тем более что большинство ответов, которые я видел до сих пор, в основном неудовлетворительные.
В современном C ++ (примечание: я действительно не знаю, где стоит C), ваш профессор, кажется, ошибается в обоих случаях.
Во-первых, эта часть прямо здесь:
В C ++ это в основном то же самое, что и :
Это означает, что ваше время эквивалентно чему-то вроде этого:
Это означает, что поскольку вы просто выходите из своего if, если while не будет выполняться в любом случае, на самом деле нет причины помещать это if здесь, поскольку то, что вы делаете после цикла и в if, в любом случае эквивалентны. Хотя я должен сказать, что по какой-то причине они были другими, вам нужно иметь это, если.
Так что на самом деле, это если утверждение не особенно полезно, если я не ошибаюсь.
Вторая часть, где вещи становятся волосатыми:
Суть проблемы заключается в том, что вывод модуля отрицательного числа выводит.
В современном C ++ это, кажется, в основном четко определено :
И позже:
Как правильно указывает автор цитируемого ответа, важная часть этого уравнения здесь:
Если взять пример вашего случая, вы получите что-то вроде этого:
Единственный улов - последняя строка:
Это означает, что в таком случае только знак, по- видимому, определяется реализацией. Это не должно быть проблемой в вашем случае, потому что вы все равно возводите это значение в квадрат.
Тем не менее, имейте в виду, что это не обязательно относится к более ранним версиям C ++ или C99. Если это то, что использует ваш профессор, возможно, именно поэтому.
РЕДАКТИРОВАТЬ: Нет, я ошибаюсь. Это похоже на случай с C99 или позже :
И еще одно место :
Так что да. Даже в C99 это, кажется, не влияет на вас. Уравнение то же самое.
источник
(-1)%10
может произвести-1
или1
; это означает, что он может произвести-1
или9
, и в последнем случае(-1)/10
произведет-1
и код OP никогда не завершится.(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
первое уравнение совпадало. Как и в других состояниях ответов , это не так, как это работает в рамках текущего стандарта (ов), но это то, как он работал. Дело не в знаке остатка как такового, а в том, как округляется деление и каким должен быть остаток , чтобы удовлетворить уравнению.(-1)*10+9=-1
так выбор(-1)/10=-1
и(-1)%10=9
не нарушает определяющее уравнение. С другой стороны, выбор(-1)%10=1
не может удовлетворить основное уравнение, независимо от того, как(-1)/10
он выбран; нетq
такого целого числа , чтоq*10+1=-1
.Как уже отмечали другие, специальная обработка для n == 0 бессмысленна, поскольку для каждого серьезного программиста на C очевидно, что "while (n)" делает свою работу.
Поведение при n <0 не так очевидно, поэтому я бы предпочел увидеть эти две строки кода:
или хотя бы комментарий:
Честно говоря, в какое время вы начали считать, что n может быть отрицательным? При написании кода или при чтении замечаний вашего учителя?
источник
Это напоминает мне о задании, которое мне не удалось
Еще в 90-х. Лектор рассказывал о циклах, и, короче говоря, нашим заданием было написать функцию, которая возвращала бы число цифр для любого заданного целого числа> 0.
Так, например, количество цифр в
321
будет3
.Хотя в присваивании просто сказано написать функцию, которая возвращает количество цифр, ожидалось, что мы будем использовать цикл, который делится на 10, пока ... вы не получите его, как описано в лекции .
Но использование циклов не было явно указано, поэтому я:
took the log, stripped away the decimals, added 1
и был впоследствии подвергнут критике перед всем классом.Дело в том, что целью задания было проверить наше понимание того, что мы узнали во время лекций . Из лекции, которую я получил, я узнал, что учитель информатики был немного придурком (но, может быть, подонком с планом?)
В вашей ситуации:
Я бы определенно дал два ответа:
источник
Обычно в заданиях не все оценки ставятся только потому, что код работает. Вы также получаете оценки за простоту чтения, эффективность и элегантность решения. Эти вещи не всегда взаимоисключающие.
Одна вещь, которую я не могу понять, это «использовать значимые имена переменных» .
В вашем примере это не имеет большого значения, но если вы работаете над проектом с миллионами строк, читаемость кода становится очень важной.
Еще одна вещь, которую я склонен видеть в C-коде, это люди, которые пытаются выглядеть умно. Вместо использования while (n! = 0) я покажу всем, насколько я умен, написав while (n), потому что это означает то же самое. Что ж, это происходит в вашем компиляторе, но, как вы и предполагали, более старая версия вашего учителя не реализовала его так же.
Типичным примером является обращение к индексу в массиве, в то же время увеличивая его; Numbers [i ++] = iPrime;
Теперь следующий программист, который работает над кодом, должен знать, увеличивается ли я до или после назначения, просто чтобы кто-то мог похвастаться.
Мегабайт дискового пространства дешевле, чем рулон туалетной бумаги, иди для ясности, а не пытаясь сэкономить место, твои коллеги-программисты будут счастливее.
источник
++i
увеличивается до оценки, аi++
затем увеличивается.while(n)
это также общая языковая особенность. Основываясь на такой логике, я видел много подобного кодаif (foo == TRUE)
. Я согласен re: имена переменных, хотя.while(n)
это и не самый плохой пример для этого (мне «больше нравится»if(strcmp(one, two))
)i++
и++i
изменять код C, который должен использоваться в производстве.Я бы не стал спорить о том, лучше ли оригинальное или современное определение «%», но любой, кто пишет два оператора return в такую короткую функцию, вообще не должен учить программированию на С. Дополнительный возврат - это оператор goto, и мы не используем goto в C. Более того, код без проверки на ноль будет иметь тот же результат, дополнительный возврат затруднит чтение.
источник
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; }
Постановка задачи сбивает с толку, но числовой пример проясняет значение суммы цифр квадрата числа . Вот улучшенная версия:
Функция, которую вы написали, хороша за исключением двух деталей:
long
чтобы соответствовать всем значениям в указанном диапазоне, посколькуlong
стандарт C гарантирует, что тип должен иметь как минимум 31 бит значения, следовательно, диапазон, достаточный для представления всех значений в [-10 7 , 10 7 ] . (Обратите внимание, что типint
достаточно для возвращаемого типа, максимальное значение которого равно568
.)%
отрицательных операндов не является интуитивно понятным, и его спецификация варьировалась между стандартом C99 и предыдущими выпусками. Вы должны документально подтвердить, почему ваш подход действителен даже для отрицательных входных данных.Вот модифицированная версия:
Ответ учителя имеет несколько недостатков:
int
может иметь недостаточный диапазон значений.0
.n = INT_MIN
.Учитывая дополнительные ограничения в постановке задачи (C99 и диапазон значений для
n
), только первый недостаток является проблемой. Дополнительный код все еще дает правильные ответы.Вы должны получить хорошую оценку в этом тесте, но объяснение требуется в письменном тесте, чтобы показать ваше понимание проблем отрицательного
n
, в противном случае учитель может предположить, что вы не знали и просто повезло. На устном экзамене вы получили бы вопрос, и ваш ответ прибил бы его.источник