Kapitel: Mein erstes Programm

Was können wir schon? Ein Taschenrechner

Du hast in Was ist die Konsole angefangen und bist über Variablen, Datentypen, Vergleiche, Bedingungen und Schleifen bis hierher gekommen. Zeit, das alles einmal zusammen in einem echten kleinen Programm einzusetzen: einem Taschenrechner.

Der Plan

Unser Taschenrechner soll:

  1. nach einer ersten Zahl a fragen,
  2. nach einem Rechenzeichen (+, -, * oder /) fragen,
  3. nach einer zweiten Zahl b fragen,
  4. das Ergebnis berechnen und ausgeben,
  5. fragen, ob wir noch eine Berechnung machen wollen.

Bei den ersten drei Punkten müssen wir jeweils prüfen, ob die Eingabe überhaupt gültig ist - und falls nicht, erneut fragen. Genau dafür nutzen wir do-while, das du schon aus Wiederholungen kennst.

Wie schon in den vorherigen Kapiteln gehen wir weiterhin davon aus, dass immer mindestens ein Zeichen eingegeben wird - eine komplett leere Eingabe (nur Enter drücken) würde unsere einfachen Prüfungen zum Absturz bringen. Richtiges Abfangen solcher Fälle kommt in einem späteren Kapitel.

Schritt 1: Zahl a einlesen

Vorher noch eine kleine Änderung gegenüber den bisherigen Beispielen: a und b deklarieren wir diesmal nicht in Main, sondern direkt in der Klasse - also im Klassenscope. Damit sind sie in der ganzen Klasse sichtbar, nicht nur innerhalb von Main. Ein guter Anlass, sich den Scope-Begriff aus Variablen nochmal in Erinnerung zu rufen:

internal class Program
{
    static double a;
    static double b;

    static void Main(string[] args)
    {
        // ...
    }
}
static bei a und b ist hier Pflicht: Main selbst ist static (das steckt in jedem C#-Programm ganz am Anfang, auch wenn wir es bisher nie erklärt haben). Eine static-Methode kann direkt nur auf static-Mitglieder der Klasse zugreifen - wären a und b nicht static, könnte Main gar nicht auf sie zugreifen. Dir fällt vielleicht auch auf, dass kein private davorsteht: Ein Klassenmitglied ohne ausdrücklichen Zugriffsmodifizierer ist in C# automatisch private - wir hätten also genauso private static double a; schreiben können.
Das ist kein Vorschlag, wie du das immer machen solltest - normalerweise bleiben a und b als ganz normale lokale Variablen in Main deutlich passender. Wir verschieben sie hier bewusst nur, um den Klassenscope einmal konkret zu sehen.

Jetzt lesen wir a genauso ein wie vorher - nur ohne sie neu zu deklarieren (kein double mehr davor), wir weisen ihr nur noch einen Wert zu:

string eingabeA;
bool istZahlA;

do
{
    Console.Write("Zahl a: ");
    eingabeA = Console.ReadLine();
    istZahlA = char.IsDigit(eingabeA[0]);

    if (istZahlA == false)
    {
        Console.WriteLine("Das ist keine Zahl, versuch es nochmal.");
    }
}
while (istZahlA == false);

a = double.Parse(eingabeA);
double.Parse funktioniert genauso wie int.Parse, das du im Kapitel Konsole kennengelernt hast - nur dass dabei eine Kommazahl (double) statt einer Ganzzahl (int) herauskommt. Wir verwenden hier double, damit später auch eine Division mit Rest ein sinnvolles Ergebnis liefert.

Schritt 2: Rechenzeichen einlesen

Beim Rechenzeichen reicht "ist es eine Ziffer" nicht - wir müssen prüfen, ob es genau eines von +, -, * oder / ist. Dafür nutzen wir switch - und einen kleinen Trick, den du dort noch nicht gesehen hast: Mehrere case-Zeilen können sich ohne break dazwischen denselben Block teilen.

char operatorZeichen;
bool istGueltig;

do
{
    Console.Write("Rechenzeichen (+ - * /): ");
    operatorZeichen = Console.ReadLine()[0];

    switch (operatorZeichen)
    {
        case '+':
        case '-':
        case '*':
        case '/':
            istGueltig = true;
            break;
        default:
            istGueltig = false;
            Console.WriteLine("Unbekanntes Rechenzeichen, versuch es nochmal.");
            break;
    }
}
while (istGueltig == false);
case '+': case '-': case '*': case '/': stehen hier ohne eigenen Block direkt hintereinander. Trifft einer davon zu, fällt C# einfach zum nächsten Code durch, bis ein break kommt - dadurch führen alle vier Fälle denselben Block aus (istGueltig = true;).
Console.ReadLine()[0] greift direkt auf das erste Zeichen der Eingabe zu, ohne sie erst in einer eigenen Variable zwischenzuspeichern - genau wie eingabeA[0] eben.

Schritt 3: Zahl b einlesen

Genau das gleiche Vorgehen wie bei Zahl a - auch b bekommt nur einen Wert zugewiesen, keine neue Deklaration:

string eingabeB;
bool istZahlB;

do
{
    Console.Write("Zahl b: ");
    eingabeB = Console.ReadLine();
    istZahlB = char.IsDigit(eingabeB[0]);

    if (istZahlB == false)
    {
        Console.WriteLine("Das ist keine Zahl, versuch es nochmal.");
    }
}
while (istZahlB == false);

b = double.Parse(eingabeB);

Schritt 4: Berechnen

Jetzt wissen wir sicher, dass a und b Zahlen sind und operatorZeichen eines von + - * / ist. Deshalb brauchen wir diesmal keinen default-Fall mehr - wir haben ja schon geprüft, dass nur diese vier Fälle vorkommen können:

double ergebnis = 0;

switch (operatorZeichen)
{
    case '+':
        ergebnis = a + b;
        break;
    case '-':
        ergebnis = a - b;
        break;
    case '*':
        ergebnis = a * b;
        break;
    case '/':
        ergebnis = a / b;
        break;
}

Console.WriteLine(a + " " + operatorZeichen + " " + b + " = " + ergebnis);
Division durch 0.0 stürzt bei double übrigens nicht ab, sondern liefert den speziellen Wert Infinity. Probier es ruhig mal aus!

Schritt 5: Nochmal?

Zum Schluss verpacken wir den kompletten Ablauf von oben in eine ganz normale while-Schleife, gesteuert über eine eigene bool-Variable:

bool repeat = true;

while (repeat)
{
    // ... die Schritte 1 bis 4 von oben ...

    Console.Write("Nochmal? (j/n) ");
    string weiter = Console.ReadLine();

    if (weiter != "j")
    {
        repeat = false;
    }
}
repeat startet als true, damit die Schleife auf jeden Fall einmal läuft - das hätten wir hier auch mit do-while lösen können. Bewusst siehst du hier aber die zweite Möglichkeit: eine eigene bool-Variable, die wir am Ende manuell auf false setzen, sobald Schluss sein soll.
Kleine Randbemerkung: Lokale Variablen in Main (wie repeat, eingabeA oder operatorZeichen) bekommen keinen Modifizierer wie private davor - private gehört zu Feldern einer Klasse, nicht zu Variablen innerhalb einer Methode. Genau das sind ja a und b von vorhin: echte Klassenfelder, bei denen private automatisch gilt, auch ohne es hinzuschreiben.

Das komplette Programm

Und so sieht alles zusammengesetzt aus - diesmal inklusive Klasse und den beiden Feldern a und b:

namespace ConsoleApp1
{
    internal class Program
    {
        static double a;
        static double b;

        static void Main(string[] args)
        {
            bool repeat = true;

            while (repeat)
            {
                string eingabeA;
                bool istZahlA;

                do
                {
                    Console.Write("Zahl a: ");
                    eingabeA = Console.ReadLine();
                    istZahlA = char.IsDigit(eingabeA[0]);

                    if (istZahlA == false)
                    {
                        Console.WriteLine("Das ist keine Zahl, versuch es nochmal.");
                    }
                }
                while (istZahlA == false);

                a = double.Parse(eingabeA);

                char operatorZeichen;
                bool istGueltig;

                do
                {
                    Console.Write("Rechenzeichen (+ - * /): ");
                    operatorZeichen = Console.ReadLine()[0];

                    switch (operatorZeichen)
                    {
                        case '+':
                        case '-':
                        case '*':
                        case '/':
                            istGueltig = true;
                            break;
                        default:
                            istGueltig = false;
                            Console.WriteLine("Unbekanntes Rechenzeichen, versuch es nochmal.");
                            break;
                    }
                }
                while (istGueltig == false);

                string eingabeB;
                bool istZahlB;

                do
                {
                    Console.Write("Zahl b: ");
                    eingabeB = Console.ReadLine();
                    istZahlB = char.IsDigit(eingabeB[0]);

                    if (istZahlB == false)
                    {
                        Console.WriteLine("Das ist keine Zahl, versuch es nochmal.");
                    }
                }
                while (istZahlB == false);

                b = double.Parse(eingabeB);

                double ergebnis = 0;

                switch (operatorZeichen)
                {
                    case '+':
                        ergebnis = a + b;
                        break;
                    case '-':
                        ergebnis = a - b;
                        break;
                    case '*':
                        ergebnis = a * b;
                        break;
                    case '/':
                        ergebnis = a / b;
                        break;
                }

                Console.WriteLine(a + " " + operatorZeichen + " " + b + " = " + ergebnis);

                Console.Write("Nochmal? (j/n) ");
                string weiter = Console.ReadLine();

                if (weiter != "j")
                {
                    repeat = false;
                }
            }
        }
    }
}

Kurz zusammengefasst: Mit diesem einen Programm hast du praktisch alles verwendet, was du bisher gelernt hast - Variablen und Datentypen, Console.Write und Console.ReadLine, Vergleiche, if/else und switch, sowie while- und do-while-Schleifen. Genau das ist Programmieren: viele kleine, einzelne Bausteine zu etwas Grösserem zusammensetzen.