Iowana: C-Programmierung: conio.h unter Linux für getch()

Hi Forum,

in der Schule haben wir heute angefangen, C zu lernen. Leider ist das alles auf Windows zugeschnitten. Hier zuhause, ich besitze nur Linux und kaufe mir kein Windows, funktioniert schon unser erstes Hello-World-Programm nicht:

  
#include <stdio.h>  
#include <conio.h>  
  
main()  
{  
 printf("Hello\nWorld!");  
 getch();  
}  

Der gcc motzt, es gäbe keine conio.h. Tja, der muss es wissen, also habe ich im Internet nach der Linux-Variante von conio.h geschaut, fand immer nur "ncurses". Ist das die richtige Header-Datei für getch()?
Und wie muss ich das dann im inlude-Befehl schreiben?

#include <ncurses>
#include <ncurses.h>

findet gcc nicht.

Danke
Iowana

  1. Hallo.

    Der erste Google-Treffer zu 'conio.h' sagt:

    "MS-DOS spezifische Funktionen in conio.h Programme, die darauf aufbauen, sind nicht portabel, da conio.h nicht zur ANSI-Standardbibliothek gehört und z.B. auf einer Sun oder in Linux nicht verfügbar ist."

    Ich würde dir empfehlen - soweit möglich - die MS-spezifischen Funktionen durch entsprechende aus der C Standardbibliothek zu ersetzen. Das in stdio.h deklarierte Äquivalent zu getch() heißt beispielsweise getchar().

    Ansonsten steht dir unter Linux auch WINE zur Verfügung, das genau das leisten dürfte, was du suchst (womit ich aber keinerlei Erfahrung habe) .

    Gruß
     Christoph

  2. hallo,

    #include <stdio.h>
    #include <conio.h>

    main()
    {
    printf("Hello\nWorld!");
    getch();
    }

      
    Christoph hat dir ja schon die Antwort gegeben. Ich frage mich allmählich, ob es in den Schulen Gang und Gebe ist, schlampigen Code zu lehren, da ich selbst damit konfrontiert war.  
    Normalerweise sollte das Programm die erfolreiche Ausführung des Programms an das Betriebssystems zurückliefern, also 0 zurückliefern und <conio.h> sollte, wie bereits angedeutet, aus Kompatibilitätsgründen nicht verwendet werden. Das Programm würde dann in etwa so aussehen:  
      
    ~~~c
      
    #include <stdio.h>  
      
    int main(void)   {  
        printf("Hello\nWorld!");  
        getchar();  
      
      return 0;  
    }  
    
    

    Markus.

    --
    http://www.apostrophitis.at
    六 7東曲 人港ラ
    1. Hallo Markus,

      Das Programm würde dann in etwa so aussehen:

      getchar() ist keine Alternative zu getch(), da es etwas anderes bewirkt. Siehe dazu auch mein anderes Posting.

      Viele Grüße,
      Christian

      --
      "I have always wished for my computer to be as easy to use as my telephone; my wish has come true because I can no longer figure out how to use my telephone." - Bjarne Stroustrup
  3. Hallo,

    in der Schule haben wir heute angefangen, C zu lernen. Leider ist das alles auf Windows zugeschnitten. Hier zuhause, ich besitze nur Linux und kaufe mir kein Windows, funktioniert schon unser erstes Hello-World-Programm nicht:

    #include <stdio.h>
    #include <conio.h>

    main()
    {
    printf("Hello\nWorld!");
    getch();
    }

      
    Du solltest Deinen Lehrer übrigens schlagen, dass er schlechten Stil lehrt. Zumindest einen Rückgabetyp sollte er der Main-Funktion geben (und am besten gleich auch noch einen Rückgabewert) - und die Parameter der Funktion (selbst wenn sie nicht verwendet werden) sollten durchaus explizit angegeben werden, d.h. besserer Stil wäre:  
      
    ~~~c
    #include <stdio.h>  
    // sonstiger Code (siehe unten)  
      
    int main (int argc, char **argv) {  
      printf ("Hello\nWorld!");  
      getch();  
      return 0;  
    }
    

    Der gcc motzt, es gäbe keine conio.h. Tja, der muss es wissen, also habe ich im Internet nach der Linux-Variante von conio.h geschaut, fand immer nur "ncurses". Ist das die richtige Header-Datei für getch()?

    Es gibt unter Linux keine Funktion getch(). getch() macht unter DOS ja nichts anderes, als (ohne das Zeichen auf die Konsole auszugeben) auf die eingabe eines beliebigen Zeichens zu warten. Dafür gibt's unter ANSI C keine Funktion - d.h. portabel bekommst Du das nicht hin.

    getchar() (ANSI C, eingegebene Zeichen werden angezeigt) dagegen reagiert unter Linux jedoch so, dass es erst ein Zeichen zurückliefert, sobald vom Terminal Zeichen gekommen sind - das ist *nicht* notwendigerweise dann, sobald Zeichen eingegeben werden. Im Normalfall ist das Terminal nämlich im zeilenbasierten Modus - d.h. das Programm bekommt die Eingabe erst, sobald eine Zeile komplett eingegeben wurde.

    Wenn Du unter Linux die Funktionalität von getch() nutzen willst, musst Du folgendes tun:

    1. Den Zeilenmodus des Terminals deaktivieren.
    2. Den Echo-Modus (automatische Ausgabe von eingegebenen Zeichen) des Terminals deaktivieren
    3. Das Zeichen per getchar() einlesen
    4. Den Echo-Modus wieder aktivieren
    5. Den Zeilenmodus wieder aktivieren

    Da das ganze ne ganze Menge mit POSIX-Systemprogrammierung zu tun hat und zum C-Lernen eigentlich ein völlig ungeeigneter Start ist, habe ich die Funktion getch() mal kurzfristig für POSIX-Betriebsysteme (hab's allerdings nur unter Linux getestet - nicht schlagen, wenn's unter FreeBSD o.ä. knallt) nachprogrammiert, wenn Du das in Dein Hello-World-Programm reinkopierst oder in eine externe .c-Datei kopierst und dann dagegen linkst, wird Dein Hello-World-Programm genauso funktionieren, wie unter Windows.

    #include <stdio.h>  
    #include <termios.h>  
    #include <unistd.h>  
    #include <string.h>  
      
    // Nachimplementierung der getch()-Funktion aus DOS-conio.h  
    // für POSIX-Betriebsysteme  
    // Lizenz: Public domain  
    int getch () {  
     int ch;  
     struct termios old_t, tmp_t;  
      
     // alte Attribute holen  
     if (tcgetattr (STDIN_FILENO, &old_t)) {  
      // moeglicherweise kein Terminal  
      // moeglicherweise anderer Fehler  
      return -1;  
     }  
     // struktur kopieren  
     memcpy (&tmp_t, &old_t, sizeof (old_t));  
     // Zeilenmodus sowie Spezialzeichen deaktivieren,  
     // Bildschirmausgabe von eingegebenen Zeichen  
     // deaktivieren  
     tmp_t.c_lflag &= ~ICANON & ~ECHO;  
     // Neue Flags setzen  
     if (tcsetattr (STDIN_FILENO, TCSANOW, (const struct termios *)&tmp_t)) {  
      // moeglicherweise irgend ein Fehler  
      return -1;  
     }  
     // zeichen einlesen (darauf verlassen,  
     // STDIN_FILENO blockierend ist)  
     ch = getchar ();  
     // Alte Flags zuruecksetzen  
     // Rueckgabewert ignorieren, da das Terminal bei Fehler sowieso  
     // nicht korrekt reagieren wird, wir dagegen aber nichts tun  
     // koennen  
     tcsetattr (STDIN_FILENO, TCSANOW, (const struct termios *)&old_t);  
     // Zeichen zurueckgeben  
     return ch;  
    }
    

    Viele Grüße,
    Christian

    --
    "I have always wished for my computer to be as easy to use as my telephone; my wish has come true because I can no longer figure out how to use my telephone." - Bjarne Stroustrup
    1. [...] die Parameter der Funktion (selbst wenn sie nicht verwendet werden) sollten durchaus explizit angegeben werden, d.h. besserer Stil wäre:
      [...]
      int main (int argc, char **argv)
      [...]

      Noch besserer Stil wäre es meiner unmaßgeblichen Meinung nach, die Parameter ausdrücklich als leer anzugeben, wenn man sie gar nicht benutzt, also:

      int main(void)

      Aber wie gesagt, nur meine Meinung.

      Def

  4. Nochmal ich.

    Christian hat natürlich recht: getchar() wird in der Regel so implementiert, dass erst nach Eingabe eines Zeilenumbruchs ein Wert geliefert wird.

    Das Konzept des Tastendrucks gibt es in ANSI C nicht, daher ist auch das klassische "press any key to continue" unmöglich (das getch() im Hallo-Welt-Beispiel sollte genau dazu dienen: das Programm wird erst nach Nutzereingabe beendet, was beispielsweise verhindert, dass sich das Konsolenfenster nach Ausführung sofort wieder schließt).

    Neben Christians Lösungsvorschlag wäre auch folgender Workaround denkbar: In stdlib.h gibt es die Funktion system(char*), die es ermöglicht, Befehle an die Shell zu senden.
    Unter Windows kann man dann system("pause"); verwenden, unter Linux müsste etwas wie "read -n 1" funktionieren - ob das auch eleganter geht, weiß ich nicht. Die Portabilität ist dann natürlich wieder futsch!

    Ansonsten warst du mit Ncurses auf der richtigen Spur - ein #include <curses.h> hätte zum Ziel führen sollen, deinem gcc ist aber anscheinend die entsprechende Bibliothek unbekannt.

    Falls du sie selbst bauen möchtest: http://ftp.gnu.org/pub/gnu/ncurses/
    Andernfalls rät die Doku, für RPMs bei http://rpmfind.net/ zu suchen...

    Gruß
     Christoph

    1. Ähem...

      [...] ein #include <curses.h> hätte zum Ziel führen sollen [...]

      sollte latürnich #include <ncurses.h> heißen...

      [x] Ja, ich habe die Vorschau benutzt!

      PS: bei Verwendung von Ncurses musst du dem Linker die einzubindende Bibliothek bekanntgeben (beim gcc z.B. mit -lncurses)

    2. Hallo Christoph,

      Ansonsten warst du mit Ncurses auf der richtigen Spur - ein #include <curses.h> hätte zum Ziel führen sollen, deinem gcc ist aber anscheinend die entsprechende Bibliothek unbekannt.

      Meinst Du nicht, ncurses ist für einen C-Anfänger etwas Overkill? Vor allem, wenn's bloß um die Funktionalität von getch() geht? In ncurses gibt es nämlich eine ganze Menge Konzepte, die man erst verstehen muss, bevor man die Bibliothek korrekt verwenden kann - einem Anfänger würde ich eher nicht raten, sich damit auseinanderzusetzen.

      Viele Grüße,
      Christian

      --
      "I have always wished for my computer to be as easy to use as my telephone; my wish has come true because I can no longer figure out how to use my telephone." - Bjarne Stroustrup
      1. Hallo Christian.

        Meinst Du nicht, ncurses ist für einen C-Anfänger etwas Overkill? Vor allem, wenn's bloß um die Funktionalität von getch() geht?

        Sicher, nur für getch() gehört die Verwendung von Ncurses wohl eher zur Kategorie Kanonen-und-Spatzen. Irgendwann wird sich Iowana aber mit dem Einbinden externer Bibliotheken auseinander setzen müssen, warum also nicht jetzt?
        Und wenn man sich an dem HOWTO entlanghangelt, sollte es einem motivierten Anfänger möglich sein, schnell erste Ergebnisse (bunte Schrift z.B.) zu produzieren.

        In ncurses gibt es nämlich eine ganze Menge Konzepte, die man erst verstehen muss, bevor man die Bibliothek korrekt verwenden kann - einem Anfänger würde ich eher nicht raten, sich damit auseinanderzusetzen.

        Aus persönlichem Interesse: Was würdest Du denn einem C-Anfänger unter Linux empfehlen, dem die Stream-basierten Ein- und Ausgabemöglichkeiten von ANSI-C zu 'langweilig' sind? Ncurses ist sicherlich kein QBASIC, aber Spielereien wie Hangman und später dann vielleicht Game-Of-Life oder ähnliches sollten doch relativ einfach zu realisieren sein(?).

        Gruß
         Christoph

        1. Hallo Christoph,

          In ncurses gibt es nämlich eine ganze Menge Konzepte, die man erst verstehen muss, bevor man die Bibliothek korrekt verwenden kann - einem Anfänger würde ich eher nicht raten, sich damit auseinanderzusetzen.

          Aus persönlichem Interesse: Was würdest Du denn einem C-Anfänger unter Linux empfehlen, dem die Stream-basierten Ein- und Ausgabemöglichkeiten von ANSI-C zu 'langweilig' sind?

          Ganz ehrlich: Ich kenne da nichts. Ncurses ist kompliziert, aber etwas ähnlich einfach zu verwendendes wie das QBasic-Zeugs kenne ich nicht.

          Ncurses ist sicherlich kein QBASIC, aber Spielereien wie Hangman und später dann vielleicht Game-Of-Life oder ähnliches sollten doch relativ einfach zu realisieren sein(?).

          Meines Wissens nicht - zumindest nicht so einfach, wie QBasic es einem macht.

          Viele Grüße,
          Christian

          --
          "I have always wished for my computer to be as easy to use as my telephone; my wish has come true because I can no longer figure out how to use my telephone." - Bjarne Stroustrup