piątek, 18 września 2015

Instrukcje sterujące

Dotychczas w prezentowanych przykładach instrukcje były wykonywane w kolejności, w jakiej zostały zapisane. Na szczęście to nie koniec możliwości. C# oferuje instrukcje warunkowe, pętle oraz "przełączniki", które pozwalają sterować przepływem programu. Osoby znające rodzinę języków C odnajdą wiele znajomych koncepcji, jednak omówione zostaną również elementy unikalne dla C#.

Zaczynając od podstaw, instrukcje if oraz else umożliwiają wykonanie określonego bloku kodu w zależności od przekazanej wartości logicznej (typ bool - true dla prawdy lub false dla fałszu). Dla warunków prawdziwych wykonany zostanie pierwszy blok kodu, w przeciwnym razie uruchomiony zostanie blok kodu po instrukcji else. Użycie else jest opcjonalne.

using System;

namespace HelloWorld
{
    public class Program
    {
        public static void Main(string[] args)
        {
            bool showHello = true;

            if (showHello)
            {
                // Pierwszy blok kodu
                Console.WriteLine("Hello world in C#");
            }
            else
            {
                // Drugi blok kodu
                Console.Write("So... ");
            }

            Console.Write("Let's start!");
            Console.ReadLine();
        }
    }
}

Blok kodu po instrukcji else - tj. { i } - jest opcjonalny. Jeżeli warunek dotyczy tylko jednej linii kodu, można go pominąć. Jednakże, dla zachowania czytelności często warto użyć bloków kodu. Jak już wspomniano w poprzednim wpisie Komentarze i dyrektywy, kompilator ignoruje białe znaki, w tym wcięcia. Spójrzmy na poniższy przykład.

using System;

namespace HelloWorld
{
    public class Program
    {
        public static void Main(string[] args)
        {
            bool showHello = false;

            if (showHello)
                Console.Write("Hello world in C#");
                if (1 == 1)
                    Console.Write("Let's start!");
            else
                Console.Write("We know what it is in C#");  
        }
    }
}

Jeśli warunek if(showHello) będzie prawdziwy, chcemy wyświetlić komunikat "Hello world in C#". Następnie, niezależnie od tego, komunikat "Let's start!" wyświetli się zawsze, ponieważ warunek "zawsze prawdziwy" if (1 == 1) zawsze będzie spełniony. W przeciwnym razie chcemy wyświetlić "We know what it is in C#". Czy oczekiwana ścieżka przepływu dla tego przykładu to wyświetlenie "We know what it is in C#"?



Niestety oczekiwana ścieżka różni się od wykonanej. Dlaczego? Instrukcja else nie jest powiązana z pierwszym warunkiem, lecz z drugim. Czytelnicy, którzy spróbują wykonać przykład, zapewne zauważą ostrzeżenie. Warunek if(1 == 1) powoduje, że komunikat "We know what it is in C#" nigdy nie zostanie osiągnięty.

Wstawienie bloków kodu może wydłużyć kod, jednak zdecydowanie zwiększa jego czytelność. Visual Studio umożliwia konfigurację automatycznego formatowania kodu. Szczegółowy opis znajdziesz tutaj: Text Editor Options Dialog Box.

Przykład sterowania przepływem w zależności od czynników zewnętrznych, na przykład wciśnięcia klawisza.

using System;

namespace HelloWorld
{
    public static class Program
    {
        public static void Main()
        {
            Console.WriteLine("Do you know what a C#?");
            Console.Write("Press [Y] yes or [N] no: ");

            ConsoleKeyInfo cKeyInfo = Console.ReadKey();
            Console.WriteLine();
            bool showHello = cKeyInfo.Key == ConsoleKey.Y ? true : false;

            if (showHello)
            {
                Console.WriteLine("Hello world C#");
                showHello = false;

                if (!showHello)
                {
                    Console.WriteLine("Let's start!");
                }
            }
            else
            {
                Console.WriteLine("We know what it is C#");
            }

            Console.ReadKey();
        }
    }
}

Zaczynamy od wyświetlenia pytania (linie 9, 10). W linii 12 użyta została metoda ReadKey, a jej wynik (struktura reprezentująca wciśnięty klawisz) został przypisany do zmiennej cKeyInfo.

Kolejna linia to ciekawy przykład operatora trójargumentowego.

bool showHello = cKeyInfo.Key == ConsoleKey.Y;

Ten operator jest skróconą formą instrukcji if, w której wyrażenie przed znakiem ? (w tym przypadku cKeyInfo.Key == ConsoleKey.Y) jest oceniane. Musi zwracać typ bool. Jeśli jest prawdziwe, zwracana jest wartość po znaku ? (w tym przypadku true), a w przeciwnym razie wartość po znaku : (tutaj false). Warunek sprawdza, czy wciśnięto klawisz Y - jeśli tak, zwraca "prawda", w przeciwnym razie "fałsz".

Dalej, w bloku kodu sprawdzamy, czy wartość w zmiennej showHello to true. Jeśli tak, wyświetlany jest komunikat "Hello world in C#", a następnie warunek jest negowany przez znak !, co pozwala na wyświetlenie komunikatu "Let's start!". Na koniec metody Main w linii 31 ponownie używamy ReadKey, aby zatrzymać aplikację przed zamknięciem.

Do tej pory wyrażenia logiczne w instrukcjach warunkowych opierały się na bezpośrednio przekazanych wartościach typu bool. W kolejnym przykładzie zobaczymy użycie operatorów relacyjnych.

using System;

namespace HelloWorld
{
    public static class Program
    {
        public static void Main()
        {
            Console.Write("How many years have you been programming in C#? ");

            string inputText = Console.ReadLine();
            int progYears = default(int);

            if (int.TryParse(inputText, out progYears))
            {
                if (progYears < 0)
                {
                    Console.Write("You can't have less than 0 years of programming.");
                }
                else
                {
                    if (progYears <= 1)
                    {
                        Console.Write("You are a novice programmer.");
                    }
                    else
                    {
                        if (progYears <= 5)
                        {
                            Console.Write("You're gaining experience.");
                        }
                        else
                        {
                            Console.Write("You're a seasoned programmer.");
                        }
                    }
                }
            }
            else
            {
                Console.Write("The entered value does not represent a number.");
            }

            Console.ReadKey();
        }
    }
}

Powyższy kod pyta, jak długo programujesz w C#, a następnie wyświetla odpowiedni komunikat w zależności od odpowiedzi. Zaczynamy od wyświetlenia pytania, a następnie, w linii 11, używamy metody ReadLine, aby odczytać wprowadzony tekst. W pierwszej instrukcji warunkowej wykorzystujemy metodę TryParse, która zwraca "prawdę", jeśli wartość pierwszego parametru (ciągu znaków) można przekształcić na liczbę całkowitą. Więcej informacji o metodzie TryParse znajdziesz w notatce o obsłudze wyjątków oraz konwersji typów liczbowych. Jeżeli użytkownik wprowadzi znaki niemożliwe do konwersji na liczbę całkowitą, pojawi się komunikat "The entered value does not represent a number". W przeciwnym wypadku wykonywane są kolejne instrukcje warunkowe.

Warunek w linii 16 sprawdza, czy zmienna progYears jest mniejsza od zera, zabezpieczając przed wprowadzeniem nieprawidłowej liczby ujemnej. Następnie, jeśli wartość jest równa lub mniejsza niż jeden, pojawia się komunikat "You are a novice programmer". W przeciwnym wypadku sprawdzamy, czy liczba lat jest mniejsza lub równa pięciu, wyświetlając "You're gaining experience", lub "You're a seasoned programmer", jeśli jest to więcej niż pięć lat.

Operatory relacyjne

Nazwa                 Instrukcja       Przykład
---------------------------------------------------
Mniejszy              <            a < b
Większy               >            a > b
Mniejszy lub równy    <=           a <= b
Większy lub równy     >=           a >= b
Równy                 ==           a == b
Różny                 !=           a != b

Kod w powyższym przykładzie jest mocno zagnieżdżony, przez co traci na czytelności. Poprawmy to, łącząc instrukcje else i if.

using System;

namespace HelloWorld
{
   public static class Program
   {
      public static void Main()
      {
         Console.Write("How many years have you been programming in C#? ");

         string inputText = Console.ReadLine();
         int progYears = default(int);

         if (!int.TryParse(inputText, out progYears))
         {
            Console.Write("The entered value does not represent a number.");
         }
         else if (progYears < 0)
         {
            Console.Write("You can't have less than 0 years of programming.");
         }
         else if (progYears <= 1)
         {
            Console.Write("You are a novice programmer.");
         }
         else if (progYears <= 5)
         {
            Console.Write("You're gaining experience.");
         }
         else
         {
            Console.Write("You're a seasoned programmer.");
         }

         Console.ReadKey();
      }
   }
}

Po modyfikacji kod jest znacznie czytelniejszy. Zmieniono również pierwszy warunek na:

if (!int.TryParse(inputText, out progYears))
Teraz, jeśli wartość nie zostanie przekształcona na liczbę całkowitą, pojawi się komunikat "The entered value does not represent a number".

Jak zachowa się kod, jeśli odwrócimy kolejność warunków?


Wprowadzenie wartości 1 spełnia warunek else if(progYears <= 5). Konstrukcja else if po spełnieniu pierwszej instrukcji pomija pozostałe, więc zamiana instrukcji na if spowodowałaby wyświetlenie obu komunikatów dla wartości 1: "You're gaining experience" i "You are a novice programmer".

Operatory logiczne umożliwiają tworzenie złożonych warunków, np. if(progYears > 1 && progYears <= 5), gdzie sprawdzamy, czy wartość progYears jest większa od 1 i jednocześnie mniejsza lub równa 5. Warunek jest spełniony, tylko gdy obie części są prawdziwe. Sprawdzenie wykonuje się od lewej do prawej strony. Jeśli używamy operatora koniunkcji && i pierwsza część jest fałszywa, kolejne nie są już sprawdzane. W przeciwieństwie do operatora alternatywy ||, gdzie sprawdzanie kontynuowane jest do pierwszej prawdziwej części.

Operatory logiczne

Nazwa                      Instrukcja       Przykład
------------------------------------------------------------------
Negacja logiczna (NOT)        !               !a
Koniunkcja warunkowa (AND)    &&              a && b
Alternatywa warunkowa (OR)    ||              a || b

Na zakończenie spróbujmy zmodyfikować kod tak, aby warunek sprawdzał, czy liczba lat znajduje się w określonym zakresie.

using System;

namespace HelloWorld
{
    public static class Program
    {
        public static void Main()
        {
            Console.Write("How many years have you been programming in C#? ");

            string inputText = Console.ReadLine();
            int progYears = default(int);

            if (!int.TryParse(inputText, out progYears))
            {
                Console.Write("The entered value does not represent a number.");
            }
            else if (progYears > 5)
            {
                Console.Write("You're a seasoned programmer.");
            }
            else if (progYears > 1 && progYears <= 5)
            {
                Console.Write("You're gaining experience.");
            }
            else if (progYears > 0 && progYears <= 1)
            {
                Console.Write("You are a novice programmer.");
            }
            else
            {
                Console.Write("You can't have less than 0 years of programming.");
            }

            Console.ReadKey();
        }
    }
}

Instrukcje if oraz else pozwalają na określenie różnych ścieżek działania programu w zależności od wartości zmiennych. W kolejnych notatkach omówione zostaną instrukcja "switch" oraz pętle. Zapraszam do dalszej lektury.

Troska Robert