Чи потрібно явно обробляти негативні числа або нуль при підсумовуванні цифр у квадраті?


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) так в моїй версії функції числа п буде не обов'язково входити в цикл. Це працювало б так само.

Потім я спробував обидві версії функції на своєму комп’ютері вдома, і я отримав абсолютно однакові відповіді на всі приклади, які я пробував. Отже, sum_of_digits_squared(-123)дорівнює sum_of_digits_squared(123)(що знову ж таки дорівнює 14) (навіть без деталей, які я, мабуть, мав би додати). Дійсно, якщо я спробую надрукувати на екрані цифри числа (від найменшого до найбільшого за значенням), у 123випадку, що я отримую, 3 2 1і у -123випадку, коли я отримую -3 -2 -1(що насправді є цікавим). Але в цій проблемі це не має значення, оскільки ми розміщуємо цифри.

Отже, хто помиляється?

EDIT : Моє погано, я забув вказати і не знав, що це важливо. Версія C, що використовується в нашому класі та тестах, повинна бути C99 або новішою . Тому я здогадуюсь (читаючи коментарі), що моя версія в будь-якому випадку отримала правильну відповідь.


120
n = n * (-1)- смішний спосіб писати n = -n; Тільки академік би про це навіть подумав. Не кажучи вже про те, щоб додати зайві дужки.
користувач207421

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 '. Мова C має оператор унарного мінусу.
Каз

Відповіді:


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 Якщо це так, то вчитель повинен роздати завдання, яке вимагає, щоб такий тест працював правильно.
клотт

38
@ilkkachu Добре, я вважаю, що в цілому ви розумієте, тому що я ціную чіткість щодо стислості - і майже весь час, не обов'язково лише в педагогічній обстановці. Але з урахуванням сказаного іноді стислість - це чіткість, і якщо ви можете домовитись про те, щоб основний код обробляв як загальний, так і крайній регістри, не захаращуючи код (і аналіз покриття) спеціальними випадками для кращих випадків , це гарна річ! Я думаю, що це варто оцінити навіть на рівні початківця.
Стів Самміт

49
@ilkkachu До цього аргументу вам неодмінно слід також додати тести на n = 1 тощо. У цьому немає нічого особливого n=0. Введення непотрібних гілок і ускладнень не полегшує код, але це ускладнює, оскільки тепер ви не тільки повинні показати, що загальний алгоритм правильний, ви також повинні думати про всі спеціальні випадки окремо.
Voo

107

Ваш код ідеально чудовий

Ви абсолютно правильні, а ваш вчитель помиляється. Немає абсолютно ніяких причин додати цю додаткову складність, оскільки це зовсім не впливає на результат. Він навіть вводить помилку. (Дивись нижче)

По-перше, окрема перевірка, якщо nдорівнює нулю, очевидно, зовсім непотрібна, і це дуже легко здійснити. Якщо чесно, я фактично ставлю під сумнів компетентність ваших викладачів, якщо він має заперечення проти цього. Але я здогадуюсь, що у кожного може час від часу боліти мозок. Однак я думаю, що це while(n)слід змінити, while(n != 0)оскільки це додає трохи додаткової ясності, навіть не витрачаючи зайву лінію. Хоча це незначна річ.

Другий - трохи зрозуміліший, але він все-таки помиляється.

Про це говорить стандарт C11 6.5.5.p6 :

Якщо показник a / b є представним, вираз (a / b) * b + a% b дорівнює a; в іншому випадку поведінка як a / b, так і% b не визначена.

Зноска говорить про це:

Це часто називають "усіченням до нуля".

Відсікання до нуля означає, що абсолютне значення for 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-бітним цілим числом в будь-якій архітектурі. A longгарантовано зможе утримувати будь-яке значення в діапазоні [-2147483647; 2147483647]. Стандарт С11 5.2.4.2.1 LONG_MIN часто-2147483648але максимальне (так, максимальне, це від’ємне число), допустиме значення для LONG_MINє 2147483647.

Які зміни я б вніс у ваш код?

Ваш код добре, як є, тому це насправді не скарги. Це більше схоже на те, що якщо мені справді, дійсно потрібно сказати що-небудь про ваш код, є деякі дрібниці, які можуть зробити його трохи крихітнішим.

  • Назви змінних можуть бути трохи кращими, але це коротка функція, яку легко зрозуміти, тому це не є великою справою.
  • Ви можете змінити умову з nна n!=0. Семантично це 100% еквівалент, але це робить трохи зрозумілішим.
  • Перемістіть декларацію c(яку я перейменував digit) у внутрішню петлю, оскільки вона використовується лише там.
  • Змініть тип аргументу, 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 Ви кажете "може". Ви впевнені, що це насправді відбувається чи це дозволено стандартом чи чимось, або ви просто спекулюєте?
клотт

6
@MartinRosenau: Гаразд, але використання цих перемикачів зробить це вже не С. У GCC / clang немає жодних комутаторів, які б порушували ціле ділення на будь-якій ISA, про яку я знаю. Навіть незважаючи на те, що ігнорування бітового знака може, можливо, дати прискорення, використовуючи звичайний мультиплікативний зворотний для постійних дільників. (Але всі знайомі МСА, які мають інструкцію щодо апаратного підрозділу, реалізують його шляхом C99, обрізаючись до нуля, тому C %і /оператори можуть компілювати лише idivна x86, або sdivна ARM чи будь-що інше. Все-таки це не пов’язано із значною мірою більш швидкий кодовий ген для дільників, що мають постійний час компіляції)
Пітер Кордес

5
@TonyK AFIK, це зазвичай вирішується, але згідно стандарту це UB.
клатч

20

Мені не подобається ні ваша версія, ні ваша вчительська. Ваша версія вчителя робить зайві тести, які ви правильно вказали, непотрібні. Оператор мод C не є належним математичним модом: від'ємне число mod 10 дасть негативний результат (правильний математичний модуль завжди невід'ємний). Але так як ви все одно їх квадратуєте, різниці немає.

Але це далеко не очевидно, тому я додав би до вашого коду не чеки вашого вчителя, а великий коментар, який пояснює, чому це працює. Наприклад:

/ * ПРИМІТКА. Це працює для негативних значень, оскільки модуль отримує квадрат * /


9
C's %найкраще називати залишком , тому що це навіть для підписаних типів.
Пітер Кордес

15
Квадратура важлива, але я думаю, що це очевидна частина. Що слід зазначити, це те, що (напр.) -7 % 10 Насправді буде-7 більше, ніж 3.
Яків Райхле

5
"Належний математичний модуль" нічого не означає. Правильний термін - "евклідовий модуль" (пам’ятайте суфікс!), І це дійсно те, що %оператор С не є.
Ян

Мені подобається ця відповідь, оскільки вона вирішує питання про кілька способів інтерпретації модуля. Ніколи не залишайте подібної речі випадковість / тлумачення. Це не код гольфу.
Харпер -

1
"правильний математичний модуль завжди негативний" - Не дуже. Результатом модульної операції є клас еквівалентності , але прийнято трактувати результат як найменше невід'ємне число, що належить до цього класу.
клотт

10

ПРИМІТКА. Як я писав цю відповідь, ви пояснили, що ви використовуєте C. Більшість моєї відповіді стосується C ++. Однак, оскільки у вашому заголовку все ще є C ++, а питання все ще позначене C ++, я все-таки вирішив відповісти на випадок, якщо це все-таки корисно іншим людям, тим більше, що більшість відповідей, які я бачив до цих пір, є переважно незадовільними.

У сучасному мові C ++ (Примітка: я не знаю, де C стоїть на цьому), здається, ваш професор помиляється з обох питань.

Спочатку ця частина тут:

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

У C ++ це в основному те саме, що :

if (!n) {
        return 0;
}

Це означає, що ваш час еквівалентне чомусь подібному:

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

Це означає, що ви просто виходите з вашого, якщо коли час все одно не буде виконано, то насправді немає приводу ставити це, якщо тут, оскільки те, що ви робите після циклу, і в рівному випадку є рівнозначним. Хоча я мушу сказати, що це чомусь було іншим, вам це знадобиться.

Так справді, це якщо твердження не особливо корисне, якщо я не помиляюся.

Друга частина - це те, що все стає волохатим:

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. Якщо саме цим користується ваш професор, то це може бути причиною.


EDIT: Ні, я помиляюся. Це, мабуть, має місце і для 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і код ОП ніколи не припиняється.
тушканчик

Не могли б ви вказати на джерело для цього? Мені дуже важко вірити (-1) / 10 - -1. Це має бути 0. Також (-1)% 10 = 9, здається, порушує керуюче рівняння.
Чіпстер

1
@Chipster, почніть з (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 не настільки очевидно, тому я вважаю за краще бачити ці 2 рядки коду:

if (n < 0) 
    n = -n;

або принаймні коментар:

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

Чесно кажучи, в який час ви почали вважати, що п може бути негативним? Під час написання коду чи під час читання зауважень вчителя?


1
Незалежно від того, чи N негативний, N квадрата буде додатним. Отже, навіщо в першу чергу видаляти знак? -3 * -3 = 9; 3 * 3 = 9. Чи змінилася математика за 30-ти років, відколи я це навчився?
Меровекс

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

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

Як правило, у завданнях не всі позначки даються лише тому, що код працює. Ви також отримуєте оцінки за те, що рішення легко читається, ефективно та елегантно. Ці речі не завжди є взаємовиключними.

Я не можу достатньо strees це "використовувати значущі імена змінних" .

У вашому прикладі це не має великої різниці, але якщо ви працюєте над проектом з мільйонними рядками читабельності коду стає дуже важливим.

Ще одна річ, яку я схильний бачити з кодом С - це люди, які намагаються виглядати розумно. Замість того, щоб використовувати час (n! = 0), я покажу всім, наскільки я розумний, пишу під час (n) бо це означає те саме. Добре це робиться в компіляторі, який у вас є, але, як ви запропонували, ви старша версія вчителя не реалізувала його так само.

Поширений приклад - посилання на індекс у масиві, одночасно збільшуючи його; Числа [i ++] = iPrime;

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

Мегабайт місця на диску дешевше, ніж рулон туалетного паперу, для наочності, а не для економії місця, ваші колеги-програмісти будуть щасливішими.


2
Я програмував на C лише кілька разів, і я знаю, що ++iприрости перед оцінкою і i++збільшення після цього. while(n)також є загальною рисою мови. На основі такої логіки я бачив безліч кодів if (foo == TRUE). Я згоден відносно: імен змінних.
alan ocallaghan

3
Як правило, це не погана порада, але уникати основних мовних особливостей (які люди неминуче все-таки натрапляють) з метою оборонного програмування є надмірним. Короткий, чіткий код у будь-якому випадку частіше зрозумілий. Тут ми не говоримо про божевільні перламутрові чи башти, лише про основні мовні особливості.
alan ocallaghan

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

1
І крім того, ви дійсно не повинні дозволяти будь-кому, хто не знає різниці між i++та ++iзмінювати код C, який слід використовувати у виробництві.
клатч

2
@PaulMcCarthy Ми обидва за читабельність коду. Але ми не згодні з тим, що це означає. Також це не об’єктивно. Що легко читати для однієї людини, може бути важким для іншої. Особистість та передумови значного впливу на це. Моя головна думка полягає в тому, що ви не отримуєте максимальної читабельності, сліпо дотримуючись деяких правил.
клют

0

Я б не заперечував, чи краще оригінальне або сучасне визначення поняття "%", але той, хто записує два зворотні заяви на таку коротку функцію, взагалі не повинен навчати програмуванню на C. Додаткове повернення - це оператор goto, і ми не використовуємо goto в C. Крім того, код без нульової перевірки матиме такий же результат, додаткове повернення ускладнює читання.


4
"Додаткове повернення - це оператор goto, і ми не використовуємо goto в C." - це поєднання дуже широкого узагальнення та дуже далекого розтягування.
СС Енн

1
"Ми", безумовно, використовуємо goto в C. З цим немає нічого поганого.
клатч

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 Standard та попередніми виданнями. Ви повинні задокументувати, чому ваш підхід діє навіть для негативних даних.

Ось модифікована версія:

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.