Files
programming-book/main.typ

398 lines
20 KiB
Typst
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#import "@preview/ilm:1.4.1": *
#import "@preview/codly:1.3.0": *
#import "@preview/codly-languages:0.1.1": *
#set text(lang: "pl")
#let start_date = datetime(year: 2025, month: 9, day: 21)
#show: ilm.with(
title: [Programowanie],
author: "Gabriel Kaszewski",
date: start_date,
date-format: "[year]",
abstract: [
Celem tej książki jest nauczenie czytelnika wspaniałej umiejętności jaką jest programowanie.
],
preface: [
#align(center + horizon)[
Książkę dedykuję mojemu Dziadkowi, #linebreak() który wprowadził mnie w świat programowania.
]
],
bibliography: bibliography("refs.bib"),
figure-index: (enabled: true),
table-index: (enabled: true),
listing-index: (enabled: true),
)
#show: codly-init.with()
= Wstęp
Witam Ciebie bardzo serdecznie w mojej książce o programowaniu. Cieszę się, że zdecydowałeś drogi czytelniku, się na ten krok, który otworzy przed Tobą drzwi do świata technologii, w którym nie ma rzeczy niemożliwych oraz nie ma ograniczeń. Mam nadzieję, że ta książka pozwoli Ci zapuścić korzenie w tym świecie i rozwiniesz skrzydła.
Możesz zastanawiać się czy ta książka jest dla Ciebie i to jest dobre pytanie, dla kogo jest ta książka?
Moim celem jest nauczenie kogokolwiek programowania, niezależnie od wieku, płci, wykształcenia czy doświadczenia. Jeśli potrafisz czytać i pisać, to ta książka jest dla Ciebie. Nie tracimy, więc czasu, zaparzmy sobie herbatę i zaczynajmy.
Słowem wstępu, chciałbym Cię poinformować, że zamysłem tej książki jest nauka programowania w szerokim tego słowa znaczeniu. Nie będę skupiał się na konkretnym języku programowania, ale na ogólnych zasadach, konceptach i paradygmatach, które występują w programowaniu. Dzięki temu, niezależnie od tego, jakiego języka programowania użyjesz w przyszłości, będziesz miał solidne podstawy, które pozwolą Ci szybko się go nauczyć. Natomiast przykłady kodu będą głównie w języku C\# w wersji 12, nie martw się jeśli nigdy nie miałeś z nim do czynienia, albo jeśli wersja się nie zgadza, ponieważ zagadnienia, które poruszam  uniwersalne i występują (mniej lub bardziej) w każdym języku programowania.
Z racji tej, że skupiam się na esencji tej sztuki jaką jest programowanie, to niestety ta książka nie poinstruuje jak zainstalować środowisko programistyczne i podobne rzeczy. Zakładam, że czytelnik potrafi sobie z tym poradzić samodzielnie. Jeśli jednak nie, to w internecie jest mnóstwo poradników jak to zrobić. Proszę o wybaczenie jeśli zawiodę kogoś tym faktem.
Struktura rozdziałów będzie mniej więcej taka sama. Najpierw wprowadzę temat, następnie pokażę przykład kodu, potem wyjaśnię jak on działa, a na końcu dam kilka ćwiczeń do samodzielnego rozwiązania. Odpowiedzi do ćwiczeń znajdziesz na końcu książki. Zachęcam do samodzielnego rozwiązywania zadań oraz do eksperymentowania z kodem. To najlepszy sposób na naukę programowania.
= Podstawy programowania
W tym rozdziale przedstawię podstawowe koncepty programowania, które fundamentem dla dalszej nauki. Omówię takie tematy jak zmienne, typy danych, instrukcje sterujące programem, funkcje czy klasy i obiekty.
== Pierwszy program
Tradycją jest, że pierwszy program w etapie edukacji programowania to program "Hello, World!", który wyświetla tytułowy tekst na ekranie. Oto jak wygląda ten program w języku C\#.
=== Kod
#codly(languages: codly-languages, zebra-fill: none, display-icon: false, display-name: false)
```cs
using System;
Console.WriteLine("Hello, World!");
```
=== Rezultat
Po uruchomieniu powyższego kodu, na ekranie pojawi się tekst:
```
Hello, World!
```
=== Wyjaśnienie
Te dwa wiersze kodu wystarczą, aby wyświetlić tekst na ekranie. Niesamowite, prawda? Przyjrzyjmy się bliżej, jak to działa.
1. `using System;` - Ten wiersz importuje przestrzeń nazw `System`, która zawiera podstawowe klasy i funkcje, takie jak `Console`. Dzięki temu możemy wyświetlić na podstawowym urządzaniu wyjściowym (zazwyczaj jest to ekran konsoli) tekst.
2. `Console.WriteLine("Hello, World!");` - Ta linia kodu wywołuje metodę `WriteLine` klasy `Console`, która wyświetla tekst przekazany jako argument. W tym przypadku, przekazujemy tekst `"Hello, World!"`, który zostanie wyświetlony na ekranie. Co więcej, metoda `WriteLine` automatycznie dodaje znak nowej linii po wyświetleniu tekstu, więc kursor przechodzi do następnej linii.
Okej, ale to nadal brzmi jak czarna magia, prawda? Spróbuję wyjaśnić to nastepującą analogią. Ten wiersz informuje komputer, ze hej, będę uzywał standardowego zestawu narzędzi o nazwie `System`. `Console` to jedno z tych narzedzi (jak mlotek), a `WriteLine()` to konkretna czynność, którą tym narzedziem wykonujemy ("uderz w gwozdz z napisem `Hello World`").
Istotna uwaga, w wielu językach programowania, w tym w C\#, każda linia kodu kończy się średnikiem (`;`). To sygnalizuje kompilatorowi, że to koniec instrukcji. Pamiętaj o tym, ponieważ pominięcie średnika spowoduje błąd podczas kompilacji.
== Czym jest program?
Kompilacja? Błąd? O co chodzi? Takie pytania pewnie pojawiły się w Twojej głowie. Spokojnie, wszystko to wyjaśnię. Na początku warto zrozumieć, czym jest program.
Program to nic innego jak zestaw instrukcji, które komputer może wykonać. Instrukcje te napisane w języku programowania, który jest zrozumiały dla ludzi, ale musi zostać przetłumaczony na język maszynowy, który jest zrozumiały dla komputera. Ten proces tłumaczenia nazywa się kompilacją. Kiedy program jest skompilowany, powstaje plik wykonywalny, który można uruchomić na komputerze.
Bardzo istotne jest, by pamiętać, ze program wykonuje się od góry do dołu.
Zaczyna się od punktu wejścia, często jest to funkcja `main()`.
Natomiast nie wszystkie języki programowania wymagają kompilacji. Niektóre języki, takie jak Python czy JavaScript, interpretowane, co oznacza, że instrukcje tłumaczone na język maszynowy przez specjalny program zwany interpreterem, w czasie rzeczywistym, podczas uruchamiania programu. Programy w językach interpretowanych wymagają od użytkownika posiadania interpretera zainstalowanego na komputerze, oraz często wolniejsze od programów skompilowanych.
Warto wspomnieć, że między kompilacją, a uruchomieniem programu, występuje jeszcze etap linkowania, który polega na dołączeniu do naszego programu dodatkowych bibliotek, które dostarczają dodatkową funkcjonalność. Na przykład, w naszym pierwszym programie, linia `using System;` wskazuje, że korzystamy z biblioteki `System`, która zawiera klasę `Console` i jej metodę `WriteLine`.
Różnicą między językami kompilowanymi, a interpretowanymi też inne aspekty, takie jak informowanie o błędach, w językach kompilowanych błędy wykrywane podczas kompilacji co oznacza, że program nie uruchomi się dopóki wszystkie błędy nie zostaną naprawione. W językach interpretowanych błędy wykrywane podczas uruchamiania programu, co oznacza, że program może się uruchomić, ale może napotkać błąd w trakcie działania i wtedy się zatrzymać. Czy to oznacza, że w programach w językach kompilowanych jeśli podczas etapu kompilacji nie wystąpią błędy to program będzie działał bezbłędnie? Niestety nie, ponieważ mogą wystąpić błędy logiczne, bądź błędy wykonania, które nie wykrywane podczas kompilacji.
Natomiast kompilator stoi na straży poprawności składniowej kodu, co jest bardzo przydatne, bo pozwala programiście na uniknięciu wielu błędów już na etapie pisania kodu.
== Typy danych
W następnym rozdziale wprowadzę koncept "pudełek" na dane, czyli zmiennych. W prawdziwym świecie, mamy różne rodzaje pudełek na różne przedmioty, nie wsadzimy przecież telewizora do pudełka po butach, bo się po prostu nie zmieści. Podobnie jest w naszych cyfrowych "pudełkach". Różne rodzaje danych wymagają różnych typów. To właśnie typ określa, jaką wartość można przechować w zmiennej. Decyduje on również o tym, ile miejsca ta wartość zajmie w pamięci komputera. Wybór odpowiedniego typu danych jest kluczowy, ponieważ wpływa na wydajność programu oraz na to, jakie operacje można wykonywać na tych danych. O tym ile miejsca w pamięci zajmuje dany typ danych, decyduje architektura systemu operacyjnego na którym działa program (np. 32-bitowy lub 64-bitowy) oraz implementacja języka programowania.
Poniżej omówię kilka podstawowych typów danych, które powszechnie używane w programowaniu wraz z ich rozmiarem w pamięci na typowej 64-bitowej architekturze i przykłady ich zastosowania.
#table(
columns: (auto, auto, 1fr, auto),
inset: 6pt,
align: horizon,
table.header([Typ danych], [Rozmiar w pamięci], [Zakres wartości], [Przykład wartości]),
"byte", "8 bitów", "0 do 255", "200",
"sbyte", "8 bitów", "-128 do 127", "-100",
"short", "16 bitów", "-32 768 do 32 767", "30000",
"ushort", "16 bitów", "0 do 65 535", "60000",
"int", "32 bity", [$-2^31$ do $2^31 - 1$], "2000000000",
"uint", "32 bity", [0 do $2^32 - 1$], "4000000000",
"long", "64 bity", [$-2^63$ do $2^63 - 1$], "9000000",
"ulong", "64 bity", [0 do $2^64 - 1$], "18000000000000",
"float", "32 bity", [$plus.minus 1.5 times 10^-45$ do $plus.minus 3.4 times 10^38$], "3.14f",
"double", "64 bity", [$plus.minus 5.0 times 10^-324$ do $plus.minus 1.7 times 10^308$], "3.14159265358979",
"decimal", "128 bitów", [$plus.minus 1.0 times 10^-28$ do $plus.minus 7.9 times 10^28$], "79.99m",
"char", "16 bitów", "Pojedynczy znak Unicode", "'A'",
"string", "Zmienny", "Ciąg znaków Unicode", `"Hello, World!"`,
"bool", "8 bitów", "true lub false", "true",
)
W tabeli powyżej przedstawiłem podstawowe typy danych w języku C\#. Oczywiście, istnieją też bardziej zaawansowane typy danych, takie jak tablice, listy, czy klasy, które omówię w dalszej części książki.
Warto zauważyć, że mamy do typów liczbowych podział na liczby ze znakiem i bez znaku. Liczby ze znakiem (np. `int`, `long`) mogą przechowywać zarówno wartości dodatnie, jak i ujemne, natomiast liczby bez znaku (np. `uint`, `ulong`) mogą przechowywać tylko wartości dodatnie, ale za to mają większy zakres wartości dodatnich, ponieważ nie muszą rezerwować miejsca na liczby ujemne.
Typ `float` jest typem zmiennoprzecinkowym pojedynczej precyzji, co oznacza, że przechowuje liczby z mniejszą dokładnością (około 7 cyfr znaczących). Natomiast `double` jest typem zmiennoprzecinkowym podwójnej precyzji, który przechowuje liczby z większą dokładnością (około 15-16 cyfr znaczących). Typ `decimal` jest używany głównie do obliczeń finansowych, ponieważ oferuje bardzo wysoką precyzję i unika problemów związanych z reprezentacją liczb zmiennoprzecinkowych.
Możesz się zastanawiać, dlaczego typ `bool` zajmuje 8 bitów, skoro może przyjmować tylko dwie wartości: `true` lub `false`. Powodem jest to, że w większości architektur komputerowych, najmniejszą jednostką pamięci, którą można zaadresować, jest bajt (8 bitów). Dlatego nawet jeśli zmienna typu `bool` potrzebuje tylko jednego bitu do przechowywania swojej wartości, to i tak zajmuje cały bajt w pamięci. Aczkolwiek istnieją sposoby, by zaoszczędzić miejsce, na przykład poprzez pakowanie wielu wartości `bool` w jeden bajt, ale to już bardziej zaawansowany temat.
== Zmienne
No dobrze, nasz pierwszy program był bardzo prosty i przez to można by powiedzieć, że nie był zbyt ciekawy, bo przecież programy, do których jesteśmy przyzwyczajeni robią coś więcej niż tylko wyświetlanie tekstu na ekranie.
Jedną z podstawowych rzeczy, które pozwalają programom robić coś więcej, zmienne. Zmienne to wspomniane przeze mnie juz takie "pudełka", w których możemy przechowywać różne wartości, takie jak liczby, tekst, czy inne dane. Jak sama nazwa wskazuje, wartość przechowywana w zmiennej może się zmieniać w trakcie działania programu.
Jest to niesamowicie użyteczna właściwość, ponieważ pozwala programom na dynamiczne reagowanie na różne sytuacje i dane wejściowe.
=== Przykład
Zastosowanie zmiennych w praktyce najlepiej zobrazować na przykładzie. Poniżej znajduje się prosty program, który pyta użytkownika o jego wiek, a następnie wyświetla go na ekranie.
#codly(languages: codly-languages, zebra-fill: none, display-icon: false, display-name: false)
```cs
using System;
int age;
Console.WriteLine("Enter your age:");
age = Convert.ToInt32(Console.ReadLine());
Console.WriteLine("You are " + age + " years old.");
int nextYearAge = age + 1;
Console.WriteLine("Next year, you will be " + nextYearAge + " years old.");
```
=== Wyjaśnienie
Przyjrzyjmy się, jak działa ten program krok po kroku.
1. `int age;` - Ta linia deklaruje zmienną o nazwie `age`, która jest typu `int` (liczba całkowita). Deklaracja zmiennej mówi kompilatorowi, że chcemy zarezerwować miejsce w pamięci na przechowywanie wartości tego typu.
2. `Console.WriteLine("Enter your age:");` - Ta linia wyświetla na ekranie prośbę o podanie wieku.
3. `age = Convert.ToInt32(Console.ReadLine());` - W tej linijce dzieje się sporo, ale spokojnie, zacznijmy od środka. `Console.ReadLine()` czeka az uzytkownik wpisze cokolwiek i nacisnie klawisz `Enter`. *Wazne*: cokolwiek zostanie wpisane, nasz program potraktuje to jako `string` (tekst). Nastepnie ten tekst wpisany przez uzytkownika trafia do metody `Convert.ToInt32()`, ta funkcja probuje "przerobic" nasz tekst na liczbe calkowita (int).
4. `Console.WriteLine("You are " + age + " years old.");` - Ta linia wyświetla na ekranie wiek użytkownika, korzystając z wartości przechowywanej w zmiennej `age`.
5. `int nextYearAge = age + 1;` - Ta linia deklaruje nową zmienną `nextYearAge`, która przechowuje wartość wieku użytkownika powiększoną o 1, co reprezentuje jego wiek w następnym roku.
6. `Console.WriteLine("Next year, you will be " + nextYearAge + " years old.");` - Ta linia wyświetla na ekranie wiek użytkownika w następnym roku, korzystając z wartości przechowywanej w zmiennej `nextYearAge`.
=== Ćwiczenia
1. Napisz program, który pyta użytkownika o jego imię i wyświetla powitanie z użyciem tego imienia.
2. Napisz program, który pyta użytkownika o dwie liczby całkowite i wyświetla ich sumę.
3. Napisz program, który pyta użytkownika o promień koła i oblicza oraz wyświetla jego pole powierzchni (użyj wzoru: pole = $pi * r^2, "gdzie" pi approx 3.14$).
== Instrukcje sterujące programem
W tym rozdziale omówię w jaki sposób jesteśmy w stanie sterować przepływem wykonania naszego programu. W programowaniu mamy dostępne kilka rodzai instrukcji sterujących. to instrukcje warunkowe, czyli wykonaj kod jeśli dany warunek jest spełniony. Instrukcje powtarzające się (pętle), czyli wykonaj dany fragment X razy. Instrukcje skoku, czyli bezpośrednio przejdź do danego miejsca. Z tych ostatnich generalnie nie powinno się korzystać, bo wprowadzają chaos i trudność analizy kodu później. No i na samym końcu mamy hybrydowe zestawienie, które łaczy skok z warunkami (switch, match).
=== Instrukcje warunkowe
Jak wspomniałem instrukcje warunkowe pozwalają wykonać kod, gdy pewny warunek jest spełniony. Jest to bardzo uzyteczne narzędzie, bez którego programy nie byłyby za ciekawe.
==== Przykład
```cs
int age = 20;
if (age >= 18)
{
Console.WriteLine("Gratuluję, jesteś pełnoletni!");
}
else
{
Console.WriteLine("Niestety, jesteś niepełnoletni.");
}
age = 25;
if (age >= 35)
{
Console.WriteLine("Mozesz byc prezydentem.");
}
else if (age >= 21)
{
Console.WriteLine("Mozesz byc posłem.");
}
else if (age >= 18)
{
Console.WriteLine("Możesz głosować.");
}
else
{
Console.WriteLine("Nie możesz byc prezydentem, posłem ani głosować.");
}
```
==== Rezultat
Wynik działania powyższego kodu to:
```
Gratuluję, jesteś pełnoletni!
Mozesz byc posłem.
```
==== Wyjaśnienie
Jak widać na przykładzie instrukcja warunkowa składa się z konstrukcji
```cs
if (warunek)
{
// kod
}
```
co oznacza, jeśli (if) warunek będzie prawdziwy to wykonaj kod, który jest między klamrami.
Natomiast, co jeśli mamy taka sytuację chcemy, by pewien kod wykonał się jeśli warunek jest spełniony, a jeśli nie (else) to inny fragment powinien się wykonać. Do tego właśnie słuzy konstrukcja
```cs
if (warunek)
{
// waurnek spelniony, wykonaj ten fragment
}
else
{
// w przeciwnym razie wykonaj to
}
```
W przykładzie przedstawiłem jeszcze jedna konstrukcje `if else`, która oznacza, jeśli warunek 1 nie został spełniony to sprawdź i wykonaj warunek 2. Mozemy jeszcze tak jak w przykłdzie dodać `else` na końcu przez co dostaniemy taka konstrukcję: sprawdź warunek 1, jeśli nie jest spełniony sprawdź warunek 2, jeśli warunek 1 i warunek 2 niespełnione to wykonaj kod od `else`.
Wazne, mozemy mieć wiele `if else` i one sprawdzane od góry do dołu.
==== Ćwiczenia
=== Pętle
==== Przykład
==== Wyjaśnienie
==== Ćwiczenia
=== Instrukcje skoku
==== Przykład
==== Wyjaśnienie
==== Ćwiczenia
== Funkcje
=== Przykład
=== Wyjaśnienie
=== Ćwiczenia
== Klasy i obiekty
=== Przykład
=== Wyjaśnienie
=== Ćwiczenia
== Dokumentacja kodu
=== Przykład
=== Wyjaśnienie
== Podsumowanie
= Pamięć
== Jak działa pamięć komputera
== Zarządzanie pamięcią
=== Manualne zarządzanie pamięcią
==== Przykład
==== Wyjaśnienie
==== Ćwiczenia
=== Automatyczne zarządzanie pamięcią (Garbage Collector)
==== Przykład
==== Wyjaśnienie
==== Ćwiczenia
=== Inny rodzaj zarządzania pamięcią (Borrow Checker)
==== Przykład
==== Wyjaśnienie
==== Ćwiczenia
== Zalety i wady różnych metod zarządzania pamięcią
== Podsumowanie
= Paradygmaty programowania
== Programowanie proceduralne
=== Przykład
=== Wyjaśnienie
=== Ćwiczenia
== Programowanie obiektowe
=== Przykład
=== Wyjaśnienie
=== Ćwiczenia
== Programowanie funkcyjne
=== Przykład
=== Wyjaśnienie
=== Ćwiczenia
= Programowanie obiektowe
== Dziedziczenie
=== Przykład
=== Wyjaśnienie
=== Ćwiczenia
== Polimorfizm
=== Przykład
=== Wyjaśnienie
=== Ćwiczenia
== Enkapsulacja
=== Przykład
=== Wyjaśnienie
=== Ćwiczenia
== Abstrakcja
=== Przykład
=== Wyjaśnienie
=== Ćwiczenia
= Podstawy alogrytmów i struktur danych
== Notacja dużego "O"
=== Przykład
=== Wyjaśnienie
=== Ćwiczenia
== Struktury danych
=== Tablice
==== Przykład
==== Wyjaśnienie
==== Ćwiczenia
=== Listy
==== Przykład
==== Wyjaśnienie
==== Ćwiczenia
=== Stosy
==== Przykład
==== Wyjaśnienie
==== Ćwiczenia
=== Kolejki
==== Przykład
==== Wyjaśnienie
==== Ćwiczenia
=== Drzewa
==== Przykład
==== Wyjaśnienie
==== Ćwiczenia
=== Grafy
==== Przykład
==== Wyjaśnienie
==== Ćwiczenia
=== Krotki
==== Przykład
==== Wyjaśnienie
==== Ćwiczenia
=== Słowniki (Mapy)
==== Przykład
==== Wyjaśnienie
==== Ćwiczenia
== Algorytmy sortowania
=== Sortowanie bąbelkowe
==== Przykład
==== Wyjaśnienie
==== Ćwiczenia
=== Sortowanie przez wybieranie
==== Przykład
==== Wyjaśnienie
==== Ćwiczenia
=== Szybkie sortowanie (Quicksort)
==== Przykład
==== Wyjaśnienie
==== Ćwiczenia
== Algorytmy wyszukiwania
=== Wyszukiwanie liniowe
==== Przykład
==== Wyjaśnienie
==== Ćwiczenia
=== Wyszukiwanie binarne
==== Przykład
==== Wyjaśnienie
==== Ćwiczenia
== Podsumowanie
= Programowanie współbieżne
== Wątki
=== Przykład
=== Wyjaśnienie
=== Ćwiczenia
== Procesy
=== Przykład
=== Wyjaśnienie
=== Ćwiczenia
== Asynchroniczność
=== Przykład
=== Wyjaśnienie
=== Ćwiczenia
== Synchronizacja
=== Przykład
=== Wyjaśnienie
=== Ćwiczenia
== Podsumowanie
= Programowanie zdarzeniowe (event-driven programming)
=== Przykład
=== Wyjaśnienie
=== Ćwiczenia
= Operacje na plikach
=== Przykład
=== Wyjaśnienie
=== Ćwiczenia
= Dobre praktyki programistyczne
== Dokumentacja kodu
== DRY (Don't Repeat Yourself)
== KISS (Keep It Simple, Stupid)
== YAGNI (You Aren't Gonna Need It)
= Testowanie oprogramowania
== Przykład
== Wyjaśnienie
== Ćwiczenia
= Zaawansowane tematy
== Wzorce projektowe
=== Przykład
=== Wyjaśnienie
=== Ćwiczenia
== Metodyki tworzenia oprogramowania
=== Przykład
=== Wyjaśnienie
=== Ćwiczenia
== Refaktoryzacja kodu
=== Przykład
=== Wyjaśnienie
=== Ćwiczenia
== Optymalizacja kodu
=== Przykład
=== Wyjaśnienie
=== Ćwiczenia
== Kompozycja
=== Przykład
=== Wyjaśnienie
== Debugowanie
= Odpowiedzi do ćwiczeń