Konsolen-Input-Stream



  • Es gibt mit cin und der Win32-Konsole eine Menge Probleme, ich liste ein paar auf:

    • Man benötigt getline() um mehr als ein Wort einlesen zu können.
    • Der User kann immer beliebig viele Zeichen eingeben und erst im Programm werden sie abgeschnitten.
    • Der User kann wenn ein char erwünscht auch eine beliebig lange Zahl eingeben - das kann zu Absürzen führen.
    • Der User kann für den Zahlentyp zu große Zahlen eingeben.
    • Eingaben ohne Enter sind nicht möglich.
    • Maus, und andere Eingaben können nicht abgefragt werden.

    Wenn man also ein ordentliches Konsolenprogramm schreiben will, muss man sich jedesmal die gewünschten Funktionen mit der WinAPI schreiben.

    Da kam mir die Idee einen Konsolen-Input-Stream (oder zumindest einen Konsolen-Input-Puffer, der wird wahrscheinlich reichen) zu programmieren. Die Methoden sollen alle gleich einem basic_istream bzw. einem basic_streambuf sein. Allerdings erledigen wir intern das Einlesen der Zeichen mit der WinAPI.

    Mit dem Einlesen komme ich schon selbst klar. Aber mit dem internen Aufbau von Input-Puffern und Streams aus der STL kenn ich mich nicht besonders aus.

    Also suche ich einen oder zwei Mitstreiter für dieses Projekt, die sich mit der STL genauer auskennen. Wenn jemand glaubt, dass er die Win-Konsolen-API unglaublich gut beherrscht kann er sich ebenfalls melden.

    Die Sprache ist wie wahrscheinlich schon bemerkt C++.

    Compiler ist egal, ich benütze MSVC6 - zu dem soll es dann auch kompatibel sein. Wenn wir mehrere Compiler im Projekt benützen hilft uns das vielleicht sogar, da der MSVC6 offensichtlich dazu neigt, Dinge zu erlauben die andere Compiler nicht erlauben.

    Bei Interesse oder Vorschlägen hier melden :).

    MfG SideWinder



  • Hallo,
    wenn ich das richtig sehe, dann willst du Eingaben alla getch haben. Das passt aber absolut nicht zu dem Konzept eines Stroms und damit auch nicht hinter die >>-Schnittstelle.

    Wie genau stellst du dir das ganze denn vor?



  • Ja, das soll ein getch() mit Typenüberprüfung werden. Also wenn ein unsigned short int gewünscht wird, kann man nur integer eingeben und auch nur so große Zahlen wie mit einem unsigned short int möglich sind.

    Da hatte ich mir eigentlich schon ein op>>-Konzept gedacht. Warum das kein Strom ist, weis ich nicht 🙄. Aber wenn du es sagst...

    Also ich hatte vor, meinen Stream (sofern er jetzt überhaupt einer ist) möglichst an basic_istream anzugleichen -> der Benützer muss von cin nicht mehr umdenken.

    Die Implementieurng sollte eben anders sein: Operatoren überprüfen eben auf die oben genannten Probleme. Auch Maus soll implementiert werden.

    Hier dann mal ein kleines Ideenscript (mögliche Überprüfung auf unsigned short int):

    klasse & klasse::operator >> ( unsigned short int & Objekt )
    {
        // Wenns ne Template-Klasse wie basic_istream ist, dann auch string anpassen:
        std::basic_string<Char,Traits> ReadAll;
        Char ReadNow ( '\0' );
    
        while ( ( ReadNow = VOM_PUFFER_ZEICHEN_HOLEN () ) && ( ReadNow != '\r' )
        {
            if ( IST DIE EINGABE OKAY? ) 
            {
                ReadAll += ReadNow;
                AUSGABE
            }
            else if ( ReadNow == '\b' )
            {
                STRING UM EINS ZURÜCKSETZEN
                ZEICHEN WIEDER LÖSCHEN
            }        
        }
    
        STRING IN EINEN UNSIGNED SHORT INT UMWANDELN UND IN OBJEKT SPEICHERN    
    }
    

    Also ungefähr so wie ReadDigits() aus der Konsolen-FAQ nur eben für alle Typen und möglichst angepasst - auch für char und Maus soll was dar sein.

    MfG SideWinder

    [ Dieser Beitrag wurde am 09.07.2002 um 18:03 Uhr von SideWinder editiert. ]



  • BTW: Der Puffer soll auch abgefragt werden können auf bestimmte Eingaben - auf Deutsch, er soll kbhit() ersetzen können.

    MfG SideWinder



  • Hallo,
    was du machen willst geht nur, wenn du die einzelnen >>-Operatoren eingenhändig implementierst (hast du ja schon angedeutet). Mit einem Puffer allein ist dir da nicht geholfen.

    Da die I/O-Operatoren nicht virtuell sind und da du das iostream-Puffer-Konzept für deine Wünsche nicht verwenden kannst, musst du dir letztlich wohl sowieso komplett neue Klassen bauen.



  • @Hume: Danke, erstmal.

    Wenn ich also jetzt meine eigene Klasse bauen will - ganz unabhängig von basic_istream. Soll ich sie wenigstens von der Struktur ähnlich (op>>, get, getline, tellg, etc.) bauen oder soll ich ganz auf das Konzept ganz verzichten?

    Soll ich Puffer und "Stream" (in welchem Sinn auch immer) wiederrum trennen oder soll ich gleich bei einer Klasse bleiben?

    Kann man noch so Dinge wie char_traits benützen oder schließen sich die dann auch aus?

    Wenn mir auch noch diese Fragen beantwortet werden, beginne ich mal mit ein bisschen Code und experimentieren.

    MfG SideWinder



  • Wenn ich also jetzt meine eigene Klasse bauen will - ganz unabhängig von basic_istream. Soll ich sie wenigstens von der Struktur ähnlich (op>>, get, getline, tellg, etc.) bauen oder soll ich ganz auf das Konzept ganz verzichten?

    Das kommt darauf an, was du willst. Wenn du es Leuten die an cin, cout gewöhnt sind besonders einfach machen willst, dann wäre gut wenn deine Klassen ein iostream-Interface hätten.

    Soll ich Puffer und "Stream" (in welchem Sinn auch immer) wiederrum trennen oder soll ich gleich bei einer Klasse bleiben?

    Ich würde schon Puffer (also die low-level-Klasse die für das Lesen und Schreiben von Bytes zuständig ist) und Formatierlogik von einander trennen. Das bringt dir a) saubere Schnittstellen und b) gute Erweiter- bzw. Veränderbarkeit.
    Der Nachteil dieser Herangehensweise ist sicherlich, dass das ganze für dich (bzw. alle anderen Implementierer) etwas komplexer wird.

    Kann man noch so Dinge wie char_traits benützen

    Die Frage ist, wie sinnvoll diese char_traits überhaupt sind. Mal ehrlich. Der Normalsterbliche wird nicht mehr als zwei Varianten benötigen. Einmal simple char und einmal was UNICODE-mäßiges (also wahrscheinlich wchar_t).



  • Maus?
    Falls Maus, dann tendiert das ruck zuck zu lauter Längenbrgrenzen Eingabefeldern, Buttons, Fenstern, Menus und nem kleinen Nachrichtenverschickenden Framework.
    Wie wird das in so nem "stream" überhaupt mit Corsortasten laufen?
    Also ich mein sowas wie das Einlesen einer Burchzahl 355/113, wenn der user gerade in der 113 ist und ein paarmal BS drückt dann müßte ja die Einlesefunktion irgendwie vom Einlesen des Nenners zurückspringen auf Einlesen des Zählers.
    Das wird nicht so einfach.



  • @volkard: Keine Angst, es bleibt nur beim Maus-Input. Damit kann man dann brav die Leute auch mal klicken lassen - was imho nicht schlecht ist. Einlesen lässt es sich auch ganz leicht: ReadConsoleInput() und dann auf ein Mausevent warten.

    Also ich mein sowas wie das Einlesen einer Burchzahl 355/113, wenn der user gerade in der 113 ist und ein paarmal BS drückt dann müßte ja die Einlesefunktion irgendwie vom Einlesen des Nenners zurückspringen auf Einlesen des Zählers.

    Soll dann ungefähr so laufen, wie das einlesen von beliebig langen long doubles aus der Konsolen-FAQ (ReadDigits()). Für Brüche hab ich weniger übrig - wer seine Brüche-Klasse dann mit einem op>> überlädt muss sich darüber ärgern ;).

    @Hume:

    Das kommt darauf an, was du willst. Wenn du es Leuten die an cin, cout gewöhnt sind besonders einfach machen willst, dann wäre gut wenn deine Klassen ein iostream-Interface hätten.

    Dann werde ich möglichst das Interface beibehalten.

    Ich würde schon Puffer (also die low-level-Klasse die für das Lesen und Schreiben von Bytes zuständig ist) und Formatierlogik von einander trennen. Das bringt dir a) saubere Schnittstellen und b) gute Erweiter- bzw. Veränderbarkeit.
    Der Nachteil dieser Herangehensweise ist sicherlich, dass das ganze für dich (bzw. alle anderen Implementierer) etwas komplexer wird.

    Aber was soll bei mir da noch großartig editiert werden in der Formatierklasse? Der Puffer liest ein sorgt für die richtigen Ein- & Ausgaben. Was soll da die Klasse noch machen? Ich hab ja keine Manipulatoren, etc.

    Die Frage ist, wie sinnvoll diese char_traits überhaupt sind. Mal ehrlich. Der Normalsterbliche wird nicht mehr als zwei Varianten benötigen. Einmal simple char und einmal was UNICODE-mäßiges (also wahrscheinlich wchar_t).

    Dann verwende ich ganz einfach einen generischen Datentyp? TCHAR, wäre eine Möglichkeit - <windows.h> muss sowieso includiert werden.

    MfG SideWinder



  • Ich hab mich jetzt noch ein bisschen schlau gemacht - die Sache wird schwieriger als ich dachte. Die Aufteilung habe ich mir jetzt so vorgestellt:

    Formatklasse cinput (c hier für console und nicht als MS-Präfix) holt Zeichen vom Puffer und prüft sie. Sind sie in Ordnung können sie beruhigt dem Programm gegeben werden, ansonsten sind ihre Tage gezählt.

    Die Pufferklasse cbuffer hat leider etwas mehr zu tun bekommen. Wenn man im CIB nämlich bis zum nächsten Zeichen liest (was nicht unbedingt gleich am Anfang stehen muss), werden alle anderen Events (wie Maus, Fenster, oder Fokus) gelöscht. Damit gehen dem Programm aber vielleicht wichtige Daten verloren (im Falle der Maus auch der Formatklasse). Deswegen habe ich mir das so ausgemalt - ob es funktioniert müsst ihr mir sagen ;):

    Beim ersten Aufruf bleibt mir kein anderer Weg als den CIB bis zum ersten gewünschten Record zu untersuchen - ohne daraus zu lösche (also mit einer peek-Funktion). Alle Records die vorher im CIB sind werden in einen eigenen Puffer geladen (welche Datenstruktur ist den dafür geeignet? Liste, oder - immerhin muss schnell eingefügt und gelöscht werden können). Der gewünschte Rekord geht an die Formatklasse die daraus dann ihr Ding machen kann. Ein Flag wird gesetzt - für einen kleinen Geschwindigkeitsboni: Wenn ich gerade ein Zeichen gelesen habe steht im hauseigenen Puffer sicher keines drinnen. Falls direkt danach wieder ein Zeichen gelesen werden kann brauch ich den Puffer gar nicht mehr zu untersuchen und kann gleich mit dem CIB beginnen. Das Flag wird erst dann ersetzt, wenn ein bestimmter anderer Rekord abgefragt wird aber nicht mehr im hauseigenen Puffer steht.

    Ist zwar immer noch kompliziert, aber immerhin lässt sich das schon visualisieren.

    Die cinput-Klasse wird kein Template sondern eine ganz normale Klasse, damit nichts schief gehen kann mit den größen der Datentypen wird einfach der generische Typ TCHAR hergenommen. Mit ihm können auf bestimmten Systemen zwar immer noch Probleme auftauchen, aber alle Windows-System die überhaupt mit dieser Klasse zurechtkommen sind fürs erste abgedeckt.

    Die op>> werden überladen, allerdings hätte ich hierzu noch eine Frage: Benötige ich eine Überladung für signed, normal und unsigned? Normalerweise ja nur für normal=signed und unsigned. Bei einem normalen char aus C++, weis man aber nicht, ob man signed oder unsigned weglassen kann - falls TCHAR=char kann also einfach die Übergabe eines TCHARs zu Problemen führen, oder? Andererseits gibt es sicherlich auch Datentypen die kein unsigned kennen - was dann?

    Die Methoden get() und getline() werden eingefügt. get() ruft einfach op>>(char) auf und getline() kann nun auch mehrere Wörter lesen, zusätzlich wird es für std::string überladen.

    Was haltet ihr davon?

    Ich muss das Projekt jetzt leider schon am Beginn längere Zeit hinlegen, da ich schon morgen in den Urlaub wandere.

    MfG SideWinder



  • Original erstellt von SideWinder:
    Was haltet ihr davon?

    Bau lieber mit Marc++us' improved Console nen Snake-Clon.



  • Bau lieber mit Marc++us' improved Console nen Snake-Clon.

    Nein, will ich aber nicht :p. Ich verbessere meinen Käse einfach solange bis er soweit ist, um dir vorgestellt zu werden. Wir tauschen dann einfach dein OS mit Programmer85 gegen meinen Console-Input-Quatsch.

    MfG SideWinder

    Edit: Code doch noch rausgenommen - noch zu fehlerhaft 🙄.

    [ Dieser Beitrag wurde am 10.07.2002 um 20:53 Uhr von SideWinder editiert. ]


Anmelden zum Antworten