Ich hatte vor kurzem einen Test in meiner Klasse. Eines der Probleme war das folgende:
Schreiben Sie bei gegebener Zahl n eine Funktion in C / C ++, die die Summe der Ziffern der quadrierten Zahl zurückgibt . (Folgendes ist wichtig). Der Bereich von n ist [- (10 ^ 7), 10 ^ 7]. Beispiel: Wenn n = 123 ist, sollte Ihre Funktion 14 zurückgeben (1 ^ 2 + 2 ^ 2 + 3 ^ 2 = 14).
Dies ist die Funktion, die ich geschrieben habe:
int sum_of_digits_squared(int n)
{
int s = 0, c;
while (n) {
c = n % 10;
s += (c * c);
n /= 10;
}
return s;
}
Sah für mich richtig aus. Nun kam der Test zurück und ich stellte fest, dass der Lehrer mir aus einem Grund, den ich nicht verstehe, nicht alle Punkte gegeben hat. Ihm zufolge hätte ich, damit meine Funktion vollständig ist, das folgende Detail hinzufügen sollen:
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;
}
Das Argument dafür ist, dass die Zahl n im Bereich [- (10 ^ 7), 10 ^ 7] liegt, also eine negative Zahl sein kann. Aber ich sehe nicht, wo meine eigene Version der Funktion fehlschlägt. Wenn ich richtig verstehe, von der Bedeutung while(n)
ist while(n != 0)
, nicht while (n > 0)
, so in meiner Version der Funktion die Anzahl n nicht die Schleife eintreten würde scheitern. Es würde genauso funktionieren.
Dann habe ich beide Versionen der Funktion auf meinem Computer zu Hause ausprobiert und für alle Beispiele, die ich ausprobiert habe, genau die gleichen Antworten erhalten. Also, sum_of_digits_squared(-123)
ist gleich sum_of_digits_squared(123)
(was wiederum gleich ist 14
) (auch ohne das Detail, das ich anscheinend hätte hinzufügen sollen). In der Tat, wenn ich versuche, die Ziffern der Zahl (von der geringsten bis zur größten Bedeutung) auf den Bildschirm zu drucken, in dem 123
Fall, den ich bekomme 3 2 1
und in dem -123
Fall, den ich bekomme -3 -2 -1
(was eigentlich irgendwie interessant ist). Aber bei diesem Problem wäre es egal, da wir die Ziffern quadrieren.
Also, wer ist falsch?
EDIT : Mein schlechtes, ich habe vergessen anzugeben und wusste nicht, dass es wichtig ist. Die in unserer Klasse und unseren Tests verwendete Version von C muss C99 oder neuer sein . Ich denke also (durch Lesen der Kommentare), dass meine Version in irgendeiner Weise die richtige Antwort bekommen würde.
n = n * (-1)
ist eine lächerliche Art zu schreibenn = -n
; Nur ein Akademiker würde daran denken. Ganz zu schweigen von den redundanten Klammern.n = n * (-1)
? Wut ??? Was Ihr Professor sucht, ist Folgendes: `n = -n '. Die C-Sprache hat einen unären Minusoperator.Antworten:
Zusammenfassung einer Diskussion, die in den Kommentaren versickert:
n == 0
. Derwhile(n)
Test wird diesen Fall perfekt behandeln.%
mit negativen Operanden anders definiert wurde. Auf einigen älteren Systemen (einschließlich, insbesondere, frühe Unix auf einem PDP-11, wo Dennis Ritchie ursprünglich entwickelt , C), das Ergebnisa % b
war immer im Bereich[0 .. b-1]
, was bedeutet , dass -123% 10 war 7. Auf einem solchen System den Test im Voraus fürn < 0
wäre notwendig.Der zweite Punkt gilt jedoch nur für frühere Zeiten. In den aktuellen Versionen sowohl des C- als auch des C ++ - Standards wird die Ganzzahldivision so definiert, dass sie gegen 0 abschneidet. Es stellt sich also heraus, dass Sie
n % 10
garantiert die (möglicherweise negative) letzte Ziffer erhalten,n
auch wenn sien
negativ ist.Also die Antwort auf die Frage "Was bedeutet das
while(n)
?" ist "Genau das Gleiche wiewhile(n != 0)
" und die Antwort auf "Funktioniert dieser Code sowohl für negative als auch für positiven
Zwecke ordnungsgemäß ?" ist "Ja, unter jedem modernen, standardkonformen Compiler." Die Antwort auf die Frage "Warum hat der Ausbilder sie dann notiert?" ist wahrscheinlich, dass ihnen keine signifikante Neudefinition der Sprache bekannt ist, die 1999 mit C und 2010 mit C ++ passiert ist.quelle
n == 0
mindestens macht es für jeden Leser sofort und völlig offensichtlich, was in diesem Fall passiert. Ohne sie muss sich der Leser davon überzeugen, dass die Schleife tatsächlich übersprungen wird und der Standardwert vons
return der richtige ist.n=0
. Das Einführen unnötiger Verzweigungen und Komplikationen macht den Code nicht einfacher, sondern auch schwieriger, da Sie jetzt nicht nur zeigen müssen, dass der allgemeine Algorithmus korrekt ist, sondern auch alle Sonderfälle separat betrachten müssen.Ihr Code ist vollkommen in Ordnung
Sie sind absolut richtig und Ihr Lehrer ist falsch. Es gibt absolut keinen Grund, diese zusätzliche Komplexität hinzuzufügen, da sie das Ergebnis überhaupt nicht beeinflusst. Es führt sogar einen Fehler ein. (Siehe unten)
Erstens ist die separate Prüfung, ob
n
Null ist, offensichtlich völlig unnötig und dies ist sehr einfach zu realisieren. Um ehrlich zu sein, stelle ich die Kompetenz Ihres Lehrers in Frage, wenn er Einwände dagegen hat. Aber ich denke, jeder kann von Zeit zu Zeit einen Hirnfurz haben. Ich denke jedoch, dasswhile(n)
dies geändert werden sollte,while(n != 0)
da es ein wenig mehr Klarheit ohne auch nur eine zusätzliche Zeile zu kosten. Es ist jedoch eine Kleinigkeit.Der zweite ist etwas verständlicher, aber er liegt immer noch falsch.
Dies ist der C11-Standard 6.5.5.p6 sagt :
Die Fußnote sagt dies:
Abschneiden gegen Null bedeutet, dass der Absolutwert für
a/b
gleich dem Absolutwert(-a)/b
für alle ista
und istb
, was wiederum bedeutet, dass Ihr Code vollkommen in Ordnung ist.Modulo ist eine einfache Mathematik, kann aber nicht intuitiv sein
Ihr Lehrer hat jedoch den Punkt, dass Sie vorsichtig sein sollten, da die Tatsache, dass Sie das Ergebnis quadrieren, hier tatsächlich entscheidend ist. Das Berechnen
a%b
gemäß der obigen Definition ist einfach, kann aber Ihrer Intuition widersprechen. Bei Multiplikation und Division ist das Ergebnis positiv, wenn die Operanden das gleiche Vorzeichen haben. Aber wenn es um Modulo geht, hat das Ergebnis das gleiche Vorzeichen wie der erste Operand. Der zweite Operand beeinflusst das Vorzeichen überhaupt nicht. Zum Beispiel7%3==1
aber(-7)%(-3)==(-1)
.Hier ist ein Ausschnitt, der dies demonstriert:
Ironischerweise hat Ihr Lehrer seinen Standpunkt bewiesen, indem er sich geirrt hat.
Der Code Ihres Lehrers ist fehlerhaft
Ja, das ist es tatsächlich. Wenn die Eingabe
INT_MIN
UND die Architektur das Zweierkomplement ist UND das Bitmuster, bei dem das Vorzeichenbit 1 ist und alle Wertbits 0 sind, KEIN Trap-Wert ist (die Verwendung des Zweierkomplements ohne Trap-Werte ist sehr häufig), führt der Code Ihres Lehrers zu einem undefinierten Verhalten auf der Linien = n * (-1)
. Ihr Code ist - wenn auch nur geringfügig - besser als sein. Und wenn man bedenkt, einen kleinen Fehler einzuführen, indem man den Code unnötig komplex macht und einen Wert von absolut Null erreicht, würde ich sagen, dass Ihr Code VIEL besser ist.Mit anderen Worten, in Kompilierungen mit INT_MIN = -32768 (obwohl die resultierende Funktion keine Eingabe von <-32768 oder> 32767 empfangen kann) verursacht die gültige Eingabe von -32768 undefiniertes Verhalten, da das Ergebnis von - (- 32768i16) kann nicht als 16-Bit-Ganzzahl ausgedrückt werden. (Tatsächlich würde -32768 wahrscheinlich kein falsches Ergebnis verursachen, da - (- 32768i16) normalerweise -32768i16 ergibt und Ihr Programm negative Zahlen korrekt verarbeitet.) (SHRT_MIN kann je nach Compiler -32768 oder -32767 sein.)
Ihr Lehrer hat jedoch ausdrücklich angegeben, dass
n
dies im Bereich [-10 ^ 7; 10 ^ 7]. Eine 16-Bit-Ganzzahl ist zu klein. Sie müssten [mindestens] eine 32-Bit-Ganzzahl verwenden. Die Verwendungint
scheint seinen Code sicher zu machen, außer dass diesint
nicht unbedingt eine 32-Bit-Ganzzahl ist. Wenn Sie für eine 16-Bit-Architektur kompilieren, sind beide Codefragmente fehlerhaft. Aber Ihr Code ist immer noch viel besser, weil dieses Szenario denINT_MIN
oben erwähnten Fehler mit seiner Version wieder einführt . Um dies zu vermeiden, können Sielong
stattdessen schreibenint
, was in beiden Architekturen eine 32-Bit-Ganzzahl ist. A kannlong
garantiert jeden Wert im Bereich [-2147483647; 2147483647]. C11 Standard 5.2.4.2.1LONG_MIN
ist häufig-2147483648
aber der maximal zulässige Wert (ja, maximal, es ist eine negative Zahl) fürLONG_MIN
ist2147483647
.Welche Änderungen würde ich an Ihrem Code vornehmen?
Ihr Code ist in Ordnung, es handelt sich also nicht wirklich um Beschwerden. Es ist eher so, wenn ich wirklich, wirklich etwas über Ihren Code sagen muss, gibt es einige kleine Dinge, die es nur ein kleines bisschen klarer machen könnten.
n
bis ändernn!=0
. Semantisch ist es 100% äquivalent, aber es macht es ein bisschen klarer.c
(in die ich umbenannt habedigit
) in die while-Schleife, da sie nur dort verwendet wird.long
um sicherzustellen, dass er den gesamten Eingabesatz verarbeiten kann.Tatsächlich kann dies etwas irreführend sein, da die Variable - wie oben erwähnt -
digit
einen negativen Wert erhalten kann, eine Ziffer an sich jedoch niemals positiv oder negativ ist. Es gibt ein paar Möglichkeiten, dies zu umgehen, aber das ist WIRKLICH ein Trottel, und ich würde mich nicht für so kleine Details interessieren. Insbesondere die separate Funktion für die letzte Ziffer geht zu weit. Ironischerweise ist dies eines der Dinge, die der Code Ihres Lehrers tatsächlich löst.sum += (digit * digit)
zusum += ((n%10)*(n%10))
und überspringen Sie die Variabledigit
vollständig.digit
wenn negativ. Ich würde jedoch dringend davon abraten, den Code komplexer zu gestalten, nur um einen Variablennamen sinnvoll zu machen. Das ist ein sehr starker Code-Geruch.int last_digit(long n) { int digit=n%10; if (digit>=0) return digit; else return -digit; }
Dies ist nützlich, wenn Sie diese Funktion an einer anderen Stelle verwenden möchten.c
wie Sie es ursprünglich getan haben. Dieser Variablenname gibt keine nützlichen Informationen, ist aber auch nicht irreführend.Aber um ehrlich zu sein, sollten Sie an dieser Stelle zu einer wichtigeren Arbeit übergehen. :) :)
quelle
INT_MIN
und die Architektur das Zweierkomplement verwendet (was sehr häufig vorkommt), führt der Code Ihres Lehrers zu undefiniertem Verhalten. Autsch. Das wird Spuren hinterlassen. ;-)(a/b)*b + a%b ≡ a
der Code des OP zusätzlich von der Tatsache abhängt, dass/
auf Null gerundet wird, und dass(-c)*(-c) ≡ c*c
. Es könnte argumentiert werden, dass die zusätzlichen Kontrollen gerechtfertigt sind, obwohl der Standard all dies garantiert, da dies nicht offensichtlich genug ist. (Natürlich könnte man genauso gut argumentieren, dass es eher einen Kommentar geben sollte, der die relevanten Standardabschnitte verknüpft, aber die Stilrichtlinien variieren.)%
und/
Operatoren nuridiv
auf x86 odersdiv
auf ARM oder was auch immer kompiliert werden können schnellere Code-Gen für Teiler mit konstanter Kompilierungszeit)Ich mag weder deine Version noch die deines Lehrers ganz. Die Version Ihres Lehrers führt die zusätzlichen Tests durch, auf die Sie richtig hinweisen, und ist nicht erforderlich. Der Mod-Operator von C ist kein richtiger mathematischer Mod: Eine negative Zahl mod 10 erzeugt ein negatives Ergebnis (der richtige mathematische Modul ist immer nicht negativ). Aber da Sie es trotzdem quadrieren, kein Unterschied.
Aber das ist alles andere als offensichtlich, deshalb würde ich Ihrem Code nicht die Überprüfungen Ihres Lehrers hinzufügen, sondern einen großen Kommentar, der erklärt, warum es funktioniert. Z.B:
/ * HINWEIS: Dies funktioniert für negative Werte, da der Modul quadriert wird * /
quelle
%
wird am besten als Rest bezeichnet , da dies auch für signierte Typen der Fall ist.-7 % 10
tatsächlich-7
eher als 3 sein wird.%
Operator von C.HINWEIS: Während ich diese Antwort schrieb, haben Sie klargestellt, dass Sie C verwenden. Der Großteil meiner Antwort bezieht sich auf C ++. Da Ihr Titel jedoch immer noch C ++ enthält und die Frage immer noch mit C ++ gekennzeichnet ist, habe ich mich trotzdem für eine Antwort entschieden, falls dies für andere Personen noch nützlich ist, zumal die meisten Antworten, die ich bisher gesehen habe, größtenteils unbefriedigend sind.
In modernem C ++ (Hinweis: Ich weiß nicht wirklich, wo C dazu steht) scheint Ihr Professor in beiden Punkten falsch zu liegen.
Erstens ist dieser Teil genau hier:
In C ++ ist dies im Grunde dasselbe wie :
Das heißt, Ihre Zeit entspricht so etwas:
Das heißt, da Sie nur in Ihrem if beenden, wenn die Weile sowieso nicht ausgeführt wird, gibt es wirklich keinen Grund, dies zu setzen, wenn hier, da das, was Sie nach der Schleife tun, und im if sowieso gleichwertig sind. Obwohl ich sagen sollte, dass dies aus irgendeinem Grund anders war, müssten Sie dies haben, wenn.
Also wirklich, diese if-Anweisung ist nicht besonders nützlich, wenn ich mich nicht irre.
Im zweiten Teil wird es haarig:
Das Herzstück des Problems ist, was die Ausgabe des Moduls einer negativen Zahl ausgibt.
In modernem C ++ scheint dies größtenteils gut definiert zu sein :
Und später:
Wie das Poster der zitierten Antwort richtig zeigt, ist der wichtige Teil dieser Gleichung hier:
Wenn Sie ein Beispiel für Ihren Fall nehmen, erhalten Sie ungefähr Folgendes:
Der einzige Haken ist die letzte Zeile:
Das bedeutet, dass in einem solchen Fall nur das Vorzeichen implementierungsdefiniert zu sein scheint. Das sollte in Ihrem Fall kein Problem sein, weil Sie diesen Wert sowieso quadrieren.
Beachten Sie jedoch, dass dies nicht unbedingt für frühere Versionen von C ++ oder C99 gilt. Wenn Ihr Professor dies verwendet, könnte dies der Grund sein.
EDIT: Nein, ich liege falsch. Dies scheint auch für C99 oder höher der Fall zu sein :
Und noch ein Ort :
Also ja. Selbst in C99 scheint dies Sie nicht zu beeinträchtigen. Die Gleichung ist dieselbe.
quelle
(-1)%10
dass produziert werden könnte-1
oder1
; es bedeutet, dass es produzieren könnte-1
oder9
, und im letzteren Fall(-1)/10
wird es produzieren-1
und der OP-Code wird niemals enden.(a/b)*b + a%b == a
und lass danna=-1; b=10
geben(-1/10)*10 + (-1)%10 == -1
. Wenn nun-1/10
tatsächlich abgerundet wird (in Richtung -inf), dann haben wir(-1/10)*10 == -10
, und Sie müssen haben,(-1)%10 == 9
damit die erste Gleichung übereinstimmt. Wie in den anderen Antworten heißt es nicht, wie es innerhalb der aktuellen Standards funktioniert, aber so hat es früher funktioniert. Es geht nicht wirklich um das Vorzeichen des Restes als solches, sondern darum, wie die Division rundet und was der Rest dann sein muss , um die Gleichung zu erfüllen.(-1)*10+9=-1
also die Auswahl(-1)/10=-1
und(-1)%10=9
nicht gegen die maßgebliche Gleichung verstößt. Andererseits kann die Wahl(-1)%10=1
die maßgebliche Gleichung nicht erfüllen, egal wie(-1)/10
gewählt wird; Es gibt keine ganze Zahl,q
so dassq*10+1=-1
.Wie andere betont haben, ist die spezielle Behandlung für n == 0 Unsinn, da für jeden ernsthaften C-Programmierer offensichtlich ist, dass "while (n)" die Arbeit erledigt.
Das Verhalten für n <0 ist nicht so offensichtlich, deshalb würde ich diese 2 Codezeilen lieber sehen:
oder zumindest einen Kommentar:
Ehrlich gesagt, zu welcher Zeit haben Sie angefangen zu überlegen, dass n negativ sein könnte? Beim Schreiben des Codes oder beim Lesen der Bemerkungen Ihres Lehrers?
quelle
Dies erinnert mich an eine Aufgabe, bei der ich versagt habe
Schon in den 90ern. Der Dozent hatte sich über Schleifen Gedanken gemacht, und kurz gesagt, unsere Aufgabe war es, eine Funktion zu schreiben, die die Anzahl der Ziffern für eine bestimmte ganze Zahl> 0 zurückgibt.
So zum Beispiel der Anzahl der Stellen in
321
wäre3
.Obwohl in der Aufgabe lediglich gesagt wurde, dass eine Funktion geschrieben werden soll, die die Anzahl der Ziffern zurückgibt, wurde erwartet, dass wir eine Schleife verwenden, die durch 10 geteilt wird, bis ... Sie sie erhalten, wie in der Vorlesung beschrieben .
Aber die Verwendung von Schleifen wurde nicht explizit angegeben, so dass ich:
took the log, stripped away the decimals, added 1
und anschließend vor der ganzen Klasse lambastiert wurde.Der Zweck der Aufgabe war es, unser Verständnis dessen zu testen, was wir während der Vorlesungen gelernt hatten . Durch die Vorlesung, die ich erhielt, erfuhr ich, dass der Computerlehrer ein bisschen ein Idiot war (aber vielleicht ein Idiot mit einem Plan?)
In Ihrer Situation:
Ich hätte definitiv zwei Antworten gegeben:
quelle
Im Allgemeinen werden bei Aufgaben nicht alle Noten vergeben, nur weil der Code funktioniert. Sie erhalten auch Noten dafür, dass eine Lösung einfach zu lesen, effizient und elegant ist. Diese Dinge schließen sich nicht immer gegenseitig aus.
Eine, die ich nicht genug verstehen kann, ist "Verwenden Sie aussagekräftige Variablennamen" .
In Ihrem Beispiel macht es keinen großen Unterschied, aber wenn Sie an einem Projekt mit Millionen Zeilen Code-Lesbarkeit arbeiten, wird dies sehr wichtig.
Eine andere Sache, die ich mit C-Code sehe, sind Leute, die versuchen, klug auszusehen. Anstatt while (n! = 0) zu verwenden, zeige ich allen, wie schlau ich bin, indem ich while (n) schreibe, weil es dasselbe bedeutet. Nun, es funktioniert in dem Compiler, den Sie haben, aber wie Sie vorgeschlagen haben, hat die ältere Version Ihres Lehrers es nicht auf die gleiche Weise implementiert.
Ein häufiges Beispiel ist das Verweisen auf einen Index in einem Array, während dieser gleichzeitig erhöht wird. Zahlen [i ++] = iPrime;
Jetzt muss der nächste Programmierer, der an dem Code arbeitet, wissen, ob ich vor oder nach der Zuweisung inkrementiert werde, damit jemand angeben kann.
Ein Megabyte Speicherplatz ist billiger als eine Rolle Toilettenpapier. Um Klarheit zu schaffen, anstatt Platz zu sparen, sind Ihre Programmierkollegen glücklicher.
quelle
++i
Schritte vor der Auswertung undi++
danach inkrementiert werden.while(n)
ist auch eine gemeinsame Sprachfunktion. Basierend auf dieser Logik habe ich viel Code gesehenif (foo == TRUE)
. Ich stimme jedoch den Variablennamen zu.while(n)
nicht das schlechteste Beispiel dafür ist (ich "mag"if(strcmp(one, two))
umso mehr)i++
und ihn++i
ändern.Ich würde nicht darüber streiten, ob die ursprüngliche oder die moderne Definition von '%' besser ist, aber jeder, der zwei return-Anweisungen in eine so kurze Funktion schreibt, sollte C-Programmierung überhaupt nicht unterrichten. Zusätzliche Rückgabe ist eine goto-Anweisung, und wir verwenden goto in C nicht. Außerdem hätte der Code ohne die Nullprüfung das gleiche Ergebnis, zusätzliche Rückgabe erschwerte das Lesen.
quelle
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; }
Die Problemstellung ist verwirrend, aber das numerische Beispiel verdeutlicht die Bedeutung der Summe der Ziffern der quadrierten Zahl . Hier ist eine verbesserte Version:
Die Funktion, die Sie geschrieben haben, ist bis auf 2 Details in Ordnung:
long
, der für alle Werte im angegebenen Bereich geeignetlong
ist, da der Typ durch den C-Standard garantiert, dass er mindestens 31 Wertbits aufweist, daher ein Bereich, der ausreicht, um alle Werte in [-10 7 , 10 7 ] darzustellen . (Beachten Sie, dass der Typint
für den Rückgabetyp ausreicht, dessen Maximalwert ist568
.)%
für negative Operanden ist nicht intuitiv und seine Spezifikation variiert zwischen dem C99-Standard und früheren Ausgaben. Sie sollten dokumentieren, warum Ihr Ansatz auch für negative Eingaben gültig ist.Hier ist eine modifizierte Version:
Die Antwort des Lehrers weist mehrere Mängel auf:
int
möglicherweise einen unzureichenden Wertebereich.0
.n = INT_MIN
.Angesichts der zusätzlichen Einschränkungen in der Problemstellung (C99 und Wertebereich für
n
) ist nur der erste Fehler ein Problem. Der zusätzliche Code liefert immer noch die richtigen Antworten.Sie sollten in diesem Test eine gute Note bekommen, aber die Erklärung ist in einem schriftlichen Test erforderlich, um Ihr Verständnis der Probleme für das Negative zu zeigen
n
, andernfalls kann der Lehrer annehmen, dass Sie sich dessen nicht bewusst waren und einfach Glück hatten. In einer mündlichen Prüfung hätten Sie eine Frage bekommen und Ihre Antwort hätte sie getroffen.quelle