Double-Variablen korrekt per cin einlesen

javaDeveloper2011

Erfahrenes Mitglied
Hallo,

ich möchte gerne drei Double-Variablen hintereinander korrekt einlesen.
Dabei giebt es leider einige Probleme:
  • cin.fail() reagiert nicht, bei:
    • "5000hgh", hier würde einfach "5000" eingelesen und "hgh" kommt gleich in die nächst Variable, wo dann der Fehler auftritt
    • "fhzf400", hier wird für das aktuelle Einlesen ein Fehler gemeldet, jedoch gets gleich weiter zur nächsten Variablen, die dann "400" zugewiesen bekommt, der User hat damit keine Changese mehr den ersten Wert zu berichtigen
    ("5000hgh" und "fhzf400" sind natürlich nur Beispiele dafür, dass an Anfang oder Ende ein korrekter double-Wert steht, aber trotzdem ein logischer Fehler vorliegt)
  • Soweit ich das verstanden hab, setzt
C++:
cin.clear();
(nur) die Fehler-Flags zurück.?
cin.get() und cin.sync() hab ich immer wieder an allen möglichen stellen eingesetzt und rausgenommen, wo wäre das jetzt eigentlich sinfoll?
  • Ich würde gerne das Komma als Dezimaltrennzeichen verwenden, unter Linux funktioniert das mit
C++:
cin.imbue(locale("de_DE.uft-8"));
und
C++:
fout.imbue(locale("de_DE.uft-8"));
Bei Windows hingegen hilft auch stundenlanges googeln nichts.
  • Noch was:
    eingetlich möchte ich auch das man das Programm zu jeder Zeit per ESC-Druck beenden kann, da es in einer Endlosschleife läuft. Falls irgendwie möglich, sollte zu Anfang das Komandozeilenfenster gecleart werden, nicht bloß runterscrollen oder Leerzeilen schreiben!
    Beides selbstverständlich für Linux, Windows, Mac und auch ältere Versionen, sonst macht das ganze für mich keinen Sinn.

Freue mich sehr auf eure Hilfe, weil ich hier mittlerweile am verzweifeln bin,
javaDeveloper2011
 
Zuletzt bearbeitet von einem Moderator:
Hi.
ich möchte gerne drei Double-Variablen hintereinander korrekt einlesen.
Dabei giebt es leider einige Probleme:
cin.fail() reagiert nicht, bei:

"5000hgh", hier würde einfach "5000" eingelesen und "hgh" kommt gleich in die nächst Variable, wo dann der Fehler auftritt
Das ist doch völlig OK, oder nicht?
"fhzf400", hier wird für das aktuelle Einlesen ein Fehler gemeldet, jedoch gets gleich weiter zur nächsten Variablen, die dann "400" zugewiesen bekommt, der User hat damit keine Changese mehr den ersten Wert zu berichtigen
Das liegt aber dann an deinem Code.
Soweit ich das verstanden hab, setzt
C++:
cin.clear();
(nur) die Fehler-Flags zurück.?
Ja.
cin.get() und cin.sync() hab ich immer wieder an allen möglichen stellen eingesetzt und rausgenommen, wo wäre das jetzt eigentlich sinfoll?
sync() wäre sinnvoll wenn du die gepufferte Eingabe löschen möchtest.

Wie willst du denn eigentlich einlesen? Zeilenweise? Dann nimm getline und verarbeite die gelese Zeile mit einem istringstream:
C++:
while (getline(cin, line)) {
  istringstream input(line);
  double d1, d2, d3;
  if (input >> d1 >> d2 >> d3 && input.peek() == EOF) {
  }
}
(ist nur ein Beispiel, es hängt davon ab wie du Einlesen willst)
Ich würde gerne das Komma als Dezimaltrennzeichen verwenden, unter Linux funktioniert das mit
C++:
cin.imbue(locale("de_DE.uft-8"));
und
C++:
fout.imbue(locale("de_DE.uft-8"));
Eigentlich sollte es "utf-8" heißen.

Unter Windows heißen die Locales leider etwas anders. Versuch mal "de". Das funktioniert zumindest mit dem MS Compiler.
Noch was:
eingetlich möchte ich auch das man das Programm zu jeder Zeit per ESC-Druck beenden kann, da es in einer Endlosschleife läuft. Falls irgendwie möglich, sollte zu Anfang das Komandozeilenfenster gecleart werden, nicht bloß runterscrollen oder Leerzeilen schreiben!
Beides selbstverständlich für Linux, Windows, Mac und auch ältere Versionen, sonst macht das ganze für mich keinen Sinn.
Dann mußt du unterschiedlichen Code schreiben. Oder z.B. ncurses (PDcurses unter Windows) verwenden.

Gruß
 
Zuletzt bearbeitet von einem Moderator:
Hi,

leider werde auch auß deiner antwort noch nicht wirklich schlau.

Das ist doch völlig OK, oder nicht?
Natürlich nicht! Der User giebt irgendwas ein, ob er nun dachte dass nam mit Komma Dezimalzahlen trennt, ein Prozent-, oder Euro-Zeichen anhängt, oder sich einfach vertiptt, am Ende haben manche Varialen den wert 0, andere irgend einen ungewollten oder es kommt eine - für den User unverständliche - Fehlermeldung nach der anderen.
bewirkt nur dass die Variablen bei jeklichem Fehler einfach bei 0 bleiben und weiter gemacht wird.

Wie ich einlesen will:
- es erscheint ein Name (der Variablen) in der Promt und danach der Cursor.
- Der User giebt in die gleiche Zeile einen Double-wert ein und drückt Enter
- Falls es kein komplett absolut korrekter double-Wert war, erscheint ne Fehlermeldung und in der Zeile drunter wieder Name + Cursor
- Wenn alles gut ging gets genau so mit der nächsten Variablen weiter.

Ich hab auch mal ein kleines Beispiel in Java geschriben, um zu zeigen wie das Einlesen funktionieren soll, Komma als Dezimltrennzeichen funktioniert, Clear-Screen hab ich jetzt eindgültig aufgegeben und Beenden mit ESC hab ich nichtmal in Java hinbekommen, aber ich denke du weisst, was ich vorhabe:
Java:
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.IOException;
import java.text.DecimalFormat;
import java.text.ParseException;
import java.util.Locale;

public class ReadTest{

    public static void main(String[] args) {
		double v1 = 0, v2 = 0;
		boolean v1_korrekt = false, v2_korrekt = false;
		DecimalFormat deutschesFormat = (DecimalFormat) DecimalFormat.getInstance(Locale.GERMAN);
      	BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
		
		while(!v1_korrekt){
			System.out.print("Variable 1: ");
			try{
				String s = reader.readLine();
				try{
					v1 = deutschesFormat.parse(s).doubleValue();
					v1_korrekt = true;
				}
				catch(ParseException ex){
					System.err.println("Bitte geben Sie eine gültige Dezimalzahl ein!");
				}
			}
			catch(IOException ex){
				System.err.println(ex.toString());
			}
		}

		while(!v2_korrekt){
			System.out.print("Variable 2: ");
			try{
				String s = reader.readLine();
				try{
					v1 = deutschesFormat.parse(s).doubleValue();
					v2_korrekt = true;
				}
				catch(ParseException ex){
					System.err.println("Bitte geben Sie eine gültige Dezimalzahl ein!");
				}
			}
			catch(IOException ex){
				System.err.println(ex.toString());
			}
		}

		System.out.println(v1 + " + "  + v2 + " = " + (v1+v2));
		System.out.println(v1 + " - "  + v2 + " = " + (v1-v2));
		System.out.println(v1 + " * "  + v2 + " = " + (v1*v2));
		System.out.println(v1 + " / "  + v2 + " = " + (v1/v2));
	}

}

javaDeveloper2011
 
Hi.
Natürlich nicht! Der User giebt irgendwas ein, ob er nun dachte dass nam mit Komma Dezimalzahlen trennt, ein Prozent-, oder Euro-Zeichen anhängt, oder sich einfach vertiptt, am Ende haben manche Varialen den wert 0, andere irgend einen ungewollten oder es kommt eine - für den User unverständliche - Fehlermeldung nach der anderen.
Das hat aber nichts mit dem Einlesen an sich zu tun. Du mußt halt einfach prüfen ob nach dem erwarteten Wert noch ungültige Zeichen kommen.
bewirkt nur dass die Variablen bei jeklichem Fehler einfach bei 0 bleiben und weiter gemacht wird.
Du mußt schon prüfen ob überhaupt etwas eingelesen wurde. Scheinbar hast du das nicht gemacht.
Wie ich einlesen will:
- es erscheint ein Name (der Variablen) in der Promt und danach der Cursor.
Das dürfte recht einfach sein.
- Der User giebt in die gleiche Zeile einen Double-wert ein und drückt Enter
Dann lies die Zeile mit getline aus und verabeite diese mit einem istringstream.
- Falls es kein komplett absolut korrekter double-Wert war, erscheint ne Fehlermeldung und in der Zeile drunter wieder Name + Cursor
s.o.
- Wenn alles gut ging gets genau so mit der nächsten Variablen weiter.
Schreibe dir eine Funktion die du mit den verschiedenen Variablen aufrufen kannst.

Gruß
 
Hallo deepthroat,

ne extra Funktion und getline() waren ne sehr gute Idee.

Ich hab sie auch noch in eine Header-Datei ausgelagert, und will noch Funktionen für andere Datentypen hinzufügen.
der Code:
C++:
#ifndef DOUBLE_IO_H
#define DOUBLE_IO_H

#include <iostream>
#include <string>
#include <sstream>
#include <stdlib.h>
#include <stdexcept>

// Name zur Anzeige bei Eingabeaufforderung
// Variable wird nicht-konstant übergeben (per Referenz), aber auch zurückgegeben
double& readVar(const std::string& name, double& var) {
	bool korrekt = false;

	while(!korrekt){
		std::cout << name << ": ";
		std::string s;
		std::getline(std::cin, s);
		bool alleZeichenZahlen = true, erstesKomma = true;
		// Im string sind erlaubt...
		for(int i=0; i<s.length(); i++){
			// ... Zahlen
			if(isdigit(s[i])){
				continue;
			}
			// ... 1 x ',' außer ganz am Anfang oder ganz am Ende
			else if(s[i] == ',' && erstesKomma && i > 0 && i < s.length()-1){
				s[i] = '.';			// atof() akzeptiert nur Punkt als Dezimaltrennzeichen
				erstesKomma = false;
				continue;
			}
			else{
				alleZeichenZahlen = false;
				break;
			}
		}
		if(alleZeichenZahlen){
			var = atof(s.c_str());
			korrekt = true;
		}
		else{
			std::cerr<< "*** Bitte geben Sie eine gueltige Dezimalzahl ein! ***" << std::endl;
		}
	}

	return var;
}

// Nur Name erforderlich, günstig wenn zuvor keine Variable vorliegt, so wird der Code kürzer   
double& readVar(const std::string& name) {
	double d;	// Zwar ne lokale variable, die per Referenz übergeben wird, funktioniert aber irgendwie
	return readVar(name, d);
}

// Name und
// double-Variable zur Ausgabe
void writeVar(const std::string& name, const double& var) {
	std::ostringstream oss;
	oss << var;
	std::string s = oss.str();
	// Bei ganzen Zahlen
	try{
		s.replace(s.find('.'), 1, ",");	// Komma als Dezimaltrennzeichen
	}
	catch(std::out_of_range ex){
	}
	std::cout << name << ": " << s << std::endl;
}

#endif

Das mit Komma statt Punkt hab ich jetzt manuel gelöst, war kaum mehr aufwand und damit erspaare ich mir das ganze mit locals und ihrer portierung.
Meine Frage: In Zeile 51 übergebe ich ne Referenz auf eine nicht-globale Variable, eigentlich doch verboten, aber hier ist es sehr praktisch und funktioniert auch einwandfrei. Kann ich das dann so lassen?

An sonsten wäre da noch die Sache mit den Umlauten, es geht lediglich um einige statische Strings wie "gültig" im oberen Listing \u00FC bewikt beim mingw32-Cross-Compiler leider auch nichts, irgend ne Idee?

Über Vorschläge zu clearscreen und Programm Beenden per ESC freue ich mich natürlich weterhin,
javaDeveloper2011
 
Zuletzt bearbeitet von einem Moderator:
Hallo,

Das mit der Referenz in Zeile 51 halte ich für keine gute Idee, da dies nur solange funktioniert bis der Speicherbereich in der die lokale Variable lag überschrieben wird. Eigentlich ist es hier aber auch nicht notwendig eine Referenz zurück zu geben. Du kannst einfach einen double zurückgeben, dann hast du das Problem nicht.
 
Hi,

hast recht, ich hatte nicht mehr dran gedacht, dass der Compiler trotzdem je nach Parameterzahl die richtige überladene Funktion raussucht, auch wenn ein Mal ein Wert und das andere Mal ne Referenz zurückgegeben wird.

Gruß
 
Hi.

Da sind noch einige Fehler drin.

Insbesondere prüfst du immer noch nicht ob überhaupt etwas eingelesen wurde. Falls cin in einem Fehlerzustand ist, hast du eine Endlosschleife.

Und ich sehe nirgendwo das du auch Gleitkommazahlen in wissenschaftlicher Notation und neg. Zahlen akzeptieren würdest.

\edit:
An sonsten wäre da noch die Sache mit den Umlauten, es geht lediglich um einige statische Strings wie "gültig" im oberen Listing \u00FC bewikt beim mingw32-Cross-Compiler leider auch nichts, irgend ne Idee?
Du müßtest dem GCC erstmal mitteilen welcher Zeichensatz zur Laufzeit verwendet werden soll. (-fexec-charset)

Wenn du das Programm unter einem deutschen Windows ausführst, ist der aktive Zeichensatz in der Konsole cp850. Das kannst du z.B. mit "chcp 65001" zu UTF-8 ändern (bzw. SetConsoleOutputCP). Du mußt aber noch die Schriftart der Konsole z.B. auf Lucida umstellen (damit Unicode Zeichen auch angezeigt werden können).

Du kannst auch den Eingabezeichensatz (Encoding deiner Datei) angeben (-finput-charset), dann mußt du nicht Escapesequenzen in Stringliteralen benutzen.

Gruß
 
Zuletzt bearbeitet:
Hi,

Ich prüfe jetzt auch noch ob überhaupt etwas eingegeben wurde, wenn eine gültige Zahl eingegeben wurde, aber es Technisch ein Problem giebt, hilft es mit ja auch nichts zur nächsten Variablen weiter zu gehen. Im Hauptprogramm, dass den Header benutzt gibts sowieso ne Englosschleife, und das ist so gewollt.

Dass extrem große bzw. kleine Zahlen (die mit e drinnen) und negative nicht erlaubt sind finde ich gerade wunderbar, denn die meisten meiner Programme können sowieso nicht sinfoll mit ihnen arbeiten, so entfällt die nachträglich Prüfung auf > 0.

Bevor es wieder Probleme mit der Portierung, ect. giebt lasse ich die Paar umlaute lieber als ue, oe, ae, ... stehen, trotzdem Danke. Wahrscheinlich probiere ich es irgendwann nochmal.

javaDeveloper2011
 
Zurück