Download as pdf or txt
Download as pdf or txt
You are on page 1of 202

Mirosław J.

Kubiak

C#
Zadania z programowania
z przykładowymi rozwiązaniami

WYDANIE III
Wszelkie prawa zastrzeżone. Nieautoryzowane rozpowszechnianie całości
lub fragmentu niniejszej publikacji w jakiejkolwiek postaci jest zabronione.
Wykonywanie kopii metodą kserograficzną, fotograficzną, a także kopiowanie
książki na nośniku filmowym, magnetycznym lub innym powoduje naruszenie
praw autorskich niniejszej publikacji.

Wszystkie znaki występujące w tekście są zastrzeżonymi znakami firmowymi


bądź towarowymi ich właścicieli.

Autor oraz wydawca dołożyli wszelkich starań, by zawarte w tej książce


informacje były kompletne i rzetelne. Nie biorą jednak żadnej odpowiedzialności
ani za ich wykorzystanie, ani za związane z tym ewentualne naruszenie praw
patentowych lub autorskich. Autor oraz wydawca nie ponoszą również żadnej
odpowiedzialności za ewentualne szkody wynikłe z wykorzystania informacji
zawartych w książce.

Redaktor prowadzący: Małgorzata Kulik

Projekt okładki: Studio Gravite / Olsztyn


Obarek, Pokoński, Pazdrijowski, Zaprucki
Grafika na okładce została wykorzystana za zgodą Shutterstock.com

Helion S.A.
ul. Kościuszki 1c, 44-100 Gliwice
tel. 32 231 22 19, 32 230 98 63
e-mail: helion@helion.pl
WWW: http://helion.pl (księgarnia internetowa, katalog książek)

Drogi Czytelniku!
Jeżeli chcesz ocenić tę książkę, zajrzyj pod adres
http://helion.pl/user/opinie/cshza3_ebook
Możesz tam wpisać swoje uwagi, spostrzeżenia, recenzję.

ISBN: 978-83-283-8673-0

Copyright © Helion S.A. 2021

 Poleć książkę na Facebook.com  Księgarnia internetowa


 Kup w wersji papierowej  Lubię to! » Nasza społeczność
 Oceń książkę

8ba61431c02548173fa6085d93015e5a
8
Spis treści
Od autora 5
Rozdział 1. Jak język C# komunikuje się z użytkownikiem? 9
Informacje ogólne 9
Obsługa sytuacji wyjątkowych 18

Rozdział 2. Instrukcje sterujące przebiegiem programu


— instrukcje wyboru 23
Instrukcje wyboru 23
Instrukcja if ... else 24
Instrukcja switch ... case 24

Rozdział 3. Instrukcje sterujące przebiegiem programu


— instrukcje iteracyjne 35
Instrukcje iteracyjne 35
Pętla for 36
Pętla do ... while 37
Pętla while 37

Rozdział 4. Tablice i kolekcje 69


Tablice 69
Kolekcje 69
Tablice jednowymiarowe 70
Tablice dwuwymiarowe 74
Pętla foreach 90
Działania na macierzach 97
Łańcuchy tekstowe 105
Konkatenacja 108

8ba61431c02548173fa6085d93015e5a
8
4 Spis treści

Programowanie uogólnione i klasy generyczne 109


Proste metody generyczne 110
Proste klasy generyczne 111
Listy generyczne 114

Rozdział 5. Elementy programowania obiektowego 117


Informacje ogólne 117
Klasy, pola, metody 118
Rekurencja 129
Klasa Osoba 134
Dziedziczenie 136

Rozdział 6. Pliki tekstowe i pliki o dostępie swobodnym 141


Informacje ogólne 141
Pliki tekstowe 141
Pliki o dostępie swobodnym 156
Serializacja 157

Rozdział 7. Wprowadzenie do współbieżności 161


Informacje ogólne 161
Wprowadzenie do programowania równoległego 162
Wielowątkowość 172
Mój pierwszy wątek 172
Praca z wątkami 176
Priorytety wątków 181
Klasa Task 183
Moje pierwsze zadanie 184
Praca z zadaniami 186
Synchronizacja zadań 188

Rozdział 8. Podążając w kierunku


funkcyjnego paradygmatu programowania 193
Wstęp 193
Co to jest paradygmat programowania? 194
Czym jest programowanie funkcyjne? 195
Funkcyjna natura biblioteki LINQ 196

Polecana literatura 199


Bibliografia 199
Zbiory zadań z programowania 200

8ba61431c02548173fa6085d93015e5a
8
Od autora
Drugie wydanie książki C#. Zadania z programowania z przykładowymi roz-
wiązaniami ukazało się nakładem wydawnictwa Helion w 2018 roku.
W trzecim wydaniu wymieniłem wszystkie listingi programów i uaktual-
niłem je do wersji C# 8.0 i C# 9.0. Dodałem rozdział 8. poświęcony progra-
mowaniu funkcyjnemu w C#.
Wszystkie programy zawarte w tej książce zostały skompilowane w bezpłat-
nym, nowoczesnym i w pełni zintegrowanym środowisku programistycznym
Microsoft Visual Studio Community 20191.
Wyjściowy wzorzec kodu programu dla kompilatora Visual C# jest nastę-
pujący:
using System;

namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
}
}
}

1 Bezpłatne środowisko można pobrać ze strony https://visualstudio.microsoft.com/pl/vs/


community/.

8ba61431c02548173fa6085d93015e5a
8
6 C#. Zadania z programowania z przykładowymi rozwiązaniami

Uzyskamy go, klikając Plik/Nowy Projekt, po czym w oknie Utwórz nowy


projekt wybieramy język C#, a następnie szablon Aplikacja konsoli (.NET
Core). Klikamy Dalej. Przechodzimy do okna Konfiguruj nowy projekt. W polu
Nazwa: należy wpisać nazwę projektu, a w polu Lokalizacja: ścieżkę dostępu
do pliku, na koniec zaś zatwierdzamy, klikając przycisk OK.
Następująca linijka kodu:
using System;

oznacza, że do programu należy dołączyć przestrzeń nazw (ang. namespace)


System. Przestrzeń ta definiuje rejon deklaracji, umożliwiając oddzielenie
jednego zestawu nazw od drugiego. Nazwy zadeklarowane w jednej prze-
strzeni nazw nie wchodzą w konflikt z takimi samymi nazwami zadekla-
rowanymi w innej przestrzeni nazw.
Zakładam, że Czytelnik zna podstawowe elementy języka C# i elementarne
zasady programowania w tym języku oraz że potrafi posługiwać się środo-
wiskiem programistycznym Microsoft Visual Studio Community 2019 na
poziomie podstawowym, to znaczy umożliwiającym napisanie nowego lub
wczytanie gotowego projektu dla aplikacji konsolowych.

Mirosław J. Kubiak

8ba61431c02548173fa6085d93015e5a
8
Od autora 7

Instrukcja dla Czytelników,


którzy nie chcą korzystać ze środowiska
Microsoft Visual Studio Community 2019
Proponujemy IDEONE.COM — to bardzo ciekawy projekt adresowany
do osób zainteresowanych programowaniem (http://ideone.com/).
Ten darmowy kompilator online i narzędzie do debugowania pozwala
kompilować i uruchamiać kod w ponad 60 językach programowania, rów-
nież w C#.
Wzorzec kodu programu dla kompilatora online jest następujący:
using System;

public class Test


{
public static void Main()
{
// your code goes here
}
}

Obsługa tego kompilatora, dostępnego także w polskiej wersji językowej,


jest bardzo prosta i intuicyjna, a zatem nie wymaga specjalnych objaśnień. Inny
niż w kompilatorze Microsoft Visual Studio Community 2019 jest mecha-
nizm wprowadzania danych do programu. Czytelnicy nie powinni mieć
z tym problemu, analizując przykładowy program.
Oto zmodyfikowany kod programu Zadanie 1.1, przystosowany dla kom-
pilatora online.
using System;

public class Test


{
public static void Main()
{
double a, b, pole;

Console.WriteLine("Program oblicza pole prostokąta.");


//Console.WriteLine("Podaj bok a.");
a = double.Parse(Console.ReadLine());
//Console.WriteLine("Podaj bok b.");
b = double.Parse(Console.ReadLine());

8ba61431c02548173fa6085d93015e5a
8
8 C#. Zadania z programowania z przykładowymi rozwiązaniami

pole = a * b;

Console.Write("Pole prostokąta o boku a = {0} i boku b =


´{1}", a, b);
Console.Write(" wynosi {0}.", pole);
}
}

Życząc owocnego kompilowania, zachęcam Czytelników do eksperymento-


wania również z tym kompilatorem. Napisanie programu dla innego kompi-
latora niż Microsoft Visual Studio Community 2019 nie powinno sprawić
żadnych trudności średnio zaawansowanemu programiście.

8ba61431c02548173fa6085d93015e5a
8
Rozdział 1.
Jak język C#
komunikuje się
z użytkownikiem?
W tym rozdziale zamieściłem proste zadania, wraz z przykładowymi rozwią-
zaniami, ilustrujące, w jaki sposób komputer komunikuje się z użytkownikiem
w języku C#.

Informacje ogólne
Każda aplikacja powinna mieć możliwość komunikowania się z użytkowni-
kiem. Za pomocą prostych przykładów pokażę, w jaki sposób program
napisany w języku C# komunikuje się z użytkownikiem poprzez standar-
dowe operacje wejścia – wyjścia.
Operacje wejścia – wyjścia w języku C# są realizowane przez strumienie.
Strumień jest pojęciem abstrakcyjnym. Może on wysyłać i pobierać infor-
macje i jest połączony z fizycznym urządzeniem (np. z klawiaturą czy ekra-
nem) poprzez system operacji wejścia – wyjścia.
W języku C# zdefiniowano dwa typy strumieni: bajtowe i znakowe. Trzy
predefiniowane strumienie są dostępne przez właściwości Console.In,
Console.Out i Console.Error dla wszystkich programów korzystających

8ba61431c02548173fa6085d93015e5a
8
10 C#. Zadania z programowania z przykładowymi rozwiązaniami

z przestrzeni nazw System. Strumień Console.Out odwołuje się do standardo-


wego strumienia wyjściowego, którym domyślnie jest konsola. Podczas wywo-
łania w programie Console.WriteLine() informacje są automatycznie wysy-
łane do Console.Out. Polecenie Console.In odwołuje się do standardowego
wejścia, którym domyślnie jest klawiatura.
Standardowe strumienie są strumieniami znakowymi i umożliwiają zapis
i odczyt znaków.
Zadanie
1.1 Napisz program, który oblicza pole prostokąta. Wartości boków a i b wpro-
wadzamy z klawiatury. W programie należy przyjąć, że zmienne a, b oraz pole
są typu double (typu rzeczywistego).
Listing 1.1. Przykładowe rozwiązanie

using System;

namespace Zadanie_11 // Zadanie 1.1.


{
class Program
{
static void Main(string[] args)
{
double a, b, pole;

Console.WriteLine("Program oblicza pole prostokąta.");


Console.WriteLine("Podaj bok a.");
a = double.Parse(Console.ReadLine());
Console.WriteLine("Podaj bok b.");
b = double.Parse(Console.ReadLine());

pole = a * b; // Obliczamy pole prostokąta.

Console.Write("Pole prostokąta o boku a = {0} i boku b =


´{1}", a, b);
Console.WriteLine(" wynosi {0}.", pole);
}
}
}

Wyjaśnienia wymaga następująca linijka kodu:


using System;

8ba61431c02548173fa6085d93015e5a
8
Rozdział 1. ♦ Jak język C# komunikuje się z użytkownikiem? 11

Dyrektywa using pozwala skorzystać z przestrzeni nazw System. Dyrek-


tywa using musi się znaleźć na początku pliku, przed innymi deklaracjami.
W programie może być wiele dyrektyw using.
Następująca linijka kodu:
namespace Zadanie11 // Zadanie 1.1.

informuje, że przestrzeń nazw namespace będzie nazywała się Zadanie11.


Komentarze w języku C# oznaczany dwoma ukośnikami //. To jest komen-
tarz: //Zadanie 1.1.
Linijka kodu:
double a, b, pole;

umożliwia deklaracje zmiennych a, b i pole w programie, w którym wszystkie


zmienne są typu double (typu rzeczywistego).
Następująca linijka kodu:
Console.WriteLine("Program oblicza pole prostokąta.");

wyświetla na ekranie komputera komunikat:


Program oblicza pole prostokąta.

Czytanie z klawiatury liczby rzeczywistej a umożliwia linijka kodu:


a = double.Parse(Console.ReadLine());

gdzie korzystając z metody statycznej Parse, zamieniliśmy wczytany łańcuch


na liczbę typu rzeczywistego — double. Przez analogię możemy do programu
wczytywać liczby innych typów.
Pole prostokąta zostaje obliczone w instrukcji:
pole = a * b; // Obliczamy pole prostokąta.

Za wyświetlenie wartości zmiennych a i b oraz zmiennej pole, wraz z odpowied-


nim opisem, odpowiedzialne są następujące linijki kodu:
Console.Write("Pole prostokąta o boku a = {0} i boku b = {1}", a, b);
Console.Write(" wynosi {0}.", pole);

Do formatowania danych numerycznych będziemy korzystali z drugiej


formy funkcji WriteLine():
WriteLine(„łańcuch formatujący”, arg0, arg1, arg2, ..., argN);

8ba61431c02548173fa6085d93015e5a
8
12 C#. Zadania z programowania z przykładowymi rozwiązaniami

Łańcuch formatujący zawiera dwa elementy: zwykłe znaki, które są wyświe-


tlone na ekranie, oraz specyfikatory formatu. Elementy te mają następu-
jącą formę:
{numer argumentu, szerokość: fmt}

gdzie numer argumentu oznacza liczony od zera numer argumentu, który ma


zostać wyświetlony. Minimalną szerokość pola określa szerokość, format zaś
jest określony przez fmt1.
Rezultat działania programu można zobaczyć na rysunku 1.1.

Rysunek 1.1.
Efekt działania Program oblicza pole prostokąta.
programu Podaj bok a.
1,02
Zadanie 1.1
Podaj bok b.
2
Pole prostokąta o boku a = 1,02 i boku b = 2 wynosi 2,04.

Zadanie
1.2 Napisz program, który wyświetla na ekranie komputera wartość predefi-
niowanej stałej π = 3,14...; należy przyjąć format wyświetlania tej stałej
z dokładnością do pięciu miejsc po przecinku.
Listing 1.2. Przykładowe rozwiązanie

using System;

namespace Zadanie_12 // Zadanie 1.2.


{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Program wyświetla stałą pi z zadaną
´dokładnością.");
Console.WriteLine("Pi = {0:#.#####}.", Math.PI);
}
}
}

1 Zob. zadanie 1.2.

8ba61431c02548173fa6085d93015e5a
8
Rozdział 1. ♦ Jak język C# komunikuje się z użytkownikiem? 13

W programie zapis:
Console.WriteLine("Pi = {0:#.#####}.", Math.PI);

oznacza, że na wyświetlenie liczby π przeznaczono sześć pól (#.#####),


w tym pięć na część ułamkową. Klasa Math jest standardową klasą języka
C#, która umożliwia dowolne obliczenia matematyczne.
Rezultat działania programu można zobaczyć na rysunku 1.2.

Rysunek 1.2.
Efekt działania Program wyświetla stałą pi z zadaną dokładnością.
programu Pi = 3,14159.
Zadanie 1.2

Zadanie 1.2 można przedstawić w innej formie, używając deklaracji using


static System.Math. Umożliwia ona import statycznych elementów klasy
System.Math. Alternatywny przykład rozwiązania tego zadania znajduje się
poniżej:
using System;
using static System.Math;

namespace Zadanie_12a // Zadanie 1.2a.


{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Program wyświetla stałą pi z zadaną
´dokładnością.");
Console.WriteLine("Pi = {0:#.#####}.", PI);
}
}
}

W tym przykładzie element klasy Math o nazwie PI możemy wywołać bez


przestrzeni nazw. Taki przykład rozwiązania jest szczególnie przydatny
w programowaniu funkcyjnym2, gdzie najważniejsze są funkcje. Ich zacho-
wanie zależy tylko od ich argumentów wejściowych.

2 Zobacz rozdział 8.

8ba61431c02548173fa6085d93015e5a
8
14 C#. Zadania z programowania z przykładowymi rozwiązaniami

Zadanie
1.3 Napisz program, który wyświetla na ekranie komputera pierwiastek kwa-
dratowy z wartości predefiniowanej π = 3,14... z dokładnością do dwóch
miejsc po przecinku.
Listing 1.3. Przykładowe rozwiązanie

using System;
using static System.Math;

namespace Zadanie_13 // Zadanie 1.3.


{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Program wyświetla pierwiastek kwadratowy
´z liczby pi");
Console.WriteLine("z dokładnością do dwóch miejsc
´po przecinku.");
Console.WriteLine("Sqrt(pi) = {0:#.##}.", Sqrt(PI));
}
}
}

Metoda Sqrt() pozwala obliczyć pierwiastek kwadratowy. Jest ona metodą


standardowej klasy Math.
Rezultat działania programu można zobaczyć na rysunku 1.3.

Rysunek 1.3.
Efekt działania Program wyświetla pierwiastek kwadratowy z liczby pi
programu z dokładnością do dwóch miejsc po przecinku.
Sqrt(pi) = 1,77.
Zadanie 1.3

Zadanie
1.4 Napisz program, który oblicza objętość kuli o promieniu r. Wartość promie-
nia r wprowadzamy z klawiatury. W programie należy przyjąć, że zmienne
promień r i objętość są typu double (typu rzeczywistego). Dla tych zmiennych
należy przyjąć format wyświetlania ich na ekranie z dokładnością do dwóch
miejsc po przecinku.

8ba61431c02548173fa6085d93015e5a
8
Rozdział 1. ♦ Jak język C# komunikuje się z użytkownikiem? 15

Listing 1.4. Przykładowe rozwiązanie


using System;
using static System.Math;

namespace Zadanie_14 // Zadanie 1.4.


{
class Program
{
static void Main(string[] args)
{
double r, objętość;

Console.WriteLine("Program oblicza objętość kuli o promieniu


´r.");
Console.WriteLine("Podaj promień r.");
r = double.Parse(Console.ReadLine());

objętość = 4.0 * PI * r * r * r / 3; // Obliczamy objętość kuli.

Console.WriteLine("Objętość kuli o promieniu r = {0:##.##}


´wynosi {1:##.##}.", r, objętość);
}
}
}

Objętość kuli o promieniu r oblicza następująca linijka kodu:


objętość = 4.0 * PI * r * r * r / 3; // Obliczamy objętość kuli.

w której potęgowanie zamieniono na mnożenie.


Rezultat działania programu można zobaczyć na rysunku 1.4.

Rysunek 1.4.
Efekt działania Program oblicza objętość kuli o promieniu r.
programu Podaj promień r.
1
Zadanie 1.4
Objętość kuli o promieniu r = 1 wynosi 4,19.

Zadanie
1.5 Napisz program, który oblicza wynik dzielenia całkowitego bez reszty dla
dwóch liczb całkowitych: a = 37 i b = 11.

W języku C# w przypadku zastosowania operatora dzielenia / dla liczb


całkowitych reszta wyniku jest pomijana (tak samo jest w językach C,
C++ oraz Java).

8ba61431c02548173fa6085d93015e5a
8
16 C#. Zadania z programowania z przykładowymi rozwiązaniami

Listing 1.5. Przykładowe rozwiązanie

using System;

namespace Zadanie_15 // Zadanie 1.5.


{
class Program
{
static void Main(string[] args)
{
int a = 37; int b = 11;

Console.WriteLine("Program wyświetla wynik dzielenia


´całkowitego");
Console.WriteLine("bez reszty dla dwóch liczb całkowitych.");
Console.WriteLine();
Console.WriteLine("Dla liczb a = " + a + " i b = " + b);
Console.WriteLine(a + "/" + b + " = " + a / b + ".");
}
}
}

Rezultat działania programu można zobaczyć na rysunku 1.5.

Rysunek 1.5.
Efekt działania Program wyświetla wynik dzielenia całkowitego
programu bez reszty dla dwóch liczb całkowitych.
Zadanie 1.5
Dla liczb a = 37 i b = 11
37/11 = 3.

Zadanie
1.6 Napisz program, który oblicza resztę z dzielenia całkowitego dwóch liczb cał-
kowitych: a = 37 i b = 11.

Należy zastosować operator reszty z dzielenia całkowitego modulo, który


oznaczamy w języku C# jako %. Podobnie jak w językach C, C++ oraz Java
operator ten umożliwia uzyskanie tylko reszty z dzielenia, wartość całko-
wita zaś jest pomijana.

Listing 1.6. Przykładowe rozwiązanie

using System;

namespace Zadanie_16 // Zadanie 1.6.


{
class Program

8ba61431c02548173fa6085d93015e5a
8
Rozdział 1. ♦ Jak język C# komunikuje się z użytkownikiem? 17

{
static void Main(string[] args)
{
int a = 37; int b = 11;

Console.WriteLine("Program oblicza resztę z dzielenia


´całkowitego");
Console.WriteLine("dwóch liczb całkowitych.");
Console.WriteLine();
Console.WriteLine("Dla liczb a = " + a + " i b = " + b);
Console.WriteLine(a + "%" + b + " = " + a % b + ".");
}
}
}

Rezultat działania programu można zobaczyć na rysunku 1.6.

Rysunek 1.6.
Efekt działania Program oblicza resztę z dzielenia całkowitego
programu dwóch liczb całkowitych.
Zadanie 1.6
Dla liczb a = 37 i b = 11
37%11 = 4.

Zadanie
1.7 Napisz program, który oblicza sumę, różnicę, iloczyn i iloraz dla dwóch liczb,
x i y, wprowadzanych z klawiatury. W programie przyjmujemy, że zmienne
x i y są typu double (typu rzeczywistego). Wszystkie zmienne należy wyświetlić
z dokładnością do dwóch miejsc po przecinku.
Listing 1.7. Przykładowe rozwiązanie

using System;

namespace Zadanie_17 // Zadanie 1.7.


{
class Program
{
static void Main(string[] args)
{
double x, y, suma, różnica, iloczyn, iloraz;

Console.WriteLine("Program oblicza sumę, różnicę, iloczyn


´i iloraz");
Console.WriteLine("dla dwóch liczb x i y wprowadzanych
´z klawiatury.");
Console.WriteLine("Podaj x.");

8ba61431c02548173fa6085d93015e5a
8
18 C#. Zadania z programowania z przykładowymi rozwiązaniami

x = double.Parse(Console.ReadLine());
Console.WriteLine("Podaj y.");
y = double.Parse(Console.ReadLine());

suma = x + y;
różnica = x - y;
iloczyn = x * y;
iloraz = x / y;

Console.WriteLine("Dla x = {0:##.##} i y = {1:##.##}", x, y);


Console.WriteLine("suma = {0:##.##},", suma);
Console.WriteLine("różnica = {0:##.##},", różnica);
Console.WriteLine("iloczyn = {0:##.##},", iloczyn);
Console.WriteLine("iloraz = {0:##.##}.", iloraz);
}
}
}

Rezultat działania programu można zobaczyć na rysunku 1.7.

Rysunek 1.7.
Efekt działania Program oblicza sumę, różnicę, iloczyn i iloraz
programu dla dwóch liczb x i y wprowadzanych z klawiatury.
Podaj x.
Zadanie 1.7
12
Podaj y.
5
Dla x = 12 i y = 5
suma = 17,
różnica = 7,
iloczyn = 60,
iloraz = 2,4.

Obsługa sytuacji wyjątkowych


Jeśli do programu Zadanie 1.1 wprowadzimy z klawiatury poprawne dane, to
jego działanie polegające na obliczeniu pola prostokąta na podstawie
wprowadzonych wymiarów dwóch boków zakończy się pomyślnie. Kiedy
zaś zamiast oczekiwanych liczb wprowadzimy dowolny znak, to program
„wyłoży się” i pojawią się błędy związane z wykonaniem programu.
W celu uzyskania większej odporności programów na wszelkiego rodzaju
awarie programowe i sprzętowe język C# oferuje wbudowaną obsługę sytua-
cji wyjątkowych (ang. exception handling). Wyjątki (ang. exceptions) definiują

8ba61431c02548173fa6085d93015e5a
8
Rozdział 1. ♦ Jak język C# komunikuje się z użytkownikiem? 19

jednolity mechanizm komunikowania o błędach, które pojawiają się pod-


czas wykonywania programu.
W C# wszystkie wyjątki reprezentowane są przez klasy3. Wszystkie klasy
wyjątków są wyprowadzone z wbudowanej klasy Exception, która jest
częścią przestrzeni nazw System.
Zarządzanie wyjątkami w C# obsługiwane jest przez cztery słowa kluczowe:
try, catch, throw, i finally. Tworzą one powiązany podsystem, w którym
użycie jednego wiąże się z użyciem innego.
W naszych rozważaniach obsługę wyjątków omówię tylko pobieżnie (odsy-
łając Czytelników do bibliografii na końcu książki). Do przechwytywania
wyjątku posłużymy się blokiem instrukcji try...catch o następującej, sche-
matycznej budowie:
try
{
......... // Instrukcja niebezpieczna mogąca powodować wyjątek.
}

catch (TypWyjątku exOb)


{
......... // Instrukcja obsługi wyjątku.
}

gdzie TypWyjątku jest typem wyjątku, który wystąpił. Określenie exOb jest
opcjonalne.
Przykładową obsługę sytuacji wyjątkowych zilustruję w zadaniu 1.8, które
jest modyfikacją zadania 1.1.
Zadanie
1.8 Napisz program, który oblicza pole prostokąta. Wartości boków a i b wprowa-
dzamy z klawiatury. W programie należy przyjąć, że zmienne a, b oraz pole
są typu double (typu rzeczywistego). Dodatkowo do programu wbuduj ob-
sługę sytuacji wyjątkowych4.

3 Zob. rozdział 5.
4 Zachęcam Czytelnika, aby w ramach dodatkowych ćwiczeń utrwalających wszystkie pozo-
stałe programy zawarte w książce wyposażył w obsługę sytuacji wyjątkowych.

8ba61431c02548173fa6085d93015e5a
8
20 C#. Zadania z programowania z przykładowymi rozwiązaniami

Listing 1.8. Przykładowe rozwiązanie

using System;

namespace Zadanie_18 // Zadanie 1.8.


{
class Program
{
static void Main(string[] args)
{
double a, b, pole;

try
{
Console.WriteLine("Program oblicza pole prostokąta.");
Console.WriteLine("Podaj bok a.");
a = double.Parse(Console.ReadLine());
Console.WriteLine("Podaj bok b.");
b = double.Parse(Console.ReadLine());

pole = a * b; // Obliczamy pole.

Console.Write("Pole prostokąta o boku a = {0} i boku


´b = {1}", a, b);
Console.WriteLine(" wynosi {0}.", pole);
}

catch (FormatException)
{
Console.WriteLine("Nie wczytano liczby. Koniec
´programu.");
}
}
}
}

Obsłużenie sytuacji krytycznej związanej z wprowadzeniem do programu


błędnych danych zawarte jest w następujących linijkach kodu:
try
{
Console.WriteLine("Program oblicza pole prostokąta.");
Console.WriteLine("Podaj bok a.");
a = double.Parse(Console.ReadLine());
Console.WriteLine("Podaj bok b.");
b = double.Parse(Console.ReadLine());

pole = a * b;

8ba61431c02548173fa6085d93015e5a
8
Rozdział 1. ♦ Jak język C# komunikuje się z użytkownikiem? 21

Console.Write("Pole prostokąta o boku a = {0} i boku


´b = {1}", a, b);
Console.WriteLine(" wynosi {0}.", pole);
}

Wyjątek FormatException znajdujący się w linijkach kodu:


catch (FormatException)
{
Console.WriteLine("Nie wczytano liczby. Koniec
´programu.");
}

jest uruchamiany z chwilą, kiedy zamiast oczekiwanej dowolnej liczby typu


double wprowadzimy dowolny znak, np. a. Wyjątek nie jest uruchamiany,
jeśli do działającego programu wprowadzimy poprawne dane.
Rezultat działania programu, w którym została wygenerowana sytuacja wyjąt-
kowa, można zobaczyć na rysunku 1.8.

Rysunek 1.8.
Efekt działania Program oblicza pole prostokąta.
programu Podaj bok a.
a
Zadanie 1.8 Nie wczytano liczby. Koniec programu.

8ba61431c02548173fa6085d93015e5a
8
22 C#. Zadania z programowania z przykładowymi rozwiązaniami

8ba61431c02548173fa6085d93015e5a
8
Rozdział 2.
Instrukcje sterujące
przebiegiem programu
— instrukcje wyboru
W tym rozdziale przedstawiłem typowe zadania, wraz z przykładowymi roz-
wiązaniami, wykorzystujące instrukcje wyboru.

Instrukcje wyboru
Instrukcje sterujące przebiegiem programu są jednym z najważniejszych
elementów w każdym języku programowania. Instrukcje te, w połączeniu
z wyrażeniami, umożliwiają zapisanie dowolnego algorytmu programu.
Instrukcje sterujące w języku C# można podzielić na:
 instrukcje wyboru,
 instrukcje iteracyjne (znane jako pętle),
 instrukcje skoku.
W niniejszym rozdziale przedstawiam typowe zadania z wykorzystaniem
instrukcji wyboru, w rozdziale 3. natomiast zadania z wykorzystaniem
instrukcji iteracyjnych.

8ba61431c02548173fa6085d93015e5a
8
24 C#. Zadania z programowania z przykładowymi rozwiązaniami

W języku C# istnieją dwie instrukcje wyboru, które służą do przeprowa-


dzania operacji na podstawie wartości wyrażenia:
 instrukcja if ... else,
 instrukcja switch ... case.

Instrukcja if ... else


Instrukcja if ... else służy do sprawdzania poprawności wyrażenia
warunkowego i — w zależności od tego, czy dany warunek jest prawdziwy,
czy nie — pozwala wykonać różne bloki programu.
Jej ogólna postać jest następująca:
if (warunek)
{
......... // Instrukcje do wykonania, kiedy warunek jest prawdziwy.
}
else
{
......... // Instrukcje do wykonania, kiedy warunek jest fałszywy.
}

Blok else jest opcjonalny, a instrukcja warunkowa w wersji skróconej ma


postać:
if (warunek)
{
......... // Instrukcje do wykonania, kiedy warunek jest prawdziwy.
}

Instrukcja switch ... case


Instrukcja switch ... case pozwala w wygodny i przejrzysty sposób spraw-
dzić ciąg warunków i wykonać kod w zależności od tego, czy są one praw-
dziwe, czy fałszywe. Jej ogólna postać jest następująca:
switch (wyrażenie)
{
case wartość_1 : instrukcje_1;
break;
case wartość_2 : instrukcje_2;
break;
........................
case wartość_n : instrukcje_n;

8ba61431c02548173fa6085d93015e5a
8
Rozdział 2. ♦ Instrukcje sterujące przebiegiem programu — instrukcje wyboru 25

break;
default : instrukcje;
}

Instrukcja break przerywa wykonanie całego bloku case. UWAGA: jej brak
może doprowadzić do uzyskania nieoczekiwanych wyników i pojawienia się
błędów w programie.
Zadanie
2.1 Napisz program, który dla trzech liczb, a, b, c, wprowadzonych z klawiatury
sprawdza, czy tworzą one trójkę pitagorejską.

W teorii liczb trójka pitagorejska to takie trzy liczby całkowite dodatnie a,


b, c, które spełniają równanie Pitagorasa: a2 + b2 = c2.

Listing 2.1. Przykładowe rozwiązanie

using System;

namespace Zadanie_21 // Zadanie 2.1.


{
class Program
{
static void Main(string[] args)
{
int a, b, c;

Console.WriteLine("Program sprawdza, czy wczytane liczby a,


´b, c to trójka pitagorejska.");
Console.WriteLine("Podaj a.");
a = int.Parse(Console.ReadLine());
Console.WriteLine("Podaj b.");
b = int.Parse(Console.ReadLine());
Console.WriteLine("Podaj c.");
c = int.Parse(Console.ReadLine());

if ((a * a + b * b) == c * c)
{
Console.Write("Liczby ");
Console.Write("a = " + a + ", ");
Console.Write("b = " + b + ", ");
Console.Write("c = " + c);
Console.WriteLine(" są trójką pitagorejską.");
}
else
{
Console.Write("Liczby ");

8ba61431c02548173fa6085d93015e5a
8
26 C#. Zadania z programowania z przykładowymi rozwiązaniami

Console.Write("a = " + a + ", ");


Console.Write("b = " + b + ", ");
Console.Write("c = " + c);
Console.WriteLine(" nie są trójką pitagorejską.");
}
}
}
}

Sprawdzenie twierdzenia Pitagorasa dla wczytanych liczb a, b, c zostało


zawarte w następujących linijkach kodu:
if ((a * a + b * b) == c * c)
{
Console.Write("Liczby ");
Console.Write("a = " + a + ", ");
Console.Write("b = " + b + ", ");
Console.Write("c = " + c);
Console.WriteLine(" są trójką pitagorejską.");
}
else
{
Console.Write("Liczby ");
Console.Write("a = " + a + ", ");
Console.Write("b = " + b + ", ");
Console.Write("c = " + c);
Console.WriteLine(" nie są trójką pitagorejską.");
}

Łatwo sprawdzić, że liczby a = 3, b = 4 i c = 5 tworzą trójkę pitagorejską


(spełniają twierdzenie Pitagorasa) i na ekranie pojawi się komunikat:
Liczby … są trójką pitagorejską, natomiast liczby a = 1, b = 2 i c = 3 nie
tworzą trójki pitagorejskiej (nie spełniają twierdzenia Pitagorasa) i na ekranie
pojawi się komunikat: Liczby … nie są trójką pitagorejską.
Rezultat działania programu dla a = 9, b = 12 i c = 15 można zobaczyć na
rysunku 2.1.

Rysunek 2.1.
Efekt działania Program sprawdza, czy wczytane liczby a, b, c to trójka pitagorejska.
programu Podaj a.
9
Zadanie 2.1
Podaj b.
12
Podaj c.
15
Liczby a = 9, b = 12, c = 15 są trójką pitagorejską.

8ba61431c02548173fa6085d93015e5a
8
Rozdział 2. ♦ Instrukcje sterujące przebiegiem programu — instrukcje wyboru 27

Zadanie
2.2 Napisz program, który z wykorzystaniem instrukcji if oblicza pierwiastki
równania kwadratowego ax2 + bx + c = 0, w którym zmienne a, b, c to
liczby rzeczywiste wprowadzane z klawiatury. Wszystkie zmienne wyświe-
tlamy na ekranie z dokładnością do dwóch miejsc po przecinku.
Listing 2.2. Przykładowe rozwiązanie

using System;
using static System.Math;

namespace Zadanie_22 // Zadanie 2.2.


{
class Program
{
static void Main(string[] args)
{
double a, b, c, delta, x1, x2;

Console.WriteLine("Program oblicza pierwiastki równania


´ax ^ 2 + bx + c = 0.");
Console.WriteLine("Podaj a.");
a = double.Parse(Console.ReadLine());

if (a == 0)
{
Console.WriteLine("Niedozwolona wartość współczynnika
´a.");
}
else
{
Console.WriteLine("Podaj b.");
b = double.Parse(Console.ReadLine());
Console.WriteLine("Podaj c.");
c = double.Parse(Console.ReadLine());

delta = b * b - 4 * a * c;

if (delta < 0)
{
Console.WriteLine();
Console.Write("Dla ");
Console.Write("a = {0}, ", a);
Console.Write("b = {0}, ", b);
Console.Write("c = {0} ", c);
Console.Write("brak pierwiastków rzeczywistych.");
}
else
{

8ba61431c02548173fa6085d93015e5a
8
28 C#. Zadania z programowania z przykładowymi rozwiązaniami

if (delta == 0)
{
x1 = - b / (2 * a);
Console.WriteLine();
Console.Write("Dla ");
Console.Write("a = {0}, ", a);
Console.Write("b = {0}, ", b);
Console.Write("c = {0} ", c);
Console.WriteLine("trójmian ma jeden pierwiastek
´podwójny x1 = {0:##.##}.", x1);
}
else
{
x1 = (- b - Sqrt(delta)) / (2 * a);
x2 = (- b + Sqrt(delta)) / (2 * a);
Console.WriteLine();
Console.Write("Dla ");
Console.Write("a = {0}, ", a);
Console.Write("b = {0}, ", b);
Console.Write("c = {0} ", c);
Console.WriteLine("trójmian ma dwa
´pierwiastki: x1 = {0:##.##},
´x2 = {1:##.##}.", x1, x2);
}
}
}
}
}
}

W pierwszej części programu sprawdzamy, czy wartość współczynnika a jest


równa zero. Ilustrują to następujące linijki kodu:
if (a == 0)
{
Console.WriteLine("Niedozwolona wartość współczynnika a.");
}
else
{
..........
}

Jeśli wartość współczynnika a = 0, to zostanie wyświetlony komunikat:


Niedozwolona wartość współczynnika a i program zostanie zakończony.
Dla a różnego od zera program będzie oczekiwał na wprowadzenie wartości
b i c. Po ich wprowadzeniu zostanie obliczona delta według wzoru:
delta = b * b – 4 * a * c;

8ba61431c02548173fa6085d93015e5a
8
Rozdział 2. ♦ Instrukcje sterujące przebiegiem programu — instrukcje wyboru 29

Jeśli delta < 0, to zostanie wyświetlony komunikat: …brak pierwiastków


rzeczywistych.
Dla delta = 0 równanie kwadratowe ma jeden pierwiastek podwójny,
który obliczymy ze wzoru:
x1 = - b / (2 * a);

Dla delta > 0 równanie kwadratowe ma dwa pierwiastki, które obliczymy


ze wzorów:
x1 = (- b - Sqrt(delta)) / (2 * a);
x2 = (- b + Sqrt(delta)) / (2 * a);

Dla na przykład a = 1, b = 5 i c = 4 wartości pierwiastków równania


wynoszą odpowiednio: x1 = -4 i x2 = -1.
Dla na przykład a = 1, b = 4 i c = 4 trójmian ma jeden pierwiastek
podwójny: x1 = -2.
Dla na przykład a = 1, b = 2 i c = 3 trójmian nie ma pierwiastków
rzeczywistych.
Rezultat działania programu dla a = 1, b = 5 i c = 4 można zobaczyć na
rysunku 2.2.

Rysunek 2.2.
Efekt działania Program oblicza pierwiastki równania ax^2 + bx + c = 0.
programu Podaj a.
1
Zadanie 2.2
Podaj b.
5
Podaj c.
4

Dla a = 1, b = 5, c = 4 trójmian ma dwa pierwiastki: x1 = -4, x2 = -1.

Zadanie
2.3 Napisz program, który z wykorzystaniem instrukcji switch oblicza pier-
wiastki równania kwadratowego ax2 + bx + c = 0, w którym zmienne a, b, c
to liczby rzeczywiste wprowadzane z klawiatury. Wszystkie zmienne wyświe-
tlamy z dokładnością do dwóch miejsc po przecinku.

8ba61431c02548173fa6085d93015e5a
8
30 C#. Zadania z programowania z przykładowymi rozwiązaniami

Należy wprowadzić do programu zmienną pomocniczą liczba_pier


´wiastków.

Listing 2.3. Przykładowe rozwiązanie

using System;
using static System.Math;

namespace Zadanie_23 // Zadanie 2.3.


{
class Program
{
static void Main(string[] args)
{
double a, b, c, delta, x1, x2;
byte liczba_pierwiastków = 0;

Console.WriteLine("Program oblicza pierwiastki równania


´ax ^ 2 + bx + c = 0.");
Console.WriteLine("Podaj a.");
a = double.Parse(Console.ReadLine());

if (a == 0)
{
Console.WriteLine("Niedozwolona wartość współczynnika
´a.");
}
else
{
Console.WriteLine("Podaj b.");
b = double.Parse(Console.ReadLine());
Console.WriteLine("Podaj c.");
c = double.Parse(Console.ReadLine());

delta = b * b - 4 * a * c;

if (delta < 0) liczba_pierwiastków = 0;


if (delta == 0) liczba_pierwiastków = 1;
if (delta > 0) liczba_pierwiastków = 2;

switch (liczba_pierwiastków)
{
case 0:
{
Console.WriteLine();
Console.Write("Dla ");
Console.Write("a = {0}, ", a);
Console.Write("b = {0}, ", b);
Console.Write("c = {0} ", c);

8ba61431c02548173fa6085d93015e5a
8
Rozdział 2. ♦ Instrukcje sterujące przebiegiem programu — instrukcje wyboru 31

Console.Write("brak pierwiastków
´rzeczywistych.");
}

break;
case 1:
{
x1 = - b / (2 * a);
Console.WriteLine();
Console.Write("Dla ");
Console.Write("a = {0}, ", a);
Console.Write("b = {0}, ", b);
Console.Write("c = {0} ", c);
Console.WriteLine("trójmian ma jeden
´pierwiastek podwójny x1 = {0: ##.##}.",
´x1);
}
break;

case 2:
{
x1 = (- b - Sqrt(delta)) / (2 * a);
x2 = (- b + Sqrt(delta)) / (2 * a);
Console.WriteLine();
Console.Write("Dla ");
Console.Write("a = {0}, ", a);
Console.Write("b = {0}, ", b);
Console.Write("c = {0} ", c);
Console.Write("trójmian ma dwa pierwiastki
´x1 = {0: ##.##} i ", x1);
Console.WriteLine("x2 = {0: ##.##}.", x2);
}
break;
}
}
}
}
}

Zmienna pomocnicza liczba_pierwiastków przyjmuje trzy wartości w zależ-


ności od znaku zmiennej delta. Ilustrują to następujące linijki kodu:
if (delta < 0) liczba_pierwiastków = 0;
if (delta == 0) liczba_pierwiastków = 1;
if (delta > 0) liczba_pierwiastków = 2;

Rezultat działania programu dla a = 1, b = 4 i c = 4 można zobaczyć na


rysunku 2.3.

8ba61431c02548173fa6085d93015e5a
8
32 C#. Zadania z programowania z przykładowymi rozwiązaniami

Rysunek 2.3.
Efekt działania Program oblicza pierwiastki równania ax^2 + bx + c = 0.
programu Podaj a.
1
Zadanie 2.3
Podaj b.
4
Podaj c.
4

Dla a = 1, b = 4, c = 4 trójmian ma jeden pierwiastek podwójny x1 = -2.

Zadanie
2.4 Napisz program, który oblicza wartość niewiadomej x z równania ax + b = c.
Wartości a, b, c należą do zbioru liczb rzeczywistych i są wprowadzane
z klawiatury. Dodatkowo należy zabezpieczyć program na wypadek sytuacji,
kiedy wprowadzona wartość a = 0. Wszystkie zmienne wyświetlamy z dokład-
nością do dwóch miejsc po przecinku.

Listing 2.4. Przykładowe rozwiązanie


using System;

namespace Zadanie_24 // Zadanie 2.4.


{
class Program
{
static void Main(string[] args)
{
double a, b, c, x;

Console.WriteLine("Program oblicza wartość x z równania


´liniowego ax + b = 0.");
Console.WriteLine("Podaj a.");
a = double.Parse(Console.ReadLine());

if (a == 0)
{
Console.WriteLine("Niedozwolona wartość współczynnika
´a.");
}
else
{
Console.WriteLine("Podaj b.");
b = double.Parse(Console.ReadLine());
Console.WriteLine("Podaj c.");
c = double.Parse(Console.ReadLine());

8ba61431c02548173fa6085d93015e5a
8
Rozdział 2. ♦ Instrukcje sterujące przebiegiem programu — instrukcje wyboru 33

x = (c - b) / a;

Console.WriteLine();
Console.Write("Dla ");
Console.Write("a = {0:##.##}, ", a);
Console.Write("b = {0:##.##}, ", b);
Console.Write("c = {0:##.##} ", c);
Console.WriteLine("wartość x = {0:##.##}.", x);
}
}
}
}

Rezultat działania programu można zobaczyć na rysunku 2.4.

Rysunek 2.4.
Efekt działania Program oblicza wartość x z równania liniowego ax + b = 0.
programu Podaj a.
1
Zadanie 2.4
Podaj b.
6
Podaj c.
2

Dla a = 1, b = 6, c = 2 wartość x = -4.

Zadanie
2.5 Napisz program, w którym użytkownik zgaduje całkowitą liczbę losową
z przedziału od 0 do 9 generowaną przez komputer.

W języku C# liczby pseudolosowe generujemy za pomocą klasy:


Random r = new Random(); .

Listing 2.5. Przykładowe rozwiązanie

using System;
using static System.Math;

namespace Zadanie_25 // Zadanie 2.5.


{
class Program
{
static void Main(string[] args)
{
Random r = new Random();
double losuj_liczbę, zgadnij_liczbę;

8ba61431c02548173fa6085d93015e5a
8
34 C#. Zadania z programowania z przykładowymi rozwiązaniami

Console.WriteLine("Program losuje liczbę od 0 do 9.


´Zgadnij ją.");

losuj_liczbę = Round(10 * (r.NextDouble()));


zgadnij_liczbę = double.Parse(Console.ReadLine());

if (zgadnij_liczbę == losuj_liczbę)
{
Console.WriteLine("Gratulacje! Zgadłeś liczbę!");
}
else
{
Console.WriteLine("Bardzo mi przykro, ale wylosowana
´liczba to {0}.", losuj_liczbę);
}
}
}
}

Funkcja Round() w poniższej linijce kodu:


losuj_liczbę = Round(10 * (r.NextDouble()));

umożliwia zaokrąglenie liczby zmiennoprzecinkowej do liczby całkowitej.


Rezultat działania programu można zobaczyć na rysunku 2.5.

Rysunek 2.5.
Efekt działania Program losuje liczbę od 0 do 9. Zgadnij ją.
programu 9
Gratulacje! Zgadłeś liczbę!
Zadanie 2.5

8ba61431c02548173fa6085d93015e5a
8
Rozdział 3.
Instrukcje sterujące
przebiegiem programu
— instrukcje iteracyjne
W tym rozdziale przedstawiłem typowe zadania, wraz z rozwiązaniami,
wykorzystujące instrukcje iteracyjne, czyli popularne pętle. O ile początku-
jący programiści nie mają problemu z programami, w których zastosowano
instrukcję for, to zamiana tej instrukcji na do ... while oraz while nastręcza
pewnych trudności. Proste przykłady z instrukcją for zostały tu rozwiązane
zarówno z użyciem do ... while, jak i while.

Instrukcje iteracyjne
Iteracja (łac. iteratio — powtarzanie) jest to czynność powtarzania (najczę-
ściej wielokrotnego) tej samej instrukcji (albo wielu instrukcji) w pętli.
W języku C# istnieją cztery instrukcje iteracyjne:
 for,
 do ... while,
 while,
 foreach1.

1 Zadania z wykorzystaniem tej instrukcji zostały omówione w rozdziale 4.

8ba61431c02548173fa6085d93015e5a
8
36 C#. Zadania z programowania z przykładowymi rozwiązaniami

Pętla for
Pętlę for stosujemy, kiedy dokładnie wiemy, ile razy pętla ma zostać wyko-
nana. Istnieje wiele wariantów pętli for, ale zawsze możemy wyróżnić trzy
główne części:
1. Inicjalizacja jest to zwykle instrukcja przypisania stosowana
do ustawienia początkowej wartości zmiennej sterującej.
2. Warunek jest wyrażeniem relacyjnym, które określa moment
zakończenia wykonywanej pętli.
3. Inkrementacja (zwiększanie) lub dekrementacja (zmniejszanie)
definiuje sposób modyfikacji zmiennej sterującej pętlą
po zakończeniu każdego przebiegu (powtórzenia).
Te trzy główne składowe oddzielone są od siebie średnikami.
Pętla for wykonywana jest dopóty, dopóki wartość warunku wynosi true.
Kiedy warunek osiągnie wartość false, to działanie programu jest kontynuo-
wane od pierwszej instrukcji znajdującej się za pętlą.
W języku C# zmienna sterująca pętlą for nie musi być typu całkowitego,
znakowego czy logicznego — może być ona również typu double (typu
rzeczywistego).
Pętla for może być wykonywana tyle razy, ile wartości znajduje się
w przedziale:
inicjalizacja; warunek; zwiększanie

lub
inicjalizacja; warunek; zmniejszanie

Oto ogólna postać tej instrukcji:


for (inicjalizacja; warunek; zwiększanie)
{
.......... // Instrukcje do wykonania.
}

lub
for (inicjalizacja; warunek; zmniejszanie)
{
.......... // Instrukcje do wykonania.
}

8ba61431c02548173fa6085d93015e5a
8
Rozdział 3. ♦ Instrukcje sterujące przebiegiem programu — instrukcje iteracyjne 37

W języku C# możliwa jest zmiana przyrostu zmiennej sterującej pętlą.

Pętla do ... while


Kolejną instrukcją iteracyjną jest do ... while. Oto jej ogólna postać:
do
{
.......... // Instrukcje do wykonania.
}
while (warunek);

Cechą charakterystyczną instrukcji iteracyjnej do ... while jest to, że bez


względu na wartość zmiennej warunek pętla musi zostać wykonana co naj-
mniej jeden raz. Program po napotkaniu instrukcji do ... while wchodzi
do pętli i wykonuje instrukcje znajdujące się w nawiasach klamrowych {},
a następnie sprawdza, czy warunek jest spełniony. Jeśli tak, to program wraca
na początek pętli, jeśli jednak warunek osiągnie wartość false (fałsz), pętla
się zakończy.

Pętla while
Następną instrukcją iteracyjną jest while. Oto jej ogólna postać:
while (warunek)
{
.......... // Instrukcje do wykonania.
}

Cechą charakterystyczną tej instrukcji jest sprawdzenie jeszcze przed wyko-


naniem instrukcji znajdujących się w bloku {...}, czy warunek jest speł-
niony. W szczególnym wypadku pętla może nie zostać wcale wykonana.
Instrukcja while powoduje wykonywanie instrukcji dopóty, dopóki
warunek jest prawdziwy.

Zadanie
3.1 Napisz program, który za pomocą instrukcji for dla danych wartości x
zmieniających się w przedziale od 0 do 10 obliczy wartość funkcji y = 3*x.

Listing 3.1. Przykładowe rozwiązanie

using System;

namespace Zadanie_31 // Zadanie 3.1.

8ba61431c02548173fa6085d93015e5a
8
38 C#. Zadania z programowania z przykładowymi rozwiązaniami

{
class Program
{
static void Main(string[] args)
{
int x, y;

Console.WriteLine("Program oblicza wartość funkcji y = 3*x");


Console.WriteLine("dla x zmieniającego się od 0 do 10.");
Console.WriteLine();

for (x = 0; x <= 10; x++)


{
y = 3 * x;
Console.WriteLine("x = " + x + '\t' + " y = " + y);
}
}
}
}

W pętli:
for (x = 0; x <= 10; x++)
{
y = 3 * x;
Console.WriteLine("x = " + x + '\t' + " y = " + y);
}

kolejne wartości x, zmieniające się automatycznie od x = 0 (inicjalizacja)


do x <= 10 (warunek) z krokiem równym 1 (zwiększanie), są podstawiane
do wzoru:
y = 3 * x;

a następnie zostają wyświetlone na ekranie dzięki użyciu polecenia:


Console.WriteLine("x = " + x + '\t' + " y = " + y);

W instrukcji powyżej znak + powoduje wyświetlenie wartości x za poprzedza-


jącym ją łańcuchem tekstowym. Korzystając z operatora +, możemy „skleić”
razem wszystko, co chcemy wyświetlić w instrukcji WriteLine(). Znak '\t'
oznacza przejście do następnej pozycji w tabulacji linii.
Rezultat działania programu można zobaczyć na rysunku 3.1.

8ba61431c02548173fa6085d93015e5a
8
Rozdział 3. ♦ Instrukcje sterujące przebiegiem programu — instrukcje iteracyjne 39

Rysunek 3.1.
Efekt działania Program oblicza wartość funkcji y = 3*x
programu dla x zmieniającego się od 0 do 10.
Zadanie 3.1
x=0 y=0
x=1 y=3
x=2 y=6
x=3 y=9
x=4 y = 12
x=5 y = 15
x=6 y = 18
x=7 y = 21
x=8 y = 24
x=9 y = 27
x = 10 y = 30

Zadanie
3.2 Napisz program, który za pomocą instrukcji do ... while dla danych wartości
x zmieniających się w przedziale od 0 do 10 oblicza wartość funkcji y = 3*x.

Listing 3.2. Przykładowe rozwiązanie

using System;

namespace Zadanie_32 // Zadanie 3.2.


{
class Program
{
static void Main(string[] args)
{
int x = 0, y = 0; // Ustalenie wartości początkowych.

Console.WriteLine("Program oblicza wartość funkcji y = 3*x");


Console.WriteLine("dla x zmieniającego się od 0 do 10.");
Console.WriteLine();

do
{
y = 3 * x;
Console.WriteLine("x = " + x + '\t' + " y = " + y);
x++;
}
while (x <= 10);
}
}
}

8ba61431c02548173fa6085d93015e5a
8
40 C#. Zadania z programowania z przykładowymi rozwiązaniami

Pętla do ... while:


do
{
y = 3 * x;
Console.WriteLine("x = " + x + '\t' + " y = " + y);
x++;
}
while (x <= 10);

nie ma wbudowanego mechanizmu zmiany sterującej nią zmiennej, dla-


tego musimy go do niej dobudować. Rolę zmiennej sterującej pełni tutaj
zmienna x. Powinniśmy tę zmienną przed pętlą wyzerować, stąd zapis:
x = 0;

Następnie x należy zwiększać o krok, który w naszym wypadku wynosi 1.


Ilustruje to następująca linijka kodu:
x++;

Pętla będzie tak długo powtarzana, aż zostanie spełniona zależność x <= 10.
Zwróćmy uwagę, że warunek sprawdzający zakończenie działania pętli,
tzn. while (x <= 10), znajduje się na jej końcu.
Zadanie
3.3 Napisz program, który za pomocą instrukcji while dla danych wartości x
zmieniających się w przedziale od 0 do 10 oblicza wartość funkcji y = 3*x.

Listing 3.3. Przykładowe rozwiązanie

using System;

namespace Zadanie_33 // Zadanie 3.3.


{
class Program
{
static void Main(string[] args)
{
int x = 0, y = 0; // Ustalenie wartości początkowych.

Console.WriteLine("Program oblicza wartość funkcji y = 3*x");


Console.WriteLine("dla x zmieniającego się od 0 do 10.");
Console.WriteLine();

while (x <= 10)


{
y = 3 * x;
Console.WriteLine("x = " + x + '\t' + " y = " + y);
x++;

8ba61431c02548173fa6085d93015e5a
8
Rozdział 3. ♦ Instrukcje sterujące przebiegiem programu — instrukcje iteracyjne 41

}
}
}
}

Pętla while:
while (x <= 10)
{
y = 3 * x;
Console.WriteLine("x = " + x + '\t' + " y = " + y);
x++;
}

podobnie jak do ... while, nie ma wbudowanego mechanizmu zmiany


sterującej nią zmiennej, musimy więc go do niej dobudować. Rolę zmiennej
sterującej pełni tutaj zmienna x, którą powinniśmy przed pętlą wyzerować,
stąd zapis:
x = 0;

Następnie należy ją zwiększać o krok, który w naszym wypadku wynosi 1.


Ilustruje to następująca linijka kodu:
x++;

Pętla będzie tak długo powtarzana, aż stanie się prawdziwa zależność x <= 10.
Zwróćmy uwagę, że warunek sprawdzający zakończenie działania pętli, tzn.
while (x <= 10), znajduje się na jej początku.
Zadanie
3.4 Napisz program, który za pomocą instrukcji for wyświetla liczby całko-
wite z przedziału od 1 do 20.
Listing 3.4. Przykładowe rozwiązanie
using System;

namespace Zadanie_34 // Zadanie 3.4.


{
class Program
{
static void Main(string[] args)
{
int i;

Console.WriteLine("Program wyświetla liczby całkowite od 1


´do 20.");
Console.WriteLine();

for (i = 1; i <= 20; i++)

8ba61431c02548173fa6085d93015e5a
8
42 C#. Zadania z programowania z przykładowymi rozwiązaniami

{
if (i < 20)
Console.Write(i + ", ");
else
Console.WriteLine(i + ".");
}
}
}
}

Rezultat działania programu można zobaczyć na rysunku 3.2.

Rysunek 3.2.
Efekt działania Program wyświetla liczby całkowite od 1 do 20.
programu
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20.
Zadanie 3.4

Zadanie
3.5 Napisz program, który za pomocą instrukcji do ... while wyświetla liczby
całkowite z przedziału od 1 do 20.

Listing 3.5. Przykładowe rozwiązanie

using System;

namespace Zadanie_35 // Zadanie 3.5.


{
class Program
{
static void Main(string[] args)
{
int i = 1; // Ustalenie wartości początkowej.

Console.WriteLine("Program wyświetla liczby całkowite od 1


´do 20.");
Console.WriteLine();

do
{
if (i < 20)
Console.Write(i + ", ");
else
Console.WriteLine(i + ".");
i++;
}
while (i <= 20);
}
}
}

8ba61431c02548173fa6085d93015e5a
8
Rozdział 3. ♦ Instrukcje sterujące przebiegiem programu — instrukcje iteracyjne 43

Zadanie
3.6 Napisz program, który za pomocą instrukcji while wyświetla liczby całko-
wite z przedziału od 1 do 20.

Listing 3.6. Przykładowe rozwiązanie

using System;

namespace Zadanie_36 // Zadanie 3.6.


{
class Program
{
static void Main(string[] args)
{
int i = 1; // Ustalenie wartości początkowej.

Console.WriteLine("Program wyświetla liczby całkowite od 1


´do 20.");
Console.WriteLine();

while (i <= 20)


{
if (i < 20)
Console.Write(i + ", ");
else
Console.WriteLine(i + ".");
i++;
}
}
}
}

Zadanie
3.7 Napisz program, który za pomocą instrukcji for sumuje liczby całkowite
z przedziału od 1 do 100.

Listing 3.7. Przykładowe rozwiązanie

using System;

namespace Zadanie_37 // Zadanie 3.7.


{
class Program
{
static void Main(string[] args)
{
int i, suma = 0;

8ba61431c02548173fa6085d93015e5a
8
44 C#. Zadania z programowania z przykładowymi rozwiązaniami

Console.WriteLine("Program sumuje liczby całkowite od 1


´do 100.");
Console.WriteLine();

for (i = 1; i <= 100; i++)


{
suma += i; // suma + i;
}
Console.WriteLine("Suma liczb od 1 do 100 wynosi
´" + suma + ".");
}
}
}

Za sumowanie liczb od 1 do 100 odpowiedzialne są następujące linijki kodu:


for (i = 1; i <= 100; i++)
{
suma += i; // suma + i;
}

gdzie += jest złożonym operatorem przypisania. Oczywiście przed pętlą


zmienna suma musi zostać wyzerowana:
suma = 0;

Rezultat działania programu można zobaczyć na rysunku 3.3.

Rysunek 3.3.
Efekt działania Program sumuje liczby całkowite od 1 do 100.
programu
Suma liczb od 1 do 100 wynosi 5050.
Zadanie 3.7

Zadanie
3.8 Napisz program, który za pomocą instrukcji do ... while sumuje liczby
całkowite z przedziału od 1 do 100.

Listing 3.8. Przykładowe rozwiązanie

using System;

namespace Zadanie_38 // Zadanie 3.8.


{
class Program
{
static void Main(string[] args)
{
int i = 1, suma = 0; // Ustalenie wartości początkowych.

8ba61431c02548173fa6085d93015e5a
8
Rozdział 3. ♦ Instrukcje sterujące przebiegiem programu — instrukcje iteracyjne 45

Console.WriteLine("Program sumuje liczby całkowite od 1


´do 100.");
Console.WriteLine();

do
{
suma += i; // suma + i;
i++;
}
while (i <= 100);

Console.WriteLine("Suma liczb od 1 do 100 wynosi


´" + suma + ".");
}
}
}

Zadanie
3.9 Napisz program, który za pomocą instrukcji while sumuje liczby całkowite
z przedziału od 1 do 100.

Listing 3.9. Przykładowe rozwiązanie

using System;

namespace Zadanie_39 // Zadanie 3.9.


{
class Program
{
static void Main(string[] args)
{
int i = 1, suma = 0; // Ustalenie wartości początkowych.

Console.WriteLine("Program sumuje liczby całkowite od 1


´do 100.");
Console.WriteLine();

while (i <= 100)


{
suma += i; // suma + i;
i++;
}

Console.WriteLine("Suma liczb od 1 do 100 wynosi


´" + suma + ".");
}
}
}

8ba61431c02548173fa6085d93015e5a
8
46 C#. Zadania z programowania z przykładowymi rozwiązaniami

Zadanie
3.10 Napisz program, który za pomocą instrukcji for sumuje liczby parzyste
z przedziału od 1 do 100.

Należy skorzystać z właściwości operatora modulo (%).

Listing 3.10. Przykładowe rozwiązanie

using System;

namespace Zadanie_310 // Zadanie 3.10.


{
class Program
{
static void Main(string[] args)
{
int i, suma = 0;

Console.WriteLine("Program sumuje liczby parzyste od 1


´do 100.");
Console.WriteLine();

for (i = 1; i <= 100; i++)


{
if (i % 2 == 0) suma += i;
}

Console.WriteLine("Suma liczb parzystych od 1 do 100 wynosi


´" + suma + ".");
}
}
}

Za sumowanie liczb parzystych występujących w przedziale od 1 do 100


odpowiedzialne są następujące linijki kodu:
for (i = 1; i <= 100; i++)
{
if (i % 2 == 0) suma += i;
}

Do wyodrębnienia liczb parzystych wykorzystaliśmy właściwości opera-


tora modulo (%). Użyliśmy w tym celu zapisu i%2 == 0 — jeśli reszta z dzie-
lenia całkowitego zmiennej i%2 wynosi zero, to mamy do czynienia z liczbą
parzystą, którą dodajemy do zmiennej suma.
Rezultat działania programu można zobaczyć na rysunku 3.4.

8ba61431c02548173fa6085d93015e5a
8
Rozdział 3. ♦ Instrukcje sterujące przebiegiem programu — instrukcje iteracyjne 47

Rysunek 3.4.
Efekt działania Program sumuje liczby parzyste od 1 do 100.
programu
Suma liczb parzystych od 1 do 100 wynosi 2550.
Zadanie 3.10

Zadanie
3.11 Napisz program, który za pomocą instrukcji do ... while sumuje liczby
parzyste z przedziału od 1 do 100.

Należy skorzystać z właściwości operatora modulo (%).

Listing 3.11. Przykładowe rozwiązanie

using System;

namespace Zadanie_311 // Zadanie 3.11.


{
class Program
{
static void Main(string[] args)
{
int i = 1, suma = 0; // Ustalenie wartości początkowych.

Console.WriteLine("Program sumuje liczby parzyste od 1


´do 100.");
Console.WriteLine();

do
{
if (i % 2 == 0) suma += i;
i++;
}
while (i <= 100);

Console.WriteLine("Suma liczb parzystych od 1 do 100 wynosi


´" + suma + ".");
}
}
}

Zadanie
3.12 Napisz program, który za pomocą instrukcji while sumuje liczby parzyste
z przedziału od 1 do 100.

8ba61431c02548173fa6085d93015e5a
8
48 C#. Zadania z programowania z przykładowymi rozwiązaniami

Należy skorzystać z właściwości operatora modulo (%).

Listing 3.12. Przykładowe rozwiązanie

using System;

namespace Zadanie_312 // Zadanie 3.12.


{
class Program
{
static void Main(string[] args)
{
int i = 1, suma = 0; // Ustalenie wartości początkowych.

Console.WriteLine("Program sumuje liczby parzyste od 1


´do 100.");
Console.WriteLine();

while (i <= 100)


{
if (i % 2 == 0) suma += i;
i++;
}

Console.WriteLine("Suma liczb parzystych od 1 do 100 wynosi


´" + suma + ".");
}
}
}

Zadanie
3.13 Napisz program, który za pomocą instrukcji for sumuje liczby nieparzyste
z przedziału od 1 do 100.

Należy skorzystać z właściwości operatora modulo (%) i operatora negacji (!).

Listing 3.13. Przykładowe rozwiązanie

using System;

namespace Zadanie_313 // Zadanie 3.13.


{
class Program
{
static void Main(string[] args)
{

8ba61431c02548173fa6085d93015e5a
8
Rozdział 3. ♦ Instrukcje sterujące przebiegiem programu — instrukcje iteracyjne 49

int i, suma = 0;

Console.WriteLine("Program sumuje liczby nieparzyste od 1


´do 100.");
Console.WriteLine();

for (i = 1; i <= 100; i++)


{
if (!(i % 2 == 0)) suma += i;
}

Console.WriteLine("Suma liczb nieparzystych od 1 do 100


´wynosi " + suma + ".");
}
}
}

Za dodawanie liczb nieparzystych znajdujących się w przedziale od 1 do 100


odpowiedzialne są następujące linijki kodu:
for (i = 1; i <= 100; i++)
{
if (!(i % 2 == 0)) suma += i;
}

Do wyodrębnienia liczb nieparzystych wykorzystaliśmy właściwości ope-


ratora modulo (%) i właściwości operatora negacji (!). Ten drugi przekształca
warunek prawdziwy na fałszywy, a fałszywy na prawdziwy. Jeśli reszta z dzie-
lenia całkowitego zmiennej (!(i % 2 == 0)) jest różna od zera, to mamy
do czynienia z liczbą nieparzystą, którą dodajemy do zmiennej suma.
Rezultat działania programu można zobaczyć na rysunku 3.5.

Rysunek 3.5.
Efekt działania Program sumuje liczby nieparzyste od 1 do 100.
programu
Suma liczb nieparzystych od 1 do 100 wynosi 2500.
Zadanie 3.13

Zadanie
3.14 Napisz program, który za pomocą instrukcji do ... while sumuje liczby
nieparzyste z przedziału od 1 do 100.

Należy skorzystać z właściwości operatora modulo (%) i operatora negacji (!).

8ba61431c02548173fa6085d93015e5a
8
50 C#. Zadania z programowania z przykładowymi rozwiązaniami

Listing 3.14. Przykładowe rozwiązanie

using System;

namespace Zadanie_314 // Zadanie 3.14.


{
class Program
{
static void Main(string[] args)
{
int i = 1, suma = 0; // Ustalenie wartości początkowych.

Console.WriteLine("Program sumuje liczby nieparzyste od 1


´do 100.");
Console.WriteLine();

do
{
if (!(i % 2 == 0)) suma += i;
i++;
}
while (i <= 100);

Console.WriteLine("Suma liczb nieparzystych od 1 do 100


´wynosi " + suma + ".");
}
}
}

Zadanie
3.15 Napisz program, który za pomocą instrukcji while sumuje liczby nieparzyste
z przedziału od 1 do 100.

Należy skorzystać z właściwości operatora modulo (%) i operatora negacji (!).

Listing 3.15. Przykładowe rozwiązanie

using System;

namespace Zadanie_315 // Zadanie 3.15.


{
class Program
{
static void Main(string[] args)
{
int i = 1, suma = 0; // Ustalenie wartości początkowych.

8ba61431c02548173fa6085d93015e5a
8
Rozdział 3. ♦ Instrukcje sterujące przebiegiem programu — instrukcje iteracyjne 51

Console.WriteLine("Program sumuje liczby nieparzyste od 1


´do 100.");
Console.WriteLine();

while (i <= 100)


{
if (!(i % 2 == 0)) suma += i;
i++;
}

Console.WriteLine("Suma liczb nieparzystych od 1 do 100


´wynosi " + suma + ".");
}
}
}

Zadanie
3.16 Napisz program, który za pomocą instrukcji for znajduje największą i naj-
mniejszą liczbę ze zbioru n wylosowanych liczb całkowitych z przedziału
od 0 do 99 (w zadaniu n = 5) oraz oblicza średnią ze wszystkich wyloso-
wanych liczb.

Przetestuj program dla n = 2.

Listing 3.16. Przykładowe rozwiązanie

using System;
using static System.Math;

namespace Zadanie_316 // Zadanie 3.16.


{
class Program
{
static void Main(string[] args)
{
int ilość_liczb = 5, i;
double liczba, suma = 0, min, max;

Console.WriteLine("Program losuje " + ilość_liczb + " liczb


´całkowitych od 0 do 99,");
Console.WriteLine("a następnie znajduje najmniejszą
´i największą");
Console.WriteLine("oraz oblicza średnią ze wszystkich
´wylosowanych liczb.");

Random r = new Random();


min = Round(100 * (r.NextDouble()));

8ba61431c02548173fa6085d93015e5a
8
52 C#. Zadania z programowania z przykładowymi rozwiązaniami

Console.WriteLine();
Console.Write("Wylosowano liczby: " + min + ", ");

max = min;
suma += max;

for (i = 2; i <= ilość_liczb; i++)


{
liczba = Round(100 * (r.NextDouble()));
if (i < ilość_liczb)
Console.Write(liczba + ", ");
else
{
Console.Write(liczba + ".");
Console.WriteLine();
}

if (max < liczba) max = liczba;


if (liczba < min) min = liczba;

suma += liczba;
}

Console.WriteLine();
Console.WriteLine("Największa liczba to " + max + ".");
Console.WriteLine();
Console.WriteLine("Najmniejsza liczba to " + min + ".");
Console.WriteLine();
Console.WriteLine("Średnia wynosi " + suma / ilość_liczb +
´".");
}
}
}

W programie najpierw losujemy liczbę pseudolosową z określonego prze-


działu i przypisujemy jej wartość chwilowego min:
min = Round(100 * (r.NextDouble()));

W kolejnym kroku chwilowej wartości max nadajemy chwilową wartość min:


max = min;

Następnie w pętli:
for (i = 2; i <= ilość_liczb; i++)

wczytujemy pozostałe liczby, czyli ilość_liczb - 1, i sprawdzamy, czy następna


wylosowana liczba jest większa od liczby, która w trakcie poprzednich iteracji
była największa. Jeśli tak, to staje się ona na czas bieżącej iteracji liczbą

8ba61431c02548173fa6085d93015e5a
8
Rozdział 3. ♦ Instrukcje sterujące przebiegiem programu — instrukcje iteracyjne 53

największą (max). Analogicznie postępujemy ze zbadaniem wartości naj-


mniejszej — sprawdzamy, czy wylosowana w bieżącej iteracji liczba jest
mniejsza od liczby, która w trakcie poprzednich iteracji była najmniejsza.
Ilustrują to następujące linijki kodu:
if (max < liczba) max = liczba;
if (liczba < min) min = liczba;

Sumę wszystkich wylosowanych liczb obliczają linijki kodu przed pętlą


suma += max i w pętli suma += liczba, ich średnia zaś jest obliczana i wyświe-
tlana na ekranie przez następującą linijkę kodu:
Console.WriteLine("Średnia wynosi " + suma / ilość_liczb + ".");

Rezultat działania programu można zobaczyć na rysunku 3.6.

Rysunek 3.6.
Efekt działania Program losuje 5 liczb całkowitych od 0 do 99,
programu a następnie znajduje najmniejszą i największą
oraz oblicza średnią ze wszystkich wylosowanych liczb.
Zadanie 3.16
Wylosowano liczby: 68, 60, 74, 31, 13.

Największa liczba to 74.

Najmniejsza liczba to 13.

Średnia wynosi 49,2.

Zadanie
3.17 Napisz program, który za pomocą instrukcji do ... while znajduje największą
i najmniejszą liczbę ze zbioru n wylosowanych liczb całkowitych z przedziału
od 0 do 99 (w zadaniu n = 5) oraz oblicza średnią ze wszystkich wyloso-
wanych liczb.

Przetestuj program dla n = 2.

Listing 3.17. Przykładowe rozwiązanie

using System;
using static System.Math;

namespace Zadanie_317 // Zadanie 3.17.


{
class Program

8ba61431c02548173fa6085d93015e5a
8
54 C#. Zadania z programowania z przykładowymi rozwiązaniami

{
static void Main(string[] args)
{
int ilość_liczb = 5, i = 2;
double liczba, suma = 0, min, max;

Console.WriteLine("Program losuje " + ilość_liczb + " liczb


´całkowitych od 0 do 99,");
Console.WriteLine("a następnie znajduje najmniejszą
´i największą");
Console.WriteLine("oraz oblicza średnią ze wszystkich
´wylosowanych liczb.");

Random r = new Random();


min = Round(100 * (r.NextDouble()));
Console.WriteLine();
Console.Write("Wylosowano liczby: " + min + ", ");

max = min;
suma += max;

do
{
liczba = Round(100 * (r.NextDouble()));

if (i < ilość_liczb)
Console.Write(liczba + ", ");
else
{
Console.Write(liczba + ".");
Console.WriteLine();
}

if (max < liczba) max = liczba;


if (liczba < min) min = liczba;

suma += liczba;
i++;
}
while (i <= ilość_liczb);

Console.WriteLine();
Console.WriteLine("Największa liczba to " + max + ".");
Console.WriteLine();
Console.WriteLine("Najmniejsza liczba to " + min + ".");
Console.WriteLine();
Console.WriteLine("Średnia wynosi " + suma / ilość_liczb +
´".");
}
}
}

8ba61431c02548173fa6085d93015e5a
8
Rozdział 3. ♦ Instrukcje sterujące przebiegiem programu — instrukcje iteracyjne 55

Zadanie
3.18 Napisz program, który za pomocą instrukcji while znajduje największą
i najmniejszą liczbę ze zbioru n wylosowanych liczb całkowitych z prze-
działu od 0 do 99 (w zadaniu n = 5) oraz oblicza średnią ze wszystkich
wylosowanych liczb.

Przetestuj program dla n = 2.

Listing 3.18. Przykładowe rozwiązanie

using System;
using static System.Math;

namespace Zadanie_318 // Zadanie 3.18.


{
class Program
{
static void Main(string[] args)
{
int ilość_liczb = 5, i = 2;
double liczba, suma = 0, min, max;

Console.WriteLine("Program losuje " + ilość_liczb + " liczb


´całkowitych od 0 do 99,");
Console.WriteLine("a następnie znajduje najmniejszą
´i największą");
Console.WriteLine("oraz oblicza średnią ze wszystkich
´wylosowanych liczb.");

Random r = new Random();


min = Round(100 * (r.NextDouble()));
Console.WriteLine();
Console.Write("Wylosowano liczby: " + min + ", ");

max = min;
suma += max;

while (i <= ilość_liczb)


{
liczba = Round(100 * (r.NextDouble()));

if (i < ilość_liczb)
Console.Write(liczba + ", ");
else
{
Console.Write(liczba + ".");
Console.WriteLine();
}

8ba61431c02548173fa6085d93015e5a
8
56 C#. Zadania z programowania z przykładowymi rozwiązaniami

if (max < liczba) max = liczba;


if (liczba < min) min = liczba;

suma += liczba;
i++;
}

Console.WriteLine();
Console.WriteLine("Największa liczba to " + max + ".");
Console.WriteLine();
Console.WriteLine("Najmniejsza liczba to " + min + ".");
Console.WriteLine();
Console.WriteLine("Średnia wynosi " + suma / ilość_liczb +
´".");
}
}
}

Zadanie
3.19 Napisz program wyświetlający tabliczkę mnożenia dla liczb z przedziału od
1 do 100 (tzn. 10 × 10) z wykorzystaniem podwójnej pętli for.

Listing 3.19. Przykładowe rozwiązanie

using System;

namespace Zadanie_319 // Zadanie 3.19.


{
class Program
{
static void Main(string[] args)
{
int n = 10, wiersz, kolumna;

Console.WriteLine("Program wyświetla tabliczkę mnożenia


´dla liczb od 1 do 100.");
Console.WriteLine();

for (wiersz = 1; wiersz <= n; wiersz++)


{
for (kolumna = 1; kolumna <= n; kolumna++)
{
Console.Write(wiersz * kolumna + "\t");
}
Console.WriteLine();
}
}
}
}

8ba61431c02548173fa6085d93015e5a
8
Rozdział 3. ♦ Instrukcje sterujące przebiegiem programu — instrukcje iteracyjne 57

Rezultat działania programu można zobaczyć na rysunku 3.7.

Program wyświetla tabliczkę mnożenia dla liczb od 1 do 100.


1 2 3 4 5 6 7 8 9 10

2 4 6 8 10 12 14 16 18 20

3 6 9 12 15 18 21 24 27 30

4 8 12 16 20 24 28 32 36 40

5 10 15 20 25 30 35 40 45 50

6 12 18 24 30 36 42 48 54 60

7 14 21 28 35 42 49 56 63 70

8 16 24 32 40 48 56 64 72 80

9 18 27 36 45 54 63 72 81 90

10 20 30 40 50 60 70 80 90 100

Rysunek 3.7. Efekt działania programu Zadanie 3.19


Zadanie
3.20 Napisz program wyświetlający tabliczkę mnożenia dla liczb z przedziału od
1 do 100 (tzn. 10 × 10) z wykorzystaniem podwójnej pętli do ... while.

Listing 3.20. Przykładowe rozwiązanie

using System;

namespace Zadanie_320 // Zadanie 3.20.


{
class Program
{
static void Main(string[] args)
{
int n = 10, wiersz, kolumna;

Console.WriteLine("Program wyświetla tabliczkę mnożenia


´dla liczb od 1 do 100.");
Console.WriteLine();

wiersz = 1; // Wartość początkowa.

do
{
kolumna = 1; // Wartość początkowa.

8ba61431c02548173fa6085d93015e5a
8
58 C#. Zadania z programowania z przykładowymi rozwiązaniami

do
{
Console.Write(wiersz * kolumna + "\t");
kolumna++;
}
while (kolumna <= n);

wiersz++;
Console.WriteLine();
}
while (wiersz <= n);
}
}
}

Zadanie
3.21 Napisz program wyświetlający tabliczkę mnożenia dla liczb z przedziału od
1 do 100 (tzn. 10 × 10) z wykorzystaniem podwójnej pętli while.

Listing 3.21. Przykładowe rozwiązanie

using System;

namespace Zadanie_321 // Zadanie 3.21.


{
class Program
{
static void Main(string[] args)
{
int n = 10, wiersz, kolumna;

Console.WriteLine("Program wyświetla tabliczkę mnożenia


´dla liczb od 1 do 100.");
Console.WriteLine();

wiersz = 1; // Wartość początkowa.

while (wiersz <= n)


{
kolumna = 1; // Wartość początkowa.

while (kolumna <= n)


{
Console.Write(wiersz * kolumna + "\t");
kolumna++;
}

wiersz++;
Console.WriteLine();

8ba61431c02548173fa6085d93015e5a
8
Rozdział 3. ♦ Instrukcje sterujące przebiegiem programu — instrukcje iteracyjne 59

}
}
}
}

Zadanie
3.22 Napisz program, który wyświetla duże litery alfabetu od A do Z i od Z do
A z wykorzystaniem pętli for.

Listing 3.22. Przykładowe rozwiązanie

using System;

namespace Zadanie_322 // Zadanie 3.22.


{
class Program
{
static void Main(string[] args)
{
char znak;

Console.WriteLine("Program wyświetla duże litery alfabetu


´od A do Z i od Z do A.");
Console.WriteLine();

for (znak = 'A'; znak <= 'Z'; znak++)


{
if (znak < 'Z')
Console.Write(znak + ", ");
else
{
Console.Write(znak + ".");
Console.WriteLine();
}

Console.WriteLine();

for (znak = 'Z'; znak >= 'A'; znak--)


{
if (znak > 'A')
Console.Write(znak + ", ");
else
{
Console.Write(znak + ".");
Console.WriteLine();
}
}

8ba61431c02548173fa6085d93015e5a
8
60 C#. Zadania z programowania z przykładowymi rozwiązaniami

}
}
}

Rezultat działania programu można zobaczyć na rysunku 3.8.

Rysunek 3.8.
Efekt działania Program wyświetla duże litery alfabetu od A do Z i od Z do A.
programu
A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z.
Zadanie 3.22
Z, Y, X, W, V, U, T, S, R, Q, P, O, N, M, L, K, J, I, H, G, F, E, D, C, B, A.

Zadanie
3.23 Napisz program, który wyświetla duże litery alfabetu od A do Z i od Z do A
z wykorzystaniem pętli do ... while.

Listing 3.23. Przykładowe rozwiązanie

using System;

namespace Zadanie_323 // Zadanie 3.23.


{
class Program
{
static void Main(string[] args)
{
char znak;

Console.WriteLine("Program wyświetla duże litery alfabetu


´od A do Z i od Z do A.");
Console.WriteLine();

znak = 'A'; // Ustalenie wartości początkowej.

do
{
if (znak < 'Z')
Console.Write(znak + ", ");
else
{
Console.Write(znak + ".");
Console.WriteLine();
}
znak++;
}
while (znak <= 'Z');

8ba61431c02548173fa6085d93015e5a
8
Rozdział 3. ♦ Instrukcje sterujące przebiegiem programu — instrukcje iteracyjne 61

Console.WriteLine();

znak = 'Z'; // Ustalenie wartości początkowej.

do
{
if (znak > 'A')
Console.Write(znak + ", ");
else
{
Console.Write(znak + ".");
Console.WriteLine();
}
znak--;
}
while (znak >= 'A');
}
}
}

Zadanie
3.24 Napisz program, który wyświetla duże litery alfabetu od A do Z i od Z do A
z wykorzystaniem pętli while.

Listing 3.24. Przykładowe rozwiązanie

using System;

namespace Zadanie_324 // Zadanie 3.24.


{
class Program
{
static void Main(string[] args)
{
char znak;

Console.WriteLine("Program wyświetla duże litery alfabetu


´od A do Z i od Z do A.");
Console.WriteLine();

znak = 'A'; // Ustalenie wartości początkowej.

while (znak <= 'Z')


{
if (znak < 'Z')
Console.Write(znak + ", ");
else
{
Console.Write(znak + ".");

8ba61431c02548173fa6085d93015e5a
8
62 C#. Zadania z programowania z przykładowymi rozwiązaniami

Console.WriteLine();
}
znak++;
}

Console.WriteLine();

znak = 'Z'; // Ustalenie wartości początkowej.

while (znak >= 'A')


{
if (znak > 'A')
Console.Write(znak + ", ");
else
{
Console.Write(znak + ".");
Console.WriteLine();
}
znak--;
}
}
}
}

Zadanie
3.25 Wiedząc, że 1233 = 122 + 332, napisz program, który znajduje wszyst-
kie liczby z przedziału od 1000 do 9999 spełniające taką ciekawą zależ-
ność. Program dodatkowo liczy ich ilość.
Listing 3.25. Przykładowe rozwiązanie

using System;

namespace Zadanie_325 // Zadanie 3.25.


{
class Program
{
static void Main(string[] args)
{
int j = 0; // Licznik poszukiwanych liczb czterocyfrowych.

for (int i = 1000; i < 10000; i++)


{
int pdc = i / 100; // pdc — pierwsze 2 cyfry.
int odc = i % 100; // odc — ostatnie 2 cyfry.

if (pdc * pdc + odc * odc == i)


{

8ba61431c02548173fa6085d93015e5a
8
Rozdział 3. ♦ Instrukcje sterujące przebiegiem programu — instrukcje iteracyjne 63

Console.WriteLine(i + " = " + pdc + " * " + pdc +


´" + " + odc + " * " + odc);
++j;
}
}

Console.WriteLine();
Console.WriteLine("Znaleziono " + j + " liczby.");

}
}
}

Rezultat działania programu można zobaczyć na rysunku 3.9.

Rysunek 3.9.
Efekt działania 1233 = 12 * 12 + 33 * 33
programu 8833 = 88 * 88 + 33 * 33
Zadanie 3.25
Znaleziono 2 liczby.

Zachęcam Czytelników, aby to i następne zadanie rozwiązali z wykorzy-


staniem pozostałych pętli do ... while i while.
Zadanie
3.26 Wiedząc, że 990100 = 9902 + 1002, napisz program, który znajduje wszystkie
liczby z przedziału od 100 000 do 999 999 spełniające taką ciekawą zależ-
ność. Program dodatkowo liczy ich ilość.
Listing 3.26. Przykładowe rozwiązanie

using System;

namespace Zadanie_326 // Zadanie 3.26.


{
class Program
{
static void Main(string[] args)
{
int j = 0; // Licznik poszukiwanych liczb sześciocyfrowych.

for (Int32 i = 100000; i < 1000000; i++)


{
Int32 ptc = i / 1000; // ptc — pierwsze 3 cyfry.
Int32 otc = i % 1000; // otc — ostatnie 3 cyfry.

if (ptc * ptc + otc * otc == i)


{

8ba61431c02548173fa6085d93015e5a
8
64 C#. Zadania z programowania z przykładowymi rozwiązaniami

Console.WriteLine(i + " = " + ptc + " * " + ptc +


´" + " + otc + " * " + otc);
++j;
}
}

Console.WriteLine();
Console.WriteLine("Znaleziono " + j + " liczbę.");

}
}
}

Rezultat działania programu można zobaczyć na rysunku 3.10.

Rysunek 3.10.
Efekt działania 990100 = 990 * 990 + 100 * 100
programu
Znaleziono 1 liczbę.
Zadanie 3.26

Zadanie
3.27 Liczbę π można obliczać na wiele sposobów. Napisz program, który za pomocą
metody Monte Carlo oblicza liczbę π z określoną dokładnością. Skorzystaj
z zamieszczonego rysunku i następującej listy kroków:
1. Wpisz koło o promieniu r w kwadrat o boku 2r.
2. Losowo wygeneruj punkty i umieść je w kwadracie.
3. Wyznacz liczbę punktów, które znajdują się jednocześnie
w kwadracie i kole.
4. Niech promień r będzie wyznaczony przez stosunek liczby punktów
znajdujących się w kole do liczby punktów znajdujących się
w kwadracie.
5. π~ 4.0 · r.

Zauważmy, że im więcej punktów wygeneruje program, tym przybliżenie


liczby π będzie lepsze. Niestety wydłuży się też czas oczekiwania na wynik.

Aby zmierzyć czas obliczeń, należy skorzystać z właściwości System.Envi


´ronment.TickCount. Wartość tej właściwości jest tworzona na podsta-
wie czasomierza systemu i jest przechowywana jako 32-bitowa liczba
całkowita ze znakiem. Pobiera ona wyrażony w milisekundach czas, jaki
upłynął od określonego zdarzenia. Do tego zadania wrócimy w rozdziale 7.

8ba61431c02548173fa6085d93015e5a
8
Rozdział 3. ♦ Instrukcje sterujące przebiegiem programu — instrukcje iteracyjne 65

Dodatkowo należy zmierzyć czas obliczeń sekwencyjnych.


Listing 3.27. Przykładowe rozwiązanie

using System;
using static System.Math;

namespace Zadanie_327 // Zadanie 3.27.


{
class Program
{
static void Main(string[] args)
{
Int32 liczba_punktów = 100000000, i;
int licznik = 0;
double x, y, pi;

Console.WriteLine("Proszę czekać...");
Random r = new Random();

int start = Environment.TickCount; // Zliczanie taktów procesora


// start.

for (i = 1; i < liczba_punktów; i++)


{
// x i y to liczby losowe z przedziału [0, 1)
x = r.NextDouble();
y = r.NextDouble();

if (x * x + y * y <= 1)
{
licznik += 1; // Liczy punkty znajdujące się w kole.

8ba61431c02548173fa6085d93015e5a
8
66 C#. Zadania z programowania z przykładowymi rozwiązaniami

}
}

pi = 4.0 * licznik / liczba_punktów;

Console.WriteLine("Wartość pi = " + PI + ".");


Console.WriteLine("Obliczona wartość pi = " + pi + ".");
Console.WriteLine("Błąd obliczeń wynosi: " + Abs(PI - pi) +
´".");

int stop = Environment.TickCount; // Zliczanie taktów procesora stop.

int różnica = stop - start;


Console.WriteLine("Czas obliczeń: " + (różnica).ToString() +"
´ms.");
}
}
}

Rezultat działania programu można zobaczyć na rysunku 3.11.

Rysunek 3.11.
Efekt działania Proszę czekać...
programu Wartość pi = 3,14159265358979
Obliczona wartość pi = 3,14141548
Zadanie 3.27
Błąd obliczeń wynosi: 0,000177173589793078
Czas obliczeń: 8440 ms

Zadanie
3.28 Korzystając z dowolnej instrukcji iteracyjnej, napisz program, który gene-
ruje następujący ciąg liczb: 00112233445566778899.

Listing 3.28. Przykładowe rozwiązanie

using System;

namespace Zadanie_328 // Zadanie 3.28.


{
class Program
{
static void Main(string[] args)
{
int x;

Console.WriteLine("Program generuje następujący ciąg


´liczb: ");

for (int i = 0; i < 20; i++)

8ba61431c02548173fa6085d93015e5a
8
Rozdział 3. ♦ Instrukcje sterujące przebiegiem programu — instrukcje iteracyjne 67

{
x = i / 2;
Console.Write(x);
};

Console.WriteLine();
}
}
}

Rezultat działania programu można zobaczyć na rysunku 3.12.

Rysunek 3.12.
Efekt działania Program generuje następujący ciąg liczb:
programu 00112233445566778899
Zadanie 3.28

8ba61431c02548173fa6085d93015e5a
8
68 C#. Zadania z programowania z przykładowymi rozwiązaniami

8ba61431c02548173fa6085d93015e5a
8
Rozdział 4.
Tablice i kolekcje
W tym rozdziale przedstawiłem typowe zadania, wraz z przykładowymi roz-
wiązaniami, wykorzystujące tablice jedno- i dwuwymiarowe oraz kolekcje.

Tablice
Tablice — zbiór zmiennych tego samego typu (np. typu całkowitego, typu
rzeczywistego), do których odwołujemy się poprzez jedną wspólną nazwę —
są bardzo praktyczne, ponieważ oferują wygodny sposób połączenia powiąza-
nych ze sobą zmiennych. Ich główną zaletą jest to, że łatwo można na nich
przeprowadzać różne operacje, np. sortowanie.
Mimo że tablice w języku C# mogą być używane tak jak w innych językach,
mają jeden specjalny atrybut: są zaimplementowane jako obiekty1. W deklaracji
tablicy musimy określić typ wartości, jaki ma przechowywać tablica, a także
liczbę elementów tablicy. W języku C# tablice mogą być jednowymiarowe,
dwuwymiarowe itd.

Kolekcje
Kolekcja jest to obiekt przechowujący inne obiekty, np. tablice. Najbardziej
znaną i najczęściej stosowaną kolekcją w języku C# jest lista ArrayList().
Tak jak tablica, może przechowywać wiele elementów, jest jednak łatwiejsza
i wygodniejsza w obsłudze. W przeciwieństwie do tablic lista ArrayList

1 Więcej o obiektach Czytelnicy znajdą w rozdziale 5.

8ba61431c02548173fa6085d93015e5a
8
70 C#. Zadania z programowania z przykładowymi rozwiązaniami

nie wymaga deklaracji ani określenia, z ilu elementów ma się składać. Kolejne
elementy dodajemy do listy lub z niej usuwamy, korzystając z metod Add()
i RemoveAt(). Kolekcja ArrayList() ma wiele innych metod, np. Sort(),
które upraszczają jej obsługę i zwiększają funkcjonalność. Przestrzenią nazw,
której będziemy używali w programach, stosując kolekcje, jest System.Col
´lections.

Tablice jednowymiarowe
Oto przykład zadeklarowania tablicy jednowymiarowej i związanej z nią
zmiennej:
typ_tablicy [] nazwa_tablicy= new typ_tablicy[rozmiar_tablicy];

gdzie typ_tablicy określa podstawowy typ tablicy, ten zaś jednocześnie


definiuje typ danej znajdującej się w każdej komórce tablicy. Liczba elemen-
tów, które będzie zawierać tablica, jest określona przez rozmiar_tablicy.
Oto przykład zadeklarowania tablicy jednowymiarowej o nazwie dane typu
całkowitego zawierającej 10 elementów:
int [] dane = new int [10]; // Deklaracja tablicy typu int2.

Ta deklaracja tablicy działa tak jak deklaracja obiektu.


Dostęp do konkretnej wartości w tablicy jest realizowany za pośrednictwem
indeksu, który wskazuje dany element. Dla deklaracji tablicy:
int [] dane = new int [10];

aby uzyskać dostęp do pierwszego elementu tablicy dane, powinniśmy podać


indeks 0, drugi element dostępny jest przez indeks 1 itd. Ostatni element
tablicy ma indeks równy rozmiarowi tablicy 10 – 1, czyli 9, co przedstawia
reprezentacja graficzna tablicy:

0 1 2 3 4 5 6 7 8 9

Oto prosty przykład ilustrujący posługiwanie się tablicą jednowymiarową.

2 Deklaracja tablicy jednowymiarowej w języku C# jest podobna do deklaracji tablicy jed-


nowymiarowej w języku Java.

8ba61431c02548173fa6085d93015e5a
8
Rozdział 4. ♦ Tablice i kolekcje 71

Zadanie
4.1 Napisz program, który w 10-elementowej tablicy jednowymiarowej o nazwie
dane umieszcza liczby z przedziału od 0 do 9 (zobacz reprezentację gra-
ficzną tablicy zamieszczoną po tym poleceniu).

Indeks tablicy Wartość tablicy


0 0
1 1
2 2
3 3
4 4
5 5
6 6
7 7
8 8
9 9

Listing 4.1. Przykładowe rozwiązanie

using System;

namespace Zadanie_41 // Zadanie 4.1.


{
class Program
{
static void Main(string[] args)
{
int n = 10, i;
int[] dane = new int[n]; // Deklaracja tablicy typu int o rozmiarze n.

Console.WriteLine("Program wyświetla zawartość tablicy


´jednowymiarowej " + n + " - elementowej.");
Console.WriteLine();

for (i = 0; i < dane.Length; i++) // Długość tablicy (liczbę


// elementów) odczytujemy, korzystając z właściwości Length.
{
dane[i] = i; // Wpisywanie do tablicy kolejnych liczb od 0 do 9.
Console.WriteLine("dane[" + i + "] = " + dane[i]);
// Wyświetlenie zawartości tablicy.
}

8ba61431c02548173fa6085d93015e5a
8
72 C#. Zadania z programowania z przykładowymi rozwiązaniami

}
}
}

Następujące linijki kodu:


for (i = 0; i < dane.Length; i++) // Długość tablicy (liczbę elementów) odczytujemy,
// korzystając z właściwości Length.
{
dane[i] = i; // Wpisywanie do tablicy kolejnych liczb od 0 do 9.
Console.WriteLine("dane[" + i + "] = " + dane[i]);
// Wyświetlenie zawartości tablicy.
}

wpisują do tablicy o nazwie dane liczby od 0 do 9 oraz wyświetlają jej zawar-


tość. Długość tablicy (liczbę elementów) odczytujemy, korzystając z wła-
ściwości Length.
Rezultat działania programu można zobaczyć na rysunku 4.1.

Rysunek 4.1.
Efekt działania Program wyświetla zawartość tablicy jednowymiarowej 10-elementowej.
programu
dane[0] = 0
Zadanie 4.1
dane[1] = 1
dane[2] = 2
dane[3] = 3
dane[4] = 4
dane[5] = 5
dane[6] = 6
dane[7] = 7
dane[8] = 8
dane[9] = 9

Zadanie
4.2 Napisz program, który w 10-elementowej tablicy jednowymiarowej o nazwie
dane umieszcza liczby z przedziału od 9 do 0 (zobacz reprezentację gra-
ficzną tablicy zamieszczoną po tym poleceniu).

Indeks tablicy Wartość tablicy


0 9
1 8
2 7
3 6
4 5

8ba61431c02548173fa6085d93015e5a
8
Rozdział 4. ♦ Tablice i kolekcje 73

Indeks tablicy Wartość tablicy


5 4
6 3
7 2
8 1
9 0

Zadanie to rozwiążemy poprawnie, zamieniając w zadaniu 4.1 linijkę kodu:


dane[i] = i;
na następującą:
dane[i] = n – 1 - i;

Listing 4.2. Przykładowe rozwiązanie


using System;

namespace Zadanie_42 // Zadanie 4.2.


{
class Program
{
static void Main(string[] args)
{
int n = 10, i;
int[] dane = new int[n]; // Deklaracja tablicy typu int.

Console.WriteLine("Program wyświetla zawartość tablicy


´jednowymiarowej " + n + "-elementowej.");
Console.WriteLine();

for (i = 0; i < dane.Length; i++)


{
dane[i] = n - 1 - i; // Wpisywanie do tablicy kolejnych liczb
// od 9 do 0.
Console.WriteLine("dane[" + i + "] = " + dane[i]);
// Wyświetlenie zawartości tablicy.
}
}
}
}

Rezultat działania programu można zobaczyć na rysunku 4.2.

8ba61431c02548173fa6085d93015e5a
8
74 C#. Zadania z programowania z przykładowymi rozwiązaniami

Rysunek 4.2.
Efekt działania Program wyświetla zawartość tablicy jednowymiarowej 10-elementowej.
programu
dane[0] = 9
Zadanie 4.2
dane[1] = 8
dane[2] = 7
dane[3] = 6
dane[4] = 5
dane[5] = 4
dane[6] = 3
dane[7] = 2
dane[8] = 1
dane[9] = 0

Tablice dwuwymiarowe
Tablice dwuwymiarowe w języku C# deklarujemy podobnie jak tablice
jednowymiarowe. Oto przykład zadeklarowania tablicy dwuwymiarowej:
typ_tablicy [,] nazwa_tablicy = new typ_tablicy[rozmiar_tablicy,
´rozmiar_tablicy];3

Dostęp do elementów tablicy jest realizowany za pośrednictwem dwóch


indeksów, które wskazują dany element tablicy. Oto przykład ilustrujący
deklarację tablicy dwuwymiarowej 10 × 10 typu całkowitego:
int [,] macierz = new int[10, 10];

Tablicę dwuwymiarową o nazwie macierz, jako produkt o wymiarze 10 × 10.


Wartości liczbowe możemy wpisywać do tablicy wierszami lub kolumnami.
Pierwszy element tablicy o nazwie macierz (i o indeksach [0, 0]) ma wartość
równą 1, tzn. macierz[0, 0] = 1. Kolejne elementy mają następujące war-
tości: macierz[0, 1] = 2, macierz[1, 0] = 3 itd. Ostatni element tablicy ma
indeks równy wymiarowi tablicy minus 1, czyli 9, a zatem w naszym przy-
padku macierz[9, 9] = 7.

3 Drodzy Czytelnicy, jeśli programujecie w C/C++ lub w Javie, bądźcie ostrożni, deklarując
wielowymiarowe tablice w języku C# albo z nich korzystając. W C/C++ lub w Javie wymiar
tablicy oznaczany jest odrębną parą nawiasów kwadratowych, w C# zaś wymiary oddziela
się przecinkiem.

8ba61431c02548173fa6085d93015e5a
8
Rozdział 4. ♦ Tablice i kolekcje 75

1 2 0 0 0 0 0 0 0 0
3 1 0 0 0 0 0 0 0 0
0 0 1 0 0 0 0 0 0 0
0 0 0 1 0 0 0 0 0 0
0 0 0 0 1 0 0 0 0 0
0 0 0 0 0 1 0 0 0 0
0 0 0 0 0 0 1 0 0 0
0 0 0 0 0 0 0 1 0 0
0 0 0 0 0 0 0 0 1 0
0 0 0 0 0 0 0 0 0 7

Zadanie
4.3 Napisz program, który w zadeklarowanej tablicy dwuwymiarowej 10 × 10
o nazwie macierz umieszcza na przekątnej liczbę 1, a poza przekątną 0.
Dodatkowo program powinien obliczać sumę wyróżnionych w tablicy ele-
mentów, tzn. elementów znajdujących się na jej przekątnej.

1 0 0 0 0 0 0 0 0 0
0 1 0 0 0 0 0 0 0 0
0 0 1 0 0 0 0 0 0 0
0 0 0 1 0 0 0 0 0 0
0 0 0 0 1 0 0 0 0 0
0 0 0 0 0 1 0 0 0 0
0 0 0 0 0 0 1 0 0 0
0 0 0 0 0 0 0 1 0 0
0 0 0 0 0 0 0 0 1 0
0 0 0 0 0 0 0 0 0 1

Listing 4.3. Przykładowe rozwiązanie

using System;

namespace Zadanie_43 // Zadanie 4.3.


{
class Program
{

8ba61431c02548173fa6085d93015e5a
8
76 C#. Zadania z programowania z przykładowymi rozwiązaniami

static void Main(string[] args)


{
int n = 10, i, j, suma;
int[,] macierz = new int[n, n];

Console.WriteLine("Program wyświetla zawartość tablicy


´dwuwymiarowej " + n + " x " + n + ".");
Console.WriteLine();

// Wpisywanie do tablicy 1 na przekątnej, a 0 poza przekątną.


for (i = 0; i < n; i++)
{
for (j = 0; j < n; j++)
{
if (i == j)
macierz[i, j] = 1;
else
macierz[i, j] = 0;
}
}
// Wyświetlenie zawartości tablicy.
for (i = 0; i < n; i++)
{
for (j = 0; j < n; j++)
{
Console.Write(macierz[i, j] + " ");
}
Console.WriteLine();
}

suma = 0;

for (i = 0; i < n; i++)


{
suma += macierz[i, i];
}

Console.WriteLine();
Console.WriteLine("Suma wyróżnionych elementów w tablicy
´wynosi " + suma + ".");
}
}
}

Do wpisywania liczb do tablicy o nazwie macierz użyliśmy dwóch pętli for.


Następujące linijki kodu instrukcji warunkowej if:
if (i == j)
macierz[i, j] = 1;
else
macierz[i, j] = 0;

8ba61431c02548173fa6085d93015e5a
8
Rozdział 4. ♦ Tablice i kolekcje 77

odpowiadają za wpisywanie do tablicy liczby 1 na przekątnej, a 0 poza


przekątną.
Oto cały kod odpowiedzialny za wpisywanie do tablicy liczby 1 na przekątnej
i 0 poza przekątną:
for (i = 0; i < n; i++)
{
for(j = 0; j < n; j++)
{
if (i == j)
macierz[i, j] = 1;
else
macierz[i, j] = 0;
}
}

Za wyświetlenie zawartości tablicy na ekranie komputera odpowiadają


następujące linijki kodu:
for (i = 0; i < n; i++)
{
for(j = 0; j < n; j++)
{
Console.Write(macierz[i, j] + " ");
}
Console.WriteLine();
}

Za obliczanie sumy elementów znajdujących się na przekątnej tablicy odpo-


wiadają następujące linijki kodu:
for (i = 0; i < n; i++)
{
suma += macierz[i, i];
}

Oczywiście zmienną suma trzeba wcześniej wyzerować:


suma = 0;

Rezultat działania programu można zobaczyć na rysunku 4.3.

8ba61431c02548173fa6085d93015e5a
8
78 C#. Zadania z programowania z przykładowymi rozwiązaniami

Rysunek 4.3.
Efekt działania Program wyświetla zawartość tablicy dwuwymiarowej 10 x 10.
programu
1000000000
Zadanie 4.3
0100000000
0010000000
0001000000
0000100000
0000010000
0000001000
0000000100
0000000010
0000000001

Suma wyróżnionych elementów w tablicy wynosi 10.

Zadanie
4.4 Napisz program, który w zadeklarowanej tablicy dwuwymiarowej 10 × 10
o nazwie macierz umieszcza na przekątnej liczby z przedziału od 0 do 9,
a poza przekątną 0. Dodatkowo program powinien obliczać sumę wyróżnio-
nych w tablicy elementów, tzn. elementów znajdujących się na jej przekątnej.

0 0 0 0 0 0 0 0 0 0
0 1 0 0 0 0 0 0 0 0
0 0 2 0 0 0 0 0 0 0
0 0 0 3 0 0 0 0 0 0
0 0 0 0 4 0 0 0 0 0
0 0 0 0 0 5 0 0 0 0
0 0 0 0 0 0 6 0 0 0
0 0 0 0 0 0 0 7 0 0
0 0 0 0 0 0 0 0 8 0
0 0 0 0 0 0 0 0 0 9

Zadanie to rozwiążemy poprawnie, zamieniając w zadaniu 4.3. linijkę kodu:


macierz[i, j] = 1;
na następującą:
macierz[i, j] = i;

8ba61431c02548173fa6085d93015e5a
8
Rozdział 4. ♦ Tablice i kolekcje 79

Listing 4.4. Przykładowe rozwiązanie


using System;

namespace Zadanie_44 // Zadanie 4.4.


{
class Program
{
static void Main(string[] args)
{
int n = 10, i, j, suma;
int[,] macierz = new int[n, n];

Console.WriteLine("Program wyświetla zawartość tablicy


´dwuwymiarowej " + n + " x " + n + ".");
Console.WriteLine();

// Wpisywanie liczb do tablicy.


for (i = 0; i < n; i++)
{
for (j = 0; j < n; j++)
{
if (i == j)
macierz[i, j] = i;
else
macierz[i, j] = 0;
}
}

// Wyświetlenie zawartości tablicy.


for (i = 0; i < n; i++)
{
for (j = 0; j < n; j++)
{
Console.Write(macierz[i, j] + " ");
}
Console.WriteLine();
}

suma = 0;

for (i = 0; i < n; i++)


{
suma += macierz[i, i];
}

Console.WriteLine();
Console.WriteLine("Suma wyróżnionych elementów w tablicy
´wynosi " + suma + ".");
}
}
}

8ba61431c02548173fa6085d93015e5a
8
80 C#. Zadania z programowania z przykładowymi rozwiązaniami

Rezultat działania programu można zobaczyć na rysunku 4.4.


Rysunek 4.4.
Efekt działania Program wyświetla zawartość tablicy dwuwymiarowej 10 x 10.
programu
0000000000
Zadanie 4.4
0100000000
0020000000
0003000000
0000400000
0000050000
0000006000
0000000700
0000000080
0000000009

Suma wyróżnionych elementów w tablicy wynosi 45.

Zadanie
4.5 Napisz program, który w zadeklarowanej tablicy dwuwymiarowej 10 × 10
o nazwie macierz umieszcza liczby 1 i 0 zgodnie z zamieszczoną po polece-
niu interpretacją graficzną. Program dodatkowo powinien obliczać sumę
wyróżnionych elementów.

0 0 0 0 0 0 0 0 0 1
0 0 0 0 0 0 0 0 1 0
0 0 0 0 0 0 0 1 0 0
0 0 0 0 0 0 1 0 0 0
0 0 0 0 0 1 0 0 0 0
0 0 0 0 1 0 0 0 0 0
0 0 0 1 0 0 0 0 0 0
0 0 1 0 0 0 0 0 0 0
0 1 0 0 0 0 0 0 0 0
1 0 0 0 0 0 0 0 0 0

Sumę wyróżnionych elementów w tablicy obliczymy, zastępując linijki kodu


w zadaniu 4.3:
for (i = 0; i < n; i++)
{
suma += macierz[i, i];
}

8ba61431c02548173fa6085d93015e5a
8
Rozdział 4. ♦ Tablice i kolekcje 81

Zadanie to rozwiążemy poprawnie, zamieniając w zadaniu 4.3 linijki kodu:


if (i == j)
macierz[i, j] = 1;
else
macierz[i, j] = 0;
na następujące:
if (n == i + j + 1)
macierz[i, j] = 1;
else
macierz[i, j] = 0;

następującymi:
for (i = 0; i < n; i++)
{
suma += macierz[i, n – i - 1];
}

Listing 4.5. Przykładowe rozwiązanie

using System;

namespace Zadanie_45 // Zadanie 4.5.


{
class Program
{
static void Main(string[] args)
{
int n = 10, i, j, suma;
int[,] macierz = new int[n, n];

Console.WriteLine("Program wyświetla zawartość tablicy


´dwuwymiarowej " + n + " x " + n + ".");
Console.WriteLine();

// Wpisywanie do tablicy.
for (i = 0; i < n; i++)
{
for (j = 0; j < n; j++)
{
if (n == i + j + 1)
macierz[i, j] = 1;
else
macierz[i, j] = 0;
}
}

// Wyświetlenie zawartości tablicy.

8ba61431c02548173fa6085d93015e5a
8
82 C#. Zadania z programowania z przykładowymi rozwiązaniami

for (i = 0; i < n; i++)


{
for (j = 0; j < n; j++)
{
Console.Write(macierz[i, j] + " ");
}
Console.WriteLine();
}

suma = 0;

for (i = 0; i < n; i++)


{
suma += macierz[i, n - i - 1];
}
Console.WriteLine();
Console.WriteLine("Suma wyróżnionych elementów w tablicy
´wynosi " + suma + ".");

}
}
}

Rezultat działania programu można zobaczyć na rysunku 4.5.

Rysunek 4.5.
Efekt działania Program wyświetla zawartość tablicy dwuwymiarowej 10 x 10.
programu
0000000001
Zadanie 4.5
0000000010
0000000100
0000001000
0000010000
0000100000
0001000000
0010000000
0100000000
1000000000

Suma wyróżnionych elementów w tablicy wynosi 10.

Zadanie
4.6 Napisz program, który w zadeklarowanej tablicy dwuwymiarowej 10 × 10
o nazwie macierz umieszcza liczby z przedziału od 0 do 9 zgodnie z zamiesz-
czoną po poleceniu interpretacją graficzną. Program dodatkowo powinien
obliczać sumę wyróżnionych elementów.

8ba61431c02548173fa6085d93015e5a
8
Rozdział 4. ♦ Tablice i kolekcje 83

0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 1 0
0 0 0 0 0 0 0 2 0 0
0 0 0 0 0 0 3 0 0 0
0 0 0 0 0 4 0 0 0 0
0 0 0 0 5 0 0 0 0 0
0 0 0 6 0 0 0 0 0 0
0 0 7 0 0 0 0 0 0 0
0 8 0 0 0 0 0 0 0 0
9 0 0 0 0 0 0 0 0 0

Zadanie to rozwiążemy poprawnie, zamieniając w zadaniu 4.5 linijki kodu:


if (n == i + j + 1)
macierz[i, j] = 1;
else
macierz[i, j] = 0;
na następujące:
if (n == i + j + 1)
macierz[i, j] = i;
else
macierz[i, j] = 0;

Listing 4.6. Przykładowe rozwiązanie


using System;

namespace Zadanie_46 // Zadanie 4.6.


{
class Program
{
static void Main(string[] args)
{
int n = 10, i, j, suma;
int[,] macierz = new int[n, n];

Console.WriteLine("Program wyświetla zawartość tablicy


´dwuwymiarowej " + n + " x " + n + ".");
Console.WriteLine();

// Wpisywanie do tablicy.
for (i = 0; i < n; i++)
{
for (j = 0; j < n; j++)

8ba61431c02548173fa6085d93015e5a
8
84 C#. Zadania z programowania z przykładowymi rozwiązaniami

{
if (n == i + j + 1)
macierz[i, j] = i;
else
macierz[i, j] = 0;
}
}

// Wyświetlenie zawartości tablicy.


for (i = 0; i < n; i++)
{
for (j = 0; j < n; j++)
{
Console.Write(macierz[i, j] + " ");
}
Console.WriteLine();
}

suma = 0;

for (i = 0; i < n; i++)


{
suma += macierz[i, n - i - 1];
}

Console.WriteLine();
Console.WriteLine("Suma wyróżnionych elementów w tablicy
´wynosi " + suma + ".");
}
}
}

Rezultat działania programu można zobaczyć na rysunku 4.6.

Rysunek 4.6.
Efekt działania Program wyświetla zawartość tablicy dwuwymiarowej 10 x 10.
programu
0000000000
Zadanie 4.6
0000000010
0000000200
0000003000
0000040000
0000500000
0006000000
0070000000
0800000000
9000000000

Suma wyróżnionych elementów w tablicy wynosi 45.

8ba61431c02548173fa6085d93015e5a
8
Rozdział 4. ♦ Tablice i kolekcje 85

Zadanie
4.7 Napisz program, który w zadeklarowanej tablicy dwuwymiarowej 10 × 10
umieszcza w pierwszej kolumnie liczby z przedziału od 0 do 9, w drugiej
kolumnie kwadraty tych liczb, a w pozostałych kolumnach 0 (zobacz inter-
pretację graficzną tablicy zamieszczoną po poleceniu). Dodatkowo program
powinien obliczać sumę liczb znajdujących się w pierwszej kolumnie i sumę
liczb znajdujących się w drugiej kolumnie.

0 0 0 0 0 0 0 0 0 0
1 1 0 0 0 0 0 0 0 0
2 4 0 0 0 0 0 0 0 0
3 9 0 0 0 0 0 0 0 0
4 16 0 0 0 0 0 0 0 0
5 25 0 0 0 0 0 0 0 0
6 36 0 0 0 0 0 0 0 0
7 49 0 0 0 0 0 0 0 0
8 64 0 0 0 0 0 0 0 0
9 81 0 0 0 0 0 0 0 0

Listing 4.7. Przykładowe rozwiązanie


using System;

namespace Zadanie_47 // Zadanie 4.7.


{
class Program
{
static void Main(string[] args)
{
int n = 10, i, j, suma;
int[,] tablica = new int[n, n];

Console.WriteLine("Program wyświetla zawartość tablicy


´dwuwymiarowej " + n + " x " + n + ".");
Console.WriteLine();

// Wpisywanie liczb do tablicy.


for (i = 0; i < n; i++)
{
for (j = 0; j < n; j++)
{
if (j == 0) tablica[i, j] = i;
if (j == 1) tablica[i, j] = i * i;

8ba61431c02548173fa6085d93015e5a
8
86 C#. Zadania z programowania z przykładowymi rozwiązaniami

if (j > 1) tablica[i, j] = 0;
}
}

// Wyświetlenie zawartości tablicy.


for (i = 0; i < n; i++)
{
for (j = 0; j < n; j++)
{
Console.Write(tablica[i, j] + " ");
}
Console.WriteLine();
}

// Obliczanie sumy.
suma = 0;

for (i = 0; i < n; i++)


{
suma += tablica[i, 0];
}

Console.WriteLine();
Console.WriteLine("Suma liczb znajdujących się w pierwszej
´kolumnie = " + suma + ".");

suma = 0;

for (i = 0; i < n; i++)


{
suma += tablica[i, 1];
}

Console.WriteLine();
Console.WriteLine("Suma liczb znajdujących się w drugiej
´kolumnie = " + suma + ".");
}
}
}

Następujące linijki kodu:


for (i = 0; i < n; i++)
{
for (j = 0; j < n; j++)
{
if (j == 0) tablica[i, j] = i;
if (j == 1) tablica[i, j] = i * i;
if (j > 1) tablica[i, j] = 0;
}
}

8ba61431c02548173fa6085d93015e5a
8
Rozdział 4. ♦ Tablice i kolekcje 87

są odpowiedzialne za wpisywanie liczb do tablicy — do pierwszej kolumny


liczb od 0 do 9:
if (j == 0) tablica[i, j] = i;

kwadratów tych liczb do drugiej kolumny:


if (j == 1) tablica[i, j] = i * i;

i liczby 0 do pozostałych kolumn:


if (j > 1) tablica[i, j] = 0;

Za sumowanie liczb znajdujących się w pierwszej kolumnie odpowiedzialne są


następujące linijki kodu:
for (i = 0; i < n; i++)
{
suma += tablica[i, 0];
}

Za sumowanie liczb znajdujących się w drugiej kolumnie odpowiedzialne


są następujące linijki kodu:
for (i = 0; i < n; i++)
{
suma += tablica[i, 1];
}

Oczywiście zmienna suma musi zostać wcześniej wyzerowana:


suma = 0;

Rezultat działania programu można zobaczyć na rysunku 4.7.

Rysunek 4.7.
Efekt działania Program wyświetla zawartość tablicy dwuwymiarowej 10 x 10.
programu 0 000000000
Zadanie 4.7 1 100000000
2 400000000
3 900000000
4 16 0 0 0 0 0 0 0 0
5 25 0 0 0 0 0 0 0 0
6 36 0 0 0 0 0 0 0 0
7 49 0 0 0 0 0 0 0 0
8 64 0 0 0 0 0 0 0 0
9 81 0 0 0 0 0 0 0 0
Suma liczb znajdujących się w pierwszej kolumnie = 45.
Suma liczb znajdujących się w drugiej kolumnie = 285.

8ba61431c02548173fa6085d93015e5a
8
88 C#. Zadania z programowania z przykładowymi rozwiązaniami

Zadanie
4.8 Dane są dwie tablice dwuwymiarowe 10 × 10 o nazwach a i b. Tablica a
zawiera następujące elementy:

0 1 2 3 4 5 6 7 8 9
0 1 2 3 4 5 6 7 8 9
0 1 2 3 4 5 6 7 8 9
0 1 2 3 4 5 6 7 8 9
0 1 2 3 4 5 6 7 8 9
0 1 2 3 4 5 6 7 8 9
0 1 2 3 4 5 6 7 8 9
0 1 2 3 4 5 6 7 8 9
0 1 2 3 4 5 6 7 8 9
0 1 2 3 4 5 6 7 8 9

Tablica b zawiera same zera. Napisz program, który przepisuje zawartość


tablicy a do tablicy b (zobacz interpretację graficzną tablicy zamieszczoną
po poleceniu), zamieniając kolumny na wiersze.

0 0 0 0 0 0 0 0 0 0
1 1 1 1 1 1 1 1 1 1
2 2 2 2 2 2 2 2 2 2
3 3 3 3 3 3 3 3 3 3
4 4 4 4 4 4 4 4 4 4
5 5 5 5 5 5 5 5 5 5
6 6 6 6 6 6 6 6 6 6
7 7 7 7 7 7 7 7 7 7
8 8 8 8 8 8 8 8 8 8
9 9 9 9 9 9 9 9 9 9

Listing 4.8. Przykładowe rozwiązanie

using System;

namespace Zadanie_48 // Zadanie 4.8.


{

8ba61431c02548173fa6085d93015e5a
8
Rozdział 4. ♦ Tablice i kolekcje 89

class Program
{
static void Main(string[] args)
{
int n = 10, i, j;
int[,] a = new int[n, n];
int[,] b = new int[n, n];

// Wpisywanie liczb do tablicy a.


for (i = 0; i < n; i++)
{
for (j = 0; j < n; j++)
{
a[i, j] = j;
}
}

for (i = 0; i < n; i++)


{
for (j = 0; j < n; j++)
{
b[i, j] = a[j, i]; // Przepisywanie liczb z tablicy a do tablicy b.
}
}

// Wyświetlenie zawartości tablicy a.


Console.WriteLine("Wyświetlenie zawartości tablicy a.");
Console.WriteLine();

for (i = 0; i < n; i++)


{
for (j = 0; j < n; j++)
{
Console.Write(a[i, j] + " ");
}
Console.WriteLine();
}

Console.WriteLine();
// Wyświetlenie zawartości tablicy b.
Console.WriteLine("Wyświetlenie zawartości tablicy b.");
Console.WriteLine();

for (i = 0; i < n; i++)


{
for (j = 0; j < n; j++)
{
Console.Write(b[i, j] + " ");
}
Console.WriteLine();

8ba61431c02548173fa6085d93015e5a
8
90 C#. Zadania z programowania z przykładowymi rozwiązaniami

}
}
}
}

Następująca linijka kodu:


b[i, j] = a[j, i]; // Przepisywanie liczb z tablicy a do tablicy b.

przepisuje wszystkie liczby z tablicy a do tablicy b i zamienia kolumny na


wiersze.
Rezultat działania programu można zobaczyć na rysunku 4.8.

Rysunek 4.8.
Efekt działania Wyświetlenie zawartości tablicy a.
programu
0123456789
Zadanie 4.8
0123456789
0123456789
0123456789
0123456789
0123456789
0123456789
0123456789
0123456789
0123456789

Wyświetlenie zawartości tablicy b.

0000000000
1111111111
2222222222
3333333333
4444444444
5555555555
6666666666
7777777777
8888888888
9999999999

Pętla foreach
Kolekcja jest grupą obiektów. Język C# definiuje kilka grup kolekcji, z których
jedna jest tablicą. Pętla foreach przełącza się kolejno pomiędzy elementami
kolekcji. Jej ogólna postać jest następująca:

8ba61431c02548173fa6085d93015e5a
8
Rozdział 4. ♦ Tablice i kolekcje 91

foreach (typ nazwa_zmiennej in kolekcja)


{
........ // Instrukcje do wykonania.
}

gdzie typ i nazwa_zmiennej określają typ i nazwę zmiennej iteracyjnej, która


będzie otrzymywać wartości z kolekcji w miarę iteracji pętli foreach, nato-
miast kolekcja to grupa obiektów, czyli np. tablica:
foreach (int x in dane)
{
suma += x; // Sumowanie liczb od 1 do 100 znajdujących się w tablicy.
}

Zadanie
4.9 Napisz program, który w 100-elementowej tablicy jednowymiarowej o nazwie
dane umieszcza liczby z przedziału od 1 do 100, a następnie je sumuje.

Wykorzystaj właściwości pętli foreach.

Listing 4.9. Przykładowe rozwiązanie

using System;

namespace Zadanie_49 // Zadanie 4.9.


{
class Program
{
static void Main(string[] args)
{
int n = 100, i, suma = 0;
int[] dane = new int[n]; // Deklaracja tablicy typu int.

Console.WriteLine("Program sumuje liczby od 1 do 100


´znajdujące się w tablicy jednowymiarowej.");
Console.WriteLine();

for (i = 0; i < n; i++)


{
dane[i] = i + 1; // Wpisanie do tablicy liczb od 1 do 100.
}

foreach (int x in dane)


{
suma += x; // Sumowanie liczb od 1 do 100 znajdujących się w tablicy.
}

8ba61431c02548173fa6085d93015e5a
8
92 C#. Zadania z programowania z przykładowymi rozwiązaniami

Console.WriteLine("Suma liczb od 1 do 100 wynosi " +


´suma + ".");
}
}
}

Następująca linijka kodu:


foreach (int x in dane)
{
suma += x; // Sumowanie liczb od 1 do 100 znajdujących się w tablicy.
}

sumuje liczby od 1 do 100 znajdujące się w tablicy o nazwie dane z wyko-


rzystaniem pętli foreach. Piękno i elegancja!
Zadanie
4.10 Napisz program, który w 100-elementowej tablicy jednowymiarowej o nazwie
dane umieszcza liczby z przedziału od 1 do 100, a następnie sumuje liczby
parzyste i nieparzyste należące do tego przedziału.

Wykorzystaj właściwości pętli foreach.

Listing 4.10. Przykładowe rozwiązanie

using System;

namespace Zadanie_410 // Zadanie 4.10.


{
class Program
{
static void Main(string[] args)
{
int n = 100, i, suma_p = 0, suma_np = 0;
int[] dane = new int[n]; // Deklaracja tablicy typu int.

Console.WriteLine("Program sumuje liczby parzyste


´i nieparzyste");
Console.WriteLine("z przedziału od 1 do 100 znajdujące się
´w tablicy.");

for (i = 0; i < n; i++)


{
dane[i] = i + 1; // Wpisanie liczb od 1 do 100 do tablicy.
}

foreach (int x in dane)


{

8ba61431c02548173fa6085d93015e5a
8
Rozdział 4. ♦ Tablice i kolekcje 93

if ((x % 2) == 0)
suma_p += x; // Sumowanie liczb parzystych znajdujących się
// w tablicy.
else
suma_np += x; // Sumowanie liczb nieparzystych znajdujących się
// w tablicy.
}

Console.WriteLine();
Console.WriteLine("Suma liczb parzystych od 1 do 100 wynosi
´" + suma_p + ".");
Console.WriteLine();
Console.WriteLine("Suma liczb nieparzystych od 1 do 100
´wynosi " + suma_np + ".");
}
}
}

Następujące linijki kodu:


foreach (int x in dane)
{
if ((x%2) == 0)
suma_p += x; // Sumowanie liczb parzystych znajdujących się
// w tablicy.
else
suma_np += x; // Sumowanie liczb nieparzystych znajdujących się
// w tablicy.
}

sumują liczby parzyste i nieparzyste z przedziału od 1 do 100 znajdujące


się w tablicy o nazwie dane.
Rezultat działania programu można zobaczyć na rysunku 4.9.

Rysunek 4.9.
Efekt działania Program sumuje liczby parzyste i nieparzyste
programu z przedziału od 1 do 100 znajdujące się w tablicy.
Zadanie 4.10 Suma liczb parzystych od 1 do 100 wynosi 2550.
Suma liczb nieparzystych od 1 do 100 wynosi 2500.

Zadanie
4.11 Korzystając z właściwości kolekcji ArrayList(), napisz program, który do
kolekcji tego typu dodaje sześć liczb, a następnie je sortuje. Po wykonaniu tej
operacji należy dokonać usunięcia drugiego elementu z listy, dodać nowy
element do listy i dokonać ponownego sortowania.

8ba61431c02548173fa6085d93015e5a
8
94 C#. Zadania z programowania z przykładowymi rozwiązaniami

Listing 4.11. Przykładowe rozwiązanie

using System;
using System.Collections;

namespace Zadanie_411 // Zadanie 4.11.


{
class Program
{
static void Main(string[] args)
{
ArrayList lista = new ArrayList();

lista.Add(20); // Dodawanie kolejnych elementów do listy.


lista.Add(51);
lista.Add(-72);
lista.Add(4);
lista.Add(14);
lista.Add(-4);

Console.Write("Elementy nieposortowane: ");


for (int i = 0; i < lista.Count; i++)
{
if (i < lista.Count - 1)
Console.Write(lista[i] + ", ");
else
Console.Write(lista[i] + ". ");
}

lista.Sort(); // Sortowanie listy.

Console.WriteLine();
Console.Write("Elementy posortowane: ");
for (int i = 0; i < lista.Count; i++)
{
if (i < lista.Count - 1)
Console.Write(lista[i] + ", ");
else
Console.Write(lista[i] + ". ");
}

Console.WriteLine();
lista.RemoveAt(1); // Usunięto drugi element z listy.
Console.WriteLine("Usunięto drugi element z listy i dodano
´nowy element do listy.");

lista.Add(10); // Dodano nowy element do listy.

Console.Write("Elementy nieposortowane po usunięciu drugiego


´elementu z listy i dodaniu nowego elementu: ");
for (int i = 0; i < lista.Count; i++)

8ba61431c02548173fa6085d93015e5a
8
Rozdział 4. ♦ Tablice i kolekcje 95

{
if (i < lista.Count - 1)
Console.Write(lista[i] + ", ");
else
Console.Write(lista[i] + ". ");
}

lista.Sort(); // Sortowanie listy.

Console.WriteLine();
Console.Write("Elementy posortowane: ");
for (int i = 0; i < lista.Count; i++)
{
if (i < lista.Count - 1)
Console.Write(lista[i] + ", ");
else
Console.Write(lista[i] + ". ");
}
}
}
}

Do programu dołączono przestrzeń nazw:


using System.Collections.

Rezultat działania programu można zobaczyć na rysunku 4.10.

Rysunek 4.10.
Efekt działania Elementy nieposortowane: 20, 51, -72, 4, 14, -4.
programu Elementy posortowane: -72, -4, 4, 14, 20, 51.
Usunięto drugi element z listy i dodano nowy element do listy.
Zadanie 4.11
Elementy nieposortowane po usunięciu drugiego elementu z listy i dodaniu
nowego elementu: -72, 4, 14, 20, 51, 10.
Elementy posortowane: -72, 4, 10, 14, 20, 51.

Zadanie
4.12 Korzystając z właściwości kolekcji ArrayList(), napisz program, który
do kolekcji tego typu dodaje sześć imion, a następnie je sortuje.

Listing 4.12. Przykładowe rozwiązanie

using System;
using System.Collections;

namespace Zadanie_412 // Zadanie 4.12.


{
class Program

8ba61431c02548173fa6085d93015e5a
8
96 C#. Zadania z programowania z przykładowymi rozwiązaniami

{
static void Main(string[] args)
{
ArrayList lista = new ArrayList();

lista.Add("Tomek"); // Dodawanie kolejnych elementów do listy.


lista.Add("Ania");
lista.Add("Zenek");
lista.Add("Jarek");
lista.Add("Kasia");
lista.Add("Dominika");

Console.Write("Elementy nieposortowane: ");


for (int i = 0; i < lista.Count; i++)
{
if (i < lista.Count - 1)
Console.Write(lista[i] + ", ");
else
Console.Write(lista[i] + ". ");
}

lista.Sort(); // Sortowanie listy.

Console.WriteLine();
Console.Write("Elementy posortowane: ");
for (int i = 0; i < lista.Count; i++)
{
if (i < lista.Count - 1)
Console.Write(lista[i] + ", ");
else
Console.Write(lista[i] + ". ");
}
Console.WriteLine();
}
}
}

Rezultat działania programu można zobaczyć na rysunku 4.11.

Rysunek 4.11.
Efekt działania Elementy nieposortowane: Tomek, Ania, Zenek, Jarek, Kasia, Dominika.
programu Elementy posortowane: Ania, Dominika, Jarek, Kasia, Tomek, Zenek.
Zadanie 4.12

Ćwiczenie dodatkowe
Uzupełnij program z zadania 4.12 o linijki kodu, które dokonują usunięcia
drugiego elementu z listy, a następnie dodają nowy element do listy i doko-
nują ponownego sortowania.

8ba61431c02548173fa6085d93015e5a
8
Rozdział 4. ♦ Tablice i kolekcje 97

Działania na macierzach
Macierz to w matematyce układ liczb, symboli lub wyrażeń zapisanych
w postaci prostokątnej lub kwadratowej tablicy.
Zadanie
4.13 Dane są dwie macierze 10 × 10 o nazwach a i b. Macierz a zawiera nastę-
pujące elementy:

1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1

Macierz b ma postać:

2 2 2 2 2 2 2 2 2 2
2 2 2 2 2 2 2 2 2 2
2 2 2 2 2 2 2 2 2 2
2 2 2 2 2 2 2 2 2 2
2 2 2 2 2 2 2 2 2 2
2 2 2 2 2 2 2 2 2 2
2 2 2 2 2 2 2 2 2 2
2 2 2 2 2 2 2 2 2 2
2 2 2 2 2 2 2 2 2 2
2 2 2 2 2 2 2 2 2 2

Napisz program, który dodaje obie macierze: c = a + b.

8ba61431c02548173fa6085d93015e5a
8
98 C#. Zadania z programowania z przykładowymi rozwiązaniami

Listing 4.13. Przykładowe rozwiązanie

using System;

namespace Zadanie_413 // Zadanie 4.13.


{
class Program
{
static void Main(string[] args)
{
int n = 10, i, j;
int[,] a = new int[n, n];
int[,] b = new int[n, n];
int[,] c = new int[n, n];

// Wpisywanie liczb do macierzy a.


for (i = 0; i < n; i++)
{
for (j = 0; j < n; j++)
{
a[i, j] = 1;
}
}

// Wpisywanie liczb do macierzy b.


for (i = 0; i < n; i++)
{
for (j = 0; j < n; j++)
{
b[i, j] = 2;
}
}

// Wyświetlenie zawartości macierzy a.


Console.WriteLine("Macierz a.");
for (i = 0; i < n; i++)
{
for (j = 0; j < n; j++)
{
Console.Write(a[i, j] + " ");
}
Console.WriteLine();
}
Console.WriteLine();

// Wyświetlenie zawartości macierzy b.


Console.WriteLine("Macierz b.");
for (i = 0; i < n; i++)
{

8ba61431c02548173fa6085d93015e5a
8
Rozdział 4. ♦ Tablice i kolekcje 99

for (j = 0; j < n; j++)


{
Console.Write(b[i, j] + " ");
}
Console.WriteLine();
}

// Dodawanie macierzy a + b.
for (i = 0; i < n; i++)
{
for (j = 0; j < n; j++)
{
c[i, j] = a[i, j] + b[i, j];
}
}
Console.WriteLine();

// Wyświetlenie zawartości macierzy c.


Console.WriteLine("Macierz c = a + b.");
for (i = 0; i < n; i++)
{
for (j = 0; j < n; j++)
{
Console.Write(c[i, j] + " ");
}
Console.WriteLine();
}
}
}
}

Oto fragment kodu odpowiedzialny za dodawanie macierzy:


for (i = 0; i < n; i++)
{
for (j = 0; j < n; j++)
{
c[i, j] = a[i, j] + b[i, j];
}
}

Rezultat działania programu można zobaczyć na rysunku 4.12.

8ba61431c02548173fa6085d93015e5a
8
100 C#. Zadania z programowania z przykładowymi rozwiązaniami

Rysunek 4.12.
Efekt działania Macierz a.
programu 1111111111
1111111111
Zadanie 4.13
1111111111
1111111111
1111111111
1111111111
1111111111
1111111111
1111111111
1111111111

Macierz b.
2222222222
2222222222
2222222222
2222222222
2222222222
2222222222
2222222222
2222222222
2222222222
2222222222

Macierz c = a + b.
3333333333
3333333333
3333333333
3333333333
3333333333
3333333333
3333333333
3333333333
3333333333
3333333333

Zadanie
4.14 Dane są dwie macierze 10 × 10 o nazwach a i b, których postać jest taka sama
jak w zadaniu 4.13. Napisz program, który oblicza różnicę tych macierzy:
c = b - a.

Listing 4.14. Przykładowe rozwiązanie

using System;

namespace Zadanie_414 // Zadanie 4.14.


{

8ba61431c02548173fa6085d93015e5a
8
Rozdział 4. ♦ Tablice i kolekcje 101

class Program
{
static void Main(string[] args)
{
int n = 10, i, j;
int[,] a = new int[n, n];
int[,] b = new int[n, n];
int[,] c = new int[n, n];

// Wpisywanie liczb do macierzy a.


for (i = 0; i < n; i++)
{
for (j = 0; j < n; j++)
{
a[i, j] = 1;
}
}

// Wpisywanie liczb do macierzy b.


for (i = 0; i < n; i++)
{
for (j = 0; j < n; j++)
{
b[i, j] = 2;
}
}

// Wyświetlenie zawartości macierzy a.


Console.WriteLine("Macierz a.");
for (i = 0; i < n; i++)
{
for (j = 0; j < n; j++)
{
Console.Write(a[i, j] + " ");
}
Console.WriteLine();
}
Console.WriteLine();

// Wyświetlenie zawartości macierzy b.


Console.WriteLine("Macierz b.");
for (i = 0; i < n; i++)
{
for (j = 0; j < n; j++)
{
Console.Write(b[i, j] + " ");
}
Console.WriteLine();
}

8ba61431c02548173fa6085d93015e5a
8
102 C#. Zadania z programowania z przykładowymi rozwiązaniami

// Odejmowanie macierzy b – a.
for (i = 0; i < n; i++)
{
for (j = 0; j < n; j++)
{
c[i, j] = b[i, j] - a[i, j];
}
}
Console.WriteLine();

// Wyświetlenie zawartości macierzy c.


Console.WriteLine("Macierz c = b - a.");
for (i = 0; i < n; i++)
{
for (j = 0; j < n; j++)
{
Console.Write(c[i, j] + " ");
}
Console.WriteLine();
}

}
}
}

Oto fragment kodu odpowiedzialny za odejmowanie macierzy:


for (i = 0; i < n; i++)
{
for (j = 0; j < n; j++)
{
c[i, j] = b[i, j] - a[i, j];
}
}

Rezultat działania programu można zobaczyć na rysunku 4.13.


Zadanie
4.15 Dane są dwie macierze 10 × 10 o nazwach a i b, których postać jest taka sama
jak w zadaniu 4.13. Napisz program, który oblicza iloczyn tych macierzy:
c = a * b.

Listing 4.15. Przykładowe rozwiązanie

using System;

namespace Zadanie_415 // Zadanie 4.15.


{

8ba61431c02548173fa6085d93015e5a
8
Rozdział 4. ♦ Tablice i kolekcje 103

Rysunek 4.13.
Efekt działania Macierz a.
programu 1111111111
1111111111
Zadanie 4.14 1111111111
1111111111
1111111111
1111111111
1111111111
1111111111
1111111111
1111111111

Macierz b.
2222222222
2222222222
2222222222
2222222222
2222222222
2222222222
2222222222
2222222222
2222222222
2222222222

Macierz c = b - a.
1111111111
1111111111
1111111111
1111111111
1111111111
1111111111
1111111111
1111111111
1111111111
1111111111

class Program
{
static void Main(string[] args)
{
int n = 10, i, j;

int[,] a = new int[n, n];


int[,] b = new int[n, n];
int[,] c = new int[n, n];

// Wpisywanie liczb do macierzy a.


for (i = 0; i < n; i++)

8ba61431c02548173fa6085d93015e5a
8
104 C#. Zadania z programowania z przykładowymi rozwiązaniami

{
for (j = 0; j < n; j++)
{
a[i, j] = 1;
}
}
// Wpisywanie liczb do macierzy b.
for (i = 0; i < n; i++)
{
for (j = 0; j < n; j++)
{
b[i, j] = 2;
}
}

// Wyświetlenie zawartości macierzy a.


Console.WriteLine("Macierz a.");
for (i = 0; i < n; i++)
{
for (j = 0; j < n; j++)
{
Console.Write(a[i, j] + " ");
}
Console.WriteLine();
}
Console.WriteLine();

// Wyświetlenie zawartości macierzy b.


Console.WriteLine("Macierz b.");
for (i = 0; i < n; i++)
{
for (j = 0; j < n; j++)
{
Console.Write(b[i, j] + " ");
}
Console.WriteLine();
}

// Mnożenie macierzy kwadratowych a * b.


for (i = 0; i < n; i++)
{
for (j = 0; j < n; j++)
{
c[i, j] = 0;

for (int k = 0; k < n; k++)


{
c[i, j] += a[i, k] * b[k, j];
}
}

8ba61431c02548173fa6085d93015e5a
8
Rozdział 4. ♦ Tablice i kolekcje 105

}
Console.WriteLine();

// Wyświetlenie zawartości macierzy c.


Console.WriteLine("Macierz c = a * b.");
for (i = 0; i < n; i++)
{
for (j = 0; j < n; j++)
{
Console.Write(c[i, j] + " ");
}
Console.WriteLine();
}
}
}
}

Oto fragment kodu odpowiedzialny za mnożenie dwóch macierzy:


for (i = 0; i < n; i++)
{
for (j = 0; j < n; j++)
{
c[i, j] = 0;

for (int k = 0; k < n; k++)


{
c[i, j] += a[i, k] * b[k, j];
}
}
}

Rezultat działania programu można zobaczyć na rysunku 4.14.

Łańcuchy tekstowe
W wielu językach programowania typ łańcuchowy string jest tablicą zna-
ków, natomiast w C# typ string (łańcuch tekstowy) jest obiektem4. Dlatego
typ string jest typem referencyjnym. Łańcuchy tekstowe są bardzo uży-
teczne w programach, gdyż mogą przechowywać informacje takie jak nazwy
plików, tytuły książek, nazwiska pracowników i inne ciągi określonych
znaków. Klasa String oferuje kilka metod, które operują na łańcuchach
tekstowych.

4 Zob. rozdział 5.

8ba61431c02548173fa6085d93015e5a
8
106 C#. Zadania z programowania z przykładowymi rozwiązaniami

Rysunek 4.14.
Efekt działania Macierz a.
programu 1111111111
1111111111
Zadanie 4.15 1111111111
1111111111
1111111111
1111111111
1111111111
1111111111
1111111111
1111111111

Macierz b.
2222222222
2222222222
2222222222
2222222222
2222222222
2222222222
2222222222
2222222222
2222222222
2222222222

Macierz c = a * b.
20 20 20 20 20 20 20 20 20 20
20 20 20 20 20 20 20 20 20 20
20 20 20 20 20 20 20 20 20 20
20 20 20 20 20 20 20 20 20 20
20 20 20 20 20 20 20 20 20 20
20 20 20 20 20 20 20 20 20 20
20 20 20 20 20 20 20 20 20 20
20 20 20 20 20 20 20 20 20 20
20 20 20 20 20 20 20 20 20 20
20 20 20 20 20 20 20 20 20 20

W zadaniach 4.16 i 4.17 poznamy dwie z nich: pierwsza metoda, length(),


mierzy długość łańcucha, natomiast druga, concat(), składa dwa łańcuchy
(konkatenacja). Zachęcam Czytelnika do samodzielnego zapoznania się
z pozostałymi metodami klasy String.
Zadanie
4.16 Napisz program, który umożliwia odczytywanie długości łańcucha zapamię-
tanego w odpowiedniej zmiennej. Skorzystaj z metody Length, a wynik
wyświetl na ekranie monitora.

8ba61431c02548173fa6085d93015e5a
8
Rozdział 4. ♦ Tablice i kolekcje 107

Listing 4.16. Przykładowe rozwiązanie

using System;

namespace Zadanie_416 // Zadanie 4.16.


{
class Program
{
static void Main(string[] args)
{
String imie, nazwisko;

Console.WriteLine("Program mierzy długość łańcucha imię


´i nazwisko.");
Console.WriteLine("Podaj imię.");
imie = Console.ReadLine();
Console.WriteLine("Podaj nazwisko.");
nazwisko = Console.ReadLine();

Console.WriteLine("Imię " + imie + " zawiera " + imie.Length


´+ " liter(y).");
Console.WriteLine("Nazwisko " + nazwisko + " zawiera " +
´nazwisko.Length + " liter(y).");

}
}
}

W tym zadaniu skorzystaliśmy z klasy String, która zawiera metodę Length.


Mierzy ona długość łańcucha znaków, np. imię.Length, czyli liczbę zna-
ków, z których się on składa.
Rezultat działania programu można zobaczyć na rysunku 4.15.

Rysunek 4.15.
Efekt działania Program mierzy długość łańcucha imię i nazwisko.
programu Podaj imię.
Kazimierz
Zadanie 4.16
Podaj nazwisko.
Nowakowski
Imię Kazimierz zawiera 9 liter(y).
Nazwisko Nowakowski zawiera 10 liter(y).

8ba61431c02548173fa6085d93015e5a
8
108 C#. Zadania z programowania z przykładowymi rozwiązaniami

Konkatenacja
Konkatenacja (łac. concatenatio) to łączenie ze sobą różnych/równych
tekstów. W programowaniu konkatenacja oznacza łączenie dwóch tek-
stów w jeden (ustawienie jednego za drugim) za pomocą operatora +
(znak plusa).
Zadanie
4.17 Napisz program, który składa dwa łańcuchy, a wynik tej operacji wyświetla
na ekranie komputera.

Listing 4.17. Przykładowe rozwiązanie

using System;

namespace Zadanie_417 // Zadanie 4.17.


{
class Program
{
static void Main(string[] args)
{
String łańcuch_1, lancuch_2;

Console.WriteLine("Program składa dwa łańcuchy.");


Console.WriteLine("Podaj pierwszy łańcuch.");
łańcuch_1 = Console.ReadLine();

Console.WriteLine("Podaj drugi łańcuch.");


lancuch_2 = Console.ReadLine();

Console.WriteLine(łańcuch_1 + " + " + lancuch_2 + " = " +


´String.Concat(łańcuch_1, lancuch_2));
Console.WriteLine(lancuch_2 + " + " + łańcuch_1 + " = " +
´String.Concat(lancuch_2, łańcuch_1));

if (String.Compare(łańcuch_1, lancuch_2) == 0)
Console.WriteLine("Składanie dwóch równych łańcuchów jest
´przemienne.");
else
Console.WriteLine("Składanie dwóch różnych łańcuchów nie
´jest przemienne.");
}
}
}

W języku C# składanie dwóch łańcuchów umożliwia nam metoda Concat:


String.Concat(łańcuch_1, łańcuch_2)

8ba61431c02548173fa6085d93015e5a
8
Rozdział 4. ♦ Tablice i kolekcje 109

Porównanie dwóch łańcuchów znajduje się w następującej linijce kodu:


if (String.Compare(łańcuch_1, łańcuch_2) == 0)
Console.WriteLine("Składanie dwóch równych łańcuchów jest
´przemienne.");
else
Console.WriteLine("Składanie dwóch różnych łańcuchów nie
´jest przemienne.");

Rezultat działania programu można zobaczyć na rysunku 4.16.

Rysunek 4.16.
Efekt działania Program składa dwa łańcuchy.
programu Podaj pierwszy łańcuch.
ma
Zadanie 4.17
Podaj drugi łańcuch.
ta
ma + ta = mata
ta + ma = tama
Składanie dwóch różnych łańcuchów nie jest przemienne.

Programowanie uogólnione
i klasy generyczne
Programowanie uogólnione (lub inaczej generyczne, ang. generic program-
ming) jest jednym z paradygmatów programowania. Ten rodzaj programo-
wania pozwala pisać kod programu bez wcześniejszej znajomości typów
danych, na których kod ten będzie operował.
W języku C++ programowanie uogólnione umożliwiają szablony. W języku
C# służą do tego typy generyczne. Zapewniają one pełny mechanizm tworze-
nia struktur danych, które można przekształcić na wyspecjalizowaną wersję
w celu obsługi konkretnych typów. Programiści definiują typy parametryzo-
wane w taki sposób, by dla każdej zmiennej określonego typu generycznego
używany był ten sam wewnętrzny algorytm. Jednak typy danych i sygna-
tury metod mogą się zmieniać w zależności od podanego argumentu określa-
jącego typ. Aby ułatwić programistom naukę programowania, projektanci
języka C# zdecydowali się na zastosowanie składni pozornie podobnej do
składni szablonów z języka C++. W języku C# składnia tworzenia klas
i struktur generycznych wymaga użycia nawiasów ostrych do deklarowa-
nia parametrów w deklaracji typu i do podawania argumentów, gdy typ jest
używany.

8ba61431c02548173fa6085d93015e5a
8
110 C#. Zadania z programowania z przykładowymi rozwiązaniami

Proste metody generyczne


Są używane zazwyczaj w połączeniu z generycznymi klasami. W końcu
w takich metodach parametrem i typem zwracanym jest zazwyczaj klasa
generyczna.
Generyczne metody są definiowane w podobny sposób jak generyczne klasy.
Na przykład możemy użyć metody T Max<T>(T a, T b), zwracającej większą
wartość z dwóch wartości typu T. Funkcja ta jest bardzo użyteczna bez
względu na typ danych.
Zadanie
4.18 Napisz program, który zawiera prostą metodę generyczną zwracającą większą
wartość z dwóch wartości typu T.
Listing 4.18. Przykładowe rozwiązanie

using System;

namespace Zadanie_418 // Zadanie 4.18.


{
class Program
{
static T Max<T>(T a, T b) // Prosta metoda generyczna.
{
if (a.GetHashCode() > b.GetHashCode())
return a;
else
return b;
}

static void Main(string[] args)


{
int i = 10, j = 30;

Console.WriteLine("Max(" + i + ", " + j + ") = " + Max(i, j)


´+ ".");

double a = 4.51, b = 1.89;

Console.WriteLine("Max(" + a + ", " + b + ") = " + Max(a, b)


´+ ".");

string s1 = "Science", s2 = "Fiction";

Console.WriteLine("Max(" + s1 + ", " + s2 + ") = " +


´Max(s1, s2) + ".");
}

8ba61431c02548173fa6085d93015e5a
8
Rozdział 4. ♦ Tablice i kolekcje 111

}
}

Metoda T Max<T>(T a, T b) ma postać:


static T Max<T>(T a, T b) // Prosta metoda generyczna.
{
if (a.GetHashCode() > b.GetHashCode())
return a;
else
return b;
}

gdzie metoda GetHashCode() sprawdza, czy obiekty a i b są dwoma tymi


samymi obiektami jednej klasy i czy a > b.
Metodę generyczną T Max możemy zastąpić trochę inną metodą generyczną
T Max o takim samym działaniu, lecz różniącą się w linijkach kodu. Jej
postać jest następująca:
static T Max <T> (T a, T b) where T : IComparable<T>
{
return a.CompareTo (b) > 0 ? a : b;
}

Metoda ta przyjmuje argumenty każdego typu implementującego interfejs5


IComparable<T>, do którego zalicza się większość typów wbudowanych,
np. int, double i string.
Rezultat działania programu można zobaczyć na rysunku 4.17.

Rysunek 4.17.
Efekt działania Max(10, 30) = 30.
programu Max(4,51, 1,89) = 4,51.
Max(Science, Fiction) = Science.
Zadanie 4.18

Proste klasy generyczne


Klasy generyczne pozwalają tworzyć obiekty tej samej klasy dla różnych
typów danych. Dopiero przy tworzeniu obiektu klasy generycznej podajemy
typ danych, na którym będzie ten obiekt operował.

5 Interfejs jest podobny do klasy (zob. rozdział 5.), ale różni się od niej tym, że tylko opisuje
jej składowe.

8ba61431c02548173fa6085d93015e5a
8
112 C#. Zadania z programowania z przykładowymi rozwiązaniami

Istnieje możliwość tworzenia własnych klas generycznych według wzoru:


class Typ_Generyczny<T>
{
….
}

Na przykład:
class Typ_Generyczny<T>
{
public void Dodaj(T X)
{
Console.WriteLine("{0}", X);
}
}

Po zadeklarowaniu takiej klasy w jej obrębie typ T będzie oznaczał typ


danych podany podczas jej tworzenia. Przy tworzeniu obiektu kompilator
będzie wymagał, aby podać również typ danych, na których ma ona
operować:
Typ_Generyczny <string> Moj_Generyczny = new Typ_Generyczny<string>(); .
Zadanie
4.19 Napisz program, który zawiera prostą klasą generyczną o nazwie Klasa_
´Generyczna<T>, zwracającą większą wartość z dwóch wartości typu T
(zob. zadanie 4.18).
Listing 4.19. Przykładowe rozwiązanie

using System;

namespace Zadanie_419 // Zadanie 4.19.


{
class Program
{
public class Klasa_Generyczna<T> // Prosta klasa generyczna.
{
public T Max(T a, T b)
{
if (a.GetHashCode() > b.GetHashCode())
return a;
else
return b;
}
}

static void Main(string[] args)

8ba61431c02548173fa6085d93015e5a
8
Rozdział 4. ♦ Tablice i kolekcje 113

{
Klasa_Generyczna<int> ob_Gen = new Klasa_Generyczna<int>();
// Tworzymy obiekt.

Klasa_Generyczna<double> ob_Gen1 = new


´Klasa_Generyczna<double>();
Klasa_Generyczna<string> ob_Gen2 = new
´Klasa_Generyczna<string>();

int i = 10, j = 30;

Console.WriteLine("Max(" + i + ", " + j + ") = " +


´ob_Gen.Max(i, j) + ".");

double a = 4.51, b = 1.89;

Console.WriteLine("Max(" + a + ", " + b + ") = " +


´ob_Gen1.Max(a, b) + ".");

string s1 = "Science", s2 = "Fiction";

Console.WriteLine("Max(" + s1 + ", " + s2 + ") = " +


´ob_Gen2.Max(s1, s2) + ".");
}
}
}

Rezultat działania programu można zobaczyć na rysunku 4.17.


Zadanie
4.20 Napisz program, który zawiera prostą klasą generyczną o nazwie tablica<T,
T1>, gdzie T jest typem tablicy, a T1 jest typem licznika tablicy, i metodę
wyświetl_tablice(T[] tablica, T1 rozmiar), która wyświetla zawartość
tablicy na ekranie komputera.
Listing 4.20. Przykładowe rozwiązanie
using System;

namespace Zadanie_420 // Zadanie 4.20.


{
class Program
{
public class tablica<T, T1>
{
public void wyświetl_tablice(T[] tablica, T1 rozmiar)
{
for (int i = 0; i < rozmiar.GetHashCode(); i++)
{

8ba61431c02548173fa6085d93015e5a
8
114 C#. Zadania z programowania z przykładowymi rozwiązaniami

Console.Write(tablica[i] + " ");


}
Console.WriteLine();
}
}

static void Main(string[] args)


{
tablica<int, int> tab = new tablica<int, int>();
tablica<double, int> tab1 = new tablica<double, int>();
tablica<char, int> tab2 = new tablica<char, int>();

int[] strony = { 10, 20, 30, 40, 50 }; // Tablica typu int.


double[] ceny = { 10.5, 15.3, 20.11 }; // Tablica typu double.
char[] tekst = { 'H', 'E', 'L', 'L', 'O', '!' }; // Tablica typu
// char.

tab.wyświetl_tablice(strony, 5);
tab1.wyświetl_tablice(ceny, 3);
tab2.wyświetl_tablice(tekst, 6);
}
}
}

Rezultat działania programu można zobaczyć na rysunku 4.18.

Rysunek 4.18.
Efekt działania 10 20 30 40 50
programu 10,5 15,3 20,11
HELLO!
Zadanie 4.20

Listy generyczne
Zadanie
4.21 W zadaniach 4.11 i 4.12 korzystaliśmy z właściwości kolekcji ArrayList().
Napisz program, który jest kombinacją tych dwóch zadań i korzysta z listy
generycznej List<T> i jej metod.

Przykładowe rozwiązanie zawiera tylko część rozwiązania zadania.

Listing 4.21. Przykładowe rozwiązanie

using System;
using System.Collections.Generic;

namespace Zadanie_421 // Zadanie 4.21.

8ba61431c02548173fa6085d93015e5a
8
Rozdział 4. ♦ Tablice i kolekcje 115

{
class Program
{
static void Main(string[] args)
{
List<int> lista = new List<int>(); // Lista typu int.
List<string> lista1 = new List<string>(); // Lista1 typu string.

lista.Add(20); // Dodawanie kolejnych elementów do listy.


lista.Add(51);
lista.Add(-72);
lista.Add(4);
lista.Add(14);
lista.Add(-4);

Console.Write("Elementy nieposortowane: ");


foreach (var x in lista)
{
Console.Write(x + ", ");
}

lista.Sort(); // Sortowanie listy.


Console.WriteLine();

Console.Write("Elementy posortowane: ");


foreach (var x in lista)
{
Console.Write(x + ", ");
}

lista1.Add("Tomek"); // Dodawanie kolejnych elementów do listy1.


lista1.Add("Ania");
lista1.Add("Zenek");
lista1.Add("Jarek");
lista1.Add("Kasia");
lista1.Add("Dominika");

Console.WriteLine();

Console.Write("Elementy nieposortowane: ");


foreach (var x in lista1)
{
Console.Write(x + ", ");
}

lista1.Sort(); // Sortowanie listy1.


Console.WriteLine();

Console.Write("Elementy posortowane: ");


foreach (var x in lista1)
{

8ba61431c02548173fa6085d93015e5a
8
116 C#. Zadania z programowania z przykładowymi rozwiązaniami

Console.Write(x + ", ");


}
}
}
}

Słowo kluczowe var zawarte w następujących linijkach kodu:


foreach (var x in lista)
{
Console.Write(x + ", ");
}

służy do deklarowania zmiennych lokalnych o niejawnie określonym typie.


Rezultat działania programu można zobaczyć na rysunku 4.19.

Rysunek 4.19.
Efekt działania Elementy nieposortowane: 20, 51, -72, 4, 14, -4,
programu Elementy posortowane: -72, -4, 4, 14, 20, 51,
Zadanie 4.21
Elementy nieposortowane: Tomek, Ania, Zenek, Jarek, Kasia, Dominika,
Elementy posortowane: Ania, Dominika, Jarek, Kasia, Tomek, Zenek,

8ba61431c02548173fa6085d93015e5a
8
Rozdział 5.
Elementy
programowania
obiektowego
W tym rozdziale przedstawiłem typowe zadania, wraz z przykładowymi roz-
wiązaniami, wykorzystujące zasady i reguły programowania obiektowego.

Informacje ogólne
Programowanie obiektowe jest podstawą języka C#. Wszystkie obiektowe
języki programowania (w tym również C#) mają trzy podstawowe zasady:
hermetyzacja danych (enkapsulacja), polimorfizm i dziedziczenie.
W C# podstawową jednostką enkapsulacji jest klasa (ang. class), która de-
finiuje projekt i strukturę obiektu. Klasa to zdefiniowany przez programistę
typ danych, który zawiera w polach swoje wewnętrzne dane. Dodatkowo
klasa zawiera funkcje zwane metodami, które w określony sposób operują
na tych danych. Proste klasy zawierają albo same metody, albo same dane,
jednak większość klas zawiera zarówno metody, jak i dane.

8ba61431c02548173fa6085d93015e5a
8
118 C#. Zadania z programowania z przykładowymi rozwiązaniami

Klasy, pola, metody


Enkapsulacja klasy ma dwie cenne zalety: łączy dane z kodem, który na
nich operuje, i umożliwia kontrolę dostępu do składników. Każdy obiekt,
który został stworzony na podstawie danej klasy, nazywany jest jej instancją.
Klasa określa także typ danego obiektu. Przykładem typu obiektu jest
wieża, a pojedynczą instancją obiektu np. wieża SkyTower, dla typu obiektu
budynek pojedynczą instancją może być np. Hotel Mercury.
Każdy program w języku C# składa się ze zbioru klas. Aby program napi-
sany w C# udało się uruchomić, musi on mieć statyczną funkcję Main()
w postaci:
static void Main(string[] args)
{
......... // Instrukcje do wykonania.
}

oraz przynajmniej jedną klasę:


class Program
{
static void Main(string[] args)
{
......... // Instrukcje do wykonania.
}
}

Zapis w programie:
using System;

namespace Zadanie..... // Zadanie …


{
......... // Instrukcje do wykonania.
}

oznacza, że korzystamy z przestrzeni nazw System. Przestrzenie nazw służą


do organizowania klas i innych typów w jedną hierarchiczną strukturę.
Przestrzenie nazw definiuje się za pomocą instrukcji namespace.
Schematyczny szkielet klasy w języku C # ma następującą postać:
class nazwa_klasy
{
......... // Pola.
......... // Metody.
}

8ba61431c02548173fa6085d93015e5a
8
Rozdział 5. ♦ Elementy programowania obiektowego 119

Pola służą do przechowywania stałych i zmiennych określonych typów


zarówno prostych, jak i obiektowych. Metody służą do wykonywania ope-
racji na tych danych. W klasach możemy wyróżnić m.in. następujące ele-
menty: stałe, zmienne składowe, zmienne statyczne, metody (funkcje), kon-
struktory, destruktory.
Język C# ma następujące poziomy dostępu (modyfikatory) do swoich składo-
wych klasy (pola lub metody):
 public — elementy klasy opatrzone tym identyfikatorem stają się
elementami publicznymi, czyli dostępnymi na zewnętrz klasy,
co oznacza, że inne klasy będą mogły odwoływać się do tych
elementów;
 private — elementy klasy opatrzone tym identyfikatorem stają
się elementami prywatnymi, a dostęp do nich spoza klasy będzie
niemożliwy;
 protected — elementy klasy opatrzone tym identyfikatorem stają
się elementami chronionymi (będą dostępne dla danej klasy oraz
na potrzeby klas dziedziczących, nie będą natomiast widoczne
na zewnątrz klasy).
Aby zadeklarować zmienną typu obiektowego (klasowego, referencyjnego),
należy skorzystać z następującej konstrukcji:
nazwa_klasy nazwa_zmiennej;

Do tak zadeklarowanej zmiennej można następnie przypisać obiekt, który


tworzymy za pomocą operatora new:
new nazwa_klasy();

Z kolei konstrukcja:
nazwa_klasy nazwa_zmiennej = new nazwa_klasy();

pozwala na jednoczesną deklarację zmiennej, utworzenie obiektu i przypisa-


nie go do zmiennej. Po utworzeniu obiektu do jego pól można się odwołać
za pomocą operatora . (kropka) według następującego schematu:
nazwa_obiektu.nazwa_pola;

W analogiczny sposób jak do pól uzyskujemy dostęp do metod w klasie,


czyli za pomocą operatora . (kropka) według następującego schematu:
nazwa_obiektu.nazwa_metody();

8ba61431c02548173fa6085d93015e5a
8
120 C#. Zadania z programowania z przykładowymi rozwiązaniami

Większość zamieszczonych w tej książce zadań z programowania obiekto-


wego w języku C# rozwiążemy według następującego schematu:
using System;

namespace Zadanie..... // Zadanie …


{
class Pole_prostokąta
{
deklaracja zmiennych

public void czytaj_dane() // Deklaracja i definicja metody czytaj_dane().


{
...................
}

public void przetwórz_dane() // Deklaracja i definicja metody


// przetwórz_dane().
{
...................
}

public void wyświetl_wynik() // Deklaracja i definicja metody


// wyświetl_wynik().
{
...................
}

static void Main(string[] args)


{
Pole_prostokąta pole_pr = new Pole_prostokąta();

// Deklaracja zmiennej, utworzenie obiektu i przypisanie go do zmiennej.

pole_pr.czytaj_dane(); // Wywołanie metody czytaj_dane()


pole_pr.przetwórz_dane(); // Wywołanie metody przetwórz_dane().
pole_pr.wyświetl_wynik(); // Wywołanie metody wyświetl_wynik().

}
}
}

Metoda czytaj_dane() zajmuje się tylko czytaniem danych. Za przetwo-


rzenie danych odpowiedzialna jest metoda przetwórz_dane(). Ostatnia
z metod, wyświetl_wynik(), wyświetli na ekranie komputera przetworzone

8ba61431c02548173fa6085d93015e5a
8
Rozdział 5. ♦ Elementy programowania obiektowego 121

dane (wyniki). Metody mogą mieć parametry bądź nie — w zależności od


upodobania programisty. Powyższy schemat zilustrujemy przykładem zna-
nego nam już programu, który oblicza pole prostokąta.
Zadanie
5.1 Napisz zgodnie z zasadami programowania obiektowego program, który
oblicza pole prostokąta. Klasa powinna zawierać trzy metody:
1. czytaj_dane() — umożliwia wprowadzenie do programu wartości
boków a i b z klawiatury. W programie należy przyjąć, że boki
a i b oraz zmienna oblicz_pole są typu double (typu rzeczywistego).
2. przetwórz_dane() — oblicza pole prostokąta według wzoru:
oblicz_pole = a * b.
3. wyświetl_wynik() — wyświetla wartości boków a i b oraz wartość
zmiennej oblicz_pole w określonym formacie. Dla zmiennych
a i b oraz oblicz_pole należy przyjąć format wyświetlania tych
zmiennych na ekranie z dwoma miejscami po przecinku.
Listing 5.1. Przykładowe rozwiązanie

using System;

namespace Zadanie_51 // Zadanie 5.1.


{
class Pole_prostokąta
{
double a, b, oblicz_pole;

public void czytaj_dane() // Deklaracja i definicja metody czytaj_dane().


{
Console.WriteLine("Program oblicza pole prostokąta.");
Console.WriteLine("Podaj bok a.");
a = double.Parse(Console.ReadLine());
Console.WriteLine("Podaj bok b.");
b = double.Parse(Console.ReadLine());
}

public void przetwórz_dane() // Deklaracja i definicja metody


// przetworz_dane().
{
oblicz_pole = a * b;
}

public void wyświetl_wynik() // Deklaracja i definicja metody


// wyswietl_wynik().

8ba61431c02548173fa6085d93015e5a
8
122 C#. Zadania z programowania z przykładowymi rozwiązaniami

{
Console.Write("Pole prostokąta o boku a = {0:##.##} i boku
´b = {1:##.##}", a , b);
Console.WriteLine(" wynosi {0:##.##}.", oblicz_pole);
}

static void Main(string[] args)


{
Pole_prostokąta pole_pr = new Pole_prostokąta();
// Deklaracja zmiennej, utworzenie obiektu i przypisanie go do zmiennej.

pole_pr.czytaj_dane(); // Wywołanie metody czytaj_dane().


pole_pr.przetwórz_dane(); // Wywołanie metody przetwórz_dane().
pole_pr.wyświetl_wynik(); // Wywołanie metody wyświetl_wynik().
}
}
}

W naszym programie metoda czytaj_dane():


public void czytaj_dane() // Deklaracja i definicja metody czytaj_dane().
{
Console.WriteLine("Program oblicza pole prostokąta.");
Console.WriteLine("Podaj bok a.");
a = double.Parse(Console.ReadLine());
Console.WriteLine("Podaj bok b.");
b = double.Parse(Console.ReadLine());
}

wczytuje z klawiatury wartości boków a i b.


Metoda przetwórz_dane():
public void przetwórz_dane() // Deklaracja i definicja metody
// przetwórz_dane().
{
oblicz_pole = a * b;
}

oblicza pole prostokąta według wzoru oblicz_pole = a * b.


Z kolei metoda wyświetl_wynik():
public void wyświetl_wynik() // Deklaracja i definicja metody
// wyświetl_wynik().
{
Console.Write("Pole prostokąta o boku a = {0:##.##} i boku
´b = {1:##.##}", a , b);
Console.WriteLine(" wynosi {0:##.##}.", oblicz_pole);
}

8ba61431c02548173fa6085d93015e5a
8
Rozdział 5. ♦ Elementy programowania obiektowego 123

wyświetla wartości zmiennych a i b oraz wartość zmiennej pole według


ustalonego w zadaniu formatu.
Następująca linijka kodu:
Pole_prostokąta pole_pr = new Pole_prostokąta();
// Deklaracja zmiennej, utworzenie obiektu i przypisanie go do zmiennej.

umożliwia zadeklarowanie zmiennej pole_pr, utworzenie obiektu i przypi-


sanie go do zmiennej. W programie głównym zostają wywołane wszystkie
trzy metody:
pole_pr.czytaj_dane(); // Wywołanie metody czytaj_dane().
pole_pr.przetwórz_dane(); // Wywołanie metody przetwórz_dane().
pole_pr.wyświetl_wynik(); // Wywołanie metody wyświetl_wynik().

Następne zadania zamieszczone w tej książce spróbujemy rozwiązać według


powyższego schematu.
Zadanie
5.2 Napisz zgodnie z zasadami programowania obiektowego program, który
oblicza pierwiastki równania kwadratowego ax2 + bx + c = 0 z wykorzy-
staniem instrukcji wyboru case. Klasa powinna zawierać trzy metody:
 czytaj_dane() — wczytuje dane do programu i obsługuje sytuację,
kiedy a = 0. Zmienne a, b, c to liczby rzeczywiste wprowadzane
z klawiatury.
 przetwórz_dane() — wykonuje niezbędne obliczenia
 wyświetl_wynik() — wyświetla wyniki na ekranie komputera.
Dla zmiennych a, b, c, x1, x2 należy przyjąć format ich wyświetlania
na ekranie z dokładnością do dwóch miejsc po przecinku.
Listing 5.2. Przykładowe rozwiązanie

using System;

namespace Zadanie_52 // Zadanie 5.2.


{
class Trójmian
{
double a, b, c, delta, x1, x2;
byte liczba_pierwiastków;

public void czytaj_dane() // Deklaracja i definicja metody czytaj_dane().


{

8ba61431c02548173fa6085d93015e5a
8
124 C#. Zadania z programowania z przykładowymi rozwiązaniami

Console.WriteLine("Program oblicza pierwiastki równania


´kwadratowego dla dowolnych a, b, c.");
Console.WriteLine("Podaj a.");
a = double.Parse(Console.ReadLine());

if (a == 0)
{
Console.WriteLine("Niedozwolona wartość współczynnika a.
´Naciśnij klawisz ENTER.");
Console.Read(); // Naciśnij klawisz Enter.
Environment.Exit(0); // Wyjście z programu.
}
else
{
Console.WriteLine("Podaj b.");
b = double.Parse(Console.ReadLine());
Console.WriteLine("Podaj c.");
c = double.Parse(Console.ReadLine());
}
}

public void przetwórz_dane() // Deklaracja i definicja metody.


// przetwórz_dane()
{
delta = b * b - 4 * a * c;

if (delta < 0) liczba_pierwiastków = 0;


if (delta == 0) liczba_pierwiastków = 1;
if (delta > 0) liczba_pierwiastków = 2;

switch (liczba_pierwiastków)
{
case 1:
x1 = -b / (2 * a);
break;
case 2:
{
x1 = (-b - Math.Sqrt(delta)) / (2 * a);
x2 = (-b + Math.Sqrt(delta)) / (2 * a);
}
break;
}
}

public void wyświetl_wynik() // Deklaracja i definicja metody


// wyświetl_wynik().
{
Console.WriteLine("Dla wprowadzonych liczb");
Console.WriteLine("a = {0:##.##},", a);
Console.WriteLine("b = {0:##.##},", b);

8ba61431c02548173fa6085d93015e5a
8
Rozdział 5. ♦ Elementy programowania obiektowego 125

Console.WriteLine("c = {0:##.##}", c);

switch (liczba_pierwiastków)
{
case 0:
Console.WriteLine("brak pierwiastków
´rzeczywistych.");
break;
case 1:
Console.WriteLine("trójmian kwadratowy ma jeden
´pierwiastek podwójny: x1 = {0:##.##}.", x1);
break;
case 2:
{
Console.WriteLine("trójmian kwadratowy ma dwa
´pierwiastki: ");
Console.WriteLine("x1 = {0:##.##},", x1);
Console.WriteLine("x2 = {0:##.##}.", x2);
}
break;
}
}

static void Main(string[] args)


{
Trójmian trójmian1 = new Trójmian();

trójmian1.czytaj_dane();
trójmian1.przetwórz_dane();
trójmian1.wyświetl_wynik();
}
}
}

Zadanie
5.3 Napisz zgodnie z zasadami programowania obiektowego program, który
w tablicy 10 × 10 umieszcza losowo na przekątnej liczby z przedziału od 0
do 9, a poza przekątną zera. Dodatkowo program oblicza sumę liczb znajdu-
jących się na przekątnej. Klasa powinna zawierać trzy metody:
 czytaj_dane() — umieszcza liczby w tablicy.

 przetwórz_dane() — oblicza i wyświetla sumę liczb znajdujących


się na przekątnej.
 wyświetl_wynik() — wyświetla zawartość tablicy na ekranie
komputera.

8ba61431c02548173fa6085d93015e5a
8
126 C#. Zadania z programowania z przykładowymi rozwiązaniami

Listing 5.3. Przykładowe rozwiązanie

using System;
using static System.Math;

namespace Zadanie_53 // Zadanie 5.3.


{
class Matrix
{
public void czytaj_dane(double[,] macierz, int rozmiar)
{
int i, j;

Random rand = new Random(); // Generowanie liczby pseudolosowej.

for (i = 0; i < rozmiar; i++)


{
for (j = 0; j < rozmiar; j++)
{
if (i == j)
macierz[i, j] = Round(9 *(rand.NextDouble()));
// Wpisywanie liczb pseudolosowych od 0 do 9 na przekątnej tablicy.
else
macierz[i, j] = 0;
// Wpisywanie zer poza przekątną.
}
}
}

public void przetwórz_dane(double[,] macierz, int rozmiar)


{
int i; double
suma = 0;

for (i = 0; i < rozmiar; i++)


suma = suma + macierz[i, i];

Console.WriteLine("Suma elementów na przekątnej wynosi = " +


´suma + ".");
Console.WriteLine();
}

public void wyświetl_wynik(double[,] macierz, int rozmiar)


{
int i, j;

for (i = 0; i < rozmiar; i++)


{
for (j = 0; j < rozmiar; j++)
{

8ba61431c02548173fa6085d93015e5a
8
Rozdział 5. ♦ Elementy programowania obiektowego 127

Console.Write(macierz[i, j] + " ");


}
Console.WriteLine();
}
}

static void Main(string[] args)


{
int rozmiar = 10;
double[,] tablica = new double[rozmiar, rozmiar];

Matrix matrix1 = new Matrix();

matrix1.czytaj_dane(tablica, rozmiar);
matrix1.przetwórz_dane(tablica, rozmiar);
matrix1.wyświetl_wynik(tablica, rozmiar);
}
}
}

Zadanie
5.4 Napisz zgodnie z zasadami programowania obiektowego program, który
sortuje n liczb (w programie jest ich sześć). Klasa powinna zawierać trzy
metody:
 czytaj_dane() — czyta dane i umieszcza je w tablicy liczby.
 przetwórz_dane() — sortuje wczytane dane według wybranego
algorytmu sortowania (w programie został wykorzystany algorytm
sortowania bąbelkowego).
 wyświetl_wynik() — wyświetla zawartość posortowanej tablicy
liczby na ekranie komputera.

Listing 5.4. Przykładowe rozwiązanie

using System;

namespace Zadanie_54 // Zadanie 5.4.


{
class Sortowanie
{
public void czytaj_dane(int[] liczby, int n)
{
int i;

Console.Write("Liczby nieposortowane to: ");

for (i = 0; i < n; i++)


{

8ba61431c02548173fa6085d93015e5a
8
128 C#. Zadania z programowania z przykładowymi rozwiązaniami

if (i < n - 1)
Console.Write(liczby[i] + ", ");
else
Console.Write(liczby[i] + ".");
}
Console.WriteLine();
}

public void przetwórz_dane(int[] liczby, int n)


{
int i, j, x;

for (i = 1; i <= n - 1; i++)


{
for (j = n - 1; j >= i; --j)
{
if (liczby[j - 1] > liczby[j])
{
x = liczby[j - 1];
liczby[j - 1] = liczby[j];
liczby[j] = x;
}
} // Koniec pętli j.
} // Koniec pętli i.
}

public void wyświetl_wynik(int[] liczby, int n)


{
int i;

Console.WriteLine();
Console.Write("Liczby posortowane to: ");

for (i = 0; i < n; i++)


{
if (i < n - 1)
Console.Write(liczby[i] + ", ");
else
Console.Write(liczby[i] + ".");
}
Console.WriteLine();
}

static void Main(string[] args)


{
int n = 6;
int[] liczby = new int[n];

liczby[0] = 57;
liczby[1] = 303;

8ba61431c02548173fa6085d93015e5a
8
Rozdział 5. ♦ Elementy programowania obiektowego 129

liczby[2] = 34;
liczby[3] = -1025;
liczby[4] = 8;
liczby[5] = 20;

Sortowanie bąbelki = new Sortowanie();

bąbelki.czytaj_dane(liczby, n);
bąbelki.przetwórz_dane(liczby, n);
bąbelki.wyświetl_wynik(liczby, n);
}
}
}

Rezultat działania programu można zobaczyć na rysunku 5.1.

Rysunek 5.1.
Efekt działania Liczby nieposortowane to: 57, 303, 34, -1025, 8, 20.
programu
Liczby posortowane to: -1025, 8, 20, 34, 57, 303.
Zadanie 5.4

Rekurencja
W języku C# metoda może wywołać samą siebie. Proces ten nazywa się
rekurencją (rekursją), a metoda taka nazywa się metodą rekurencyjną
(rekursywną).
Główną zaletą rekurencji jest możliwość utworzenia metod obliczających
niektóre algorytmy w sposób znacznie prostszy i czytelniejszy niż niektóre
ich odpowiedniki iteracyjne.
Zadanie
5.5 Napisz program, który rekurencyjnie oblicza kolejne wartości n!, gdzie
n > 0 (w programie należy przyjąć n = 15), i wyświetla wynik na ekranie
komputera.
Listing 5.5. Przykładowe rozwiązanie

using System;

namespace Zadanie_55 // Zadanie 5.5.


{
class Silnia1
{
public long silnia(int liczba)

8ba61431c02548173fa6085d93015e5a
8
130 C#. Zadania z programowania z przykładowymi rozwiązaniami

{
long zwrot = 1;
if (liczba >= 2)
{
zwrot = liczba * silnia(liczba - 1);
}
return zwrot;
}

static void Main(string[] args)


{
int i, n;

Silnia1 s = new Silnia1();

Console.WriteLine("Obliczanie silni dla dowolnej liczby


´całkowitej.");
Console.WriteLine("Podaj n. ");
n = int.Parse(Console.ReadLine());

for (i = 1; i <= n; i++)


{
Console.WriteLine(" " + i + "! = " + s.silnia(i));
}
}
}
}

Algorytm obliczania silni został zawarty w metodzie silnia(int liczba):


public long silnia(int liczba)
{
long zwrot = 1;
if (liczba >= 2)
{
zwrot = liczba * silnia(liczba - 1);
}
return zwrot;
}

Rezultat działania programu można zobaczyć na rysunku 5.2.


Zadanie
5.6 Napisz program, który rekurencyjnie znajduje 10 pierwszych liczb trójkąt-
nych i wyświetla je na ekranie komputera.

W matematyce liczba trójkątna to liczba, którą można przedstawić w postaci


sumy kolejnych początkowych liczb naturalnych: Tn = 1 + 2 + 3 + (n – 1) + n.
Liczby należące do tego ciągu nazywane są liczbami trójkątnymi, gdyż
można je przedstawić w formie trójkąta.

8ba61431c02548173fa6085d93015e5a
8
Rozdział 5. ♦ Elementy programowania obiektowego 131

Rysunek 5.2.
Efekt działania Obliczanie silni dla dowolnej liczby całkowitej.
programu Podaj n.
15
Zadanie 5.5
1! = 1
2! = 2
3! = 6
4! = 24
5! = 120
6! = 720
7! = 5040
8! = 40320
9! = 362880
10! = 3628800
11! = 39916800
12! = 479001600
13! = 6227020800
14! = 87178291200
15! = 1307674368000

#1 1
#2 2 1
#3 3 2 1
#4 4 3 2 1
#5 5 4 3 2 1
#6 6 5 4 3 2 1

Kolejne elementy ciągu uzyskujemy w sposób następujący:


#1 = 1
#2 = 1 + 2 = 3
#3 = 1 + 2 + 3 = 6
#4 = 1 + 2 + 3 + 4 = 10
#5 = 1 + 2 + 3 + 4 + 5 = 15
#6 = 1 + 2 + 3 + 4 + 5 + 6 = 21 itd.

Listing 5.6. Przykładowe rozwiązanie


using System;

namespace Zadanie_56 // Zadanie 5.6.


{
class trójkątne1

8ba61431c02548173fa6085d93015e5a
8
132 C#. Zadania z programowania z przykładowymi rozwiązaniami

{
public int trójkątne(int n)
{
if (n == 1)
return 1;
else
return (n + trójkątne(n - 1));
}

static void Main(string[] args)


{
int i, n = 10;

trójkątne1 liczby = new trójkątne1();

Console.WriteLine("Program znajduje " + n + " pierwszych


´liczb trójkątnych.");
Console.WriteLine();

for (i = 1; i <= n; i++)


{
Console.WriteLine("#" + i + " = " + liczby.trójkątne(i));
}
}
}
}

Algorytm obliczania kolejnych liczb trójkątnych został zawarty w metodzie


public int trójkątne(int n):
public int trójkątne(int n)
{
if (n == 1)
return 1;
else
return (n + trójkątne(n - 1));
}

Rezultat działania programu można zobaczyć na rysunku 5.3.


Zadanie
5.7 Napisz program, który rekurencyjnie znajduje 10 pierwszych liczb Fibo-
nacciego i wyświetla je na ekranie komputera.

W matematyce ciąg Fibonacciego to ciąg liczb naturalnych określony reku-


rencyjnie w następujący sposób: pierwszy wyraz to F0 = 0, drugi to F1 = 1,
każdy następny jest sumą dwóch poprzednich, tzn. Fn-1 + Fn-2 dla n > 1.
Wyrazy ciągu Fibonacciego to: 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144,
233…

8ba61431c02548173fa6085d93015e5a
8
Rozdział 5. ♦ Elementy programowania obiektowego 133

Rysunek 5.3.
Efekt działania Program znajduje 10 pierwszych liczb trójkątnych.
programu
#1 = 1
Zadanie 5.6
#2 = 3
#3 = 6
#4 = 10
#5 = 15
#6 = 21
#7 = 28
#8 = 36
#9 = 45
#10 = 55

Listing 5.7. Przykładowe rozwiązanie

using System;

namespace Zadanie_57 // Zadanie 5.7.


{
class fib1
{
public int fib(int n)
{
switch (n)
{
case 0: return 0;
case 1: return 1;
default: return fib(n - 1) + fib(n - 2);
}
}

static void Main(string[] args)


{
int i, n = 10;
fib1 liczby = new fib1();

Console.WriteLine("Program znajduje rekurencyjnie " + n + "


´pierwszych liczb Fibonacciego.");
Console.WriteLine();

for (i = 0; i < n; i++)


{
Console.WriteLine(liczby.fib(i) + " ");
}
}
}
}

8ba61431c02548173fa6085d93015e5a
8
134 C#. Zadania z programowania z przykładowymi rozwiązaniami

Algorytm obliczania kolejnych liczb Fibonacciego został zawarty w meto-


dzie public int fib(int n):
public int fib(int n)
{
switch (n)
{
case 0: return 0;
case 1: return 1;
default: return fib(n - 1) + fib(n - 2);
}
}

Rezultat działania programu można zobaczyć na rysunku 5.4.

Rysunek 5.4.
Efekt działania Program znajduje rekurencyjnie 10 pierwszych liczb Fibonacciego.
programu
0
Zadanie 5.7
1
1
2
3
5
8
13
21
34

Klasa Osoba
Zadanie
5.8 Utwórz klasę Osoba, która zawiera następujące pola: nazwisko, imię, ulica,
kod i miasto oraz dwie metody: wczytaj() i wyświetl(). Pierwsza z metod
umożliwia wprowadzanie danych, natomiast druga wyświetla je na ekranie
komputera.
Listing 5.8. Przykładowe rozwiązanie

using System;

namespace Zadanie_58 // Zadanie 5.8.


{
class Osoba
{

8ba61431c02548173fa6085d93015e5a
8
Rozdział 5. ♦ Elementy programowania obiektowego 135

String nazwisko;
String imię;
String ulica;
String kod;
String miasto;

public void wczytaj()


{
Console.WriteLine("Wprowadzamy dane.");
Console.WriteLine("====================");
Console.WriteLine("Podaj nazwisko.");
nazwisko = Console.ReadLine();
Console.WriteLine("Podaj imię.");
imię = Console.ReadLine();
Console.WriteLine("Podaj ulicę.");
ulica = Console.ReadLine();
Console.WriteLine("Podaj kod.");
kod = Console.ReadLine();
Console.WriteLine("Podaj miasto.");
miasto = Console.ReadLine();
Console.WriteLine();
}

public void wyświetl()


{
Console.WriteLine("Wyświetlanie danych.");
Console.WriteLine("====================");
Console.WriteLine("Nazwisko: " + nazwisko);
Console.WriteLine("Imię: " + imię);
Console.WriteLine("Ulica: " + ulica);
Console.WriteLine("Kod: " + kod);
Console.WriteLine("Miasto: " + miasto);
}

static void Main(string[] args)


{
Osoba pracownik = new Osoba();

pracownik.wczytaj();
pracownik.wyświetl();
}
}
}

Rezultat działania programu można zobaczyć na rysunku 5.5.

8ba61431c02548173fa6085d93015e5a
8
136 C#. Zadania z programowania z przykładowymi rozwiązaniami

Rysunek 5.5.
Efekt działania Wprowadzamy dane.
programu ====================
Podaj nazwisko.
Zadanie 5.8
Nowak
Podaj imię.
Jan
Podaj ulicę.
Lipowa 14
Podaj kod.
00-960
Podaj miasto.
Warszawa

Wyświetlanie danych.
====================
Nazwisko: Nowak
Imię: Jan
Ulica: Lipowa 14
Kod: 00-960
Miasto: Warszawa

Dziedziczenie
Dziedziczenie (ang. inheritance) jest jedną z trzech podstawowych zasad
programowania obiektowego. Jest to mechanizm współdzielenia funk-
cjonalności pomiędzy klasami, który umożliwia tworzenie hierarchicznej
klasyfikacji. Dziedziczenie pozwala stworzyć ogólną klasę, która określa
wspólne cechy zbioru powiązanych klas. W języku C# klasa, po której się
dziedziczy, nazywa się klasą bazową (ang. base class), natomiast klasa dzie-
dzicząca nosi nazwę klasy wyprowadzonej (ang. derived class). Klasa wypro-
wadzona jest specjalną wersją klasy nadrzędnej i dziedziczy wszystkie zmienne
i metody zdefiniowane w klasie bazowej, dodając własne elementy.
Ogólna forma deklaracji klasy, która dziedziczy z klasy bazowej, jest nastę-
pująca:
class nazwa_klasa_wyprowadzonej : klasa_bazowa
{
......... // Ciało klasy.
}

8ba61431c02548173fa6085d93015e5a
8
Rozdział 5. ♦ Elementy programowania obiektowego 137

Zadanie
5.9 Napisz program, który zawiera proces dziedziczenia — klasa wyprowadzona
Kadra dziedziczy właściwości po klasie bazowej Osoba i zawiera dwa dodat-
kowe pola: wykształcenie i stanowisko oraz dwie dodatkowe metody:
wczytaj1() i wyświetl1().

Proces dziedziczenia pokażemy na przykładzie znanej nam klasy Osoba.


Aby stworzyć klasę wyprowadzoną Kadra z klasy bazowej Osoba, należy
zdefiniować ją następująco:
class Kadra : Osoba // Klasa Kadra dziedziczy po klasie Osoba.
{
String wykształcenie;
String stanowisko;

public void wczytaj1()


{
wczytaj(); // Wywołujemy metodę wczytaj().
Console.WriteLine("Podaj wykształcenie.");
wykształcenie = Console.ReadLine();
Console.WriteLine("Podaj stanowisko.");
stanowisko = Console.ReadLine();
}

public void wyświetl1()


{
wyświetl(); // Wywołujemy metodę wyświetl().
Console.WriteLine("Wykształcenie: " + wyksztalcenie + ".");
Console.WriteLine("Stanowisko: " + stanowisko + ".");
Console.WriteLine();
}
}

Zwróćmy uwagę, że metoda wczytaj() została zawarta w metodzie wczytaj1(),


natomiast metoda wyświetl() została zawarta w metodzie wyświetl1().
Wywołanie w programie głównym metody wczytaj1() spowoduje automa-
tyczne wywołanie metody wczytaj(). Podobne działanie uzyskamy dla metod
wyświetl1() i wyświetl().

Listing 5.9. Przykładowe rozwiązanie

using System;

namespace Zadanie_59 // Zadanie 5.9.


{
class Osoba
{
String nazwisko;

8ba61431c02548173fa6085d93015e5a
8
138 C#. Zadania z programowania z przykładowymi rozwiązaniami

String imię;
String ulica;
String kod;
String miasto;

public void wczytaj()


{
Console.WriteLine("Wprowadzamy dane.");
Console.WriteLine("====================");
Console.WriteLine("Podaj nazwisko.");
nazwisko = Console.ReadLine();
Console.WriteLine("Podaj imię.");
imię = Console.ReadLine();
Console.WriteLine("Podaj ulicę.");
ulica = Console.ReadLine();
Console.WriteLine("Podaj kod.");
kod = Console.ReadLine();
Console.WriteLine("Podaj miasto.");
miasto = Console.ReadLine();
}

public void wyświetl()


{
Console.WriteLine();
Console.WriteLine("Wyświetlanie danych.");
Console.WriteLine("====================");
Console.WriteLine("Nazwisko: " + nazwisko);
Console.WriteLine("Imię: " + imię);
Console.WriteLine("Ulica: " + ulica);
Console.WriteLine("Kod: " + kod);
Console.WriteLine("Miasto: " + miasto);
}
}

class Kadra : Osoba // klasa Kadra dziedziczy po klasie Osoba.


{
String wykształcenie;
String stanowisko;

public void wczytaj1()


{
wczytaj(); // Wywołujemy metodę wczytaj().
Console.WriteLine("Podaj wykształcenie.");
wykształcenie = Console.ReadLine();
Console.WriteLine("Podaj stanowisko.");
stanowisko = Console.ReadLine();
}

public void wyświetl1()


{
wyświetl(); // Wywołujemy metodę wyświetl().

8ba61431c02548173fa6085d93015e5a
8
Rozdział 5. ♦ Elementy programowania obiektowego 139

Console.WriteLine("Wykształcenie: " + wykształcenie);


Console.WriteLine("Stanowisko: " + stanowisko);
Console.WriteLine();
}
}

class Program
{
static void Main(string[] args)
{
Kadra pracownik = new Kadra();

pracownik.wczytaj1();
pracownik.wyświetl1();
}
}
}

Rezultat działania programu można zobaczyć na rysunku 5.6.

Rysunek 5.6.
Efekt działania Wprowadzamy dane.
programu ====================
Podaj nazwisko.
Zadanie 5.9
Nowak
Podaj imię.
Jan
Podaj ulicę.
Lipowa 14
Podaj kod.
00-960
Podaj miasto.
Warszawa

Podaj wykształcenie.
wyższe
Podaj stanowisko.
informatyk

Wyświetlanie danych.
====================
Nazwisko: Nowak
Imię: Jan
Ulica: Lipowa 14
Kod: 00-960
Miasto: Warszawa
Wykształcenie: wyższe
Stanowisko: informatyk

8ba61431c02548173fa6085d93015e5a
8
140 C#. Zadania z programowania z przykładowymi rozwiązaniami

8ba61431c02548173fa6085d93015e5a
8
Rozdział 6.
Pliki tekstowe i pliki
o dostępie swobodnym
W tym rozdziale przedstawiłem typowe zadania, wraz z przykładowymi
rozwiązaniami, zawierające podstawowe operacje wejścia – wyjścia na plikach
tekstowych i plikach o dostępie swobodnym oraz proces serializacji i deseriali-
zacji obiektów do pliku.

Informacje ogólne
W języku C# operacje wejścia – wyjścia na plikach realizowane są poprzez
strumienie. Strumień jest pojęciem abstrakcyjnym — może on wysyłać
i pobierać informacje z fizycznego urządzenia (np. z konsoli lub dysku).
W tym rozdziale prezentuję typowe zadania, w których zostały wykorzy-
stane dwa rodzaje plików:
 tekstowe,
 o dostępie swobodnym.

Pliki tekstowe
Pliki tekstowe zawierają informacje niezakodowane, bezpośrednio czytelne.
Są one plikami o dostępie sekwencyjnym.

8ba61431c02548173fa6085d93015e5a
8
142 C#. Zadania z programowania z przykładowymi rozwiązaniami

Język C# definiuje dwa typy strumieni: bajtowe i znakowe. Strumienie baj-


tów zapewniają wygodny sposób obsługi operacji wejścia – wyjścia na baj-
tach. Ze strumieni tych korzysta się podczas zapisu lub odczytu danych
binarnych do pliku na dysku. Strumienie znakowe zostały stworzone do
obsługi operacji wejścia – wyjścia na znakach. Wszystkie klasy strumieni są
zdefiniowane wewnątrz przestrzeni nazw System.IO. Aby móc skorzystać
z tych klas, należy dołączyć następującą instrukcję na początku programu:
using System.IO;

Strumienie znakowe umożliwiają zapis do plików tekstowych i ich odczyt.


Do takich operacji będziemy korzystali ze strumienia FileStream zawartego
wewnątrz klasy StreamReader (strumień odczytu) i klasy StreamWriter
(strumień zapisu)1. Klasy te automatycznie konwertują strumień bajtów na
strumień znaków i odwrotnie.
Aby utworzyć strumień znaków połączony z plikiem tekstowym, należy
utworzyć obiekt typu FileStream:
FileStream(string nazwa_pliku, FileMode tryb);

w którym nazwa_pliku to pełna nazwa pliku wraz ze ścieżką dostępu. Para-


metr tryb określa sposób, w jaki zostanie otwarty plik, np.:
FileStream("plik.txt", FileMode.Create);

przy czym FileMode.Create oznacza, że zostanie utworzony nowy plik


o nazwie plik.txt. Jeśli wcześniej istniał plik o takiej samej nazwie, to zostanie
on zniszczony, a dane w nim zawarte zostaną bezpowrotnie utracone. Z kolei
tryb:
FileStream("plik.txt", FileMode.Open);

oznacza, że nastąpi otwarcie już istniejącego pliku plik.txt.


Aby utworzyć wyjściowy strumień znakowy, musimy umieścić obiekt typu
Stream (np. FileStream) wewnątrz klasy StreamWriter. Klasa ta oferuje kilka
konstruktorów, z których najczęściej stosowany to:

1 Klasa StreamWriter została wyprowadzona z klasy abstrakcyjnej TextWriter. Z kolei klasa


StreamReader jest wyprowadzona z klasy abstrakcyjnej TextReader. Klasy abstrakcyjne
TextWriter i TextReader stoją na szczycie hierarchii strumieni znakowych. Klasa abstrak-
cyjna jest to klasa, która nie może mieć swoich reprezentantów pod postacią obiektów.
Metody w nich zdefiniowane dostępne są dla wszystkich podklas. Więcej informacji na
ten temat można znaleźć w bibliografii (pozycje 1. – 5.).

8ba61431c02548173fa6085d93015e5a
8
Rozdział 6. ♦ Pliki tekstowe i pliki o dostępie swobodnym 143

StreamWriter(Stream strumień);

gdzie strumień jest nazwą otwartego strumienia, np.:


fout = new FileStream("dane.txt", FileMode.Create);
// Tworzymy strumień znaków połączony z plikiem tekstowym w trybie Create.
StreamWriter fstr_out = new StreamWriter(fout); // Tworzymy strumień
// wyjściowy.

Aby utworzyć wejściowy strumień znakowy, musimy umieścić obiekt typu


Stream (np. FileStream) wewnątrz klasy StreamReader. Umożliwia to kon-
struktor:
StreamReader(Stream strumień);

gdzie strumień jest nazwą otwartego strumienia, np.:


fin = new FileStream("dane.txt", FileMode.Open);
// Tworzymy strumień znaków połączony z plikiem tekstowym w trybie Open.
StreamReader fstr_in = new StreamReader(fin); // Tworzymy strumień wejściowy.

Kiedy kończymy pracę z plikiem, powinniśmy zamknąć go, stosując metodę


Close(). Zamykając plik, zwalniamy zasoby zarezerwowane dla niego, co
umożliwia ich użycie przez inny plik.

Prezentowane dalej zadania nie zawierają obsługi sytuacji wyjątkowych.


W ramach dodatkowych ćwiczeń zachęcam Czytelników do wbudowania
do programów obsługi tych sytuacji.

Zadanie
6.1 Napisz zgodnie z zasadami programowania obiektowego program, który
wczytuje z klawiatury imię i nazwisko, zapisuje te dane do pliku tekstowego
dane.txt, a następnie odczytuje je z pliku dane.txt i wyświetla na ekranie
komputera. Klasa powinna zawierać trzy metody:
 czytaj_dane() — wczytuje z klawiatury imię i nazwisko.
 zapisz_dane_do_pliku() — zapisuje imię i nazwisko do pliku
tekstowego o nazwie dane.txt.
 czytaj_dane_z_pliku() — odczytuje dane z pliku dane.txt
i wyświetla je na ekranie komputera.
Listing 6.1. Przykładowe rozwiązanie

using System;
using System.IO; // Tutaj znajduje się FileStream.

namespace Zadanie_61 // Zadanie 6.1.

8ba61431c02548173fa6085d93015e5a
8
144 C#. Zadania z programowania z przykładowymi rozwiązaniami

{
class Plik_tekstowy
{
String dane, dane1;
FileStream fout, fin;

public void czytaj_dane() // Metoda czytaj_dane().


{
Console.WriteLine("Wpisujemy dane do pliku tekstowego.");
Console.WriteLine("Podaj imię i nazwisko:");
dane = Console.ReadLine();
}

public void zapisz_dane_do_pliku() // Metoda zapisz_dane_do_pliku().


{
fout = new FileStream("dane.txt", FileMode.Create);
// Tworzymy strumień znaków połączony z plikiem tekstowym w trybie
// Create.
StreamWriter fstr_out = new StreamWriter(fout);
// Tworzymy strumień wyjściowy.
fstr_out.Write(dane); // Zapisujemy łańcuch do pliku.
fstr_out.Close(); // Zamykamy zapis.
fout.Close(); // Zamykamy plik.
}

public void czytaj_dane_z_pliku() // Metoda czytaj_dane_z_pliku()


{
Console.WriteLine();
Console.WriteLine("Odczytujemy dane z pliku tekstowego.");

fin = new FileStream("dane.txt", FileMode.Open);


// Tworzymy strumień znaków połączony z plikiem tekstowym w trybie Open.
StreamReader fstr_in = new StreamReader(fin); // Tworzymy
// strumień wejściowy.
while ((dane1 = fstr_in.ReadLine()) != null)
// Odczytujemy dane z pliku, aż napotkamy jego koniec.
{
Console.WriteLine(dane1);
}
fstr_in.Close(); // Zamykamy odczyt.
fin.Close(); // Zamykamy plik.
}

static void Main(string[] args)


{
Plik_tekstowy plik = new Plik_tekstowy();
// deklaracja zmiennej, utworzenie obiektu i przypisanie go do zmiennej

plik.czytaj_dane(); // Wywołanie metody czytaj_dane().

8ba61431c02548173fa6085d93015e5a
8
Rozdział 6. ♦ Pliki tekstowe i pliki o dostępie swobodnym 145

plik.zapisz_dane_do_pliku(); // Wywołanie metody


// zapisz_dane_do_pliku().
plik.czytaj_dane_z_pliku(); // Wywołanie metody
// czytaj_dane_z_pliku().
}
}
}

Zwróćmy uwagę, że w programie zadeklarowane są dwie zmienne łańcu-


chowe:
String dane, dane1;

Zmienna o nazwie dane przechowuje dane przed zapisaniem do pliku teksto-


wego, zmienna dane1 zaś przechowuje dane odczytane z pliku tekstowego.
Metoda czytaj_dane():
public void czytaj_dane() // Metoda czytaj_dane().
{
Console.WriteLine("Wpisujemy dane do pliku tekstowego.");
Console.WriteLine("Podaj imię i nazwisko:");
dane = Console.ReadLine();
}

wczytuje dane (imię i nazwisko) z klawiatury.


Metoda zapisz_dane_do_pliku():
public void zapisz_dane_do_pliku() // Metoda zapisz_dane_do_pliku().
{
fout = new FileStream("dane.txt", FileMode.Create);
// Tworzymy strumień znaków połączony z plikiem tekstowym w trybie
// Create.
StreamWriter fstr_out = new StreamWriter(fout);
// Tworzymy strumień wyjściowy.
fstr_out.Write(dane); // Zapisujemy łańcuch do pliku.
fstr_out.Close(); // Zamykamy zapis.
fout.Close(); // Zamykamy plik.
}

zapisuje dane poprzez właściwości klasy StreamWriter() do pliku dane.txt.


Metoda Close() zamyka zapis danych do pliku.
Ostatnia metoda, czytaj_dane_z_pliku():
public void czytaj_dane_z_pliku() // Metoda czytaj_dane_z_pliku().
{
Console.WriteLine();
Console.WriteLine("Odczytujemy dane z pliku tekstowego.");

8ba61431c02548173fa6085d93015e5a
8
146 C#. Zadania z programowania z przykładowymi rozwiązaniami

fin = new FileStream("dane.txt", FileMode.Open);


// Tworzymy strumień znaków połączony z plikiem tekstowym w trybie Open.
StreamReader fstr_in = new StreamReader(fin); // Tworzymy
// strumień wejściowy.
while ((dane1 = fstr_in.ReadLine()) != null)
// Odczytujemy dane z pliku, aż napotkamy jego koniec.
{
Console.WriteLine(dane1);
}
fstr_in.Close(); // Zamykamy odczyt.
fin.Close(); // Zamykamy plik.
}

odczytuje poprzez właściwości klasy StreamReader():


{
.............
fin = new FileStream("dane.txt", FileMode.Open);
// Tworzymy strumień znaków połączony z plikiem tekstowym w trybie Open.
StreamReader fstr_in = new StreamReader(fin); //Tworzymy
// strumień wejściowy.

while ((dane1 = fstr_in.ReadLine()) != null)


// Odczytujemy dane z pliku, aż napotkamy jego koniec.
{
Console.WriteLine(dane1);
}
fstr_in.Close(); // Zamykamy odczyt.
fin.Close(); // Zamykamy plik.
}

dane1 z pliku dane.txt i wyświetla je na ekranie komputera. Metoda Close()


zamyka odczyt danych z pliku.
Za odczytanie wszystkich danych z pliku dane.txt odpowiedzialne są pętla
while i warunek (dane1 = fstr_in.ReadLine()) != null):
while ((dane1 = fstr_in.ReadLine()) != null)
// Odczytujemy dane z pliku, aż napotkamy jego koniec.
{
Console.WriteLine(dane1);
}
fstr_in.Close(); // Zamykamy odczyt.
fin.Close(); // Zamykamy plik.
}

Deklarację zmiennej, utworzenie obiektu i przypisanie go do zmiennej


spowoduje następująca linijka kodu:
Plik_tekstowy plik = new Plik_tekstowy();

8ba61431c02548173fa6085d93015e5a
8
Rozdział 6. ♦ Pliki tekstowe i pliki o dostępie swobodnym 147

Wszystkie trzy metody zostają wywołane w programie głównym:


plik.czytaj_dane(); // Wywołanie metody czytaj_dane().
plik.zapisz_dane_do_pliku(); // Wywołanie metody
// zapisz_dane_do_pliku().
plik.czytaj_dane_z_pliku(); // Wywołanie metody
// czytaj_dane_z_pliku().

Plik dane.txt znajdziemy w katalogu .../bin/Debug/plik.txt.

Rezultat działania programu można zobaczyć na rysunku 6.1.

Rysunek 6.1.
Wpisujemy dane do pliku tekstowego.
Efekt działania
Podaj imię i nazwisko:
programu Tomasz Kowalski
Zadanie 6.1
Odczytujemy dane z pliku tekstowego.
Tomasz Kowalski

Zadanie
6.2 Napisz zgodnie z zasadami programowania obiektowego program, który
tablicę o wymiarach 10 × 10 w postaci:

1 0 0 0 0 0 0 0 0 0
0 1 0 0 0 0 0 0 0 0
0 0 1 0 0 0 0 0 0 0
0 0 0 1 0 0 0 0 0 0
0 0 0 0 1 0 0 0 0 0
0 0 0 0 0 1 0 0 0 0
0 0 0 0 0 0 1 0 0 0
0 0 0 0 0 0 0 1 0 0
0 0 0 0 0 0 0 0 1 0
0 0 0 0 0 0 0 0 0 1

zapisuje do pliku tekstowego dane.txt, a następnie zapisane dane odczytuje


z pliku dane.txt i wyświetla na ekranie komputera. Klasa powinna zawierać
trzy metody:
 czytaj_dane() — tworzy tablicę 10 × 10.

8ba61431c02548173fa6085d93015e5a
8
148 C#. Zadania z programowania z przykładowymi rozwiązaniami

 zapisz_dane_do_pliku() — zapisuje tablicę 10 × 10 do pliku


tekstowego o nazwie dane.txt.
 czytaj_dane_z_pliku() — odczytuje tablicę 10 × 10 z pliku
dane.txt i wyświetla ją na ekranie komputera.
Listing 6.2. Przykładowe rozwiązanie

using System;
using System.IO;

namespace Zadanie_62 // Zadanie 6.2.


{
class Matrix
{
FileStream fout, fin;

public void czytaj_dane(int[,] tablica, int rozmiar)


{
int i, j;

Console.WriteLine("Tworzymy tablicę 10 x 10.");


Console.WriteLine();

for (i = 0; i < rozmiar; i++)


{
for (j = 0; j < rozmiar; j++)
{
if (i == j)
tablica[i, j] = 1;
else
tablica[i, j] = 0;
Console.Write(tablica[i, j] + " ");
}
Console.WriteLine();
}
}

public void zapisz_dane_do_pliku(int[,] tablica, int rozmiar)


{
int i, j;

Console.WriteLine();
Console.WriteLine("Zapisujemy tablicę 10 x 10 do pliku
´tekstowego.");
Console.WriteLine();

fout = new FileStream("dane.txt", FileMode.Create);


StreamWriter fstr_out = new StreamWriter(fout);

for (i = 0; i < rozmiar; i++)

8ba61431c02548173fa6085d93015e5a
8
Rozdział 6. ♦ Pliki tekstowe i pliki o dostępie swobodnym 149

{
for (j = 0; j < rozmiar; j++)
{
fstr_out.Write((char)tablica[i, j]);
// Rzutujemy i zapisujemy tablicę do pliku.
Console.Write(tablica[i, j] + " ");
}
Console.WriteLine();
}

fstr_out.Close();
fout.Close();
}

public void czytaj_dane_z_pliku(int[,] tablica1, int rozmiar)


{
int i, j;

Console.WriteLine();
Console.WriteLine("Odczytujemy tablicę1 10 x 10 z pliku
´tekstowego.");
Console.WriteLine();

fin = new FileStream("dane.txt", FileMode.Open);


StreamReader fstr_in = new StreamReader(fin);

for (i = 0; i < rozmiar; i++)


{
for (j = 0; j < rozmiar; j++)
{
tablica1[i, j] = (int)fstr_in.Read();
// Rzutujemy i odczytujemy zawartość pliku, którą umieszczamy
// w tablicy1.
Console.Write(tablica1[i, j] + " ");
}
Console.WriteLine();
}

fstr_in.Close();
fin.Close();
}

static void Main(string[] args)


{
const int rozmiar = 10;

int[,] tablica = new int[rozmiar, rozmiar];


int[,] tablica1 = new int[rozmiar, rozmiar];

Matrix matrix1 = new Matrix();

8ba61431c02548173fa6085d93015e5a
8
150 C#. Zadania z programowania z przykładowymi rozwiązaniami

matrix1.czytaj_dane(tablica, rozmiar);
matrix1.zapisz_dane_do_pliku(tablica, rozmiar);
matrix1.czytaj_dane_z_pliku(tablica1, rozmiar);
}
}
}

Zwróćmy uwagę, że w programie zadeklarowane są dwie tablice: int [,]


tablica i int [,] tablica1. Pierwsza przechowuje dane przeznaczone do
zapisu w pliku, a druga przechowuje dane odczytane z pliku.
Następujące linijki kodu:
fstr_out.Write((char) tablica[i, j]); // Rzutujemy i zapisujemy tablicę do pliku.

i
tablica1[i, j] = (int)fstr_in.Read();
// Rzutujemy i odczytujemy zawartość pliku, którą umieszczamy
// w tablicy1.

oznaczają, że dokonujemy rzutowania niekompatybilnych typów (ang.


casting), zamieniając jeden typ na drugi: w pierwszym przypadku typ int
na typ char, natomiast w drugim odwrotnie.
Rezultat działania programu można zobaczyć na rysunku 6.2.
Zadanie
6.3 Napisz zgodnie z zasadami programowania obiektowego program, który
tablicę a o wymiarach 10 × 10 w postaci:

0 0 0 0 0 0 0 0 0 0
1 1 1 1 1 1 1 1 1 1
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0

8ba61431c02548173fa6085d93015e5a
8
Rozdział 6. ♦ Pliki tekstowe i pliki o dostępie swobodnym 151

Rysunek 6.2.
Efekt działania Tworzymy tablicę 10 x 10.
programu 1000000000
Zadanie 6.2 0100000000
0010000000
0001000000
0000100000
0000010000
0000001000
0000000100
0000000010
0000000001
Zapisujemy tablicę 10 x 10 do pliku tekstowego.
1000000000
0100000000
0010000000
0001000000
0000100000
0000010000
0000001000
0000000100
0000000010
0000000001
Odczytujemy tablicę1 10 x 10 z pliku tekstowego.
1000000000
0100000000
0010000000
0001000000
0000100000
0000010000
0000001000
0000000100
0000000010
0000000001

przekształca w tablicę b w postaci:

8ba61431c02548173fa6085d93015e5a
8
152 C#. Zadania z programowania z przykładowymi rozwiązaniami

0 1 0 0 0 0 0 0 0 0
0 1 0 0 0 0 0 0 0 0
0 1 0 0 0 0 0 0 0 0
0 1 0 0 0 0 0 0 0 0
0 1 0 0 0 0 0 0 0 0
0 1 0 0 0 0 0 0 0 0
0 1 0 0 0 0 0 0 0 0
0 1 0 0 0 0 0 0 0 0
0 1 0 0 0 0 0 0 0 0
0 1 0 0 0 0 0 0 0 0

i zapisuje ją do pliku tekstowego dane.txt, a następnie odczytuje tablicę


z pliku dane.txt i wyświetla ją na ekranie komputera. Klasa powinna zawierać
cztery metody:
 czytaj_dane() — tworzy tablicę a o wymiarach 10 × 10.
 przetwórz_dane() — przepisuje zawartość tablicy a do tablicy b
o wymiarach 10 × 10.
 zapisz_dane_do_pliku() — zapisuje tablicę b do pliku.
 czytaj_dane_z_pliku() — odczytuje tablicę c o wymiarach 10 × 10
z pliku i wyświetla ją na ekranie komputera.
Listing 6.3. Przykładowe rozwiązanie

using System;
using System.IO;

namespace Zadanie_63 // Zadanie 6.3.


{
class Tablice
{
FileStream fout, fin;

public void czytaj_dane(int[,] a, int n)


{
int i, j;

Console.WriteLine("Tworzymy tablicę a.");


Console.WriteLine();

for (i = 0; i < n; i++)


{

8ba61431c02548173fa6085d93015e5a
8
Rozdział 6. ♦ Pliki tekstowe i pliki o dostępie swobodnym 153

for (j = 0; j < n; j++)


{
if (i == 1)
a[i, j] = 1;
else
a[i, j] = 0;
Console.Write(a[i, j] + " ");
}
Console.WriteLine();
}
}

public void przetwórz_dane(int[,] a, int[,] b, int n)


{
int i, j;

Console.WriteLine();
Console.WriteLine("Przepisujemy zawartość tablicy
´a do tablicy b.");
Console.WriteLine();

for (i = 0; i < n; i++)


{
for (j = 0; j < n; j++)
{
b[i, j] = a[j, i]; // Przepisujemy zawartość tablicy a
// do tablicy b.
Console.Write(b[i, j] + " ");
}
Console.WriteLine();
}
}

public void zapisz_dane_do_pliku(int[,] b, int n)


{
int i, j;

Console.WriteLine();
Console.WriteLine("Zapisujemy tablicę b do pliku
´tekstowego.");
Console.WriteLine();

fout = new FileStream("dane.txt", FileMode.Create);


StreamWriter fstr_out = new StreamWriter(fout);

for (i = 0; i < n; i++)


{
for (j = 0; j < n; j++)
{
fstr_out.Write((char)b[i, j]);
Console.Write(b[i, j] + " ");
}
Console.WriteLine();

8ba61431c02548173fa6085d93015e5a
8
154 C#. Zadania z programowania z przykładowymi rozwiązaniami

}
fstr_out.Close();
fout.Close();
}

public void czytaj_dane_z_pliku(int[,] c, int n)


{
int i, j;

Console.WriteLine();
Console.WriteLine("Odczytujemy tablicę c z pliku
´tekstowego.");
Console.WriteLine();

fin = new FileStream("dane.txt", FileMode.Open);


StreamReader fstr_in = new StreamReader(fin);

for (i = 0; i < n; i++)


{
for (j = 0; j < n; j++)
{
c[i, j] = (int)fstr_in.Read();
Console.Write(c[i, j] + " ");
}
Console.WriteLine();
}
fstr_in.Close();
fin.Close();
}

static void Main(string[] args)


{
const int n = 10;

int[,] a = new int[n, n];


int[,] b = new int[n, n];
int[,] c = new int[n, n];

Tablice tab = new Tablice();

tab.czytaj_dane(a, n);
tab.przetwórz_dane(a, b, n);
tab.zapisz_dane_do_pliku(b, n);
tab.czytaj_dane_z_pliku(c, n);
}
}
}

Rezultat działania programu można zobaczyć na rysunku 6.3.

8ba61431c02548173fa6085d93015e5a
8
Rozdział 6. ♦ Pliki tekstowe i pliki o dostępie swobodnym 155

Rysunek 6.3.
Efekt działania Tworzymy tablicę a.
programu 0000000000
Zadanie 6.3 1111111111
0000000000
0000000000
0000000000
0000000000
0000000000
0000000000
0000000000
0000000000
Przepisujemy zawartość tablicy a do tablicy b.
0100000000
0100000000
0100000000
0100000000
0100000000
0100000000
0100000000
0100000000
0100000000
0100000000
Zapisujemy tablicę b do pliku tekstowego.
0100000000
0100000000
0100000000
0100000000
0100000000
0100000000
0100000000
0100000000
0100000000
0100000000
Odczytujemy tablicę c z pliku tekstowego.
0100000000
0100000000
0100000000
0100000000
0100000000
0100000000
0100000000
0100000000
0100000000
0100000000

8ba61431c02548173fa6085d93015e5a
8
156 C#. Zadania z programowania z przykładowymi rozwiązaniami

Pliki o dostępie swobodnym


Omówione zadania dotyczyły plików tekstowych, gdzie dostęp odbywał się
w sposób sekwencyjny. Język C# oferuje również dostęp do pliku w dowol-
nym porządku. W tym celu należy skorzystać z metody Seek(), która została
zdefiniowana w klasie FileStream. Metoda ta pozwala ustawić wskaźnik
pliku w dowolnym miejscu pliku. Jej ogólna postać jest następująca:
long Seek(long nowa_pozycja, SeekOrigin odniesienie)

gdzie nowa_pozycja określa nową pozycję wskaźnika pliku w bajtach,


począwszy od miejsca określonego poprzez odniesienie. Oto określone
dopuszczalne enumeracje zdefiniowane dla SeekOrigin:
 Begin — pozycja względem początku pliku,
 Current — pozycja względem aktualnej lokalizacji,
 End — pozycja względem końca pliku.

Po wywołaniu metody Seek() kolejna operacja odczytu bądź zapisu nastąpi


od nowej pozycji.
Zadanie
6.4 Napisz program, który dane typu char znajdujące się w tablicy zapisuje
w pliku o dostępie swobodnym, a następnie, po odczytaniu z pliku, wyświetla
je na ekranie komputera.
Listing 6.4. Przykładowe rozwiązanie

using System;
using System.IO;

namespace Zadanie_64 // Zadanie 6.4.


{
class Plik_swobodny
{
FileStream f;

char[] litery = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H' };

public void zapisz_czytaj_dane()


{
int i;

f = new FileStream("litery.dat", FileMode.Create);

8ba61431c02548173fa6085d93015e5a
8
Rozdział 6. ♦ Pliki tekstowe i pliki o dostępie swobodnym 157

for (i = 0; i < litery.Length; i++)


{
f.WriteByte((byte)litery[i]); // Zapisujemy dane do pliku.
}
f.Close();

f = new FileStream("litery.dat", FileMode.Open);

Console.Write("Dane odczytane z pliku to: ");


for (i = 0; i < litery.Length; i++)
{
f.Seek(i, SeekOrigin.Begin); // Ustawiamy wskaźnik na początku
// pliku.
char ch = (char)f.ReadByte(); // Odczytujemy dane z pliku.
if (i < litery.Length - 1)
Console.Write(ch + ", ");
else
Console.WriteLine(ch + ".");
}
f.Close();
}

static void Main(string[] args)


{
Plik_swobodny tab = new Plik_swobodny();

tab.zapisz_czytaj_dane();
}
}
}

Rezultat działania programu można zobaczyć na rysunku 6.4.

Rysunek 6.4.
Efekt działania
Dane odczytane z pliku to: A, B, C, D, E, F, G, H.
programu
Zadanie 6.4

Serializacja
Serializacja (ang. serialization) to proces przekształcania obiektów w strumień
bajtów z zachowaniem aktualnego stanu obiektu. Proces ten może zostać
utrwalony, np. w pamięci zewnętrznej komputera, np. poprzez zapisywanie

8ba61431c02548173fa6085d93015e5a
8
158 C#. Zadania z programowania z przykładowymi rozwiązaniami

informacji o obiektach klas w plikach tekstowych lub binarnych2. Dzięki seria-


lizacji możemy w każdej chwili zapisać stan, w jakim znajduje się obiekt, wraz
z wartościami jego elementów. Deserializacja (ang. deserialization) to proces
odwrotny, polegający na odczytaniu wcześniej zapisanego strumienia danych
i odtworzeniu na tej podstawie obiektu klasy wraz z jego stanem bezpośred-
nio sprzed serializacji.
Środowisko .NET oferuje trzy podstawowe formaty zapisu (ang. formatters)
serializowanych klas: binarny, SOAP oraz XML. Zadaniem binarnego format-
tera (BinaryFormatter) jest konwersja obiektu do formatu (jako ciąg zerojedyn-
kowy), w którym zostanie dokonana jego serializacja lub deserializacja.
Aby móc skorzystać z procesu binarnej serializacji i deserializacji obiektów,
musimy dołączyć następującą instrukcję na początku programu:
using System.Runtime.Serialization.Formatters.Binary;

Zadanie
6.5 Napisz program, który w pliku o dostępie swobodnym zamieszcza dane
z przeprowadzonych pomiarów typu double, a następnie wyświetla co drugi
pomiar na ekranie komputera. Dokonaj serializacji i deserializacji.
Listing 6.5. Przykładowe rozwiązanie

using System;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;

namespace Zadanie_65 // Zadanie 6.5.


{
class Serializacja
{
double[] pomiary = { 10.17, 12.83, 11.78, 15.23, 11.08, 13.67 };

public void zapisz_czytaj_dane()


{
Console.WriteLine("Wszystkie pomiary: ");
for (int i = 0; i < pomiary.Length; i++)
Console.WriteLine(pomiary[i] + " ");

Stream StreamWrite = new FileStream("pomiary.dat",


´FileMode.Create);
// Utworzenie pliku, który będzie zawierał strumienie danych.

2 Proces taki może również zostać przesłany do innego procesu lub innego komputera np.
poprzez sieć.

8ba61431c02548173fa6085d93015e5a
8
Rozdział 6. ♦ Pliki tekstowe i pliki o dostępie swobodnym 159

BinaryFormatter BinaryWriter = new BinaryFormatter();


// Utworzenie obiektu typu BinaryFormatter.
BinaryWriter.Serialize(StreamWrite, pomiary); // Serializacja.
StreamWrite.Close(); // Zamknięcie strumienia.

Console.WriteLine();
Console.WriteLine("Co drugi pomiar odczytany z pliku
´pomiary.dat: ");

Stream StreamRead = new FileStream("pomiary.dat",


´FileMode.Open);
// Otwarcie pliku, który zawiera strumienie danych.

BinaryFormatter BinaryReader = new BinaryFormatter();

pomiary = (double[])BinaryReader.Deserialize(StreamRead);
// Deserializacja i rzutowanie.

for (int i = 0; i < pomiary.Length; i += 2)


Console.WriteLine(pomiary[i] + " ");

StreamRead.Close(); // Zamknięcie strumienia.


}

static void Main(string[] args)


{
Serializacja ser = new Serializacja();

ser.zapisz_czytaj_dane();
}
}
}

Rezultat działania programu można zobaczyć na rysunku 6.5.

Rysunek 6.5.
Efekt działania Wszystkie pomiary:
programu 10,17
12,83
Zadanie 6.5
11,78
15,23
11,08
13,67

Co drugi pomiar odczytany z pliku pomiary.dat:


10,17
11,78
11,08

8ba61431c02548173fa6085d93015e5a
8
160 C#. Zadania z programowania z przykładowymi rozwiązaniami

8ba61431c02548173fa6085d93015e5a
8
Rozdział 7.
Wprowadzenie
do współbieżności
W tym rozdziale omówiłem na przykładzie zadań wybrane zagadnienia
dotyczące paradygmatu programowania współbieżnego.

Informacje ogólne
Paradygmat programowania (ang. programming paradigm) jest wzorcem
programowania komputerów stosowanym przez programistów i preferowa-
nym w danym okresie rozwoju informatyki lub w pewnych okoliczno-
ściach bądź zastosowaniach. Paradygmat programowania definiuje sposób
patrzenia programisty na przepływ sterowania i wykonywania programu
komputerowego. Zależności między paradygmatami programowania mogą
przybierać skomplikowane formy, ponieważ jeden język programowania
może wspierać wiele różnych paradygmatów.
Przetwarzanie współbieżne (ang. concurrent computing) to coraz modniejszy
i niewątpliwie atrakcyjniejszy paradygmat programowania, oparty na współ-
istnieniu wielu wątków lub procesów operujących na współdzielonych danych.
Współbieżność to robienie więcej niż jednej rzeczy naraz. Program współ-
bieżny jest zbiorem programów sekwencyjnych wykonywanych równolegle.
Aplikacje współbieżne odgrywają coraz ważniejszą rolę we współczesnym
świecie informatyki. Panuje trend polegający na tym, że producenci
nowoczesnych procesorów dokładają nowe rdzenie zamiast zwiększać moc

8ba61431c02548173fa6085d93015e5a
8
162 C#. Zadania z programowania z przykładowymi rozwiązaniami

obliczeniową pojedynczej jednostki. Z tego względu, aby w pełni wykorzy-


stać możliwości tych komputerów, kluczowym zagadnieniem stało się zrów-
noleglenie pewnych operacji. Producenci oprogramowania dostarczają coraz
ciekawszych narzędzi do rozwijania przetwarzania współbieżnego.
Teraz wprowadzę i pokrótce omówię nowe pojęcia: przetwarzanie równo-
ległe, wielowątkowość i programowanie asynchroniczne, a zilustruję je zada-
niami z przykładowymi rozwiązaniami.
Czytelników zainteresowanych tym ciekawym paradygmatem programowa-
nia odsyłam do polecanej literatury i bibliografii (pozycje 1., 3. i 6.), które znaj-
dują się na końcu książki.
Przetwarzanie równoległe (ang. paralell computing) to wykonywanie dużej
ilości obliczeń poprzez rozdzielenie jej na wiele wątków działających równole-
gle. W takim przetwarzaniu wielowątkowość stosuje się w celu maksymal-
nego wykorzystania dużej liczby wielordzeniowych procesorów. W prze-
twarzaniu równoległym rozdziela się pracę na wiele wątków, z których każdy
może być uruchamiany niezależnie na innym rdzeniu. Przetwarzanie rów-
noległe jest jednym z rodzajów wielowątkowości, a wielowątkowość jest jed-
nym z rodzajów współbieżności.
Wielowątkowość (ang. multithreading) to forma współbieżności, w której
korzysta się z wielu wątków wykonywania. Dosłownie wielowątkowość
odnosi się do używania wielu wątków.
Programowanie asynchroniczne (ang. asynchronous programming) to forma
współbieżności, która wykorzystuje obiekty typu Task (zadania). Obiekty
tego typu i metody asynchroniczne stanowią podstawę programowania asyn-
chronicznego.

Wprowadzenie
do programowania równoległego
Programowanie równoległe powinno być stosowane w sytuacji, gdy istnieje
duża ilość pracy obliczeniowej, którą można podzielić na niezależne frag-
menty. Istnieją dwie formy równoległości: równoległość danych i równole-
głość zadań.

8ba61431c02548173fa6085d93015e5a
8
Rozdział 7. ♦ Wprowadzenie do współbieżności 163

Jeśli do przetworzenia jest grupa elementów danych, a przetwarzanie każ-


dego fragmentu danych jest przeważnie niezależne od pozostałych frag-
mentów, to mamy do czynienia z równoległością danych. Natomiast gdy
mamy do wykonania pulę pracy, a każdy fragment pracy jest przeważnie
niezależny od pozostałych fragmentów, wtedy mamy do czynienia z równole-
głością zadań. Równoległość ta może mieć charakter dynamiczny. Do puli
pracy może zostać dodanych kilka dodatkowych fragmentów, które powstaną
na skutek jednego fragmentu pracy.
Istnieje kilka różnych sposobów na zapewnienie równoległości danych, które
dostarcza klasa Parallel z przestrzeni nazw System.Threading.Tasks. Metoda
Parallel.ForEach jest podobna do znanej nam pętli foreach i powinna
być stosowana wszędzie tam, gdzie jest to możliwe. Natomiast metoda
Parallel.For jest podobna do pętli for i powinna być używana wszędzie
tam, gdzie przetwarzanie danych zależy od indeksu.
Wiadomości uzupełniające: C# jest językiem obiektowym. Ma on dodatkowo
pewne cechy zaczerpnięte z paradygmatu programowania funkcyjnego. Pod-
stawą tego rodzaju programowania jest unikanie zmiennych, których war-
tości się zmieniają, na rzecz stosowania technik deklaratywnych. Język C#
zawiera kluczowe elementy funkcjonalności, które pozwalają stosować te
techniki. Zalicza się do nich m.in. pisanie nienazwanych funkcji, które „prze-
chwytują” zmienne określane mianem wyrażeń lambda. Jest to metoda bez
nazwy, wpisana w miejsce egzemplarza delegatu (delegate). Delegat to obiekt,
który „wie”, jak wywołać metodę. Typ delegacyjny definiuje rodzaj metody,
którą mogą wywołać egzemplarze delegatu. Dokładniej mówiąc: określa typ
zwrotny metody i typy jej parametrów. Ogólna postać typu delegacyjnego jest
następująca:
delegate int Obliczenia(int x);

Typ Obliczenia() jest zgodny z każdą metodą o typie zwrotnym int przyj-
mującą jeden parametr typu int, np.:
static int Kwadrat (int x) => x * x;

Przypisanie metody do zmiennej typu delegacyjnego powoduje utworzenie


egzemplarza delegatu:
Obliczenia k = kwadrat;

Egzemplarz ten wywołujemy tak jak zwykłą metodę:


int wynik = k(4); // Wynik to 16.

8ba61431c02548173fa6085d93015e5a
8
164 C#. Zadania z programowania z przykładowymi rozwiązaniami

Ogólna postać wyrażenia lambda jest następująca:


(parametry) => wyrażenie lub blok instrukcji

Nawias jest opcjonalny, jeżeli parametr typu jest dokładnie jeden, np.:
x => x * x;

Niezbędne wiadomości uzupełniające dotyczące wyrażeń lambda wykorzy-


stamy w zadaniach. Czytelnikom pragnącym pogłębić wiadomości w tym
temacie polecam bibliografię znajdującą się na końcu książki (pozycje 3. i 6.).
Zadanie
7.1 Napisz program sumujący sekwencyjnie i równolegle według wzoru c[i] =
a[i] + b[i] dwa wektory, których składowe tworzą elementy tablic jedno-
wymiarowych, odpowiednio a[i] i b[i]. W programie należy skorzystać
z właściwości metody Parallel.For. Dodatkowo program powinien zmie-
rzyć czasy obliczeń sekwencyjnych i równoległych i je porównać.

Do pomiaru czasu obliczeń w naszym zadaniu należy skorzystać z wła-


ściwości System.Environment.TickCount. Wartość tej właściwości jest
tworzona na podstawie czasomierza systemu i jest przechowywana jako
32-bitowa liczba całkowita ze znakiem. Pobiera ona wyrażony w milise-
kundach czas, jaki upłynął od określonego zdarzenia.

Listing 7.1. Przykładowe rozwiązanie

using System;
using System.Threading.Tasks;

namespace Zadanie_71 // Zadanie 7.1.


{
class Program
{
static void Main(string[] args)
{
int rozmiar = 10000000;

double[] a = new double[rozmiar];


double[] b = new double[rozmiar];
double[] c = new double[rozmiar];

Console.WriteLine("Rozpoczynam obliczenia...");

int start = System.Environment.TickCount; // Zliczanie taktów


// procesora.

8ba61431c02548173fa6085d93015e5a
8
Rozdział 7. ♦ Wprowadzenie do współbieżności 165

// Obliczenia sekwencyjne.
for (int i = 0; i < rozmiar; i++)
{
a[i] = 1;
}

for (int i = 0; i < rozmiar; i++)


{
b[i] = 2;
}

for (int i = 0; i < rozmiar; i++)


{
c[i] = a[i] + b[i];
}

int stop = System.Environment.TickCount; // Zliczanie taktów


// procesora.

Console.WriteLine("Obliczenia sekwencyjne trwały " +


´(stop - start).ToString() + " ms.");

// Obliczenie równoległe.
start = System.Environment.TickCount; // Zliczanie taktów procesora.

Parallel.For(0, rozmiar, i => { c[i] = a[i] + b[i]; });

stop = System.Environment.TickCount;

Console.WriteLine("Obliczenia równoległe trwały " +


´(stop - start).ToString() + " ms.");
}
}
}

Do obliczeń skorzystaliśmy z metody Parallel.For, która jest dość intui-


cyjna w użyciu. Za sumowanie równoległe dwóch wektorów odpowiadają
następujące linijki kodu, zwane wyrażeniami lambda:
Parallel.For(0, rozmiar, i => {c[i] = a[i] + b[i];});

Czas obliczeń może się różnić i być inny dla różnych konfiguracji kompu-
tera. W zadaniu nie jest wyświetlany wynik dodawania dwóch wektorów, lecz
tylko czas obliczeń sekwencyjnych i czas obliczeń równoległych.
Rezultat działania programu można zobaczyć na rysunku 7.1.

8ba61431c02548173fa6085d93015e5a
8
166 C#. Zadania z programowania z przykładowymi rozwiązaniami

Rysunek 7.1.
Efekt działania Rozpoczynam obliczenia...
programu Obliczenia sekwencyjne trwały 499 ms.
Obliczenia równoległe trwały 172 ms.
Zadanie 7.1

Zadanie
7.2 Napisz program, który na bazie modyfikacji zadania 4.15 sekwencyjnie i rów-
nolegle mnoży dwie macierze kwadratowe typu int. Dodatkowo program
powinien zmierzyć czas obliczeń sekwencyjnych i czas obliczeń równoległych.
Listing 7.2. Przykładowe rozwiązanie

using System;
using System.Threading.Tasks;

namespace Zadanie_72 // Zadanie 7.2.


{
class Program
{
static void Main(string[] args)
{
int n = 300, i, j;

int[,] a = new int[n, n];


int[,] b = new int[n, n];
int[,] c = new int[n, n];

Console.WriteLine("Rozpoczynam obliczenia...");

int start = System.Environment.TickCount; // Zliczanie taktów


// procesora.

// Obliczenia sekwencyjne.
// Wpisywanie liczb do macierzy a.
for (i = 0; i < n; i++)
{
for (j = 0; j < n; j++)
{
a[i, j] = 1;
}
}

// Wpisywanie liczb do macierzy b.


for (i = 0; i < n; i++)
{
for (j = 0; j < n; j++)
{
b[i, j] = 2;

8ba61431c02548173fa6085d93015e5a
8
Rozdział 7. ♦ Wprowadzenie do współbieżności 167

}
}

// Sekwencyjne mnożenie macierzy kwadratowych a * b.


for (i = 0; i < n; i++)
{
for (j = 0; j < n; j++)
{
c[i, j] = 0;

for (int k = 0; k < n; k++)


{
c[i, j] += a[i, k] * b[k, j];
}
}
}
Console.WriteLine();

int stop = System.Environment.TickCount; // Koniec zliczania.


Console.WriteLine("Obliczenia sekwencyjne trwały " +
´(stop - start).ToString() + " ms.");

// Obliczenia równoległe.
start = System.Environment.TickCount; // Zliczanie taktów procesora.

// Równoległe mnożenie macierzy kwadratowych a * b.


Parallel.For(0, n, l =>
{
for (int m = 0; m < n; m++)
{
c[l, m] = 0;

for (int k = 0; k < n; k++)


{
c[l, m] += a[l, k] * b[k, m];
}
}
}); // Parallel.For.

stop = System.Environment.TickCount; // Koniec zliczania.

Console.WriteLine("Obliczenia równoległe trwały " +


´(stop - start).ToString() + " ms.");
}
}
}

Za równoległe mnożenie dwóch macierzy kwadratowych odpowiada nastę-


pujący kod:

8ba61431c02548173fa6085d93015e5a
8
168 C#. Zadania z programowania z przykładowymi rozwiązaniami

// Równoległe mnożenie macierzy kwadratowych a * b.


Parallel.For(0, n, l =>
{
for (int m = 0; m < n; m++)
{
c[l, m] = 0;

for (int k = 0; k < n; k++)


{
c[l, m] += a[l, k] * b[k, m];
}
}
}); // Parallel.For.

W programie do obliczeń wykorzystaliśmy właściwości metody Parallel.For.


Rezultat działania programu można zobaczyć na rysunku 7.2.

Rysunek 7.2.
Efekt działania Rozpoczynam obliczenia...
programu
Obliczenia sekwencyjne trwały 1326 ms.
Zadanie 7.2
Obliczenia równoległe trwały 390 ms.

Zadanie
7.3 Napisz program, który za pomocą pętli Parallel.For sprawdza, czy wczytana
liczba jest liczbą pierwszą.

Listing 7.3. Przykładowe rozwiązanie

using System;
using static System.Math;
using System.Threading.Tasks;

namespace Zadanie_73 // Zadanie 7.3.


{
class Program
{
static void Main(string[] args)
{
Int32 n;

Console.WriteLine("Program sprawdza, czy wczytana liczba jest


´liczbą pierwszą.");

Console.WriteLine("Podaj liczbę.");

n = Int32.Parse(Console.ReadLine());

8ba61431c02548173fa6085d93015e5a
8
Rozdział 7. ♦ Wprowadzenie do współbieżności 169

bool pierwsza = true;

Parallel.For(2, (int) Sqrt(n) + 1, (i) =>


{
if ((n % (int)i) == 0)
{
Console.WriteLine("{0} dzieli się przez {1}.", n, i);
pierwsza = false;
}
});

if (pierwsza)
{
Console.WriteLine("Liczba {0} jest liczbą pierwszą.", n);
}
else
{
Console.WriteLine("Liczba {0} nie jest liczbą pierwszą.",
´n);
};
}
}
}

Za odnajdowanie liczby pierwszej odpowiadają następujące linijki kodu:


Parallel.For(2, (int) Sqrt(n) + 1, (i) =>
{
if ((n % (int) i) == 0)
{
Console.WriteLine("{0} dzieli się przez {1}.", n, i);
pierwsza = false;
}
});

Rezultat działania programu można zobaczyć na rysunku 7.3.


Zadanie
7.4 Napisz program, który za pomocą pętli Parallel.ForEach sprawdza, czy
wczytana liczba jest liczbą pierwszą.

Aby zaprezentować działanie pętli Parallel.ForEach, posłuż się zmodyfi-


kowaną wersją zadania 7.3, zmieniając jedynie metodę realizującą pętlę.

Listing 7.4. Przykładowe rozwiązanie

using System;
using static System.Math;
using System.Linq;

8ba61431c02548173fa6085d93015e5a
8
170 C#. Zadania z programowania z przykładowymi rozwiązaniami

Rysunek 7.3.
Efekt działania Program sprawdza, czy wczytana liczba jest liczbą pierwszą.
programu Podaj liczbę.
Zadanie 7.3
34000
34000 dzieli się przez 2.
34000 dzieli się przez 4.
34000 dzieli się przez 25.
34000 dzieli się przez 34.
34000 dzieli się przez 40.
34000 dzieli się przez 68.
34000 dzieli się przez 80.
34000 dzieli się przez 100.
34000 dzieli się przez 125.
34000 dzieli się przez 136.
34000 dzieli się przez 10.
34000 dzieli się przez 16.
34000 dzieli się przez 17.
34000 dzieli się przez 50.
34000 dzieli się przez 170.
34000 dzieli się przez 85.
34000 dzieli się przez 5.
34000 dzieli się przez 8.
34000 dzieli się przez 20.
Liczba 34000 nie jest liczbą pierwszą.

using System.Threading.Tasks;

namespace Zadanie_74 // Zadanie 7.4.


{
class Program
{
static void Main(string[] args)
{
Int32 n;

Console.WriteLine("Program sprawdza, czy wczytana liczba jest


´liczbą pierwszą.");

Console.WriteLine("Podaj liczbę.");

n = Int32.Parse(Console.ReadLine());

bool pierwsza = true;

Parallel.ForEach<int>(Enumerable.Range(2,(int) Sqrt(n) - 1),


´(i) =>
{

8ba61431c02548173fa6085d93015e5a
8
Rozdział 7. ♦ Wprowadzenie do współbieżności 171

if ((n % (int)i) == 0)
{
Console.WriteLine("{0} dzieli się przez {1}.", n, i);

pierwsza = false;
}
});

if (pierwsza)
{
Console.WriteLine("Liczba {0} jest liczbą pierwszą.", n);
}
else
{
Console.WriteLine("Liczba {0} nie jest liczbą pierwszą.",
´n);
};
}
}
}

Za znajdowanie liczby pierwszej teraz odpowiadają następujące linijki kodu:


Parallel.ForEach<int>(Enumerable.Range(2, (int) Sqrt(n) - 1), (i) =>
{
if ((n % (int) i) == 0)
{
Console.WriteLine("{0} dzieli się przez {1}.", n, i);
pierwsza = false;
}
});

Wywołanie metody Parallel.ForEach różni się nieznacznie od klasycznej


pętli foreach. Ponieważ nie można zastosować składni z wykorzystaniem
operatora in, odpowiednie dane przekazywane są jako argumenty metody.
Pierwszym z nich jest kolekcja, stworzona za pomocą statycznej metody
Enumerable.Range, która buduje sekwencję liczb naturalnych. Jej argumen-
tami są początek zakresu i liczba elementów zbioru.
Do programu dołączyliśmy przestrzeń nazw using System.Linq;.
Rezultat działania programu można zobaczyć na rysunku 7.4.

Rysunek 7.4.
Efekt działania Program sprawdza, czy wczytana liczba jest liczbą pierwszą.
programu Podaj liczbę.
1013
Zadanie 7.4
Liczba 1013 jest liczbą pierwszą.

8ba61431c02548173fa6085d93015e5a
8
172 C#. Zadania z programowania z przykładowymi rozwiązaniami

Wielowątkowość
Wątek (ang. thread) umożliwia wykonanie dodatkowych operacji równo-
cześnie. Jest to część programu realizowana współbieżnie w obrębie jed-
nego procesu. Można uruchomić wiele wątków w ramach jednego procesu.
Wątki obsługiwane są przez klasę Thread znajdującą się w przestrzeni
nazw System.Threading, którą trzeba dodatkowo włączyć do programu
poleceniem:
using System.Threading;

Liczba wątków jest teoretycznie nieograniczona, a praktycznie jest ograni-


czona przez ilość posiadanych zasobów sprzętowych. Każda aplikacja
domyślnie ma jeden wątek, nazywany wątkiem głównym.
Każdy wątek jest realizowany w ramach systemu operacyjnego, zapewniają-
cego mu specjalne odizolowane środowisko, w którym działa program.
W przypadku programów jednowątkowych w takim odizolowanym środo-
wisku działa tylko jeden wątek, który ma wyłączny dostęp do tego środowiska
i jego zasobów. W przypadku programów wielowątkowych wiele wątków
jest uruchamianych w pojedynczym procesie i współdzieli to samo środo-
wisko wykonania (zwłaszcza zasoby pamięci).

Mój pierwszy wątek


Zadanie
7.5 Napisz program, który tworzy jeden wątek i go uruchamia. Tworzenie wątku
odbywa się poprzez egzemplarz klasy Thread. Wątek uruchamiamy poprzez
wywołanie metody Start().
Listing 7.5. Przykładowe rozwiązanie

using System;
using System.Threading;

namespace Zadanie_75 // Zadanie 7.5.


{
class Program
{
static void Pierwszy_Wątek() // Metoda Pierwszy_Wątek()
{
Console.Write("Mój pierwszy wątek.");
}

8ba61431c02548173fa6085d93015e5a
8
Rozdział 7. ♦ Wprowadzenie do współbieżności 173

static void Main(string[] args)


{
Thread mw = new Thread(Pierwszy_Wątek); // Utworzenie nowego
// wątku.
mw.Start(); // Start wątku i wykonanie metody Pierwszy_Wątek().
}
}
}

Do programu dołączono przestrzeń nazw System.Threading.

Wątek główny spowoduje utworzenie nowego wątku typu Thread o nazwie


mw, do którego przekazujemy poprzez parametr naszą metodę i wywołujemy
metodę Start().
Thread mw = new Thread(Pierwszy_Watek); // Utworzenie nowego wątku.
mw.Start(); // Start wątku i wykonanie metody Pierwszy_Wątek().

Rezultat działania programu można zobaczyć na rysunku 7.5.

Rysunek 7.5.
Efekt działania
Mój pierwszy wątek.
programu
Zadanie 7.5

Zadanie
7.6 Napisz program, który tworzy jeden wątek i go uruchamia. Metoda urucho-
miona w wątku będzie realizowała pętlę wypisującą na ekranie komputera
określoną liczbę znaków 0 (w programie jest ich 100). W programie głównym
będzie realizowana pętla wypisująca na ekranie taką samą ilość znaków 1.
Listing 7.6. Przykładowe rozwiązanie

using System;
using System.Threading;

namespace Zadanie_76 // Zadanie 7.6.


{
class Program
{
static void Wypisz_Zero() // Metoda Wypisz_Zero().
{
for (int i = 0; i < 100; i++)
Console.Write("0");
}

static void Main(string[] args)


{

8ba61431c02548173fa6085d93015e5a
8
174 C#. Zadania z programowania z przykładowymi rozwiązaniami

Thread mw = new Thread(Wypisz_Zero); // Utworzenie nowego wątku.

mw.Start(); // Start wątku i wykonanie metody Wypisz_Zero().

for (int i = 0; i < 100; i++)


Console.Write("1");
}
}
}

Podczas wykonywania programu pętle przeplatają się ze sobą i są wykony-


wane w tym samym czasie.
Rezultat działania programu można zobaczyć na rysunku 7.6.

Rysunek 7.6.
Efekt działania 11111111111111111111111111111100000000000000000000000000000000000
programu 00000000000000000000000000000000000000000000000000000000000000000
1111111111111111111………
Zadanie 7.6

Zadanie
7.7 Napisz program, który oblicza liczbę π w osobnym wątku.

Skorzystaj z kodu programu znajdującego się w zadaniu 3.27, odpowiednio


go przystosowując.

Listing 7.7. Przykładowe rozwiązanie

using System;
using static System.Math;
using System.Threading;

namespace Zadanie_77 // Zadanie 7.7.


{
class Program
{
static double oblicz_Pi(Int32 liczba_punktów)
{

Int32 i;
int licznik = 0;
double x, y, pi;

Random r = new Random();

for (i = 1; i < liczba_punktów; i++)


{

8ba61431c02548173fa6085d93015e5a
8
Rozdział 7. ♦ Wprowadzenie do współbieżności 175

// x i y to liczby losowe z przedziału [0, 1).


x = r.NextDouble();
y = r.NextDouble();

if (x * x + y * y <= 1)
{
licznik += 1; // Liczy punkty znajdujące się w kole.
}
}

return (pi = 4.0 * licznik / liczba_punktów);


}

static void obliczamy_Pi()


{
Int32 liczba_punktów = 100000000;

int start = Environment.TickCount; // Zliczanie taktów procesora.


Console.WriteLine("Obliczenia, wątek nr {0}...",
´Thread.CurrentThread.ManagedThreadId);

double obl_pi = oblicz_Pi(liczba_punktów);

Console.WriteLine("Wartość pi = " + PI);


Console.WriteLine("Obliczona wartość pi = " + obl_pi);
Console.WriteLine("Błąd obliczeń wynosi: " +
´Abs(PI - obl_pi));

Console.WriteLine("Obliczenia zakończone, wątek nr {0}...",


´Thread.CurrentThread.ManagedThreadId);

int stop = Environment.TickCount; // Koniec zliczania.

Console.WriteLine("Czas obliczeń: " +


´(stop - start).ToString() +" ms.");
}

static void Main(string[] args)


{
Console.WriteLine("Proszę czekać...");

Thread w = new Thread(obliczamy_Pi);


w.Start();
}
}
}

Właściwość Thread.CurrentThread.ManagedThreadId w linijce kodu:


Console.WriteLine("Obliczenia, wątek nr {0}...",
Thread.CurrentThread.ManagedThreadId);

8ba61431c02548173fa6085d93015e5a
8
176 C#. Zadania z programowania z przykładowymi rozwiązaniami

służy do jednoznacznej identyfikacji wątku w ramach procesu.


Rezultat działania programu można zobaczyć na rysunku 7.7.

Rysunek 7.7.
Efekt działania Proszę czekać...
programu Obliczenia, wątek nr 3...
Wartość pi = 3,14159265358979
Zadanie 7.7
Obliczona wartość pi = 3,14165732
Błąd obliczeń wynosi: 6,46664102070815E-05
Obliczenia zakończone, wątek nr 3...
Czas obliczeń: 8814 ms

Praca z wątkami
Zadanie
7.8 Napisz program, który tworzy trzy odrębne wątki. Każdy z wątków reali-
zuje inne zadanie: pierwszy oblicza sumę dwóch macierzy kwadratowych
(zob. zadanie 4.13), drugi oblicza różnicę dwóch macierzy kwadratowych
(zob. zadanie 4.14). Trzeci wątek oblicza iloczyn dwóch macierzy kwadrato-
wych (zob. zadanie 4.15).

Skorzystaj z metody Join(), aby mieć pewność, że wątek zakończył pracę.

Listing 7.8. Przykładowe rozwiązanie

using System;
using System.Threading;

namespace Zadanie_78 // Zadanie 7.8.


{
class Program
{
static void suma_Macierzy() // Metoda.
{
int n = 10, i, j;

int[,] a = new int[n, n];


int[,] b = new int[n, n];
int[,] c = new int[n, n];

// Wpisywanie liczb do macierzy a.


for (i = 0; i < n; i++)
{
for (j = 0; j < n; j++)

8ba61431c02548173fa6085d93015e5a
8
Rozdział 7. ♦ Wprowadzenie do współbieżności 177

{
a[i, j] = 1;
}
}

// Wpisywanie liczb do macierzy b.


for (i = 0; i < n; i++)
{
for (j = 0; j < n; j++)
{
b[i, j] = 2;
}
}

// Dodawanie macierzy a + b.
for (i = 0; i < n; i++)
{
for (j = 0; j < n; j++)
{
c[i, j] = a[i, j] + b[i, j];
}
}
Console.WriteLine();

// Wyświetlenie zawartości macierzy c.


Console.WriteLine("Macierz c = a + b.");
for (i = 0; i < n; i++)
{
for (j = 0; j < n; j++)
{
Console.Write(c[i, j] + " ");
}
Console.WriteLine();
}
}

static void różnica_Macierzy() // Metoda.


{
int n = 10, i, j;

int[,] a = new int[n, n];


int[,] b = new int[n, n];
int[,] c = new int[n, n];

// Wpisywanie liczb do macierzy a.


for (i = 0; i < n; i++)
{
for (j = 0; j < n; j++)
{
a[i, j] = 1;
}
}
// Wpisywanie liczb do macierzy b.

8ba61431c02548173fa6085d93015e5a
8
178 C#. Zadania z programowania z przykładowymi rozwiązaniami

for (i = 0; i < n; i++)


{
for (j = 0; j < n; j++)
{
b[i, j] = 2;
}
}

// Odejmowanie macierzy b – a
for (i = 0; i < n; i++)
{
for (j = 0; j < n; j++)
{
c[i, j] = b[i, j] - a[i, j];
}
}
Console.WriteLine();

// Wyświetlenie zawartości macierzy c.


Console.WriteLine("Macierz c = b - a.");
for (i = 0; i < n; i++)
{
for (j = 0; j < n; j++)
{
Console.Write(c[i, j] + " ");
}
Console.WriteLine();
}
}

static void iloczyn_Macierzy() // Metoda.


{
int n = 10, i, j;

int[,] a = new int[n, n];


int[,] b = new int[n, n];
int[,] c = new int[n, n];

// Wpisywanie liczb do macierzy a.


for (i = 0; i < n; i++)
{
for (j = 0; j < n; j++)
{
a[i, j] = 1;
}
}

// Wpisywanie liczb do macierzy b.


for (i = 0; i < n; i++)
{
for (j = 0; j < n; j++)
{
b[i, j] = 2;

8ba61431c02548173fa6085d93015e5a
8
Rozdział 7. ♦ Wprowadzenie do współbieżności 179

}
}

// Mnożenie macierzy c = a * b.
for (i = 0; i < n; i++)
{
for (j = 0; j < n; j++)
{
c[i, j] = 0;

for (int k = 0; k < n; k++)


{
c[i, j] += a[i, k] * b[k, j];
}
}
}

Console.WriteLine();

// Wyświetlenie zawartości macierzy c.


Console.WriteLine("Macierz c = a * b.");
for (i = 0; i < n; i++)
{
for (j = 0; j < n; j++)
{
Console.Write(c[i, j] + " ");
}
Console.WriteLine();
}
}

static void Main(string[] args)


{
Console.WriteLine("Program wykonuje trzy operacje
´na macierzach kwadratowych: ");
Console.WriteLine("dodawanie, odejmowanie i mnożenie, każde
´w osobnym wątku.");

Thread sm = new Thread(suma_Macierzy); // Utworzenie nowego wątku


// sm.
Thread rm = new Thread(różnica_Macierzy); // Utworzenie nowego
// wątku rm.
Thread im = new Thread(iloczyn_Macierzy); // Utworzenie nowego
// wątku im.

sm.Start(); // Startuje wątek sm.


sm.Join(); // Czekamy, aż wątek zostanie zakończony.

rm.Start();
rm.Join();

im.Start();
Thread.Sleep(250); // Usypiamy wątek na 250 milisekund.

8ba61431c02548173fa6085d93015e5a
8
180 C#. Zadania z programowania z przykładowymi rozwiązaniami

}
}
}

Linijka kodu:
Thread.Sleep(250); // Usypiamy wątek na 250 milisekund.

powoduje wstrzymanie (uśpienie) bieżącego wątku na 250 milisekund.


Po upływie tego czasu program kończy swoje działanie.
Rezultat działania programu można zobaczyć na rysunku 7.8.
Rysunek 7.8.
Program wykonuje trzy operacje na macierzach kwadratowych: dodawanie,
Efekt działania
odejmowanie i mnożenie; każdą w osobnym wątku.
programu
Zadanie 7.8 Macierz c = a + b.
3333333333
3333333333
3333333333
3333333333
3333333333
3333333333
3333333333
3333333333
3333333333
3333333333
Macierz c = b - a.
1111111111
1111111111
1111111111
1111111111
1111111111
1111111111
1111111111
1111111111
1111111111
1111111111
Macierz c = a * b.
20 20 20 20 20 20 20 20 20 20
20 20 20 20 20 20 20 20 20 20
20 20 20 20 20 20 20 20 20 20
20 20 20 20 20 20 20 20 20 20
20 20 20 20 20 20 20 20 20 20
20 20 20 20 20 20 20 20 20 20
20 20 20 20 20 20 20 20 20 20
20 20 20 20 20 20 20 20 20 20
20 20 20 20 20 20 20 20 20 20
20 20 20 20 20 20 20 20 20 20

8ba61431c02548173fa6085d93015e5a
8
Rozdział 7. ♦ Wprowadzenie do współbieżności 181

Priorytety wątków
Zadanie
7.9 Napisz program, który oblicza liczbę π w n osobnych wątkach, gdzie n jest
liczbą logicznych rdzeni procesora1.

Skorzystaj z kodu zawartego w zadaniu 7.7.

Listing 7.9. Przykładowe rozwiązanie

using System;
using static System.Math;
using System.Threading;

namespace Zadanie_79 // Zadanie 7.9.


{
class Program
{
static double oblicz_Pi(Int32 liczba_punktów)
{

Int32 i;
int licznik = 0;
double x, y, pi;

Random r = new Random();

for (i = 1; i < liczba_punktów; i++)


{
// x i y to liczby losowe z przedziału [0, 1).
x = r.NextDouble();
y = r.NextDouble();

if (x * x + y * y <= 1)
{
licznik += 1; // Liczy punkty znajdujące się w kole.
}
}

return (pi = 4.0 * licznik / liczba_punktów);


}

1 Rdzeń procesora (rdzeń fizyczny) to fizyczna część procesora odpowiedzialna za realizację


operacji obliczeniowych, zawierająca wiele jednostek wykonawczych. Jeśli bieżąca maszyna
zawiera wiele grup procesorów, ta właściwość zwraca liczbę procesorów logicznych, które
są dostępne do użycia przez środowisko uruchomieniowe języka wspólnego (CLR).

8ba61431c02548173fa6085d93015e5a
8
182 C#. Zadania z programowania z przykładowymi rozwiązaniami

static void obliczamy_Pi()


{
Int32 liczba_punktów = 10000000;

int start = Environment.TickCount; // Zliczanie taktów procesora.

double obl_pi = oblicz_Pi(liczba_punktów);

Console.WriteLine("Pi = {0}, błąd = {1}, wątek nr {2}.",


´obl_pi, Abs(PI - obl_pi),
´Thread.CurrentThread.ManagedThreadId);

int stop = Environment.TickCount; // Koniec zliczania.

Console.WriteLine("Czas obliczeń: " +


´(stop - start).ToString() +" ms.");
}

static void Main(string[] args)


{
Console.WriteLine("Proszę czekać...");

int ile_rdzeni = Environment.ProcessorCount; // Liczy rdzenie


// logiczne
// procesora.
Console.WriteLine("Liczba rdzeni logicznych = {0}.",
´ile_rdzeni);

Thread[] w = new Thread[ile_rdzeni]; // Tworzymy tablicę wątków.

for (int i = 0; i < ile_rdzeni; ++i)


{
w[i] = new Thread(obliczamy_Pi);
w[i].Priority = ThreadPriority.Lowest; // Ustawianie
// priorytetów.
w[i].Start();
}
}
}
}

Każdy z utworzonych wątków ma „własny” generator liczb pseudolosowych.


Można przebudować ten program i skorzystać ze wspólnego generatora
(o którym można przeczytać w książce podanej w pozycji 4. bibliografii
zamieszczonej na końcu książki).

8ba61431c02548173fa6085d93015e5a
8
Rozdział 7. ♦ Wprowadzenie do współbieżności 183

Każdy z utworzonych wątków ma określony priorytet działania, wyrażony


jako wartość enumeracji ThreadPriority. Może ona przyjmować jedną
z pięciu wartości:
 Lowest (najniższy),
 BelowNormal (obniżony),
 Normal (normalny),
 AboveNormal (podwyższony),
 Highest (najwyższy).

Każda z kolejnych wartości reprezentuje rosnący priorytet. Domyślną warto-


ścią jest Normal (priorytet normalny). Dziś priorytety wątków nie mają już
tak dużego znaczenia jak kiedyś. Jest to spowodowane przede wszystkim
dużą mocą obecnych komputerów i liczbą rdzeni znajdujących się we
współczesnych procesorach. Linijka kodu:
w[i].Priority = ThreadPriority.Lowest; // Ustawianie priorytetów.

oznacza, że nasze n wątków ma ustalony najniższy priorytet Lowest.


W programie skorzystaliśmy z właściwości Environment.ProcessorCount,
która wyświetla, ile rdzeni (logicznych) ma procesor zamontowany w kompu-
terze, na którym dokonujemy obliczeń. W praktyce optymalne jest utworze-
nie tylu wątków, ile dostępnych jest wszystkich logicznych rdzeni procesora
(lub procesorów). W naszym przypadku ile_rdzeni = 8 pokazuje rysunek
7.9 a, a ile_rdzeni = 16 pokazuje rysunek 7.9 b.
Rezultaty działania programu dla procesorów: 4- i 8-rdzeniowego można
zobaczyć na rysunkach 7.9 a i 7.9 b.

Klasa Task
Wątek to niskiego poziomu narzędzie przeznaczone do zapewnienia współ-
bieżności. Ma on jednak pewne ograniczenia, które utrudniają przygoto-
wanie dużych operacji współbieżnych przez połączenie mniejszych. Z tego
rodzaju problemami można sobie poradzić za pomocą klasy Task (czyli
zadanie). Klasa ta stanowi abstrakcję wyższego poziomu, gdyż przedstawia
współbieżną operację, która nie musi być wykonywana w oddzielnym wątku.

8ba61431c02548173fa6085d93015e5a
8
184 C#. Zadania z programowania z przykładowymi rozwiązaniami

Rysunek 7.9 a.
Efekt działania Proszę czekać...
programu Liczba rdzeni logicznych = 8
Pi = 3,1416448, błąd = 5,21464102067881E-05, wątek nr 4
Zadanie 7.9
Czas obliczeń: 1389 ms.
— procesor Pi = 3,1416448, błąd = 5,21464102067881E-05, wątek nr 5
4-rdzeniowy Czas obliczeń: 1467 ms.
Pi = 3,1418852, błąd = 0,000292546410206818, wątek nr 3
Czas obliczeń: 1763 ms.
Pi = 3,1416512, błąd = 5,85464102069722E-05, wątek nr 7
Czas obliczeń: 1528 ms.
Pi = 3,141578, błąd = 1,46535897931344E-05, wątek nr 10
Czas obliczeń: 1389 ms.
Pi = 3,1406956, błąd = 0,000897053589793195, wątek nr 6
Czas obliczeń: 1841 ms.
Pi = 3,1417936, błąd = 0,00020094641020707, wątek nr 8
Czas obliczeń: 1685 ms.
Pi = 3,1417936, błąd = 0,00020094641020707, wątek nr 9
Czas obliczeń: 1778 ms.

Klasa Task jest podstawową klasą służącą do tworzenia programów korzysta-


jących z algorytmów równoległych. Ma na celu wyprzeć korzystanie z klasy
Thread w procesie tworzenia oprogramowania. Jest ona zdefiniowania w prze-
strzeni nazw System.Threading.Tasks, którą należy włączyć do kodu pro-
gramu poleceniem:
using System.Threading.Tasks;

W oparciu o typ Task działają funkcje asynchroniczne w języku C#2.

Moje pierwsze zadanie


Zadanie
7.10 Napisz program, który w oparciu o typ Task realizuje proste zadanie.
Listing 7.10. Przykładowe rozwiązanie

using System;
using System.Threading.Tasks;

namespace Zadanie_710 // Zadanie 7.10.


{
class Program
{

2 Zob. bibliografia, pozycje 1. i 6.

8ba61431c02548173fa6085d93015e5a
8
Rozdział 7. ♦ Wprowadzenie do współbieżności 185

Rysunek 7.9 b.
Efekt działania Proszę czekać...
Liczba rdzeni logicznych = 16.
programu Pi = 3,141508, błąd = 8,465358979314885E-05, wątek nr 12.
Zadanie 7.9 Pi = 3,1418636, błąd = 0,00027094641020708465, wątek nr 10.
— procesor Pi = 3,1417496, błąd = 0,0001569464102066931, wątek nr 18.
8-rdzeniowy Pi = 3,143332, błąd = 0,001739346410206899, wątek nr 6.
Czas obliczeń: 453 ms.
Czas obliczeń: 391 ms.
Czas obliczeń: 453 ms.
Czas obliczeń: 453 ms.
Pi = 3,1418636, błąd = 0,00027094641020708465, wątek nr 14.
Czas obliczeń: 454 ms.
Pi = 3,1425192, błąd = 0,0009265464102070631, wątek nr 17.
Czas obliczeń: 407 ms.
Pi = 3,1423096, błąd = 0,000716946410206809, wątek nr 11.
Czas obliczeń: 484 ms.
Pi = 3,1422652, błąd = 0,0006725464102070866, wątek nr 15.
Czas obliczeń: 485 ms.
Pi = 3,142284, błąd = 0,0006913464102069611, wątek nr 7.
Czas obliczeń: 500 ms.
Pi = 3,1417952, błąd = 0,00020254641020667208, wątek nr 13.
Czas obliczeń: 500 ms.
Pi = 3,1410944, błąd = 0,0004982535897930518, wątek nr 16.
Czas obliczeń: 500 ms.
Pi = 3,1412496, błąd = 0,0003430535897930298, wątek nr 8.
Czas obliczeń: 515 ms.
Pi = 3,1427388, błąd = 0,0011461464102069385, wątek nr 5.
Czas obliczeń: 515 ms.
Pi = 3,141554, błąd = 3,8653589792936316E-05, wątek nr 19.
Czas obliczeń: 469 ms.
Pi = 3,1419452, błąd = 0,0003525464102067666, wątek nr 20.
Czas obliczeń: 406 ms.
Pi = 3,1410696, błąd = 0,0005230535897933208, wątek nr 21.
Czas obliczeń: 375 ms.

static void Main(string[] args)


{
Task zadanie = new Task(() =>

{
Console.WriteLine("Moje pierwsze zadanie.");
});

zadanie.Start(); // Start zadania.


zadanie.Wait(); // Wstrzymanie zadania.
}
}
}

8ba61431c02548173fa6085d93015e5a
8
186 C#. Zadania z programowania z przykładowymi rozwiązaniami

Metoda Start() uruchamia bieżące zadanie, natomiast metoda Wait() je


wstrzymuje, powodując zablokowanie zadania aż do jego zakończenia i jest
odpowiednikiem metody Join() w wątku. W podstawowych czynnościach
praca z zadaniami niewiele różni się od pracy z wątkami.
Rezultat działania programu można zobaczyć na rysunku 7.10.

Rysunek 7.10.
Efekt działania
Moje pierwsze zadanie.
programu
Zadanie 7.10

Praca z zadaniami
Klasa zadania Task oferuje wiele udogodnień dotyczących języka C# i pro-
gramowania obiektowego — od tworzenia list aż po korzystanie z nowych
algorytmów równoległych zaimplementowanych np. w metodach ForEach
klasy List<>. Ilustruje to zadanie 7.11.

Zadanie
7.11 Napisz program, który tworzy pewną liczbę zadań (Task), gdzie liczba_za
´dań = 4. Działanie tego programu polega na wypisaniu na ekranie infor-
macji o rozpoczęciu pracy zadania, wypisaniu wartości wylosowanej liczby
z przedziału od 0 do 100 oraz wypisaniu informacji o zakończeniu zadania.
Listing 7.11. Przykładowe rozwiązanie

using System;
using static System.Math;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace Zadanie_711 // Zadanie 7.11.


{
class Program
{
static void Main(string[] args)
{
Random r = new Random();
int liczba_zadań = 4;

Action zadanie = () =>


{
Console.WriteLine("Start zadania nr " + Task.CurrentId +
´".");

8ba61431c02548173fa6085d93015e5a
8
Rozdział 7. ♦ Wprowadzenie do współbieżności 187

double liczba = Round(100 * (r.NextDouble()));


Console.WriteLine(" Wylosowana liczba to {0}.",
´liczba);
Console.WriteLine("Koniec zadania nr " + Task.CurrentId +
´".");
Console.WriteLine();
};

List<Task> lista_Zadań = new List<Task>(); // Lista zadań.

for (int i = 0; i < liczba_zadań; i++)


{
lista_Zadań.Add(new Task(zadanie)); // Dodanie nowego
// zadania do listy zadań.
}

lista_Zadań.ForEach(t => t.Start()); // Start i-tego zadania.


lista_Zadań.ForEach(t => t.Wait()); // Wstrzymanie i-tego zadania.
}
}
}

Delegat Action (akcja) stosujemy, jeśli chcemy zapisać metodę, która nie
zwraca żadnej wartości. W naszym przypadku ten delegat ma postać:
Action zadanie = () =>
{
Console.WriteLine("Start zadania nr " + Task.CurrentId +
´".");
double liczba = Round(100 * (r.NextDouble()));
Console.WriteLine(" Wylosowana liczba to {0}.",
´liczba);
Console.WriteLine("Koniec zadania nr " + Task.CurrentId +
´".");
Console.WriteLine();
};

Tak zdefiniowana akcja zadanie nie wymaga dodatkowych wyjaśnień.


W wygodny sposób uruchamiamy dowolną liczbę zadań. Lista zadań opi-
sana kodem:
List <Task> lista_Zadań = new List<Task>(); // Lista zadań.

umożliwia wygodne zwiększanie liczby przechowywanych w niej zadań.


Start i-tego zadania i jego wstrzymanie przedstawiają następujące linijki kodu:
lista_Zadan.ForEach(t => t.Start()); // Start i-tego zadania.
lista_Zadan.ForEach(t => t.Wait()); // Wstrzymanie i-tego zadania.

8ba61431c02548173fa6085d93015e5a
8
188 C#. Zadania z programowania z przykładowymi rozwiązaniami

Rezultat działania programu można zobaczyć na rysunku 7.11.

Rysunek 7.11.
Efekt działania Start zadania nr 1.
programu Wylosowana liczba to 11.
Koniec zadania nr 1.
Zadanie 7.11
Start zadania nr 2.
Wylosowana liczba to 17.
Koniec zadania nr 2.

Start zadania nr 3.
Wylosowana liczba to 80.
Koniec zadania nr 3.

Start zadania nr 4.
Wylosowana liczba to 93.
Koniec zadania nr 4.

Synchronizacja zadań
Przedstawię teraz metodę WaitAll(), która synchronizuje wykonywane
zadania.

Zadanie
7.12 Napisz program, który tworzy trzy dowolne zadania, a następnie je syn-
chronizuje.

Listing 7.12. Przykładowe rozwiązanie

using System;
using System.Threading.Tasks;

namespace Zadanie_712 // Zadanie 7.12.


{
class Program
{
static void Main(string[] args)
{
Task zadanie_1, zadanie_2, zadanie_3;

zadanie_1 = new Task(() =>


{
Task.Delay(100); Console.WriteLine("Zadanie nr 1
´o identyfikatorze {0} zakończone.", Task.CurrentId);
});

8ba61431c02548173fa6085d93015e5a
8
Rozdział 7. ♦ Wprowadzenie do współbieżności 189

zadanie_2 = new Task(() =>


{
Task.Delay(100); Console.WriteLine("Zadanie nr 2
´o identyfikatorze {0} zakończone.", Task.CurrentId);
});

zadanie_3 = new Task(() =>


{
Task.Delay(100); Console.WriteLine("Zadanie nr 3
´o identyfikatorze {0} zakończone.", Task.CurrentId);
});

Task[] zadania =
{
zadanie_1, zadanie_2, zadanie_3
};

foreach (Task z in zadania)


z.Start(); // Start poszczególnych zadań

Task.WaitAll(zadania); // lub
// Task.WaitAll(zadanie_1, zadanie_2, zadanie_3);
// Obie linijki kodu są sobie równoważne.
}
}
}

Metoda WaitAll nakazuje bieżącemu zadaniu czekać na wykonanie wszyst-


kich wymienionych w jej argumencie zadań. Argumentem tej metody może
być zarówno tablica zadań, jak i dowolnie duży zbiór zadań po przecinku.
Ilustrują to następujące równoważne sobie linijki kodu:
Task.WaitAll(zadanie_1, zadanie_2, zadanie_3);

lub
Task.WaitAll(zadania);

Metoda Task.Delay(100) tworzy zadanie, które kończy się po pewnym czasie


(w naszym wypadku po upływie 100 milisekund). Metoda ta jest asynchro-
nicznym odpowiednikiem metody Thread.Sleep().
Rezultat działania programu można zobaczyć na rysunku 7.12.

Rysunek 7.12.
Efekt działania Zadanie nr 1 o identyfikatorze 1 zakończone.
programu Zadanie nr 2 o identyfikatorze 2 zakończone.
Zadanie nr 3 o identyfikatorze 3 zakończone.
Zadanie 7.12

8ba61431c02548173fa6085d93015e5a
8
190 C#. Zadania z programowania z przykładowymi rozwiązaniami

Oto inny przykład zadania, w którym również została wykorzystana syn-


chronizacja zadań.

Zadanie
7.13 Napisz program, który symuluje wyścig zadań w formie sztafety. Na starcie
znajdują się dwa zadania, zadanie_1 i zadanie_2, które rozpoczynają swój
bieg. Po chwili do biegu włączają się kolejno zadanie_3 i zadanie_4. Rolę
sędziego sztafety pełni metoda WaitAll(), która zgłasza komunikat o zakoń-
czeniu wyścigu wszystkich czterech zadań.

Listing 7.13. Przykładowe rozwiązanie

using System;
using System.Threading.Tasks;

namespace Zadanie_713 // Zadanie 7.13.


{
class Program
{
static void Main(string[] args)
{
Task zadanie_1, zadanie_2, zadanie_3, zadanie_4;

Action akcja_1 = () =>


{
Console.WriteLine("Zawodnik nr {0} wystartował.",
´Task.CurrentId);
Console.WriteLine("Zawodnik nr {0} zakończył bieg.",
´Task.CurrentId);
};

Action<Task> akcja_2 = (z) =>


{
Console.WriteLine("Zawodnik nr {0} wystartował
´po zawodniku nr {1}.", Task.CurrentId, z.Id);
Console.WriteLine("Zawodnik nr {0} zakończył bieg.",
´Task.CurrentId);
};

zadanie_1 = new Task(akcja_1);


zadanie_2 = new Task(akcja_1);
zadanie_3 = zadanie_1.ContinueWith(akcja_2); // Kontynuacja
// zadania zadanie_3 po zadaniu zadanie_1.
zadanie_4 = zadanie_2.ContinueWith(akcja_2); // Kontynuacja
// zadania zadanie_4 po zadaniu zadanie_2.

zadanie_1.Start();
zadanie_2.Start();

8ba61431c02548173fa6085d93015e5a
8
Rozdział 7. ♦ Wprowadzenie do współbieżności 191

Task.WaitAll(zadanie_1, zadanie_2, zadanie_3, zadanie_4);


Console.WriteLine("");

Console.WriteLine("Wyścig został zakończony.");


}
}
}

W programie zadania uruchamiane są w dwojaki sposób, a mianowicie


zadanie_1 i zadanie_2 uruchamiane są na początku programu w sposób
tradycyjny:
zadanie_1 = new Task(akcja_1);
zadanie_2 = new Task(akcja_1);

natomiast zadanie_3 i zadanie_4 są tworzone za pomocą metody Continue


´With() jako tzw. kontynuacja zadań3 (ang. continuation tasks):
zadanie_3 = zadanie_1.ContinueWith(akcja_2); // Kontynuacja zadania zadanie_3.
// Po zadaniu zadanie_1
zadanie_4 = zadanie_2.ContinueWith(akcja_2); // kontynuacja
// zadania zadanie_4 po zadaniu zadanie_2.

Wykonywany przez te metody kod opisuje dwie akcje (opisane linijkami


kodu). Są to akcja_1:
Action akcja_1 = () =>
{
Console.WriteLine("Zawodnik nr {0} wystartował.",
´Task.CurrentId);
Console.WriteLine("Zawodnik nr {0} zakończył bieg.",
´Task.CurrentId);
};

i akcja_2:
Action<Task> akcja_2 = (z) =>
{
Console.WriteLine("Zawodnik nr {0} wystartował
´po zawodniku nr {1}.", Task.CurrentId, z.Id);
Console.WriteLine("Zawodnik nr {0} zakończył bieg.",
´Task.CurrentId);
};

3 Więcej informacji znajduje się na stronie https://msdn.microsoft.com/pl-pl/library/


ee372288(v=vs.110).aspx.

8ba61431c02548173fa6085d93015e5a
8
192 C#. Zadania z programowania z przykładowymi rozwiązaniami

Rezultat działania programu można zobaczyć na rysunku 7.13.

Rysunek 7.13.
Efekt działania Zawodnik nr 1 wystartował.
programu Zawodnik nr 1 zakończył bieg.
Zawodnik nr 2 wystartował.
Zadanie 7.13
Zawodnik nr 2 zakończył bieg.
Zawodnik nr 3 wystartował po zawodniku nr 1.
Zawodnik nr 3 zakończył bieg.
Zawodnik nr 4 wystartował po zawodniku nr 2.
Zawodnik nr 4 zakończył bieg.

Wyścig został zakończony.

8ba61431c02548173fa6085d93015e5a
8
Rozdział 8.
Podążając
w kierunku funkcyjnego
paradygmatu
programowania
W tym rozdziale omawiam zarys funkcyjnego paradygmatu programowania.
Przedstawiam różnice pomiędzy imperatywnym a funkcyjnym paradygma-
tem, ilustrując je przykładem.

Wstęp
C# to współbieżny, oparty na klasach, nowoczesny obiektowy język pro-
gramowania i ogólnego zastosowania. Od wersji 8.0 i 9.0 język ten zyskał
wiele wygodnych mechanizmów znanych z paradygmatu programowania
funkcyjnego1.

1
Ostatnio na polskim rynku księgarskim pojawiła się książka dotycząca funkcyjnego pro-
gramowania w języku C# [2].

8ba61431c02548173fa6085d93015e5a
8
194 C#. Zadania z programowania z przykładowymi rozwiązaniami

Co to jest paradygmat programowania?


Słowo paradygmat (ang. paradigm) pochodzi od greckiego słowa παράδειγμα
(gr. parádeigma) i oznacza ogólnie przyjęty sposób widzenia rzeczywistości
w danej dziedzinie.
Mówiąc o paradygmacie programowania (ang. programming paradigm),
myślimy raczej o zestawie typowych dla danej grupy języków mechani-
zmów udostępnionych programiście oraz o zbiorze sposobów interpretacji
tych mechanizmów przez semantykę języka2.
Paradygmat programowania definiuje sposób, w jaki programista patrzy
na przepływ sterowania i na wykonywanie programu komputerowego.
Wszystkie języki programowania możemy podzielić na dwie główne grupy:
 imperatywne,
 deklaratywne.
Języki imperatywne charakteryzują się tym, że programista wyraża w nich
czynności (przedstawione w postaci rozkazów), które komputer ma wyko-
nywać w ustalonej kolejności. Program jest zatem ściśle określoną listą roz-
kazów do wykonania w zadanej kolejności — programowanie jest impera-
tywne („rozkazowe”).
W językach deklaratywnych jest inaczej. Programista, pisząc program, dekla-
ruje (podaje) komputerowi pewne zależności oraz cele, które program powi-
nien osiągnąć. Nie podaje jednak wprost sposobu osiągnięcia wyników.
Języki imperatywne nakazują komputerowi, jak ma uzyskać wynik (chociaż
wyniku nie określają wprost), języki deklaratywne natomiast opisują, co ma
zostać uzyskane (bez podania bezpośredniego sposobu).

2 Semantyka języka programowania precyzyjnie określa znaczenie poszczególnych symboli


i ich funkcję w programie. Semantykę najczęściej definiuje się słownie, ponieważ więk-
szość jej zagadnień jest trudna lub wręcz niemożliwa do ujęcia w jakikolwiek formalizm.
Część błędów semantycznych, np. próbę odwołania się do nieistniejącej funkcji, daje się
wychwycić już w momencie wstępnego przetwarzania kodu programu, lecz inne mogą
ujawnić się dopiero w trakcie wykonywania programu.

8ba61431c02548173fa6085d93015e5a
8
Rozdział 8. ♦ Podążając w kierunku funkcyjnego paradygmatu programowania 195

Czym jest programowanie funkcyjne?


Programowanie funkcyjne narodziło się w latach 50. XX wieku.
„Programowanie funkcyjne — filozofia i metodyka programowania będąca
odmianą programowania deklaratywnego [rysunek 8.1], w której funkcje
należą do wartości podstawowych, a nacisk kładzie się na wartościowanie3
często rekurencyjnych [zobacz rozdział 5.] funkcji, a nie na wykonywanie
poleceń” [Wikipedia].
Mówiąc prościej: jest to programowanie z wykorzystaniem funkcji.

Rysunek 8.1.
Podział głównych
paradygmatów
programowania

W czystym programowaniu funkcyjnym raz zdefiniowana funkcja zwraca


zawsze tę samą wartość dla danych wartości argumentów, tak jak ma to miej-
sce w przypadku funkcji matematycznych.
Czym jest zatem programowanie funkcyjne?
Na wysokim poziomie to styl programowania, w którym kładzie się nacisk
na funkcje, unikając zmiany stanu4. Definicja ta jest dualistyczna i zawiera
dwa podstawowe pojęcia:
 funkcje jako wartości pierwszoklasowe,
 unikanie zmiany stanu [2].

3 Obliczenie (wyznaczenie) wartości funkcji lub wyrażenia.


4 Stan to wartości zmiennych używanych w programie podczas jego działania. Stan globalny
to wartości zmiennych globalnych przyjmowane podczas wykonywania programu.

8ba61431c02548173fa6085d93015e5a
8
196 C#. Zadania z programowania z przykładowymi rozwiązaniami

To nie język programowania czyni programowanie mniej lub bardziej funk-


cyjnym, lecz sposób pisania kodu. Oczywiście istnieją języki bardziej przy-
jazne funkcyjnemu programowaniu niż inne. A oto próbka programowania
funkcyjnego (jego niewielki fragment) na przykładzie biblioteki LINQ.

Funkcyjna natura biblioteki LINQ


Język C# (od wersji 3.0) obejmuje wiele funkcji inspirowanych językami funk-
cyjnymi, w tym bibliotekę LINQ (ang. Language Integrated Query — zapy-
tania zintegrowane)5. Biblioteka ta jest w istocie biblioteką funkcyjną, oferując
implementacje wielu popularnych działań na listach (mówiąc ogólnie, na
sekwencjach), takich jak odwzorowywanie, sortowanie i filtrowania. Oto
przykład programu, który łączy wszystkie trzy elementy:
using System;
using System.Linq;

namespace Linq_00
{
class Program
{
static void Main(string[] args)
{
var list = Enumerable.Range(1, 100).
Where(i => i % 20 == 0).OrderBy(i => i).Select(i =>
´$"{i}%");

foreach (var n in list)


Console.Write(n + " ");

Console.WriteLine();
}
}
}

Efekt działania tego programu znajduje się poniżej:

Rysunek 8.2.
Efekt działania 20% 40% 60% 80% 100%
programu

5 Do programów trzeba włączyć przestrzeń nazw System.Linq.

8ba61431c02548173fa6085d93015e5a
8
Rozdział 8. ♦ Podążając w kierunku funkcyjnego paradygmatu programowania 197

Metoda Select() dokonuje odwzorowania, metoda Where() dokonuje filtro-


wania, natomiast metoda OrderBy() dokonuje sortowania (tutaj rosnąco).
Biblioteka LINQ zawiera wiele pomocnych metod służących do wykonywania
podstawowych działań na ciągach, takich jak:
 odwzorowanie,
 filtrowanie,
 sortowanie [2].
Kilka słów objaśnienia wraz z przykładami znajduje się poniżej.
Odwzorowując dany ciąg i funkcję, odwzorowywanie daje nowy ciąg z ele-
mentami otrzymanymi przez zastosowanie danej funkcji na każdym elemencie
danego ciągu. W LINQ korzystamy wówczas z metody Select(), np.:
var list = Enumerable.Range(1, 10).Select(i => i * 3);
// => [3 6 9 12 15 18 21 24 27 30]

Dla danego ciągu i predykatu filtrowanie daje nowy ciąg złożony z elemen-
tów danego ciągu, które są zgodne z predykatem (w LINQ jest to metoda
Where()), np.:
var list = Enumerable.Range(1, 20).Where(i => i % 4 == 0); // => [4 8 12 16 20]

Mając dany ciąg i funkcję klucza wyboru, sortowanie daje nowy ciąg upo-
rządkowany zgodnie z kluczem (w LINQ są to metody: OrderBy i OrderBy
´Descending), np.:
var list = Enumerable.Range(1, 6).OrderBy(i => -i); // => [6 5 4 3 2 1]

Dalsze rozważania to temat na inną książkę.

8ba61431c02548173fa6085d93015e5a
8
198 C#. Zadania z programowania z przykładowymi rozwiązaniami

8ba61431c02548173fa6085d93015e5a
8
Polecana literatura
1. Z. Czech, Wprowadzenie do obliczeń równoległych, Wydawnictwo
Naukowe PWN, Warszawa 2010.
2. M. Herlihy, N. Shavit, Sztuka programowania wieloprocesorowego,
Wydawnictwo Naukowe PWN, Warszawa 2010.
3. P. Majdzik, Programowanie współbieżne. Systemy czasu rzeczywistego,
Helion, Gliwice 2012.
4. Praca zbiorowa, Programowanie równoległe i rozproszone, Oficyna
Wydawnicza Politechniki Warszawskiej, Warszawa 2009.

Bibliografia
1. J. Albahari, B. Albahari, C# 8.0. Leksykon kieszonkowy, Helion,
Gliwice 2020.
2. E. Buonanno, Programowanie funkcyjne w języku C#. Jak pisać lepszy
kod, Wydawnictwo Naukowe PWN, Warszawa 2019.
3. S. Cleary, Współbieżność w języku C#. Receptury, Helion, Gliwice 2017.

4. E. Gunnerson, Programowanie w języku C#, Mikom, Warszawa 2001.


5. H. Schildt, C#. Kurs podstawowy, Edition 2000, Kraków 2002.
6. M. Warczak i in., Programowanie równoległe i asynchroniczne
w C# 5.0, Helion, Gliwice 2014.
7. Wikipedia.
8. Zasoby internetu.

8ba61431c02548173fa6085d93015e5a
8
200 C#. Zadania z programowania z przykładowymi rozwiązaniami

Zbiory zadań z programowania


Zbiory zadań z programowania autora wydane w wydawnictwie Helion oraz
Wydawnictwie Naukowym PWN — https://helion.pl/autorzy/miroslaw-j-
kubiak.

8ba61431c02548173fa6085d93015e5a
8
8ba61431c02548173fa6085d93015e5a
8
8ba61431c02548173fa6085d93015e5a
8

You might also like