Arbeiten mit .NET: C-Sharp/ Arbeitsablauf/ Kontrollstrukturen/ Schleifen

Aus Wikibooks

Achtung, Baustelle! Dieses Buch wird zurzeit überarbeitet und in Arbeiten mit .NET eingegliedert.

Hinweise für Leser: Der Inhalt eines Kapitels passt unter Umständen nicht richtig zum Namen oder zur Gliederung. Gliederung und Inhaltsverzeichnis des Buches können vom Inhalt einzelner Kapitel abweichen. Verweise auf andere Kapitel können falsch sein.

Hinweis für Autoren: Bitte berücksichtigen Sie bereits jetzt die Konzeption der Buchreihe.

Schleifen wiederholen einen Teil des Programmcodes so lange, bis eine Abbruchbedingung eintritt. Und weil dieser Fall in der Programmierung so häufig auftritt, gibt es für alle erdenklichen Fälle spezielle Schleifen.

Wikipedia hat einen Artikel zum Thema:


Kopfgesteuerte Schleifen[Bearbeiten]

Diese Schleifen prüfen bevor sie den Schleifenrumpf das erste Mal durchlaufen, ob die Abbruchbedingung bereits eingetreten ist. Sie werden also mindestens 0 Mal und höchstens bis zum Eintreten der Abbruchbedingung durchlaufen:

public int Addiere(int zahl)
{
  int ergebnis = zahl;

  while (ergebnis < 100)
  {
    ergebnis += zahl;
  }

  return ergebnis;
}

Im Schleifenkopf haben wir eine Abbruchbedingung festgelegt. Diese besagt, dass solange zahl zum ergebnis addiert wird, bis ergebnis größer oder gleich 100 ist. Übergeben wir also die zahl 100, wird die Schleife nicht ein einziges Mal durchlaufen sondern gleich das ergebnis 100 zurück geliefert. Übergeben wir hingegen 99, wird die Schleife genau 1 Mal durchlaufen. Das zurück gegebene ergebnis lautet also 198.

Zählschleifen[Bearbeiten]

Zählschleifen sind ein Spezialfall von kopfgesteuerten Schleifen. Auch sie werden also mindestens 0 Mal und höchstens bis zum Eintreten der Abbruchbedingung durchlaufen:

public int Addiere(int zahl, int wieOft)
{
  int ergebnis = zahl;

  for (int i = 1; i <= wieOft; i++)
  {
    ergebnis += zahl;
  }

  return ergebnis;
}

Solange i kleiner oder gleich wieOft ist, wird diese Schleife durchlaufen.

Jede Zählschleife kann grundsätzlich durch eine entsprechende while-Schleife ersetzt werden. Das Beispiel oben würde, wenn man die Schleife als while-Schleife formulieren würde, so aussehen:

public int Addiere(int zahl, int wieOft)
{
  int ergebnis = zahl;
  
  int i = 1;
  while (i <= wieOft)
  {
    ergebnis += zahl;
    i++;
  }

  return ergebnis;
}

Weil man aber beim Programmieren sehr oft zählen muss, sind Zählschleifen eine sehr sinnvolle Sache, da sie denn Zweck des Codes (das Zählen) besser verdeutlichen.

Foreach[Bearbeiten]

Und weil man so häufig zählen muss, existiert noch eine weitere Variante von Zählschleifen: die foreach-Schleife. Angenommen, wir haben nun eine Tabelle mit 100 Datensätzen. Jetzt könnten wir natürlich die bereits bekannte Zählschleife verwenden:

for (int i = 1; i <= 100; i += 1)
{
  DataRow zeile = dataTable.Rows[i - 1]; // 0-basierter Index, 
                                         // die erste Zeile ist an Position 0.

  // Wir geben die Spalte "Name" der Tabelle aus.
  Console.WriteLine( zeile["Name"] );
}

Aber das wird kompliziert, wenn unsere Tabelle, wie es im echten Leben nun mal der Fall ist, ihre Länge ändert. Da werden Datensätze hinzugefügt und gelöscht, dass es die wahre Freude ist. Und jedesmal das Programm umzuschreiben ist dann doch sehr aufwändig. Natürlich könnten wir die Programmierung jetzt umständlicher machen:

// Wir holen uns erst die aktuelle Anzahl 
// der Zeilen in der Tabelle
int anzahl = dataTable.Rows.Count;

for (int i = 1; i <= anzahl; i += 1)
{
  DataRow zeile = dataTable.Rows[i - 1]; // 0-basierter Index, 
                                         // die erste Zeile ist an Position 0.

  // Wir geben die Spalte "Name" der Tabelle aus.
  Console.WriteLine( zeile["Name"] );
}

Wir können aber auch die foreach-Schleife verwenden:

foreach(DataRow zeile in dataTable.Rows)
{
  Console.WriteLine( zeile["Name"] );
}

Eine andere Möglichkeit könnte so aussehen:

foreach(string text in eindimensionaleListe)
{
  Console.WriteLine( text );
}

Oder so:

// Wir deklarieren ein neues 
// eindimensionales int-Array
// und füllen es gleich mit den Zahlen von 1-10. 
int[] liste = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

foreach(int zahl in liste)
{
  Console.WriteLine( zahl );
}

Die foreach-Schleife kümmert sich ganz allein darum, wann sie das Ende erreicht hat, oder ob der Index jetzt gerade 1-basiert oder 0-basiert ist. Und als typische, kopfgesteuerte Zählschleife prüft sie vor jedem Schleifendurchlauf, ob die Abbruchbedingung (Ende der Collection erreicht?) bereits eingetreten ist. Alles was wir machen müssen, ist, ihr mitzuteilen, welche Datentypen die Collection enthält und welche Collection wir verwenden wollen:

foreach([Datentyp] [Variable] in [Collection] { }

Warum wir den Datentyp extra mit angeben müssen? Denken wir an die Typumwandlung, können wir die eigentlich in der Collection enthaltenen Objekte über eine Typumwandlung ansprechen:

// Wir deklarieren ein neues 
// eindimensionales int-Array
// und füllen es gleich mit den Zahlen von 1-10. 
int[] liste = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

foreach(long zahl in liste)
{
  Console.WriteLine( zahl );
}

Fußgesteuerte Schleifen[Bearbeiten]

Diese Art Schleifen prüft nach jedem Durchlauf des Schleifenrumpfes, ob die Abbruchbedingung eingetreten ist. Sie werden also mindestens 1 Mal und höchstens bis zum Eintreten der Abbruchbedingung durchlaufen:

public int Addiere(int zahl)
{
  int ergebnis = zahl;

  do
  {
    ergebnis += zahl;
  }
  while (ergebnis < 100);

  return ergebnis;
}

Übergeben wir hier die zahl 100, wird uns als ergebnis 200 zurückgeliefert. Zwar bricht die Schleife unmittelbar nach dem ersten Durchlauf des Schleifenrumpfes ab, aber sie wird eben dieses eine Mal durchlaufen. Sinnvoll sind solche Schleifen beispielsweise dort, wo wir den Job in der Schleife mindestens einmal aber nicht unbedingt mehrfach ausführen möchten; wie etwa bei der Initialisierung von Variablen.

Endlosschleifen[Bearbeiten]

Da alle Schleifen prüfen, ob die Abbruchbedingung gilt, kann man ganz leicht eine Endlosschleife bauen:

// Beispiel: Kopfschleife - Endlos
public int Addiere(int zahl)
{
  int ergebnis = zahl;

  while (true)
  {
    ergebnis += zahl;
  }
 
  // Diese Zeile wird nie erreicht.
  // Die Methode wird nie "normal" beendet.
  return ergebnis;
}
// Beispiel: Fußschleife - Endlos
public int Addiere(int zahl)
{
  int ergebnis = zahl;

  do
  {
    ergebnis += zahl;
  }
  while (true);

  // Diese Zeile wird nie erreicht.
  // Die Methode wird nie "normal" beendet.
  return ergebnis;
}
// Beispiel: Zählschleife - Endlos
public int Addiere(int zahl, int wieOft)
{
  int ergebnis = zahl;

  for (; ; )
  {
    ergebnis += zahl;
  }

  // Diese Zeile wird nie erreicht.
  // Die Methode wird nie "normal" beendet.
  return ergebnis;
}

Diese Methoden erweisen sich nun als schwarze Löcher, denn einmal aufgerufen, können wir den Prozessor locker auf 100% Last und den Rechner zum Kochen bringen. Jedenfalls erreichen wir das Ende der Methode niemals im Guten.

Aber auch das ist durchaus sinnvoll. Stellen wir uns einfach vor, wir wollen eine Anwendung schreiben, die, einmal gestartet, für immer laufen soll. So genannte „Watchdogs“, also „Aufpasserprogramme“ haben in der Regel mindestens eine Endlosschleife.

Da uns aber die Performance des Computers gnadenlos einbricht, wenn wir nicht aufpassen, müssen wir hier kräftig gegenhalten. Einer der besten „Tricks“ an dieser Stelle ist das Multithreading.

Schleifensteuerung[Bearbeiten]

Aber auch innerhalb der Schleifen sind wir nicht auf Gedeih und Verderb der äußeren Abbruchbedingung ausgeliefert. Stattdessen können wir sehr gezielt steuern, wann es uns reicht. Nehmen wir uns dazu das Beispiel der fußgesteuerten Schleife noch einmal vor:

public int Addiere(int zahl)
{ 
  int ergebnis = zahl;

  do
  {
    ergebnis += zahl;
  }
  while (ergebnis < 100);

  return ergebnis;
}

Eigentlich wollten wir ja, dass die Schleife nur durchlaufen wird, solange das ergebnis kleiner oder gleich 100 ist. Und ursprünglich hatten wir versucht, die zahl 100 zu übergeben, was dazu führte, dass als ergebnis doch tatsächlich 200 zurück geliefert wurde. Das können wir beheben:

Break[Bearbeiten]

Das Schlüsselwort break ermöglicht es uns, sofort aus der Schleife auszusteigen. Erwartungsgemäß bekommen wir nun ein ergebnis zurück, das maximal 100 sein kann.

public int Addiere(int zahl)
{ 
  int ergebnis = zahl;

  do
  {
    if (ergebnis + zahl > 100) break;
   
    ergebnis += zahl;
  }
  while (ergebnis < 100);

  return ergebnis;
}

Der Einsatz von break in Schleifen sollte möglichst vermieden werden. Das Problem ist, dass die angegebene Schleifenbedingung (Hier: ergebnis < 100) nicht wirklich die Schleifenbedingung ist (in obigem Beispiel könnte z. B. ebenso while (true); stehen). Dadurch ist der Code schwerer zu warten.

Aber es gibt noch eine weitere Möglichkeit, die Steuerung von Schleifen zu beeinflussen:

Continue[Bearbeiten]

Das Schlüsselwort continue bricht den aktuellen Schleifendurchlauf sofort ab und beginnt unverzüglich den nächsten, falls die Abbruchbedingung es zulässt:

public int Addiere(int zahl, int wieOft)
{
  int ergebnis = zahl;

  for (int i = 1; i <= wieOft; i++)
  {
    if (i % 2 == 0) continue;

    ergebnis += zahl;
  }

  return ergebnis;
}

Jedes Mal, wenn i durch 2 teilbar ist, wird nun der aktuelle Schleifendurchlauf als beendet betrachtet, i um 1 erhöht und der nächste Schleifendurchlauf begonnen, ohne dabei die nach continue folgenden Zeilen zu durchlaufen, also auch, ohne zahl zu ergebnis zu addieren. Kurz gesagt, die Schleife wird zwar immer noch wieOft-Mal durchlaufen, aber zahl wird nur wieOft/2-Mal zu ergebnis addiert.

Praxisbeispiel: Sieb des Eratosthenes[Bearbeiten]

Das Sieb des Eratosthenes ist ein Verfahren zur Ermittlung von Primzahlen.


C#-Code:  

using System;
 
namespace Org.Wikibooks.De.CSharp.Schleifen
{
 class Program
 {
   static void Main(string[] args)
   {
     const long grenze = 1000; // Obergrenze festlegen
     bool[] gestrichen = new bool[grenze / 2 + 1];
     gestrichen.Initialize();

     long aktuellerWert = 3;   // Startwert setzen
     while (aktuellerWert <= grenze)
     {
       if (!gestrichen[aktuellerWert / 2 - 1])
       {
         // i ist prim
         Console.Write("{0}{1}", ((aktuellerWert != 3) ? ", " : ""), aktuellerWert);
         // Streiche seine Vielfache, beginne mit i*i
         for (long testWert = aktuellerWert * aktuellerWert; testWert <= grenze; testWert += aktuellerWert)
         {
            gestrichen[testWert / 2 - 1] = true;
         }

         aktuellerWert += 2;
         while ((aktuellerWert < grenze) && (gestrichen[aktuellerWert/2 - 1]))
           aktuellerWert += 2;
       }
     }

     Console.WriteLine("\nBerechnung beendet.\n[ENTER]");
     Console.ReadLine();
   }
 }
}