Programmierkurs C-Sharp: Schleifen

Aus Wikibooks

Wechseln zu: Navigation, Suche
Regal: Programmierung Programmierkurs C# Bild:Wikibooks buchseite.svg Schleifen

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
Wikipedia hat einen Artikel zum Thema:

Inhaltsverzeichnis

[Bearbeiten] Kopfgesteuerte Schleifen

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.

[Bearbeiten] Fußgesteuerte Schleifen

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.

[Bearbeiten] Zählschleifen

Auch diese Schleifen prüfen als kopfgesteuerte Schleifen vor jedem Durchlauf des Schleifenrumpfes, ob die Abbruchbedingung eingetreten ist. 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.

[Bearbeiten] Foreach

Der häufigste Fall, der uns im Zusammenhang mit Schleifen begegnet, ist die Zählschleife. Ständig müssen wir irgendwas zählen; seien es Datensätze, Indizes oder sonstwas. 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 eine 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 eine 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 );
}

[Bearbeiten] Endlosschleifen

Da alle Schleifen die Abbruchbedingung auf true prüfen, können wir hier 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.


[Bearbeiten] Schleifensteuerung

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:

[Bearbeiten] Break

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:

[Bearbeiten] Continue

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.

[Bearbeiten] Steuerung von Endlosschleifen

Mit diesem Wissen ausgestattet, verlieren Endlosschleifen sofort etwas von ihrer bedrohlichen Erscheinung, denn nun haben wir es wieder in der Hand, ob und wann die Schleife vielleicht doch verlassen wird. So können wir aus der bisherigen unbedingten Endlosschleife

// 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;
}

nun eine bedingte Endlosschleife machen:

// Beispiel: Fußschleife - Endlos
public int Addiere(int zahl)
{
  int ergebnis = zahl;

  do
  {
    ergebnis += zahl;
    
    // Wir prüfen, ob wir beenden wollen:
    if (ergebnis >= DateTime.Hour) 
      break;
  }
  while (true);

  // Diese Zeile wird erreicht,
  // sobald ergebnis größer als oder genauso groß 
  // wie die aktuelle Stunde ist.
  return ergebnis;
}

Obwohl der äußere Schleifenrumpf immer noch vorgibt, endlos laufen zu können, haben wir eine Abbruchbedingung eingebaut, so dass diese Schleife nun mindestens 1 Mal höchstens jedoch so oft, bis ergebnis größer als oder genauso groß wie die aktuelle Stunde ist.

[Bearbeiten] Praxisbeispiele

[Bearbeiten] Praxisbeispiel I: Sieb des Eratosthenes (Primzahlermittlung)


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();
   }
 }
}


Regal: Programmierung Programmierkurs C# Bild:Wikibooks buchseite.svg Schleifen
Persönliche Werkzeuge