Permalänk
Medlem

Kontroll av input C++

Hej! Jag har efter många år äntligen fått tummen ur och läser nu under hösten en C++ kurs. Dessvärre har jag stött på problem som besvärat mig i timmar... vad jag vill göra är att kontrollera om användarens inmatning motsvarar ett heltal, om inte så skall programmet fråga efter användarens inmatning igen.

Såhär ser min kod ut just nu:

while (!inmatning) { inmatning=true; cout<<"Mata in ett heltal: "; cin>>num; if(cin.fail()) { cin.clear(); cin.ignore(); cout<<"Du måste ange ett heltal!" << endl; inmatning=false; } }

Detta fungerar delvis, om jag t.ex. anger "F"

Mata in ett heltal: F
Du mÕste ange ett heltal!
Mata in ett heltal:

Anger jag istället "FF" får jag följande resultat:

Mata in ett heltal: FF
Du mÕste ange ett heltal!
Mata in ett heltal: Du mÕste ange ett heltal!
Mata in ett heltal:

och om jag istället anger ett flyttal t.ex. 2,2 hamnar programmet i en oändlig loop...

Hur ska jag gå tillväga för att programmet endast skall acceptera heltal och för att endast skriva ut felmeddelandet för felaktig inmatning EN gång, och inte en gång per tecken?` Tack på förhand!

Permalänk
Datavetare

Du måste först läsa in hela raden eftersom du bara är intresserad av att användaren matar in något och sedan vill du verifiera att det man matade in var ett heltal, vid fel vill du skippa allt. Finns en standardfunktion för att läsa en hel rad som heter getline().

Finns lite olika sätt att kolla om en sträng representerar ett heltal, enklast är nog att använda en standard C funktion som heter strtol().

Så skulle kunna se ut t.ex. så här

#include <cstdlib> #include <iostream> using namespace std; int read_num(istream &is, ostream &os, string prompt, string errmsg) { int num; string line; char *endptr; for (;;) { os << prompt; getline(is, line); // endptr will point to the first character that isn't a // number, will be the character '\0' if the whole line was // consumed num = strtol(line.c_str(), &endptr, 10); if (*endptr == '\0') break; os << errmsg << endl; } return num; } int main() { cout << read_num(cin, cout, "Mata in ett heltal: ", "Det var inte ett heltal") << endl; }

Visa signatur

Care About Your Craft: Why spend your life developing software unless you care about doing it well? - The Pragmatic Programmer

Permalänk
Medlem
Skrivet av Yoshman:

Du måste först läsa in hela raden eftersom du bara är intresserad av att användaren matar in något och sedan vill du verifiera att det man matade in var ett heltal, vid fel vill du skippa allt. Finns en standardfunktion för att läsa en hel rad som heter getline().

Finns lite olika sätt att kolla om en sträng representerar ett heltal, enklast är nog att använda en standard C funktion som heter strtol().

Så skulle kunna se ut t.ex. så här

#include <cstdlib> #include <iostream> using namespace std; int read_num(istream &is, ostream &os, string prompt, string errmsg) { int num; string line; char *endptr; for (;;) { os << prompt; getline(is, line); // endptr will point to the first character that isn't a // number, will be the character '\0' if the whole line was // consumed num = strtol(line.c_str(), &endptr, 10); if (*endptr == '\0') break; os << errmsg << endl; } return num; } int main() { cout << read_num(cin, cout, "Mata in ett heltal: ", "Det var inte ett heltal") << endl; }

Super, det fungerar precis som tänkt! Dessvärre förstår jag inte riktigt vad koden gör, känns som att detta ligger långt över min nuvarande kunskapsnivå. Ska spara detta och återkomma senare i kursen, då jag gärna undviker att använda lösningar som jag inte förstår mig på. Tack så mycket!

Permalänk
Medlem
Skrivet av addejo:

Super, det fungerar precis som tänkt! Dessvärre förstår jag inte riktigt vad koden gör, känns som att detta ligger långt över min nuvarande kunskapsnivå. Ska spara detta och återkomma senare i kursen, då jag gärna undviker att använda lösningar som jag inte förstår mig på. Tack så mycket!

En annan modell är att modifiera din existerande kod och specifikt felkontrollen och hanteringen:

if (cin.fail() || cin.peek() != '\n') { cin.clear(); while (cin.peek() != '\n') { cin.ignore(); } cout << "Du måste ange ett heltal!" << endl; inmatning = false; }

Visa signatur

.<

Permalänk
Hedersmedlem

Det största felet med originallösningen är väl att ignore endast hoppar över ett tecken om man inte säger något annat. Det kan man dock göra med till exempel

cin.ignore(100, '\n')

som hoppar över upp till 100 tecken eller till och med '\n'.

Permalänk
Datavetare
Skrivet av addejo:

Super, det fungerar precis som tänkt! Dessvärre förstår jag inte riktigt vad koden gör, känns som att detta ligger långt över min nuvarande kunskapsnivå. Ska spara detta och återkomma senare i kursen, då jag gärna undviker att använda lösningar som jag inte förstår mig på. Tack så mycket!

Ah, om du håller på med en kurs så är det helt rätt tänkt och jag borde inte ens gjort en lösning...

Men i grunden är det bara två steg.

Första är att getline() läser in alla tecken som användaren skrev fram till radslut, detta lagras i variabeln line exklusive radslutstecknet '\n'.

line.c_str() kanske inte är helt uppenbart, men det returnerar en sekvens av tecken i minnet vilket är hur C representerar strängar. Strängen avslutas med tecknet som har värdet noll, '\0'.

Andra steget är strtol() vilken tar en C-sträng som argument, en pekare till där man kan skriva ner sista tecknet som användes +1 (ja detta är definitivt överkurs) samt vilken talbas man ska tolka strängen i (10 = vanliga decimala tal, 16 = hexadecimala tal, m.fl). Så i stort sätt kollar strtol() att tecknen är 0..9, alla sådana konverteras till det tal som returneras. Första tecknet som inte är 0..9 är det som sparas i endptr vilket då blir tecknet som markerar slutet på strängen '\0' om alla tecken är 0..9, d.v.s. strängen var ett heltal.

Visa signatur

Care About Your Craft: Why spend your life developing software unless you care about doing it well? - The Pragmatic Programmer

Permalänk
Medlem

Kan man leva med att eventuellt flyttal blir trunkerat utan felutskrift så kan man ju köra felkontroll på strömmen:

int num; while (!(cin >> num)) { cout << "Du måste ange ett heltal !" << endl; cin.clear(); cin.ignore(100,'\n'); }

Vilket är en förenklad version av vad du hade från början egentligen fast med cin.ignore(100,'\n') också.

Är det otroligt viktigt att man inte kan skriva flyttal som tolkas som int så blir det nog virtual_voids lösning.

Permalänk
Hedersmedlem
Skrivet av Dreatiad:

Är det otroligt viktigt att man inte kan skriva flyttal som tolkas som int så blir det nog virtual_voids lösning.

Man kan också som ovan använda
cin.peek()
för att undersöka om något annat än radbrytning (kommatecken till exempel) ligger kvar. Det är inte trunkering som sker utan inläsningen som stannar vid icke-siffor.