Daten|teiler
Kopieren als Kulturtechnik

Die Funktion istZahl in C++

16. Februar 2009 von Christian Imhorst

Für ein kleines Programmierprojekt muss ich in C++ eine Eingabe überprüfen, ob wirklich nur ganze Zahlen, oder etwa andere Zeichen eingegeben wurden. Ich dachte da an dieses kleine Stück Quellcode:

#include <iostream>
using namespace std;
 
int main(){
 
        int zahl = 0;
        char zeichen[20];
        cout<<"Bitte Zahl eingeben: ";
        cin>>zeichen;
 
        while (!(sscanf(zeichen, "%d", &zahl))) {
        	cout<<"\nUngültige Eingabe. \nBitte nur Zahlen eingeben: ";
        	cin>>zeichen;
		}
            cout<<zahl<< " ist eine Zahl"<<endl;
 
}

Nur leider zeigt dieser Code-Schnipsel bei größeren ganzen Zahlen nur noch die Zahl 2147483647 an, was genau dem obersten Wertebereich des Datentyps int entspricht. Eine Zahl die größer ist, kann das Programm nicht anzeigen:

<em>Diese Zahl kommt einem bekannt vor...</em>

Diese Zahl kommt einem bekannt vor...

Da ich aber mit Eingaben rechnen muss, die mehr als zehn Stellen haben können, hilft mir dieses Code-Beispiel nicht weiter. Daher habe ich eine eigene Funktion istZahl() geschrieben:

#include <iostream>
#include <cstring>
using namespace std;
 
int istZahl(char zahl[10]){
	int laenge=strlen(zahl);
	for(int i=0; i<laenge; i++)
        if ( !((int)zahl[i] > 47 && zahl[i] < 58)){
		return false;
		}
	return true;
	}
 
int main(){
	char a[20];
	cout<<"Bitte eine Zahl eingeben: ";
	cin.getline(a,20);
 
	while (istZahl(a)==false){
	cout<<"\nUngültige Eingabe. \nBitte nur Zahlen eingeben: ";
	cin.getline(a,20);
}
	cout<<a<<" ist eine Zahl."<<endl;
 
	return 0;
 
}

Hier kann eine ganze Zahl mit bis zu 19 Stellen eingegeben werden, da die 20. Stelle für die Endekennung \0 benötigt wird. Alle Stellen, die darüber hinausgehen, werden einfach abgeschnitten. Sollte das nicht reichen, kann man den Wert im Quellcode noch erhöhen.

So würde ich es machen, aber vielleicht gibt es ja noch schönere Lösungen?

[Update] Wie man an den Kommentaren vielleicht schon sieht, geht die Funktion noch schöner und hat Dank Stefan den neuen Namen istZiffer():

#include <iostream>
#include <cstring>
using namespace std;
const int MAX=5;
 
bool istZiffer(char ziffer[MAX]){
	int laenge=strlen(ziffer);
	for(int i=0; i<laenge; i++)
        if (!(ziffer[i] >= '0' && ziffer[i] <= '9')) 
        // if (! isdigit (ziffer[i]))
        {
		return false;
		}
	return true;
	}
 
int main(){
	char a[MAX];
 
	do 
	{
		cout<<"Bitte Ziffern eingeben: ";
		cin.getline(a,MAX);
 
    }while (istZiffer(a)==false);
 
	cout<<a<<" ist eine Zahl."<<endl;
 
	return 0;
 
}

Das funktioniert soweit ganz gut, bis auf ein oder zwei Schönheitsfehler:

  1. Wenn ich im obigen Beispiel istZiffer() mehr als 4 Ziffern eingebe, wird alles was danach kommt einfach abgeschnitten, was aber für meine Bedürfnisse nicht weiter schlimm ist.
  2. Gebe ich aber z.B. mehr als vier Buchstaben ein, habe ich einen nervigen Programmabbruch, den ich noch nicht ganz weg bekomme.
  3. Wie dee schon sagt: Die Enter-Taste wird von der Funktion wie eine Ziffer behandelt, was ich auch noch ändern muss.

Aber Dank der vielen guten Kommentare ist die Funktion schon viel besser geworden. ;-)

Bücher zu C++

Geschrieben in Programmieren

22 Antworten

  1. Setsuna

    Hi Christian!
    Wenn du schon cstring einbindest (wobei ja string alleine denke ich genügen würde), warum verwendest du dann nicht gleich den string-Datentyp? Würde die lästige Begrenzung auf eine bestimmte Länge mal wegschaffen.

    Außerdem,

    for(int i=0; i 47 && zahl[i] < 85)){

    wird wohl nicht kompilieren, was hattest du hier gemeint? Das hier?:

    for(int i=0; zahl[i] > 47 && zahl[i] < 58)){

    Die Obergrenze war auch falsch, so würdest du alle Großbuchstaben bis ‘T’ auch als Zahl erkennen. Prinzipiell würde ich es aber auch so machen.

    ciao
    Setsuna

    PS: Bei Kommentaren werden < als tags erkannt und man muss < schreiben, damit sie angezeigt werden.

  2. Setsuna

    Außerdem empfehle ich statt der while-Schleife im main eine do-while-Schleife, dann ersparst du dir einmal getline zu schreiben. Macht aber wohl keinen Performanceunterschied

    ciao
    Setsuna

  3. zimon

    Hallo Christian,

    Du hast da in der Funktion istZahl irgendwie die for-Schleife verwurschtelt.

    Lustig: jetzt wollte ich es dir richtig hier rein schreiben, da merke ich, dass beim Preview genau die gleichen Fehler entstehen. Ich empfehle dir das WordPress-Plugin WP-Syntax: http://wordpress.org/extend/plugins/wp-syntax/

    Ansonsten könnte man in der main noch ein do while machen um den Code nicht doppelt schreiben braucht.

    Die 20 könnte man noch in eine Konstante packen, so dass sie sich leichter ändern lässt.

    Um das ganze dynamisch zu machen ist schon mehr Aufwand nötig. Vielleicht funktionierts ja mit nem String-Objekt.

    Gruß
    zimon

  4. Harper

    also ich bin jetzt kein 100%iger programmierexperte was das angeht,

    aber probier mal int64, das hat größere zahlenbereiche
    oder gleich uint64^^

  5. Betz Stefan

    Du könntest ja auch einfach mit einer for schleife über “zeichen” rutschen und für jedes Element (also indirekt auch Zeichen) prüfen ob es eine Zahl ist. So ist es egal wie viele Stellen der User eingeben würde.

    Wobei ich denke das es sogar noch einfacher gehen sollte, aber meine C/C++ Kenntnisse waren nie sonderlich berauschend.

    mfg Betz Stefan

  6. Dee

    > So würde ich es machen, aber vielleicht gibt es ja noch schönere Lösungen?

    Das vielleicht nicht, aber da sind einige Sicherheitsfehler drin, die das Programm locker zum Absturz bringen können.

    So übergibst Du an istZahl ein Array mit 10 Elementen, 20 Elemente konnte der User aber angeben und Du selbst läufst dann in der Schleife über 47. Wieso nicht “laenge”, wo Du die doch schon abfragst?

    Sprich: Überläufe sind hier garantiert. Und ich verstehe “zahl[i] < 85″ nicht. Wieso gibt es einen Fehler, wenn man etwas kleiner als “U” eingibt? Sprich “U1″ wäre mit dem Code eine Zahl.

    dee@dexus:~$ ./zahl
    Bitte eine Zahl eingeben: T
    Ungültige Eingabe. 
    Bitte nur Zahlen eingeben: U
    U ist eine Zahl.

    Und dann gibt es noch zahlreiche Fehler: Bei der Schleife fehlt ein Kleiner-Zeichen fehlt und natürlich das Inkrementieren und eine Klammer ist da auch zuviel. Der Code kompiliert also gar nicht.

    Auch das erste Beispiel kompiliert nicht, da ist eine spitze Klammer zuviel. Und vor allem erzeugt man damit nen schönen Speicherüberlauf:

    dee@dexus:~$ ./zahl
    Bitte Zahl eingeben: 137138946137846137846183 [...]
     ist eine Zahl
    *** stack smashing detected ***: ./zahl terminated
    Segmentation fault

    Das ist so fast das Schlimmste, was Dir als Programmierer passieren kann. Das kommt davon, dass Du Speicher für 20 Zeichen anlegst, aber unendlich viele bei der Abfrage zulässt. Da ist beim zweiten Beispiel ja besser gelöst.

  7. Christian Imhorst

    @all: Vielen Dank für Eure Kommentare. WordPress hat leider das ein oder andere Zeichen im Quelltext verschluckt, was mir gestern Nacht nicht mehr aufgefallen war. Ich habe es jetzt korrigiert und hoffe, dass der Quellcode nun stimmt und kompiliert. Auch den blöden Zahlendreher von 85 und 58 habe ich korrigiert.

    Es ist doch immer gut, wenn mehrere Leute über einen Quellcode schauen, damit auch Flüchtigkeitsfehler ausgeräumt werden. ;-)

    @zimon: Danke für den Tipp mit den WordPress-Plugin. Das werde ich mir heute Abend mal anschauen.

    @dee: Der Speicherüberlauf im ersten Beispiel war mir auch aufgefallen. Ein weiterer Grund, warum ich istZahl() geschrieben habe.Deine Anmerkungen istZahl() schaue ich mir an. Vielen Dank für deine Tipps.

    Viele Grüße,
    Christian

  8. .campino2k

    Hmm. Ich hätte, genau wie Harper, einen anderen Datentyp verwendet. “LONG” sollte in C++ eigentlich drin sein.

  9. Stefan W.

    a) Wieso gibt es einmal einen cast (int)zahl[i] und einmal nicht?

    b) Würde man diese Form des Tests nutzen, so würde man, der besseren Lesbarkeit wegen, ‘0’ und ‘9’ verwenden:

    if (!(zahl[i] >= '0' && zahl[i] <= '9'))

    c) Da aber isdigit nicht abgeschafft wurde genügt ein:

    if (! isdigit (zahl[i]))

    d) Was ist mit negativen Zahlen?

    e) Was ist mit 3.1415?

    f) Was ist mit 0x4c4c4c?

    g) Ach – Du meinst Ziffern?

    h) (meta:) Wo findet man denn die Syntaxelemente um Code hervorzuheben?

  10. Dee

    @Stefan W: Mittels Code-Tags z.B. (in spitzen Klammern natürlich).

    @Christian: Neben Stefans Anmerkungen noch ein paar:

    1. “char zahl[10]” finde ich wie gesagt schlechten Stil, entweder wirklich “char zahl[20]” oder einfach “char *zahl”. Dann aber gleich zu Beginn abfragen, ob der Pointer auch nicht 0 ist.

    2. Wieso ist der Rückgabewert von istZahl ein int, Du gibst aber ein “bool”-Wert zurück? (Ja, intern ist das wurscht, dennoch Kuddelmuddel.)

    3. Die leere Eingaben (also einfach Return drücken) wird als gültige Zahl erkannt. (Ist dann wohl Definitionssache, ob Nichts eine Zahl ist oder nicht. ;))

  11. Schumbi

    Hey,
    ich danke Dir für die Anregung. :-) Das kam wie gerufen, nachdem ich mich heute den halben Tag mit Containern beschäftigen durfte. :-)

    Falls Dich mein Ergebnis interessiert. Hier ist es zu finden http://pastebin.ch/1010

    lG
    Schumbi

  12. Christian Imhorst

    @Stefan: Wie du an dem Update istZiffer() siehst, habe ich deine Vorschläge so ziemlich umgesetzt, oder?

    @dee: Danke für die Hinweise. An das Problem mit der Enter-Taste muss ich wohl noch arbeiten. ;-)

    @Schumbi: Danke für den Link und schön, dass ich dir weiterhelfen konnte. ;-)

    Grüße,
    Christian

  13. Stefan W.

    a) Meine C++-Zeiten liegen weit zurück, und es war nie mehr, als ein C/C++-Kuddelmuddel.

    Nicht schön ist, daß die Funktion nicht beliebig lange Ziffernfolgen testet. Das sollte schon noch erreicht werden.

    Null Ziffern, also Nichts, ist keine Ziffer – das ist nicht Geschmackssache oder Definitionssache.

    @Dee: Ich habe spitze Klammern ++c; versucht, aber die Vorschau machte da nichts draus. Ist die Vorschau defekt? Mal posten, so…

  14. Dee

    Wegen spitzer Klammern:

    Dies
     ist
      ein
       Testcode.

    Und nun ein Test direkt in einer Zeile.

    Bei mir wird beides mehr oder weniger korrekt angezeigt. Offensichtlich wird zwar die Schriftart etwas verändert, aber es ist nicht dicktengleich und Leerzeichen werden auch ignoriert. Da müsste Christian sein CSS mal anpassen.

    Dein “++c” wird auch etwas kleiner angezeigt als der restliche Text, besser kriegt man es aktuell wohl nicht hin.

    @Christian: Du musst ja nur abfragen, ob die Länge 0 ist und dann eben false zurückliefern.

  15. spoilerhead

    if (!(ziffer[i] >= '0' && ziffer[i] <= '9'))

    ist aber nicht schön, weil es davon ausgeht, dass die zahlen im zeichensatz hintereinander liegen (was nicht garantiert ist)

    wieso net isdigit() weiterverwenden?

    um wirklich alle zahlen inkl negative und komazahlen azuzeigen würde ich eine kleine statemachine bauen

    bei interesse, schick mir eine mail, ich hab da noch so was rumliegen :D

  16. Schumbi

    Hat es eigentlich einen bestimmten Grund, warum Du ein “char[]” nimmst und keinen “string”? Ich finde diese Arrays aus char einfach unglaublich anstrengend.
    Irgendwie finde ich sieht Dein Code auch eher nach C als nach C++ aus. Abgeshene von dem “cin” und “cout”.

  17. Meillo

    Du solltest unbedingt isdigit() verwenden (wie ja schon vorgeschlagen). Da in Zeichensätzen die Zeichen 0 bis 9 aneinander liegen müssen. Die Funktion isdigit() kompensiert das.

    Dann ist es ein falsches Bestreben kleinlichst Speicher zu sparen. Warum nicht 128 Byte für das Array nehmen? Oder noch mehr.

    Desweiteren solltest du unbedingt englische Bezeichner verwenden … die ganze Welt (Außer ein paar Mitteleuropäern) wird es dir danken.

    Für das Array braucht in der Funktionsdefinition kein weiterer Speicher reserviert werden. Ein Pointer reicht als Übergabeparameter. Es wird ja nur gelesen.

    Mein Vorschlag ist somit:

    #include <iostream>
    #include <cstring>
    using namespace std;
    const int MAX=128;
    bool onlydigits(char* s){
            for(int i=0; i < strlen(s); i++) {
                    if (!isdigit(s[i])) {
                            return false;
                    }
            }
            return true;
    }
    int main(){
            char a[MAX];
            do {
                    cout<<"Bitte Ziffern eingeben: ";
                    cin.getline(a, MAX);
            } while (!onlydigits(a));
            cout << a << " ist eine Zahl." << endl;
            return 0;
    }
  18. Meillo

    EDIT:
    statt “Da in Zeichensätzen die Zeichen 0 bis 9 aneinander liegen müssen.” muss es “Da in Zeichensätzen die Zeichen 0 bis 9 NICHT aneinander liegen müssen.” heißen.

  19. Meillo

    Noch eine Ergänzung um leere Strings nicht als Zahl zu behandeln:

    Am Beginn von onlydigit() folgende drei Zeilen einfügen:

    if (s[0] == ' ' ) {
                    return false;
            }
  20. Christian Imhorst

    @Maillo: Vielen Dank für deine Hinweise und deinen Quellcode. Das Programm funktioniert soweit, nur

    if (s[0] == ' ' ) {
                    return false;
            }

    will nicht so recht. Beim Drücken der Return-Taste kommt immer noch, dass es sich dabei um eine Zahl handeln soll…

    @all: Den heutigen Abend habe ich darauf verwendet, Formatierungshilfen für die Kommentar-Funktion zu basteln und mein CSS anzupassen, so wie einige es sich gewünscht haben. Ich hoffe, dass jetzt alles korrekt funktioniert. :-)

  21. Meillo

    @Christian:
    Da wurde auch mal auf ein backslash-null (\ 0) geprüft. Leider hat das die Blog-Software gefressen.

    Damit wird geprüft ob in s überhaupt was drin ist, oder ob der string einfach leer ist. (Das könnte man auch mit strlen(s) == 0 abfragen.)

    btw: Meillo mit `e’

  22. Christian Imhorst

    Ja sorry Meillo, da hatte ich mich verschrieben.

    Ich hatte mir schon gedacht, dass du wohl folgendes meinst:

    if (ziffer[0] == '\0') {
                    return false;
            }

    Nachteil an dieser Lösung ist, dass das Programm in eine Endlosschleife geht, sobald ich den MAX-Wert mit anderen Zeichen als bloß Ziffern überschreite.

    Meta: Na toll, Preserve Code Formatting hat ein Problem mit dem Backslash…

    Viele Grüße,
    Christian