Rozdział 3. Podstawy języka C#.
Adam Boduch
Zawsze, na każdym kroku staram się podkreślać, iż tworzenie aplikacji nie opiera się jedynie na układaniu „klocków” (komponentów) w oknie formularza. Oczywiście, nowoczesne języki programowania (takie jak C#) oraz środowiska tworzenia aplikacji (Visual C# Express Edition) dają nam możliwość szybkiego oraz przyjemnego projektowania aplikacji, lecz nie na tym polega programowanie! Należy mieć wiedzę na temat podstawowych elementów języka programowania oraz podstawowych poleceń służących do sterowania pracą programu.
W tym rozdziale zajmiemy się właśnie językiem C#. Celowo podkreśliłem słowo „język”, gdyż omówię podstawowe terminy programistyczne oraz elementy każdego wysokopoziomowego języka programowania. Odstawimy w tym miejscu na chwilę przyjemne i ładne projektowanie wizualne (przy pomocy komponentów) na rzecz aplikacji konsolowych. Wszystko dlatego, iż moim zdaniem prościej jest nauczyć się danego języka na przykładach zawierających jak najmniejszą ilość kodu, tak jak to ma miejsce w przypadku programów konsolowych.
Składnia to specyficzne słowa kluczowe (elementy danego języka służące do sterowania pracą programu) oraz znaki, które muszą zostać zapisane w określonym porządku.
1 Podstawowa składnia
1.1 Najprostszy program
1.2 Jak kompilatory czytają kod
1.3 Wielkość znaków
1.4 Program musi posiadać metodę Main
1.5 Średnik kończy instrukcję
1.6 Program musi posiadać klasę
1.7 Wcięcia, odstępy
1.8 Słowa kluczowe
1.9 Symbole
2 Komentarze
3 Podzespoły, metody, klasy
3.10 Funkcje
3.11 Metody
3.12 Klasy
3.13 Przestrzenie nazw
3.14 Operator kropki
3.15 Słowo kluczowe using
4 Zmienne
4.16 Deklarowanie zmiennych
4.16.1 Deklaracja kilku zmiennych
4.17 Przydział danych
4.18 Typy danych
4.19 Restrykcje w nazewnictwie
5 Stałe
6 Operacje na konsoli
6.20 Metody klasy Console
6.21 Właściwości klasy Console
7 Operatory
7.22 Operatory porównania
7.23 Operatory arytmetyczne
7.24 Operator inkrementacji oraz dekrementacji
7.25 Operatory logiczne
7.26 Operatory bitowe
7.27 Operatory przypisania
7.28 Inne operatory
8 Instrukcje warunkowe
8.29 Instrukcja if
8.29.2 Instrukcje zagnieżdżone
8.29.3 Kilka warunków do spełnienia
8.29.3.1 Zastosowanie nawiasów
8.30 Słowo kluczowe else
8.31 Instrukcja else if
8.32 Instrukcja switch
8.32.4 Wartość domyślna
8.32.5 Instrukcja goto
9 Pętle
9.33 Pętla while
9.33.6 Warunki logiczne w pętlach
9.34 Pętla do-while
9.35 Pętla for
9.35.7 Odliczanie od góry do dołu
9.35.8 Parametry opcjonalne
9.36 Instrukcja break
9.37 Instrukcja continue
9.38 Operator warunkowy
10 Konwersja danych
11 Rzutowanie
12 Przykładowa aplikacja
13 Dyrektywy preprocesora
13.39 Deklarowanie symboli
13.40 Instrukcje warunkowe
13.41 Błędy i ostrzeżenia
14 Podsumowanie
Podstawowa składnia
Kod źródłowy musi składać się z poleceń zakończonych określonymi znakami. Nie można pozostawić w kodzie żadnego bałaganu — nawet pominięcie jednego znaku czy zwykła literówka mogą spowodować brak możliwości uruchomienia programu. Tak jak w języku polskim, pominięcie odpowiedniego znaku interpunkcyjnego jest błędem. Takie banalne z pozoru błędy mogą być prawdziwą udręką dla początkującego programisty i w konsekwencji spowodować spowolnienie prac nad programem. Z czasem wyrabia się pewien nawyk, dzięki któremu popełnia się coraz mniej błędów, a nawet jeśli — to są one dla bardziej zaawansowanego programisty łatwiejsze do wykrycia.
Na szczęście nowoczesne kompilatory potrafią bardzo precyzyjnie wskazać źródło błędu wraz ze szczegółowym komunikatem oraz numerem linii, w której on wystąpił.
Zacznijmy więc. Utwórz swój pierwszy projekt aplikacji konsolowej.
#Z menu File wybierz New Project.
#Zaznacz ikonę Console Application.
#Kliknij przycisk OK.
W tym momencie środowisko utworzy nowy projekt aplikacji konsolowej (rysunek 3.1). W edytorze kodu zostanie automatycznie wpisany startowy kod naszego programu. Taki kod możesz w tym momencie skompilować — nie zawiera on żadnych błędów i jest to praktycznie punkt startowy do rozwijania aplikacji.
Rysunek 3.1. Środowisko Visual C# z otwartym projektem
Jak widzisz, kod źródłowy składa się ze specyficznych słów (np. using, namespace
), symboli (np. nawiasy kwadratowe oraz okrągłe). Wszystko tworzy jedną spójną całość i nazywane jest składnią programu.
Najprostszy program
Napiszmy najprostszy program w języku C#. W swoim projekcie musimy pozostawić pewne niezbędne elementy programu. Kod źródłowy najprostszego programu może wyglądać następująco:
class Foo
{
static void Main(string[] args)
{
}
}
Oczywiście taka aplikacja nic nie robi, zaraz po uruchomieniu (klawisz F5) zostanie zamknięta.
Jak kompilatory czytają kod
Podczas kompilowania programu kompilator sprawdza najpierw, czy kod źródłowy nie zawiera błędów. Kod jest „czytany” linia po linii, począwszy od góry, tak więc instrukcje są wykonywane w takiej kolejności, w jakiej zostały zapisane.
Wielkość znaków
Kompilator języka C#, podobnie jak C++ czy Javy, rozróżnia wielkość znaków. Przykładowo polecenie Foo
nie jest równe poleceniu foo — z punktu widzenia kompilatora to dwa różne polecenia. Jeżeli więc wcześniej programowałeś w Delphi lub Turbo Pascalu (które nie rozróżniały wielkości znaków), musisz przywyknąć do tego, że kompilator C# rozróżnia wielkość znaków, i poświęcać dużo uwagi temu, co piszesz.
Dla przykładu doprowadź wygląd naszej prostej aplikacji do takiego kształtu:
class Foo
{
static void main(string[] args)
{
}
}
Po dokładnej analizie możesz zauważyć, że dokonałem jednej małej poprawki. Zamieniłem nazwę funkcji z Main na main. Podczas próby skompilowania takiego programu wyświetlony zostanie błąd: Program 'ConsoleApplication1.exe' does not contain a static 'Main' method suitable for an entry point
.
Komunikat mówi o tym, że aplikacja nie zawiera metody o nazwie Main, która jest punktem startu programu. Jest to pierwsza reguła charakterystyczna dla programów pisanych w C#. A mianowicie:
Program musi posiadać metodę Main
Dla prawidłowej terminologii używam tutaj pojęcia metoda, mimo iż nie wyjaśniłem, czym właściwie jest metoda! Na razie nie przejmuj się tym — zostanie to omówione dalej.
Zapamiętaj jedynie, że metoda Main
jest obowiązkowym elementem programu. To od niej program rozpoczyna swe działanie i na niej je kończy. Zmodyfikuj nasz program do takiej postaci:
class Foo
{
static void Main(string[] args)
{
System.Console.WriteLine("Witaj Świecie!");
}
}
Po uruchomieniu takiego programu (F5) na ekranie konsoli zostanie wyświetlony tekst: Witaj Świecie!
. Jest to potwierdzeniem moich słów o tym, iż to właśnie w metodzie Main
rozpoczyna się właściwe działanie programu, czyli wykonanie instrukcji:
System.Console.WriteLine("Witaj Świecie!");
Taki kod powoduje wyświetlenie na ekranie konsoli tekstu zapisanego pomiędzy apostrofami. Nie możesz zapominać o obowiązkowych nawiasach, bez których aplikacja nie zostanie skompilowana.
Na szybkim komputerze program może zostać skompilowany i uruchomiony z taką prędkością, iż nie dostrzeżemy w ogóle jego działania, gdyż od razu zostanie zamknięty. Abyśmy to my mogli decydować, kiedy aplikacja zostanie zamknięta, należy dodać do programu odpowiednie instrukcje. Jakie? Wskazówki znajdziesz w dalszej części rozdziału.
Średnik kończy instrukcję
Bardzo ważna sprawa, często staje się źródłem błędów — chodzi o średnik kończący instrukcję. Kompilator jest tylko programem, który m.in. analizuje nasz kod źródłowy. Pisząc kod, musimy poinstruować kompilator, iż w tym miejscu następuje zakończenie danej instrukcji. Oznaczamy to, stawiając na końcu danego wyrażenia średnik (taka zasada obowiązuje w większości nowoczesnych języków programowania).
Zwróć uwagę na powyższą instrukcję wyświetlającą tekst na konsoli. Jest ona zakończona średnikiem. Gdyby go nie było, kompilator w trakcie kompilacji wyświetliłby błąd: ; expected
.
Jest to bardzo ważna zasada i musisz o niej pamiętać. Nie martw się — po jakimś czasie znak średnika na końcu wyrażenia będziesz stawiał automatycznie.
Program musi posiadać klasę
Kolejne pojęcie, jakie należy wprowadzić, to klasa. O klasach szczegółowo opowiem w rozdziale 5., jednak aby kontynuować naukę, musisz chociaż wiedzieć, czym one są. Każda klasa musi mieć nazwę poprzedzoną słowem kluczowym class
:
class Foo
Nazwę klasy od słowa kluczowego musi dzielić co najmniej jedna spacja. Klasy mogą zawierać m.in. metody, takie jak metoda Main
. Zawartość klasy musi mieścić się pomiędzy klamrami — { oraz }:
class Bar { }
Tak naprawdę każda aplikacja języka C# musi posiadać przynajmniej jedną klasę lub strukturę! O strukturach opowiem w dalszej części książki; teraz jedynie zaznaczam ten fakt, gdyż może on mieć znaczenie dla osób, które programowały wcześniej np. w języku C++.
Wcięcia, odstępy
Z punktu widzenia kompilatora nie jest istotne, jak pisany jest kod, czy zawiera odstępy oraz wcięcia. Równie dobrze może być pisany w jednej linii:
class Foo { static void Main(string[] args) {
System.Console.WriteLine("Witaj Świecie!");
} }
Czytelność takiego kodu pozostawia jednak wiele do życzenia i dlatego taki sposób pisania nie jest zalecany.
Słowa kluczowe
Każdy język programowania posiada specyficzne elementy, tzw. słowa kluczowe. Mogą one oznaczać rozkaz czy instrukcję, które są w dany sposób interpretowane przez kompilator. Standardowo w środowisku Visual C# Express Edition słowa kluczowe wyróżniane są kolorem niebieskim. Do słów kluczowych C# można zaliczyć m.in. class
, void
, static
, string
. Przykładowo, słowo class oznacza deklarację klasy o danej nazwie. Deklaracja w kontekście języka programowania może zwyczajnie oznaczać utworzenie danego elementu (np. klasy).
Symbole
Symbole to pojedyncze znaki, które wraz ze słowami kluczowymi tworzą składnię. Przykładowo, znak cudzysłowu (") określa początek oraz koniec łańcucha tekstu. Spróbuj usunąć cudzysłowy z instrukcji wypisującej tekst na konsoli:
System.Console.WriteLine(Witaj Świecie!);
Podczas próby kompilacji kompilator zasygnalizuje błąd, ponieważ nieznane są dla niego instrukcje Witaj oraz Świecie, które wraz z cudzysłowem tworzą łańcuch, a zwartość łańcucha nie jest przez niego sprawdzana pod względem wystąpienia danych poleceń.
Komentarze
Najprostszym elementem każdego języka programowania są komentarze. W kodzie źródłowym możesz dodawać wzmianki — informacje absolutnie niemające wpływu na działanie programu. W trakcie kompilacji są one usuwane przez kompilator i nie znajdują się w pliku wynikowym .exe. Oczywiście kompilator nie usuwa tych komentarzy fizycznie, tzn. ze źródła programu.
Komentarze mają ogromny wpływ na proces powstawania aplikacji, szczególnie jeżeli pracujesz w grupie. W kodzie możesz zawrzeć informacje przeznaczone dla innych czytających go programistów, o tym jak on działa i co robi.
Podstawowym typem w języku C# są komentarze jednej linii w stylu języka C++. Przykład:
class Foo
{
// to jest komentarz
// metoda Main — tutaj zaczynamy działanie!
static void Main(string[] args)
{
System.Console.WriteLine("Witaj Świecie!");
}
}
Tekst znajdujący się po znakach nie będzie brany pod uwagę przez kompilator. W nowoczesnych edytorach kodu (takich jak w środowisku Visual C#) komentarze są specjalnie wyróżniane (w moim przypadku — kolorem zielonym). Tego typu komentarze nazywane są komentarzami jednej linii, z tego względu iż obowiązują jedynie w linii, która rozpoczyna się od znaków :
// tu jest komentarz
ale tutaj już nie obowiązuje
W C# dostępne są także komentarze w stylu języka C, dzięki którym możesz „skomentować” wiele linii tekstu:
/*
Tutaj jest komentarz
Tutaj również...
*/
Rozpoczęcie oraz zakończenie bloku komentarza określają znaki /* oraz */.
Komentarze mogą być w sobie zagnieżdżane, przykładowo:
/* komentarz w stylu C
// komentarz jednej linii
*/
Istnieje jeszcze jeden typ komentarza związanego z dokumentacją języka XML, lecz omówię to w 13. rozdziale książki.
Podzespoły, metody, klasy
Powiedzieliśmy sobie już o symbolach oraz słowach kluczowych, którymi można się posłużyć w trakcie pisania programów. Nim przejdziemy dalej, muszę Ci wyjaśnić kilka dodatkowych pojęć. W poprzednim rozdziale wspominałem o środowisku .NET Framework oraz bibliotece klas jako o głównym narzędziu projektowania aplikacji.
Chciałbym w tym momencie wyjaśnić pewną kwestię. Otóż środowisko .NET Framework udostępnia programistom szereg klas i bibliotek, które ułatwiają proces programowania. Jest to coś na wzór WinAPI, o którym wspominałem w poprzednim rozdziale.
Główną biblioteką w .NET Framework jest mscorlib.dll. Zawiera ona przestrzeń nazw System
, która z kolei zawiera klasę Console
! Klasy z kolei zawierają metody, m.in. WriteLine
, która służy do wypisywania tekstu na konsoli. Jak widzisz, system zależności jest dość skomplikowany, a wielość pojęć, jakie trzeba opanować, może przyprawić o ból głowy! Dlatego na razie nie będę Cię zadręczał skomplikowanymi definicjami oraz opisami tych pojęć — będziesz je poznawał stopniowo w trakcie czytania tej książki.
Na tym etapie ważne jest abyś wiedział, że istnieją metody, które — na wzór funkcji — realizują gotowe zadania takie jak np. wypisywanie tekstu na konsoli. Można powiedzieć, że są to polecenia, które można wykorzystać w aplikacji, chociaż taka terminologia nie jest raczej dopuszczalna. Nie zagłębiając się w szczegóły, postaram się opisać kilka podstawowych elementów języka programowania w C#, które dość często będą wykorzystywane w dalszej części książki.
Funkcje
Funkcje jako takie nie istnieją w C#! Zamiast tego mówimy o wspomnianych już w tej książce metodach. Idea jest w zasadzie identyczna, ale aby uniknąć nieporozumień, będę się starał nie używać słowa funkcja. Z tym słowem spotkasz się zapewne nie raz w swojej karierze, gdyż mechanizm funkcji jest obecny w wielu językach programowania.
Funkcje to wydzielony blok kodu realizujący jakieś zadanie.
Chciałbym w paru słowach przybliżyć ideę tzw. programowania proceduralnego.
Idea programowania proceduralnego zaczęła się pojawiać wraz z bardziej zaawansowanymi aplikacjami. Tradycyjny moduł projektowania nie sprawdzał się dobrze, gdy programy zaczęły być bardziej skomplikowane — wówczas ich konserwacja i naprawianie błędów były niezwykle trudne.
Ktoś mądry wymyślił wtedy, że można by było dzielić program na mniejsze części — tzw. procedury. Przykładowo, jeżeli napisano kod, który wyświetla pewien komunikat i kończy działanie programu, a ów fragment jest używany wiele razy w tej aplikacji, to należałoby go dublować wiele razy. Powoduje to nie tylko zwiększenie objętości kodu, ale również potęguje podatność na błędy. Bo co się stanie, jeżeli właśnie w tym małym, wielokrotnie powtórzonym w aplikacji fragmencie, wystąpi błąd? Należałoby wówczas przeszukać cały kod i w każdym miejscu poprawiać usterkę.
Teraz, w nowoczesnych językach programowania można umieścić pewien fragment kodu w procedurze i wywołać ją za każdym razem, kiedy zajdzie potrzeba jego wykonania!
Generalnie w językach takich jak C++, PHP, Java nie istnieją procedury, lecz funkcje. Sama idea jest identyczna, ale z uwagi na to, iż w C# procedury nie istnieją, nie będę o nich więcej wspominał.
Metody
Mam nadzieję, że masz już w głowie pewien zarys tego, czym jest metoda. Już wkrótce nauczysz się pisać własne metody oraz klasy. Aktualnie jedyne, co musisz wiedzieć, to to, że metody mogą posiadać tzw. parametry. Spójrz na poprzedni przykład użycia metody WriteLine()
. Parametrem tej metody był tekst Hello World!
. Innymi słowy — przekazujemy metodzie tekst do wyświetlenia na konsoli. Możesz myśleć o parametrach jak o danych wejściowych — przekazujesz metodzie dane, na których ona operuje. Obowiązkowym elementem każdej metody są nawiasy, w których podaje się parametry. Jednak nie wszystkie metody w środowisku .NET Framework mają parametry — w takim wypadku pozostawiamy pusty nawias, np.:
System.Console.Read();
Klasy
O klasach można powiedzieć, iż jest to zestaw metod. Przykładowo, klasa Console
zawiera zestaw metod służących do operowania na konsoli. Myśl o klasach jak o przyborniku, paczuszce zawierającej przydatne narzędzia.
Środowisko .NET Framework udostępnia szereg klas gotowych do użycia. Przykładowo, chcemy napisać program, który obliczy logarytm z danej liczby. Zamiast samemu męczyć się z pisaniem odpowiedniego kodu, możemy wykorzystać klasę Math
, która jest częścią środowiska .NET Framework, i udostępniane przez nią mechanizmy.
Przestrzenie nazw
Środowisko .NET Framework jest na pierwszy rzut oka dość specyficzne dla osób, które programowały wcześniej na platformie Win32 lub dopiero się uczą. Bo cóż oznacza zapis:
System.Console.WriteLine("Witaj Świecie!");
Przede wszystkim jest strasznie długi! Mamy tutaj kilka „instrukcji” oddzielonych znakiem kropki. Czy nie łatwiej i krócej byłoby pisać po prostu samą nazwę metody?
WriteLine("Witaj Świecie!");
Środowisko .NET Framework jest o wiele bardziej rozbudowane od swojego poprzednika — systemu WinAPI. Zawiera tysiące klas i innych typów, każdy posiada inną nazwę. W środowisku Win32 nie mogło się zdarzyć, że istniały dwie funkcje o takiej samej nazwie [#]_ , co jest możliwe w .NET Framework.
Przykładowo, metoda WriteLine()
wypisuje tekst, ale istnieje również metoda WriteLine()
, która zapisuje go do pliku (metoda klasy TextWriter
). Ich jednoczesna obecność jest niewykluczona, ponieważ należą do innych klas.
Podobnie jest w przypadku przestrzeni nazw (ang. namespace). W obrębie kilku przestrzeni nazw mogą istnieć klasy o tej samej nazwie. Np.:
namespace Bar
{
class Hello
{
}
}
namespace Foo
{
class Hello
{
}
}
Operator kropki
Pojęcie operator zostanie wprowadzone w dalszej części rozdziału. Znak kropki (.) stanowi separator pomiędzy nazwą przestrzeni nazw, klasą a nazwą metody. Przy pomocy tego operatora otrzymujemy dostęp do elementów danej klasy czy przestrzeni nazw.
Używając środowiska Visual Studio.NET (jak również Visual C# Express Edition), możesz korzystać z narzędzi wspomagających tworzenie aplikacji. Wykonaj małe doświadczenie. W edytorze napisz słowo System.
, kończąc je kropką. Po chwili powinna pojawić się rozwijana lista zawierająca listę klas oraz innych zagnieżdżonych przestrzeni nazw, które możemy wykorzystać (rysunek 3.2).
Rysunek 3.2. Lista klas oraz przestrzeni nazw w obrębie przestrzeni System
Słowo kluczowe using
Pisanie za każdym razem nazwy przestrzeni nazw, a następnie klasy i metody może być nieco męczące. Dlatego też można wykorzystać słowo kluczowe using
, które informuje kompilator, że w programie będziemy korzystali z klas znajdujących się w danej przestrzeni nazw (np. System
):
using System;
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
}
}
Zwróć uwagę, że słowo kluczowe using
znajduje się jeszcze przed klasą Program
.
Zmienne
W każdym programie, który ma więcej niż kilkanaście linijek kodu, zachodzi konieczność przechowywania tymczasowych danych w pamięci komputera. Takie dane przechowywane są jedynie w trakcie działania programu.
Zmienne definiujemy jako obszar w pamięci komputera, który służy do przechowywania danych tymczasowych (obecnych w pamięci do czasu wyłączenia programu), mających postać liczb, tekstu itp.
Zapisywanie danych w pamięci komputera w obecnych językach programowania jest bardzo proste. Należy określić unikalną nazwę, pod którą będziemy uzyskiwać dostęp do danych.
Deklarowanie zmiennych
Operacja utworzenia zmiennej nazywana jest deklaracją zmiennej. Musimy określić unikalną nazwę zmiennej, która nie może się powtarzać w obrębie danej klasy czy metody. Musimy również określić typ danej zmiennej, czyli zidentyfikować dane, jakie będziemy przechowywać w pamięci (tekst, liczby itp.).
Przykładowe zadeklarowanie zmiennej przedstawiłem poniżej:
class Foo
{
static void Main(string[] args)
{
string Bar;
}
}
W powyższym programie „utworzyłem” zmienną o nazwie Bar oraz typie string
. Jak widzisz, sposób deklaracji jest bardzo prosty, schemat można przedstawić w ten sposób:
<typ zmiennej> <nazwa zmiennej>
Nazwę od typu musi dzielić co najmniej jedna spacja.
Deklaracja kilku zmiennych
Najczęściej bywa tak, iż w aplikacji potrzebujemy wielu zmiennych. Przykładowo, jedna zmienna przechowuje pobrany login użytkownika, druga — imię użytkownika, a trzecia — jego nazwisko.
Potrzebujemy wobec tego trzech zmiennych. Jeżeli wszystkie zmienne są tego samego typu (string
), możemy zadeklarować je w ten sposób:
string Login, FName, LName;
Nazwy zmiennych musimy oddzielić znakiem przecinka. Z punktu widzenia kompilatora nie ma znaczenia to, w jaki sposób deklarujesz zmienne, więc równie dobrze możesz je zadeklarować w ten sposób:
string Login;
string FName;
string LName;
Zwróć uwagę, że instrukcja deklarowania zmiennej zakończona jest znakiem średnika!
Przydział danych
Zadeklarowaliśmy już zmienną, lecz nie przypisaliśmy do niej żadnych danych, więc jej zawartość jest pusta. Przypisanie danych do zmiennej jest równie proste jak deklaracja. W tym celu używamy operatora przypisania (=
):
class Foo
{
static void Main(string[] args)
{
string Bar = "Hello World";
}
}
W tym momencie zawartość zmiennej Bar to tekst Hello World
.
Każdy tekst zadeklarowany w ramach zmiennej musi być ujęty w cudzysłowie.
Zmodyfikujemy nieco nasz ostatni program, tak aby tekst wyświetlany w oknie konsoli był odczytywany ze zmiennej. Spójrz na poniższy kod:
using System;
class Foo
{
static void Main(string[] args)
{
string Bar = "Hello World";
Console.WriteLine(Bar);
Console.Read(); // czekaj na reakcję użytkownika!
}
}
W metodzie WriteLine
zamiast tekstu umieściliśmy nazwę zmiennej. W trakcie działania programu odczytywana jest zawartość zmiennej Bar (czyli Hello World
) i przekazywana jest metodzie WriteLine
, co oczywiście skutkuje wypisaniem tekstu na konsoli.
Zwróć uwagę, że w programie wykorzystałem metodę Read
, która w tym wypadku czeka na reakcję użytkownika. Program zostanie zamknięty wówczas, gdy użytkownik naciśnie klawisz Enter.
Jak sama nazwa wskazuje, zmienne (lub raczej ich zawartość) mogą być modyfikowane w trakcie działania programu. To, że przypisaliśmy zawartość zmiennej już w momencie jej deklaracji, nie oznacza, że nie można tego zmienić:
string Bar;
Bar = "Hello World";
Bar = "Hello my darling!";
Console.WriteLine(Bar);
Jak widzisz, tutaj napisałem zawartość zmiennej; na skutek tego na konsoli wyświetlony zostanie tekst Hello my darling!
.
Typy danych
Do tej pory deklarowaliśmy zmienne typu string
, co oznaczało, iż służą one do przechowywania tekstu. Język C# oferuje kilka innych typów danych, którymi możemy posługiwać się we własnych aplikacjach. Przykładowo, typ int
służy do przechowywania liczb całkowitych:
int X = 10;
Jest to bardzo popularny typ danych. Typy danych języka C# różnią się od siebie tzw. zakresem. Np. maksymalna wartość, jaka może zostać przypisana do zmiennej typu int, to 2,147,483,647, natomiast maksymalna możliwa wartość typu byte to 255.
Typy danych różnią się od siebie również ilością pamięci, jaką „pochłaniają”. Np. typ int
zajmuje w pamięci 4 bajty, a typ byte jedynie 1. W tabeli 3.1 zaprezentowane zostały wbudowane typy danych języka C# wraz z maksymalnym zakresem.
Tabela 3.1. Wbudowane typy danych języka C#
Typ danych | Zakres |
---|---|
byte | od 0 do 255 |
sbyte | od –128 do 127 |
short | od –32,768 do 32,767 |
int | od –2,147,483,648 do 2,147,483,647 |
uint | od 0 do 4,294,967,295 |
long | od –9,223,372,036,854,775,808 do 9,223,372,036,854,775,807 |
ulong | od 0 do 18,446,744,073,709,551,615 |
float | od –3.402823e38 do 3.402823e38 |
double | od –1.79769313486232e308 do 1.79769313486232e308 |
decimal | od –79228162514264337593543950335 do 79228162514264337593543950335 |
char | Pojedynczy znak |
string | Łańcuch znaków typu char |
bool | true lub false |
Jak widzisz, mamy do dyspozycji całą gamę typów, zarówno stało-, jak i zmiennoprzecinkowych. Specyficznym typem danych jest bool, który może przybierać jedynie dwie wartości — true
lub false
. Jednakże jest on dość często wykorzystywany przez programistów, o czym przekonasz się w trakcie czytania tej książki. Przykład użycia typu bool:
bool MyBool;
MyBool = true;
Restrykcje w nazewnictwie
Nie jest do końca prawdą, że nazwa zmiennej może być zupełnie dowolna. Niestety, istnieją pewne restrykcje, o których trzeba wiedzieć. Na przykład pierwszym znakiem nazwy zmiennej nie może być cyfra — nazwa ta musi rozpoczynać się od litery.
Nazwa zmiennej może jednak zawierać na początku _, ale już inne znaki, takie jak ( ) * & ^ % # @ ! / = + - [ } ] } ' " ; , . czy ?, nie są dozwolone.
Platforma .NET oraz sam język C# obsługują standard kodowania znaków Unicode, dlatego w nazwach zmiennych można używać polskich znaków lub jakichkolwiek innych wchodzących w skład Unicode:
byte _gżegżółka = 234;
Stałe
Stałe od zmiennych odróżnia to, że zawartość przydzielana jest jeszcze w trakcie pisania kodu i nie ulega późniejszym zmianom. Zawartości stałych nie można zmieniać w trakcie działania aplikacji. Raz przypisana wartość pozostaje w pamięci komputera aż do zakończenia działania aplikacji:
const double Version = 1.0;
Version = 2.0; // w tej linii kompilator wskaże błąd
Stałe deklarowane są prawie identycznie jak zmienne. Jedyna różnica to konieczność poprzedzenia deklaracji słowem kluczowym const
.
Do czego mogą przydać się stałe? Przykładowo, w stałej Version możesz przechowywać numer wersji swojej aplikacji. Taki numer wersji możesz wyświetlać w oknie „O programie” oraz w innych miejscach kodu źródłowego. Jeżeli uznasz, że należy zmienić numer wersji aplikacji, po prostu zmodyfikujesz zawartość stałej. Nie musisz każdorazowo zmieniać fragmentów kodu, w których wyświetlasz wersję programu.
Operacje na konsoli
Wiesz już, czym są aplikacje konsolowe. Nie posiadają one żadnych okien, kontrolek itp., interakcja z użytkownikiem jest więc słaba. Program może jedynie wypisywać tekst na konsoli (WriteLine()
) lub odczytać tekst wpisany przez użytkownika. Mogłeś zauważyć, że program, który napisaliśmy wcześniej, zamyka się zaraz po uruchomieniu i wyświetleniu tekstu. To dlatego, że nie nakazaliśmy mu czekać na „pozwolenie” użytkownika.
Metoda ReadLine()
umożliwia odczytanie tekstu wpisanego w oknie konsoli. Program zostaje wówczas wstrzymany do czasu naciśnięcia klawisza Enter. Napiszmy prostą aplikację, która pobierze od użytkownika jego imię, a następnie zapisze je w zmiennej. Kod źródłowy takiego programu prezentuje listing 3.1.
Listing 3.1. Program pobierający imię użytkownika
using System;
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Cześć, jak masz na imię?");
string name; // deklaracja zmiennej
name = Console.ReadLine(); // pobranie tekstu wpisanego przez użytkownika
Console.WriteLine("Miło mi " + name + ". Jak się masz?");
Console.ReadLine();
}
}
Możesz skompilować, a następnie uruchomić taką aplikację. Umożliwia ona wpisanie imienia, które następnie wyświetli. Przeanalizujmy ten program. Większość zawartych instrukcji powinna być już dla Ciebie znana, objaśnienia wymaga linia pobierająca imię użytkownika:
name = Console.ReadLine();
name to nazwa uprzednio zadeklarowanej zmiennej typu string
. Taka konstrukcja nakazuje przypisanie do zmiennej wartości tekstu wpisanego i pobranego w oknie konsoli. Innymi słowy, zmienna name zawiera imię pobrane przez użytkownika.
Kolejna linia kodu, którą trzeba objaśnić, to:
Console.WriteLine("Miło mi " + name + ". Jak się masz?");
Jak widzisz, łańcuch tekstu został tutaj rozdzielony — za pomocą znaków + połączono go w jedną całość. W trakcie wykonywania instrukcji w miejsce name zostanie podstawione imię pobrane od użytkownika.
Metody klasy Console
Do tej pory poznałeś dwie metody klasy Console
— WriteLine
oraz ReadLine
. W rzeczywistości klasa udostępnia więcej metod, z których najważniejsze zaprezentowałem w tabeli 3.2.
Tabela 3.2. Wybrane metody klasy Console
Metoda | Opis |
---|---|
Beep | Umożliwia odegranie dźwięku z głośnika systemowego. |
Clear | Czyści ekran konsoli. |
ResetColor | Ustawia domyślny kolor tła oraz tekstu. |
SetCursorPosition | Ustawia pozycję kursora w oknie konsoli. |
SetWindowPosition | Umożliwia ustawienie położenia okna konsoli. |
SetWindowSize | Umożliwia określenie rozmiaru okna konsoli. |
Oto prosta aplikacja demonstrująca wykorzystanie tych metod:
using System;
class Program
{
static void Main(string[] args)
{
// ustaw rozmiar okna
Console.SetWindowSize(60, 30);
// ustaw położenie tekstu
Console.SetCursorPosition(10, 10);
Console.WriteLine("Hello World!");
Console.ReadLine();
Console.Clear();
Console.Beep();
}
}
Należy zaznaczyć, że parametry tych metod nie oznaczają rozmiaru przedstawionego w pikselach! Przykładowo, pierwszym argumentem metody SetWindowSize
jest szerokość okna konsoli wyrażona w ilości kolumn tekstu. Natomiast drugi parametr określa wysokość okna wyrażoną w ilości wierszy (jedna linia tekstu równa się jednemu wierszowi).
Właściwości klasy Console
Poznałeś już pojęcie metoda klasy, potrafisz korzystać z metod, jak również przypisywać oraz odczytywać dane ze zmiennych. Właściwości klasy umożliwiają określenie pewnych zachowań danej klasy.
Przykładowo, klasa Console
posiada właściwość BackgroundColor, która umożliwia nadanie koloru tła w oknie konsoli. Ta sama klasa posiada właściwość Title, do której możemy przypisać tekst, jaki będzie umieszczony na belce tytułowej okna konsoli.
Myśl o właściwościach jak o zwykłych zmiennych, do których można przypisywać dane, a także je odczytywać. Oto przykładowe użycie dwóch wspomnianych właściwości:
using System;
class Program
{
static void Main(string[] args)
{
// nadanie wartości dla właściwości
Console.Title = "Hello World";
// określenie koloru tła (ciemny żółty)
Console.BackgroundColor = ConsoleColor.DarkYellow;
// odczyt wartości właściwości
Console.WriteLine("Tytuł tego okna to: " + Console.Title);
Console.ReadLine();
}
}
Po uruchomieniu takiego programu w oknie konsoli wypisany zostanie tekst: Tytuł tego okna to Hello World
. Na samym początku przypisałem wartość do właściwości Title (wartość Hello World
), a następnie określiłem tło tekstu. Niezrozumiała dla Ciebie może być operacja określenia tła tekstu:
Console.BackgroundColor = ConsoleColor.DarkYellow;
Dlaczego robimy to w ten, a nie inny sposób? Otóż właściwość BackgroundColor wymaga, aby przypisywane do niej dane były typu ConsoleColor
. Tak jak w przypadku zmiennych: zmiennej typu string
nie możemy przypisać liczby i odwrotnie.
ConsoleColor
nie jest klasą, a tzw. wyliczeniem. To pojęcie wyjaśnię w dalszej części książki.
Operatory
W każdym języku programowania wysokiego poziomu istnieją symbole służące do sterowania programem. Takie znaki nazywane są przez programistów operatorami. W trakcie czytania tej książki miałeś okazję zastosować operator przypisania (=). W rzeczywistości istnieje jednak o wiele więcej operatorów, które należy omówić.
Symbole operatorów w języku C# są praktycznie identyczne z tymi z języka C++ oraz Java. Jeżeli więc programowałeś wcześniej w jednym z tych języków, nie będziesz miał problemu z zapamiętaniem poszczególnych symboli.
Operatory porównania
Czynność porównywania stosujemy w naszym codziennym życiu. Jesteśmy w stanie na przykład określić, która z dwóch osób jest wyższa. Na podstawie liczby koni mechanicznych silników jesteśmy w stanie ocenić, który z nich ma większą moc.
W matematyce również obowiązują takie znaki porównania jak > (znak większości) i < (znak mniejszości). Identyczne symbole są wykorzystywane w językach programowania (patrz tabela 3.3).
Tabela 3.3. Operatory porównania
Operator | Język C# |
---|---|
Nierówności | != |
Równości | == |
Większości | > |
Mniejszości | < |
Większe lub równe | >= |
Mniejsze lub równe | <= |
W C# możemy porównywać wartości typów — np. wartości zmiennych typu int
. Przykłady porównywania wartości pokażę podczas omawiania tzw. instrukcji warunkowych w dalszej części tego rozdziału.
Operatory arytmetyczne
Nauka, jaką jest matematyka, dawno temu wykształciła umowne symbole opisujące pewne działania, jak np. dodawanie czy odejmowanie. Oczywiście komputer jako maszyna umożliwia wykonywanie tych czynności w bardzo prosty sposób przy wykorzystaniu symboli identycznych jak w matematyce (tabela 3.4).
Tabela 3.4. Operatory arytmetyczne
Operator | Język C# |
---|---|
Dodawanie | + |
Odejmowanie | - |
Mnożenie | * |
Dzielenie rzeczywiste | / |
Dzielenie całkowite | / |
Reszta z dzielenia | % |
Można tu zauważyć pewną różnicę w porównaniu z tym, czego uczyliśmy się w szkole podstawowej. W szkole bowiem jako znaku mnożenia używaliśmy kropki, natomiast komputerowym symbolem mnożenia jest gwiazdka (*). To samo dotyczy dzielenia — w szkole znak :, a w C# symbol /.
Jak widzisz, w tabeli 3.4 wyszczególniłem pozycje: „Dzielenie rzeczywiste” oraz „Dzielenie całkowite”. Te operacje realizuje ten sam operator, ale to, czy zostanie zachowana reszta z dzielenia, czy też nie, zależy od typu danych. Spójrz na poniższy przykład:
double d = 5.0;
int i = 5;
Console.WriteLine(5.0 / 5); // 1
Console.WriteLine(-i / 2); // -2
Console.WriteLine(-d / 2); // -2.5
W komentarzach zawarłem rezultat zwracany przez te operacje. Zwróć uwagę, iż zmienna i jest typu int
i pomimo że 5 dzielone przez 2 wynosi 2.5, reszta z dzielenia zostanie usunięta.
Warto wspomnieć kilka słów o operatorze dodawania (+). Jak zapewne zwróciłeś uwagę, we wcześniejszej części tego rozdziału był on używany również do łączenia ze sobą dwóch łańcuchów. Oto inne przykłady wykorzystania operatora:
Console.WriteLine(2 + 2); // rezultat: 4
Console.WriteLine(2 + "2"); // rezultat: 22
Console.WriteLine(2 + .2); // rezultat: 2.2
Jak widzisz, operator działa różnie, zależnie od sytuacji, w jakiej został użyty.
Operator inkrementacji oraz dekrementacji
W językach takich jak C#, C/C++, Java czy PHP istnieją bardzo wygodne operatory pozwalające na szybką inkrementację (zwiększenie wartości danej zmiennej) oraz dekrementację (zmniejszenie wartości danej zmiennej).
Umieszczając przed zmienną lub po niej symbol ++, powodujemy zwiększenie jej wartości o 1. Analogicznie operator -- zmniejsza zawartość zmiennej o 1. Spójrz na poniższy fragment programu:
int i = 5;
Console.WriteLine(i++);
Console.WriteLine(++i);
Jak myślisz, co zostanie wyświetlone w oknie konsoli po wykonaniu takiego kodu? Cyfra 5 oraz 7. Położenie operatora (przed lub po nazwie zmiennej) ma ogromne znaczenie. W pierwszym przypadku metoda WriteLine()
wyświetli wartość zmiennej i (czyli 5), a następnie zwiększy ją o 1. W drugim przypadku zwiększenie wartości zmiennej i nastąpi przed wysłaniem jej do metody WriteLine()
.
Oczywiście aby zwiększyć wartość danej zmiennej, można zastosować operator arytmetyczny wraz z przypisaniem:
int x = 5;
x = x + 1; // rezultat: 6
Operatory logiczne
Operatory logiczne często są nazywane operatorami boolowskimi (ang. Boolean operators). Wynika to z tego, że realizują one operacje właściwe dla algebry Boole’a.
Faktycznym zastosowaniem tych operatorów jest testowanie kilku warunków. Weźmy jakiś przykład z życia codziennego: „Jeżeli będę miał 18 lat i 20 tysięcy zł, kupię sobie samochód”. W tym zdaniu operatorem jest i. Do spełnienia kryterium (kupna samochodu) jest zatem niezbędne spełnienie łącznie dwóch warunków (posiadania 20 tysięcy zł oraz ukończenia 18 lat). Jeżeli któryś z tych warunków nie będzie prawdziwy — kryterium, czyli kupno samochodu, nie zostanie spełnione.
Podobny przykład można przenieść na platformę programową. Na przykład jeżeli zmienna X posiada wartość 20, a zmienna Y wartość 10, to zrób to i tamto. Takie przykłady będziemy realizować nieco dalej — tymczasem przyjrzyjmy się dostępnym w C# operatorom logicznym (tabela 3.5).
Tabela 3.5. Operatory logiczne
Operator | Język C# |
---|---|
Logiczne i | && |
Logiczne lub | || |
Zaprzeczenie | ! |
Operatory bitowe
Operatory bitowe oferują nieco bardziej zaawansowane działania na liczbach binarnych. Nie będziemy się tym zajmować — ważne, aby wiedzieć, że takie manipulacje są w C# możliwe, lecz wymagana jest do tego większa wiedza na temat działania komputera oraz różnych systemów liczbowych (dziesiętny, dwójkowy itp.).
Operatory bitowe prezentuje tabela 3.6.
Tabela 3.6. Operatory bitowe
Operator</td> | Język C# |
---|---|
Koniunkcja | & |
Zaprzeczenie | ~ |
Alternatywa | | |
Dysjunkcja | ^ |
Przesunięcie w lewo | << |
Przesunięcie w prawo | >> |
Operatory przypisania
Sprawa jest dosyć prosta. Przy pomocy tego operatora do zmiennej lub właściwości przypisujemy określone dane:
foo = 4;
bar = foo;
Po lewej stronie od znaku = musi znajdować się nazwa zmiennej, a po prawej — przypisywana wartość, która również może znajdować się w zmiennej.
Oprócz tego prostego operatora język C# udostępnia wiele innych, które umożliwiają przypisanie jakiejś wartości wraz z wykonaniem na niej danej czynności. Przykładowo:
x += 2;
Oznacza to: do wartości zmiennej x dodaj cyfrę 2. Równie dobrze można to wykonać w następujący sposób:
x = x + 2;
Jednak poprzednie rozwiązanie jest częściej używane i przejrzystsze.
Pozostałe operatory przypisania to: -=, *=, /=, %=, &=, |=, ^=, <<=, >>=.
Inne operatory
Naturalnie pokrótce omówione przeze mnie grupy operatorów to nie wszystko, co oferuje język C#. Na tym etapie nauki nie ma jednak sensu prezentowanie bardziej zaawansowanych operatorów, gdyż związane są one z pojęciami, których jeszcze nie omówiłem. Kolejne operatory będę omawiał w dalszej części książki, wraz z prezentowaniem nowych porcji zagadnień.
Instrukcje warunkowe
Przed chwilą była mowa o operatorach języka C#, jednak bez sensu byłoby opisywanie ich bez wzmianki o instrukcjach warunkowych. Są to konstrukcje, które służą do sprawdzania, czy dany warunek został spełniony. Jest to praktycznie podstawowy element języka programowania — dzięki instrukcjom warunkowym możemy odpowiednio zareagować na istniejące sytuacje i sterować pracą programu.
Przykładowo: użytkownik musi wpisać swoje imię na samym początku działania programu. Może się jednak zdarzyć, że użytkownik specjalnie lub omyłkowo wpisze liczbę. Jeżeli programista nie uwzględni tej możliwości i nie wprowadzi odpowiednich zabezpieczeń, może się to skończyć źle dla programu lub (w przypadku większych aplikacji) spowodować błędy związane z bezpieczeństwem systemu!
Ostatnimi czasy w przeglądarce Internet Explorer wykryto poważne błędy związane z bezpieczeństwem, ich powodem był… pasek adresów. Zagrożenie powstawało w chwili wpisania w pasku adresów odpowiedniego ciągu znaków. Oczywiste jest, jak ważne jest sprawdzanie danych dostarczanych przez użytkownika. Podstawowa zasada brzmi: nie wolno ufać danym podawanym aplikacji przez użytkownika — zawsze należy je sprawdzać przed dalszym działaniem programu.
Instrukcja if
Podstawową instrukcją warunkową w języku C# jest if. Słowo if oznacza w języku angielskim jeżeli. Ta instrukcja warunkowa umożliwia przetestowanie jakiegoś warunku (np. porównanie, czy wartość zmiennej X jest większa niż zmiennej Y) i zaprogramowanie odpowiedniej reakcji, jeżeli zostanie on spełniony.
Programując, używamy instrukcji warunkowych cały czas. Wyobraź sobie, że chcesz napisać program, który pyta użytkownika o imię. Jeżeli imię kończy się literą „a”, możemy z dużym prawdopodobieństwem stwierdzić, że jest ono kobiece. W takim wypadku program wyświetla na ekranie konsoli tekst: Witaj koleżanko!
. W przeciwnym wypadku wyświetlamy tekst: Witaj kolego!
. Do tego właśnie służą instrukcje warunkowe.
Oto przykładowy program wykorzystujący instrukcję warunkową if:
using System;
class Program
{
static void Main(string[] args)
{
int x = 5;
if (x == 5)
{
Console.WriteLine("Zmienna x ma wartość 5!");
}
Console.ReadLine();
}
}
W tym wypadku instrukcja warunkowa sprawdza, czy wartość zmiennej x równa się 5. Przypomnę, iż operator == służy do sprawdzania, czy obie wartości są sobie równe.
Ogólna budowa instrukcji if wygląda następująco:
if (warunek do spełnienia)
{
operacje do wykonania, jeżeli warunek jest prawdziwy;
}
Obowiązkowym elementem każdej instrukcji if są nawiasy okrągłe, w których musi znaleźć się warunek do sprawdzenia. Warunek może zostać spełniony (czyli wynik pozytywny) albo nie. Od tego zależy, czy wykonany zostanie kod znajdujący się pomiędzy klamrami.
Klamry w języku C# są odpowiednikiem bloku begin oraz end z języka Pascal.
Klamry nie są obowiązkowym elementem instrukcji if, aczkolwiek zalecanym ze względu na przejrzystość kodu. Jeżeli pomiędzy klamrami znajduje się tylko jedna instrukcja do wykonania, spokojnie można je usunąć:
if (x == 5)
Console.WriteLine("Zmienna x ma wartość 5!");
Generalnie jednak zalecam stosowanie klamer niezależnie od tego, ile instrukcji mamy do wykonania.
Klamry w języku C# pełnią rolę swego rodzaju pojemnika; określają np. początek oraz zakończenie metody czy klasy. Tak samo oznaczają rozpoczęcie oraz zakończenie bloku instrukcji warunkowej.
Instrukcje zagnieżdżone
Instrukcje if można dowoli zagnieżdżać:
if (x == 5)
{
Console.WriteLine("Zmienna x ma wartość 5!");
if (y >= 10)
{
Console.WriteLine("Drugi warunek również spełniony");
}
}
Jeżeli pierwszy warunek zostanie spełniony, wykonany zostanie kod wyświetlający wiadomość na ekranie konsoli. Następnie sprawdzony zostanie kolejny warunek i — po raz kolejny — jeżeli zostanie on spełniony, wykonany zostanie odpowiedni kod.
Uwaga! Każda klamra, która zostanie otwarta ({), musi być zakończona w dalszej części programu (}).
Kilka warunków do spełnienia
Zaprogramujmy teraz coś trudniejszego. Przypomnij sobie temat operatorów, a konkretnie operatorów logicznych. Umożliwiają one sprawdzanie dwóch warunków w jednej instrukcji warunkowej. Przykładowo, chcemy wyświetlić na konsoli tekst Dobry wieczór koleżanko
, ale tylko wtedy, gdy użytkownik podał kobiece imię. Dodatkowo musimy sprawdzić aktualną godzinę, powiedzmy, witamy się zwrotem dobry wieczór, jeżeli jest po godzinie 18:00.
Mamy więc dwa problemy. Po pierwsze musimy sprawdzić, jaka jest ostatnia litera imienia podanego przez użytkownika. Po drugie musimy pobrać aktualną godzinę. Być może wybiegam nieco w przyszłość i nie powinienem na tym etapie prezentować tak trudnych przykładów, ale pragnę urozmaicić Ci naukę.
Pierwszy problem można rozwiązać następująco. Otóż przy pomocy symbolu [ oraz ] możemy odwołać się do dowolnego miejsca w łańcuchu tekstowym (np. zmiennej typu string
). Przykładowo:
string Foo = "Adam";
Console.WriteLine(Foo[1]);
Znaki w łańcuchu tekstowym numerowane są od zera. Tak więc imię Adam ma długość 4 znaków, z tym że pierwszy z nich posiada indeks 0:
-----------------
Indeks | Numer
A | 0
d | 1
a | 2
m | 3
Powyższy kod wyświetli więc na konsoli literę d. W naszym programie musimy więc sprawdzić, jaka jest ostatnia litera łańcucha (naszego imienia). No dobrze, ale skąd program ma wiedzieć, jak długie imię podał użytkownik? Nie wie! Dlatego musimy użyć właściwości Length
, która pobiera i zwraca długość danego łańcucha (długość liczona w ilości znaków w tekście). Oto rozwiązanie problemu:
string Foo;
int FooLength;
Foo = "Adam";
FooLength = Foo.Length; // mamy długość łańcucha
Console.WriteLine(Foo[FooLength -1]);
W takim programie zmienna FooLength będzie zawierać długość łańcucha Foo. Ponieważ chcemy pobrać ostatni znak w tekście, od tej długości musimy odjąć cyfrę 1.
Zajmijmy się drugim problemem. Jak pobrać aktualną godzinę? W tym celu musimy skorzystać z dobrodziejstw klas .NET Framework, a konkretnie z klasy DateTime
. Posiada ona właściwość Now, która zawiera informacje o aktualnej godzinie, dniu, tygodniu itd. Ta z kolei posiada kolejną właściwość Hour, która zwraca aktualną godzinę w formie liczby całkowitej typu int
.
Jak wspomniałem, w instrukcji warunkowej za jednym razem, możemy porównywać kilka warunków. Spójrz na poniższy przykład:
string imie = Console.ReadLine();
if (imie[imie.Length - 1] == 'a' && DateTime.Now.Hour >= 18)
{
Console.WriteLine("Dobry wieczór koleżanko!");
}
Użyłem tutaj operatora &&
(logiczne i) do porównania dwóch warunków. Innymi słowy, sprawdzamy, czy ostatnią literą zmiennej imie jest a oraz czy jest po godzinie 18:00. Jeżeli te dwa warunki zostaną spełnione, dopiero wówczas będzie mógł zostać wykonany odpowiedni kod instrukcji (w tym wypadku — wyświetlenie komunikatu w oknie konsoli).
Zwróć uwagę, że w ostatnim przykładzie zamiast cudzysłowu użyłem apostrofów, aby sprawdzić, czy ostatnim znakiem tekstu jest litera a. Więcej informacji na temat łańcuchów znajdziesz w rozdziale 9.
W taki sposób możemy używać dowolnych operatorów logicznych. Przykładowo, możesz zastąpić && operatorem ||:
if (imie[imie.Length - 1] == 'a' || DateTime.Now.Hour >= 18)
{
// dalszy kod
}
Teraz do wykonania kodu z ciała instrukcji wystarczy spełnienie jednego z dwóch warunków. Innymi słowy: albo imię będzie się kończyć literą a, albo jest po godzinie 18:00.
Zastosowanie nawiasów
Na lekcjach matematyki w szkole podstawowej uczyłeś się zapewne, że nawiasy okrągłe mają najmniejsze znaczenie i ustępują ważnością nawiasom kwadratowym oraz klamrom. Zapomnij o tej regule w trakcie programowania. W języku C# w instrukcjach warunkowych posługujemy się jedynie nawiasami okrągłymi. Nie oznacza to jednak, że nie możemy sterować priorytetem operacji. Spójrz na przykład:
int x, y, z;
x = 2;
y = 4;
z = 10;
if ((x == 2 || y == 4) && z > 20)
{
Console.WriteLine("Yes, yes, yes!");
}
Zastosowałem tutaj podwójny nawias. Nakazałem tym samym, aby najpierw sprawdzony został warunek z wewnętrznego nawiasu. OK, jest on spełniany, ponieważ zmienna x zawiera cyfrę 2 i to wystarczy. Następnie sprawdzany jest drugi warunek, który nie zostaje spełniony, ponieważ zmienna z ma wartość 10, a wymagane jest, aby była to liczba większa od 20.
Ponieważ operator &&
wymaga, aby obydwa warunki zostały spełnione, kod z ciała instrukcji if nie zostanie wykonany.
Podobnie jest w przypadku obliczeń matematycznych. Jeżeli chcemy wymusić ważność danego obliczenia, stosujemy nawiasy:
x = (2 + 2) * 2;
Console.WriteLine(x);
Wskutek takiego działania najpierw zostanie obliczone działanie w nawiasie (czyli dodawanie), a następnie jego wynik (czyli 4) zostanie pomnożony przez 2.
Słowo kluczowe else
Powróćmy jeszcze raz do poprzedniego przykładu, w którym prezentowałem sposób na pobranie ostatniej litery łańcucha oraz aktualnej godziny. Jeżeli warunek został spełniony, wyświetlaliśmy tekst Dobry wieczór koleżanko
. Co zrobić w przypadku, gdy chcemy wyświetlić tekst Dzień dobry koleżanko
, jeżeli jest przed godziną 18:00? Możemy zastosować drugą instrukcję if:
// warunek sprawdza, czy jest po godzinie 18:00
if (DateTime.Now.Hour >= 18)
{
Console.WriteLine("Dobry wieczór koleżanko!");
}
// warunek sprawdza, czy jest przed godziną 18:00
if (DateTime.Now.Hour < 18)
{
Console.WriteLine("Dzień dobry koleżanko!");
}
Łatwiej w takim wypadku korzystać z instrukcji else
, która oznacza w przeciwnym wypadku.
Jest ona dość często wykorzystywana przez programistów:
// warunek sprawdza, czy jest po godzinie 18:00
if (DateTime.Now.Hour >= 18)
{
Console.WriteLine("Dobry wieczór koleżanko!");
}
// warunek sprawdza, czy jest przed godziną 18:00
else
{
Console.WriteLine("Dzień dobry koleżanko!");
}
Jeżeli warunek w instrukcji if
nie zostanie spełniony, wykonany zostanie kod z ciała instrukcji else
. Na listingu 3.2 zaprezentowany został pełny kod programu.
Listing 3.2. Kod programu stosującego instrukcje warunkowe
using System;
class Program
{
static void Main(string[] args)
{
string imie;
Console.WriteLine("Cześć! Jak masz na imię?");
imie = Console.ReadLine();
// sprawdzamy, czy imię jest kobiece
if (imie[imie.Length - 1] == 'a')
{
// warunek sprawdza, czy jest po godzinie 18:00
if (DateTime.Now.Hour >= 18)
{
Console.WriteLine("Dobry wieczór koleżanko!");
}
// warunek sprawdza, czy jest przed godziną 18:00
else
{
Console.WriteLine("Dzień dobry koleżanko!");
}
}
else
{
Console.WriteLine("Cześć Kolego!");
}
Console.ReadLine();
}
}
Jak widzisz, mamy tutaj kilka instrukcji warunkowych if
oraz else
zagnieżdżonych w sobie, co jest oczywiście dozwolone. Jeżeli użytkownik poda imię męskie, pierwszy warunek nie zostanie spełniony, czyli zostanie wykonany kod z ciała instrukcji else
(na konsoli ujrzymy tekst Cześć Kolego!
).
Instrukcja else if
Istnieje możliwość połączenia instrukcji if
i else
. Przypuśćmy, że chcemy wyświetlić wiadomość w oknie konsoli w zależności od aktualnej godziny. Spójrz na poniższy fragment kodu:
int X = DateTime.Now.Hour;
if (X == 12)
{
Console.WriteLine("Jest południe!");
}
else if (X > 12 && X < 21)
{
Console.WriteLine("No cóż... już po 12:00");
}
else if (X >= 21)
{
Console.WriteLine("Oj... toż to środek nocy");
}
else
{
Console.WriteLine("Dzień dobry");
}
Jeżeli pierwsza instrukcja nie zostanie spełniona, program przejdzie do sprawdzania kolejnej. Jeżeli i ta nie zostanie spełniona, sprawdzi kolejną i jeszcze następną. W ostateczności program wykona kod z ciała instrukcji else
.
Instrukcja switch
Jest to kolejna instrukcja warunkowa języka C#. Umożliwia sprawdzanie wielu warunków.
Spójrz przez chwilę na poprzedni przykład. Połączona instrukcja if-else if sprawdzała wartość zmiennej X i wykonywała odpowiedni kod w zależności od jej wartości. Użycie instrukcji switch
to dobry pomysł na zastąpienie wielu instrukcji if
.
Składnia takiej instrukcji jest dość specyficzna. Spójrz na poniższy przykład:
int X = 10;
switch (X)
{
case 1:
// kod nr 1
break;
case 5:
// kod nr 2
break;
}
Sprawdzamy tutaj wartość zmiennej X. Jeżeli ma ona wartość 1, wykonujemy odpowiedni kod i kończymy działanie instrukcji warunkowej. Składnia instrukcji switch
jest dość specyficzna. Bardzo ważnym elementem jest słowo kluczowe break
, które nakazuje „wyskoczenie” z instrukcji warunkowej, jeżeli warunek zostanie spełniony. Przykładowo:
int Mandat = 50;
switch (Mandat)
{
case 10:
Console.WriteLine("10 zł mogę zapłacić");
break;
case 20:
Console.WriteLine("Oj, 20 zł to troszkę dużo");
break;
}
Jeżeli wartość zmiennej Mandat wynosi 10, zostanie wykonany odpowiedni kod (w tym wypadku wyświetlenie komunikatu), po czym instrukcja switch zostanie zakończona (pozostałe warunki po słowach kluczowych case
nie będą sprawdzane). Usunięcie słowa break spowoduje błąd przy kompilacji: Control cannot fall through from one case label ('case 10:') to another
.
Wartość domyślna
W ostatnim przykładzie możesz zauważyć, iż zmiennej Mandat przypisałem wartość 50. W takim wypadku żaden z warunków nie zostanie spełniony, ponieważ w instrukcji sprawdzamy jedynie, czy wartością jest 10 lub 20. W języku C# możemy użyć słowa kluczowego default
, aby odpowiednio zareagować, gdy żaden ze wcześniejszych warunków nie zostanie spełniony:
int Mandat = 50;
switch (Mandat)
{
case 10:
Console.WriteLine("10 zł mogę zapłacić");
break;
case 20:
Console.WriteLine("Oj, 20 zł to troszkę dużo");
break;
default:
Console.WriteLine("Niezidentyfikowana suma");
break;
}
Po uruchomieniu takiego programu na ekranie konsoli wyświetlony zostanie tekst: Niezidentyfikowana suma.
Instrukcja goto
Słowo kluczowe break
nakazuje zakończenie instrukcji switch. Istnieje możliwość przeskoczenia do innej etykiety case
lub default
przy pomocy instrukcji goto
. Oto przykład:
int Mandat = 50;
switch (Mandat)
{
case 10:
Console.WriteLine("10 zł mogę zapłacić");
goto case 20;
case 20:
Console.WriteLine("Oj, 20 zł to troszkę dużo");
break;
default:
Console.WriteLine("Niezidentyfikowana suma");
goto case 10;
}
Przeanalizujmy taki kod. Ponieważ wartość zmiennej Mandat to 50, wykonany zostanie blok z etykiety default
. Zawarty w niej kod nakazuje przeskok do etykiety case 10
, a ten z kolei do case 20
. Wskutek tak sformułowanego kodu na ekranie konsoli zostanie wyświetlony tekst:
Niezidentyfikowana suma
10 zł mogę zapłacić
Oj, 20 zł to troszkę za dużo
Uwaga! W ten sposób można „zapętlić” program. Programiści określają tym mianem sytuację, w której w programie nie znajdzie się instrukcja nakazująca zakończenie danego kodu, co skutkuje ciągłym jego wykonywaniem. Oto przykład prezentujący taką sytuację:
case 10:
Console.WriteLine("10 zł mogę zapłacić");
goto case 20;
case 20:
Console.WriteLine("Oj, 20 zł to troszkę dużo");
goto case 10;
default:
Console.WriteLine("Niezidentyfikowana suma");
goto case 10;
W każdej etykiecie znajduje się instrukcja goto
, która nakazuje skok do innej etykiety case
.
Pętle
W świecie programistów pod słowem pętla kryje się pojęcie oznaczające wielokrotne wykonywanie tych samych czynności. Jest to bardzo ważny element każdego języka programowania, dlatego konieczne jest zrozumienie istoty jego działania.
Wyobraźmy sobie sytuację, w której trzeba kilka razy wykonać tę samą czynność. Może to być na przykład wyświetlenie kilku linii tekstu. Zamiast wielokrotnie używać Console.WriteLine()
, można skorzystać z pętli. Za chwilę przekonamy się, że zastosowanie pętli w programie wcale nie jest trudne.
Pętla while
Pętla while
umożliwia wielokrotne wykonywanie tego samego kodu, dopóki nie zostanie spełniony warunek jej zakończenia. W tym momencie musimy ponownie posłużyć się operatorami logicznymi oraz porównania. Ogólna budowa pętli while
wygląda następująco:
while (warunek zakończenia)
{
// kod do wykonania
}
Napiszmy jakiś prosty program, który będzie wyświetlał w pętli dowolny tekst, powiedzmy — dziesięciokrotnie. Spójrz na poniższy kod:
using System;
class Program
{
static void Main(string[] args)
{
int X = 1;
while (X <= 10)
{
Console.WriteLine("Odliczanie..." + X);
++X;
}
Console.Read();
}
}
Warunek w pętli while sprawdza, czy wartość zmiennej X jest mniejsza lub równa liczbie 10. Jeżeli tak, zostanie wykonany kod z ciała pętli, a następnie jej kolejna iteracja.
Iteracją nazywamy kolejne wykonanie pętli.
Zwróć uwagę na to, że oprócz wyświetlania stosownego komunikatu za każdym razem zwiększamy wartość zmiennej X o 1. Gdybym pominął ten kod, doszłoby do zapętlenia programu, ponieważ wartość zmiennej X stale wynosiłaby 1, tak więc warunek zakończenia pętli (czyli wartość zmiennej X większa od 1) nigdy nie zostałby spełniony.
Zaprezentuję teraz nieco trudniejszy przykład z zastosowaniem pętli while
oraz instrukcji warunkowej if
. Załóżmy, że chcemy, aby użytkownik na starcie programu podał imię kobiece. Musimy sprawdzić, czy rzeczywiście mamy do czynienia z takim imieniem, a jeżeli nie — wyświetlić odpowiedni komunikat, po czym ponownie poprosić o podanie imienia.
Na samym początku zadeklarujmy zmienną typu bool
, która na starcie będzie miała wartość false
:
bool Done = false;
Jeżeli użytkownik poda prawidłowe imię, zmienimy wartość tej zmiennej na true
(prawidłowe imię); warunek zakończenia pętli while to podanie prawidłowego imienia:
while (Done == false)
Taki warunek zakończenia pętli oznacza ni mniej, ni więcej, tylko: kontynuuj działanie pętli, dopóki wartość zmiennej Done wynosi false
. Listing 3.3 zawiera pełny kod źródłowy programu.
Listing 3.3. Zastosowanie pętli while
using System;
class Program
{
static void Main(string[] args)
{
bool Done = false;
string name;
while (Done == false)
{
Console.WriteLine("Podaj imię kobiece: ");
name = Console.ReadLine();
if (name[name.Length - 1] == 'a')
{
Done = true; // użytkownik podał prawidłowe imię
Console.WriteLine("Cześć " + name);
}
else
{
Console.WriteLine("Podałeś imię męskie :[");
}
}
Console.Read();
}
}
Możesz uruchomić taką aplikację, aby sprawdzić jej działanie. Pozostałe elementy zastosowane w tym kodzie powinny być Ci znane z lektury tego rozdziału.
Warunki logiczne w pętlach
Programując, musisz przestawić się na myślenie logiczne. Musisz analizować program, myśląc, jak go zanalizuje komputer. Należy wiedzieć, że każdy warunek, czy jest to warunek zakończenia pętli, czy warunek w instrukcji warunkowej, może być albo spełniony, albo nie. Są tylko dwie możliwości — tak lub nie.
W programie na listingu 3.3 warunek zakończenia pętli był taki:
while (Done == false)
A właściwie trafniejszym określeniem byłoby: jest to warunek kontynuowania pętli. Można go jednak zapisać również w inny sposób — np.:
while (Done != true)
Oznacza on dokładnie to samo, nakazuje kontynuowanie pętli, jeśli wartość Done jest różna od true
. Skrócony zapis takiego warunku z zastosowaniem operatorów logicznych:
while (!Done)
Przypominam, iż operator !
oznacza logiczne nie. Z punktu widzenia aplikacji oznacza to: kontynuuj działanie pętli, dopóki wartość zmiennej Done jest różna od true
.
Pętla do-while
Pętla do-while
jest bardzo podobna do while
. Właściwie oprócz budowy różni je tylko jeden szczegół. Mianowicie pętla do-while
zostanie wykonana co najmniej raz, ponieważ warunek jej zakończenia jest sprawdzany po wykonaniu kodu z jej ciała. Porównaj dwie pętle przedstawione poniżej:
int X = 20;
while (X < 20)
{
Console.WriteLine("Pętla while: to nie powinno być wykonane");
X++;
}
do
{
Console.WriteLine("Pętla do-while, no cóż — to się wykona");
X++;
}
while (X < 20);
Przypisałem zmiennej X wartość 20. W pętli while
warunkiem kontynuowania jest to, aby wartość zmiennej X była mniejsza od 20. Taka pętla nie zostanie wykonana, ponieważ warunek nie zostanie spełniony.
Warunek zakończenia do-while
jest taki sam jak w przypadku pierwszej pętli. Mimo tego po uruchomieniu programu kod z pętli zostanie wykonany raz.
Budowa do-while jest podobna do zwykłej pętli while
:
do
{
// kod pętli
}
while (warunek zakończenia);
Pętla for
Dla początkujących programistów pętla for
może wydać się najtrudniejsza do opanowania, posiada bowiem najtrudniejszą konstrukcję. Ta pętla różni się od wspomnianych wcześniej tym, iż należy zadeklarować ilość jej iteracji. Używamy jej zawsze wtedy, gdy wiemy, ile powtórzeń danej pętli chcemy wykonać.
Oto przykład prostej pętli for
:
for (int i = 1; i <= 10; i++)
{
Console.WriteLine("Odliczanie..." + i);
}
Kod z ciała pętli zostanie wykonany dziesięciokrotnie. Budowę pętli można zaprezentować następująco:
for (wartość_startowa; wartość_końcowa; licznik_pętli)
{
// kod pętli
}
Każdej pętli towarzyszy tzw. licznik pętli. Jest to zmienna, którą można zadeklarować w nagłówku pętli lub przed jej użyciem:
int i;
for (i = 1; i <= 10; i++) // dobrze
for (int i = 1; i <= 10; i++) // dobrze
Podsumujmy:
*Po każdej iteracji zwiększany zostaje licznik pętli (czyli wartość zmiennej i).
*Początkowa wartość zmiennej i jest przypisywana w nagłówku (w naszym przykładzie początkową wartością jest 1).
*W nagłówku określamy warunek zakończenia działania pętli (pętla będzie kontynuowana, dopóki wartość zmiennej i jest mniejsza lub równa 10).
W większości przypadków licznik pętli jest zwiększany każdorazowo o 1. Oczywiście jeżeli istnieje potrzeba, można to zmienić. Np. poniższy przykład zwiększa licznik o 2, wskutek czego zostanie wykonanych jedynie 10 iteracji:
for (int i = 1; i <= 20; i += 2)
{
Console.WriteLine("Odliczanie..." + i);
}
Odliczanie od góry do dołu
W prezentowanych przykładach zawsze zwiększałem licznik pętli przy pomocy operatora ++
. Nic nie stoi na przeszkodzie, aby użyć innego operatora:
for (int i = 20; i > 0; i--)
{
Console.WriteLine("Odliczanie..." + i);
}
Wartością startową dla tej pętli jest liczba 20. Pętla będzie wykonywana, dopóki wartość zmiennej i będzie większa od 0. Każda iteracja spowoduje zmniejszanie licznika o 1.
Parametry opcjonalne
Zwróć uwagę, że poszczególne informacje w nagłówku pętli są oddzielone od siebie znakiem średnika.
Pętla for w języku C# jest na tyle elastyczna, że pozwala na pominięcie dowolnych elementów nagłówka.
Oto przykład:
for (int i = 20; i > 0; )
{
Console.WriteLine("Odliczanie..." + i);
i -= 2;
}
Zwróć uwagę, że licznik pętli zmniejszamy w jej ciele. W nagłówku ta informacja została pominięta.
Istnieje możliwość pominięcia jakiegokolwiek elementu z nagłówka:
for (; ; )
W ten sposób tworzymy tzw. pętlę forever, która będzie wykonywana w nieskończoność.
Instrukcja break
Zastosowanie słowa kluczowego break
poznałeś przy okazji omawiania instrukcji switch
. Ta instrukcja ma również inne zastosowanie w połączeniu z pętlami — umożliwia ich natychmiastowe zakończenie (wyjście z nich).
Oto przykład zastosowania tego słowa kluczowego w połączeniu z pętlą for
, typu forever:
using System;
class Program
{
static void Main(string[] args)
{
int i = 0;
for (; ; )
{
++i;
Console.WriteLine("Odliczanie..." + i);
if (i == 20)
{
break;
}
}
Console.Read();
}
}
Wartość zmiennej i jest zwiększana po każdej iteracji pętli. Mamy również instrukcję warunkową, która porównuje wartość zmiennej i. Jeżeli osiągnie ona wartość 20, następuje zakończenie pętli.
Instrukcja continue
Instrukcja break
umożliwia natychmiastowe wyskoczenie z pętli, a continue
— przeskoczenie do następnej iteracji. Spójrz na poniższy listing:
using System;
class Program
{
static void Main(string[] args)
{
int i = 0;
for (; ; )
{
++i;
// pomijamy wartości parzyste
if (i % 2 == 0)
{
continue;
}
Console.WriteLine("Odliczanie..." + i);
// pomijamy wartości parzyste,
// warunkiem jest osiągnięcie wartości 101
if (i == 101)
{
break;
}
}
Console.Read();
}
}
Zastosowałem pętle typu forever, w której licznik jest zwiększany w jej ciele. Zastosowałem również instrukcję warunkową, która sprawdza, czy mamy do czynienia z parzystą wartością zmiennej i. Jeżeli tak — program pomija wykonywanie dalszego kodu i przeskakuje do kolejnej iteracji.
Dla przypomnienia: operator % zwraca resztę z dzielenia. Jeżeli po podzieleniu danej liczby przez dwa reszta z dzielenia wynosi 0, mamy do czynienia z liczbą parzystą.
Operator warunkowy
Jeżeli jesteśmy przy temacie instrukcji warunkowych, warto wspomnieć o operatorze warunkowym ?:
, który przypomina instrukcję if-else
. Często zastosowanie tego operatora zwiększa czytelność kodu przy prostych instrukcjach.
Budowa tego operatora jest dość specyficzna:
warunek ? (pierwsze wyrażenie) : (drugie wyrażenie);
Jeżeli warunek zostanie spełniony, rezultatem takiej operacji jest wyrażenie po znaku ?
. W przeciwnym wypadku rezultatem jest wyrażenie po znaku :
.
Oto przykład zastosowania tego operatora:
string S;
S = 10 > 20 ? "To nie zostanie wyświetlone nigdy" : "To zostanie wyświetlone";
Console.WriteLine(S);
W zależności od wyniku warunku do zmiennej S przypisany zostanie dany tekst. W tym przykładzie rezultat będzie zawsze ten sam, ponieważ liczba 10 nie jest większa od 20. Wskutek tego do zmiennej S przypisany zostanie tekst To zostanie wyświetlone
.
Oczywiście w warunku można stosować operatory logiczne, tak jak w zwykłej instrukcji warunkowej if
:
string S, S1, S2;
int X, Y;
X = 10;
Y = 10;
S1 = "Tak";
S2 = "Nie";
S = (X > 5 && Y == 10) ? S1 : S2;
Console.WriteLine(S);
Konwersja danych
Na początku tego rozdziału wspominałem o typach języka C#. Nie wspomniałem jednak o tym, iż niektóre typy są ze sobą niekompatybilne. Przykładowo, nie można do zmiennej typu int
przypisać zawartości zmiennej typu string
. Tak samo nie można do zmiennej typu int przypisać wartości zmiennej typu double
.
Aby poradzić sobie z tego typu problemem, możemy skorzystać z klasy Convert
, która udostępnia odpowiednie metody służące do konwersji danych. Oto przykładowy fragment programu:
string S;
int I;
S = "18";
I = Convert.ToInt32(S);
I *= 2;
S = Convert.ToString(I);
Console.WriteLine(S);
Mimo iż do zmiennej S przypisaliśmy liczbę, nadal jest to łańcuch otoczony znakami cudzysłowu. W kolejnej linii konwertujemy ten łańcuch do postaci liczby typu int
. W dalszej części kodu mnożymy uzyskaną wartość, aby ponownie skonwertować ją na typ string.
Tabela 3.7 zawiera metody służące do konwersji na różne typy danych
.
Tabela 3.7. Metody konwersji
Metoda | Opis |
---|---|
ToBoolean | Konwertuje podaną wartość na typ bool. |
ToByte | Konwertuje podaną wartość na typ byte. |
ToChar | Konwertuje podaną wartość na pojedynczy znak char. </td</tr> |
ToIn16 | Konwertuje na 16-bitową liczbę całkowitą. |
ToInt32 | Konwertuje na 32-bitową liczbę całkowitą. |
ToInt64 | Konwertuje na 64-bitową liczbę całkowitą. |
ToSingle | Konwertuje na wartość zmiennoprzecinkową. |
ToString | Konwertuje dane na łańcuch typu string. |
Rzutowanie
Rzutowanie to sposób na oszukanie kompilatora, który jednocześnie daje możliwość konwersji na różne typy danych. Oczywiście najprościej wytłumaczyć to na przykładzie. Spójrz na poniższy kod:
char C;
byte B;
C = 'A';
B = C;
Taki kod nie zostanie prawidłowo skompilowany, gdyż próbujemy do zmiennej typu byte
przypisać wartość zmiennej char
, a to są zupełnie różne typy danych! Rzutowanie to mechanizm udostępniany przez język programowania, polegający na zmianie typu danej zmiennej:
B = (byte)C;
Jak widzisz, składnia jest dość charakterystyczna. Przed nazwą zmiennej w nawiasach należy podać nazwę typu, na który będziemy rzutowali. W konsekwencji wywołania takiego kodu zmienna B będzie zawierała wartość 65, co odpowiada numerowi ASCII litery A.
Rzutowanie to również dobry sposób na zaokrąglanie, a raczej — obcinanie części liczb zmiennoprzecinkowych. Spójrz na poniższy fragment:
double d;
int i;
d = 454.54;
i = (int)d;
Wskutek działania takiego kodu do zmiennej i przypisana zostanie wartość 454.
Przykładowa aplikacja
Poznałeś już podstawowe elementy i mechanizmy języka programowania. Wystarczy to do napisania prostej gry. Zasady są proste. Komputer losuje liczbę, a zadaniem użytkownika jest odgadnięcie tej prawidłowej. Gra dwóch użytkowników, a po każdym „strzale” (turze) program podpowiada, czy należy celować wyżej, czy niżej.
Wykorzystamy pętlę for oraz instrukcje warunkowe. Zanim jednak przejdziemy do kodowania całego mechanizmu, należy pobrać od użytkowników ich imiona:
Console.Write("Podaj imię gracza nr 1: ");
player1 = Console.ReadLine();
Console.Write("Podaj imię gracza nr 2: ");
player2 = Console.ReadLine();
Zarówno player1, jak i player2 to zmienne typu string
.
Losowanie liczby to nieco trudniejsza sprawa. W .NET realizuje to klasa Random
, którą należy zainicjować. O tym jeszcze nie wspominałem, tak więc pozostawię na razie ten fragment bez komentarza:
// tworzenie nowej instancji klasy
Random RandomObj = new Random();
// wylosowanie liczby z zakresu 0-999
int X = RandomObj.Next(1000);
Cały kod źródłowy naszej gry zaprezentowałem na listingu 3.4.
Listing 3.4. Kod źródłowy programu
using System;
class Program
{
static void Main(string[] args)
{
// imiona graczy
string player1, player2;
// gra zakończona (true) czy nie (false)
bool Done = false;
// liczba podana przez użytkownika
int Number;
Console.Write("Podaj imię gracza nr 1: ");
player1 = Console.ReadLine();
Console.Write("Podaj imię gracza nr 2: ");
player2 = Console.ReadLine();
// tworzenie nowej instancji klasy
Random RandomObj = new Random();
// wylosowanie liczby z zakresu 0-999
int X = RandomObj.Next(1000);
// zmienna przechowuje imię gracza, który wykonuje „ruch”
string player = player1;
while (!Done)
{
// zamiana graczy
player = player == player1 ? player2 : player1;
Console.WriteLine(player + ", strzelaj!");
// należy przekonwertować tekst na liczbę
Number = Convert.ToInt32(Console.ReadLine());
if (X > Number)
{
Console.WriteLine("Strzelaj wyżej!");
}
else if (X < Number)
{
Console.WriteLine("Strzelaj niżej!");
}
else
{
Console.WriteLine("Gratulacje! Wygrał gracz " + player);
Console.Beep();
Done = true;
}
}
Console.Read();
}
}
Dyrektywy preprocesora
Preprocesor to mechanizm języka C#, który przetwarza kod źródłowy programu przed jego kompilacją. Dyrektywy preprocesora to specjalne słowa kluczowe, które analizowane są — jak sobie powiedzieliśmy — przed właściwą kompilacją.
Dzięki dyrektywom możemy wpłynąć na zawartość kodu, który będzie kompilowany. Podczas pisania programu cały czas testujemy jego działanie poprzez kompilowanie i uruchamianie go. W kodzie źródłowym możemy zawrzeć instrukcje, które pomocne nam będą w trakcie działania programu, np. w celu zidentyfikowania ewentualnych błędów. Ten dodatkowy kod możemy wyłączyć w momencie, gdy kompilujemy finalną wersję aplikacji gotową do dystrybucji.
Działanie dyrektyw preprocesora języka C# jest podobne do tej z języka C++.
Dyrektywy kompilatora poprzedzane są symbolem # i nie są zakańczane znakiem średnika. Np.:
#define DEBUG
Dyrektywa #define
służy do deklarowania symbolu, w tym przypadku — DEBUG
. O dyrektywie #define
możesz myśleć jak o stałych języka C#, zasada działania jest podobna.
Jeżeli używamy dyrektywy #define
, musi się ona znaleźć u góry kodu źródłowego, przed właściwymi instrukcjami języka C#.
Lista dyrektyw preprocesora języka C# jest następująca:
-
#if
-
#else
-
#elif
-
#endif
-
#define
-
#undef
-
#warning
-
#error
-
#line
-
#region
-
#endregion
-
#pragma
-
#pragma warning
-
#pragma checksum
Deklarowanie symboli
Jak wspomniałem, dyrektywa #define
służy do deklarowaniu symbolu, który jest odpowiednikiem stałych języka C#. Przy pomocy dyrektywy #if
możesz sprawdzać, czy dany symbol jest zadeklarowany, czy też nie:
#if DEBUG
Console.WriteLine("Symbol DEBUG jest zadeklarowany");
#endif
Symbole można deklarować również w trakcie kompilacji programu, używając np. kompilatora csc.exe.
Należy wówczas kompilować z poziomu linii poleceń, dołączając parametr /define:
csc.exe foo.cs /define DEBUG
Dyrektywa #undef
służy do usuwania symbolu. Ta dyrektywa również musi być zawarta na samej górze kodu źródłowego, jeszcze przed właściwymi instrukcjami programu.
Instrukcje warunkowe
Dyrektywy #if
, #elif
, #else
oraz #endif
służą do budowania instrukcji warunkowych preprocesora. Dzięki nim możemy ustalić, jaki kod zostanie skompilowany. Instrukcje warunkowe wykorzystywane są w połączeniu z wcześniej wspomnianymi symbolami.
Dyrektywa #elif
jest odpowiednikiem konstrukcji else if
z języka C#, natomiast #else
jest odpowiednikiem słowa kluczowego else
. Instrukcja warunkowa musi być zakończona dyrektywą #endif
.
Co ciekawe, w instrukcjach warunkowych preprocesora można używać operatorów logicznych. Oto przykład:
#define DEBUG
using System;
namespace FooApp
{
class Program
{
static void Main(string[] args)
{
#if (DEBUG && FOO)
Console.WriteLine("DEBUG i FOO są zadeklarowane");
#elif (DEBUG)
Console.WriteLine("DEBUG jest zadeklarowany");
#else
Console.WriteLine("Nie wiem nic");
#endif
Console.Read();
}
}
}
Błędy i ostrzeżenia
Dyrektywy #error
oraz #warning
służą do generowania błędów i ostrzeżeń w trakcie kompilacji programu. Rzadko są stosowane, najczęściej w połączeniu z instrukcjami warunkowymi:
#define DEBUG
using System;
namespace FooApp
{
class Program
{
static void Main(string[] args)
{
#if DEBUG
#warning Upss...
#error DEBUG jest zadeklarowane
#else
Console.WriteLine("Nie wiem nic");
#endif
Console.Read();
}
}
}
Po dyrektywie #error
lub #warning
należy wpisać komunikat błędu/ostrzeżenia, który zostanie wyświetlony w trakcie kompilacji programu.
Ciekawą dyrektywą jest natomiast #pragma warning
, która pozwala na wyłączenie wyświetlania niektórych ostrzeżeń. Przykładowo, deklaracja zmiennej i nieprzypisanie do niej żadnej wartości spowoduje wyświetlenie ostrzeżenia w trakcie kompilacji. Możemy temu zapobiec. Przykładowo:
#pragma warning disable 0168
int i; // <-- brak ostrzeżenia
#pragma warning restore 0168
int d; // <-- jest ostrzeżenie
Wraz z dyrektywą należy określić, czy wyłączamy pokazywanie jakiegoś błędu (disable), czy aktywujemy (restore). Należy również podać numer błędu.
Możemy jednorazowo wyłączyć lub włączyć pokazywanie wielu błędów. W takim przypadku numery błędów należy wymienić po przecinku. Dyrektywa #pragma warning disable
(bez podania numeru błędów) dezaktywuje wyświetlanie wszelkich ostrzeżeń.
Podsumowanie
Początki są zawsze trudne — czy to początki nauki języka obcego, czy nowa praca. Spotykamy się z rzeczami nowymi, do których musimy przywyknąć. Identycznie jest z nauką języka programowania. W tym rozdziale zasypałem Cię sporą dawką informacji na temat języka C#, które mogą być dla Ciebie nie do końca zrozumiałe. Niestety — na tym etapie należało wprowadzić kilka dość trudnych pojęć, którymi posługują się programiści, a które szczegółowo zostaną objaśnione w dalszej części książki.
Na podstawie wiedzy zdobytej w tym rozdziale jesteś już w stanie pisać proste aplikacje. Poznałeś bowiem podstawowe mechanizmy służące do sterowania pracą programu, takie jak instrukcje warunkowe oraz pętle. W dalszej części książki stopniowo będę zapoznawał Cię z trudniejszymi elementami języka C#.
.. [#] Funkcje WinAPI są umieszczone w bibliotekach DLL. Teoretycznie więc mogłyby istnieć dwie funkcje o tej samej nazwie, tyle że w różnych bibliotekach DLL. Mogłoby to jednak utrudniać pracę programiście — zastanawiałby się on, z którą z funkcji ma do czynienia i jak ją wykorzystać.
Rzutowanie to również dobry sposób na zaokrąglanie, a raczej — obcinanie części liczb zmiennoprzecinkowych. Spójrz na poniższy fragment:
double d; int i;
d = 454.54;
i = (int)d;