---
# System prepended metadata

title: Zagadnienia programistyczne na egzamin - opracowanie

---

# Zagadnienia programistyczne na egzamin - opracowanie

## Inżynieria oprogramowania

### 1. Przedstaw porównanie modelu Waterfall i dwóch innych, wybranych modeli wytwarzania oprogramowania.

##### Model Waterfall (proces kaskadowy)
W procesie wytwórczym zorganizowanym zgodnie z modelem kaskadowym (ang. `waterfall model`) zasadnicze działania są uporządkowane w sposób szeregowy, tworząc ciąg następujących po sobie faz, wykonywanych kolejno, jedna po drugiej, bez powracania do faz już wykonanych. Każda faza obejmuje określony rodzaj działań dotyczących całości tworzonego oprogramowania.

![](https://i.imgur.com/cXP7J1t.jpg)

Początkowa faza określenia wymagań ma na celu przygotowanie decyzji o rozpoczęciu budowy oprogramowania. Jeżeli oprogramowanie stanowi część większego systemu, to specyfikacja wymagań stawianych oprogramowaniu powstaje podczas projektowania systemu. Jeżeli nie, to specyfikacja wymagań musi być dostarczona przez zleceniodawcę oprogramowania lub wypracowana w wyniku działań marketingowych. Treść specyfikacji wymagań odzwierciedla na ogół biznesowy punkt widzenia i określa ogólnie usługi, jakich od oprogramowania oczekuje przyszły użytkownik.

Trzy następne fazy obejmują podstawowe czynności analizy, projektowania i implementacji programów. 

Kolejna faza, integracja i testowanie, ma na celu stopniowe łączenie poszczególnych jednostek programu, opracowanych w fazie implementacji w coraz większe moduły aż do poziomu całości oprogramowania. Każda jednostka, a następnie każdy powstający w procesie integracji moduł programu i w końcu całość. Oprogramowania są testowane celem sprawdzenia zgodności ich działania z projektem. Testowaniu towarzyszy usuwanie wszystkich znalezionych w programie defektów.

Wynikiem każdej fazy są odpowiednie dokumenty projektowe, które podlegają formalnej weryfikacji i zatwierdzeniu przez kierownictwo projektu. Zakończenie działań i zatwierdzenie wyników poprzedniej fazy jest niezbędnym warunkiem rozpoczęcia fazy następnej. Odbiór i wdrożenie oprogramowania następuje po zakończeniu fazy testowania akceptacyjnego, które dotyczy całego produktu i ma na celu ocenę zgodności jego działania ze specyfikacją wymagań. Po zakończeniu wdrożenia rozpoczyna się okres eksploatacji, związany z nieuchronną konserwacją wytworzonego oprogramowania.

Kaskadowy model procesu wytwórczego jest szeroko stosowany w praktyce. Do jego zalet należą jasne uporządkowanie pracy, brak powtarzania działań oraz wbudowany mechanizm weryfikacji wyników każdej fazy. Wadą jest bardzo późna ocena stopnia spełnienia wymagań użytkownika, następująca w tym modelu dopiero w fazie testowania akceptacyjnego - już po zbudowaniu i zintegrowaniu całości oprogramowania. Poprawienie ewentualnych błędów w tak późnej fazie procesu jest bardzo trudne i kosztowne. Do wad trzeba też zaliczyć niską odporność na zmiany wymagań następujące w trakcie trwania procesu. Ponieważ model kaskadowy nie przewiduje powracania do czynności wcześniejszych, nie ma w nim żadnego naturalnego miejsca na wprowadzenie ewentualnych zmian w już wykonanych działaniach. Konieczność dokonania takich zmian zaburza istotnie harmonogram całego procesu i staje się źródłem dodatkowych kosztów.

##### Proces iteracyjny
W procesie wytwórczym zorganizowanym zgodnie z modelem iteracyjnym (ang. `iterative model`) nie rozmieszcza się różnych rodzajów działań w różnych fazach procesu, lecz przewiduje iteracyjne powtarzanie wszystkich rodzajów działań - a przynajmniej elementów tych działań - w każdej fazie. Co więcej, wykonanie każdej fazy procesu może wymagać wykonania kilku następujących po sobie iteracji. W tak zorganizowanym procesie wytwórczym każda kolejna iteracja zwiększa wiedzę o wszystkich aspektach tworzonego oprogramowania i przyczynia się do lepszej oceny ryzyka wystąpienia zakłóceń. Podział procesu na fazy odzwierciedla tu kolejność podejmowania decyzji o angażowaniu coraz większych środków niezbędnych do zakończenia produkcji oprogramowania.

![](https://i.imgur.com/ZWuHwtd.jpg)


##### Proces zwinny
W procesie zwinnym (ang. `agile process`), zwanym też lekkim, odrzuca się całościowy sposób postrzegania i modelowania przyszłych rozwiązań na rzecz szybkiej implementacji bieżących wymagań i lokalnej optymalizacji w odpowiedzi na zmiany.
Skoro nie da się z góry przewidzieć całości wymagań i całości rozwiązania, które za chwilę mogą ulec zmianie, to nie warto tworzyć i dokumentować ich modeli. Zamiast tego trzeba szybko stworzyć produkt realizujący część potrzeb użytkownika, a polem rozwijać go dalej, uwzględniając przy tym zarówno zmiany, jak i nowe potrzeby.

![](https://i.imgur.com/HCIZ6KS.jpg)



### 2. Zademonstruj wykorzystanie asercji do przygotowania testu jednostkowego w JUnit.

Klasa do przetestowania:

```
public class Range {
    private final long lowerBound;
    private final long upperBound;

    public Range(long lowerBound, long upperBound) {
        this.lowerBound = lowerBound;
        this.upperBound = upperBound;
    }

    public boolean isInRange(long number) {
        return number >= lowerBound && number <= upperBound;
    }
}
```

Test jednostkowy:

```
@Test
public void shouldSayThat15rIsInRange() {
    Range range = new Range(10, 20);
    Assert.assertTrue(range.isInRange(15));
}
```

### 3. Porównaj główne paradygmaty programowania strukturalnego i obiektowego.

#### Programowanie strukturalne

-  Programowanie strukturalne jest paradygmatem programowania, zalecającym podział programu na moduły, komunikujące się poprzez dobrze określone interfejsy. Jest rozszerzeniem koncepcji programowania proceduralnego, zalecającego dzielenie kodu na procedury, wykonujące ściśle określone zadania. Procedury nie powinny korzystać z parametrów globalnych, ale przekazywać wszystkie potrzebne dane jako parametry do procedury.
-  Programowanie strukturalne wykorzystuje do rozwiązywania zadań dobrze określone struktury algorytmiczne: sekwencja, selekcja, iteracja i rekursja; unika natomiast stosowania instrukcji skoku.
-  Programowanie strukturalne oddzielnie definiuje dane, oddzielnie funkcje.

### Programowanie obiektowe

-  Programowanie obiektowe wprowadza pojęcie obiektu, w którym dane i procedury są ze sobą ściśle powiązane. Program obiektowy korzysta z obiektów, komunikujących się ze sobą w celu wykonania określonych zadań.
-  Cechy typowe dla programowania obiektowego
    -   abstrakcja - zredukowanie właściwości opisywanego obiektu do najbardziej podstawowych,
    -   hermetyzacja danych - dostęp do składowych jest ograniczony za pomocą dobrze określonego interfejsu,
    -   dziedziczenie - mechanizm umożliwiający wywodzenie nowych klas z klas już istniejących, wraz z przejmowaniem ich metod,
    -   polimorfizm - wielopostaciowość, pozwala na wybór metody spośród różnych wersji w zależności od kontekstu.


### 4. Przedstaw wady i zalety różnych metod testowania (wstępujące/zstępujące, blackbox/glass-box).

##### blackbox/glass-box

Metoda białej (szklanej) skrzynki (ang `white box testing`) umożliwia dokładne przetestowanie wszystkich składników oprogramowania oraz ułatwia znalezienie i usunięcie błędów.
Oparcie budowy testów na budowie programu oznacza jednak oparcie się na czymś, co być może jest błędne. To z kolei może uniemożliwić odnalezienie wszystkich defektów - np. jeżeli pewnych funkcji w programie nie ma, to bazując tylko na strukturze programu, nie będzie można tego braku odnaleźć. 
Metoda czarnej skrzynki (ang. `black box testing`) jest natomiast odpowiednia do testowania przez użytkownika, który ani nie zna, ani nie chce znać wewnętrznej struktury programu. Celem użytkownika jest upewnienie się, czy program wykonuje potrzebne mu funkcje i czy robi to
wystarczająco dobrze. Znaczenie może mieć także sprawdzenie, jak zachowuje się program obsługiwany niezgodnie z intencjami użytkownika.

##### wstępujące/zstępujące
Stopniową integrację programu można prowadzić w sposób wstępujący lub zstępujący. Strategia wstępująca zaczyna od łączenia jednostek najniższego poziomu w większe komponenty, te w jeszcze większe aż do osiągnięcia poziomu całego programu. Zaletą takiego podejścia jest zredukowanie liczby koniecznych do opracowania namiastek - w każdym kroku zarówno testowany komponent, jak i wszystkie wywoływane przez niego podprogramy są już dostępne. Wadą jest brak dostępności interfejsu użytkownika, ulokowanego często w głównej części programu, w początkowych krokach integracji. Strategia zstępująca przebiega w odwrotnym kierunku. Do programu głównego dołącza się podprogramy wywoływane, aż do poziomu najprostszych jednostek. Ta strategia wymaga opracowania większej liczby namiastek, ale w zamian zapewnia dostęp do interfejsu użytkownika od samego początku.

### 5. Wymień podstawowe metody optymalizacji czasowej i pamięciowej oprogramowania.

1. **Zwijanie**: Polega na wykonywaniu pewnych czynności na etapie kompilacji, a nie na etapie wykonywania programu. Takie czynności to np. inicjalizowanie zmiennych oraz wyliczanie wartości wyrażeń, w których występują tylko stałe.
2. **Zmniejszanie siły operacji**: Metoda ta polega na zastępowaniu operacji czasochłonnych operacjami szybszymi, np. zamiast potęgowania używać mnożenia (np. `a^3` zastąpić przez `a*a*a`), zamiast mnożenia używać dodawania (np. `a*2` zastąpić przez `a+a`).
3. **Usuwanie wyrażeń nadmiarowych**: W metodzie tej powtarzające się wyrażenia lub podwyrażenia oblicza się tylko raz, ich wartość przypisuje się zmiennej, a następnie odwołuje się do wartości tej zmiennej. Przykładowo, ciąg instrukcji:
    ``` turbo pascal
    x := sqrt(z) / sin(y) + 16;
    w := sqrt(z);
    u := fun (sqrt(z) / sin(y));
    ```
    zastępujemy przez:
    ``` turbo pascal
    t1 := sqrt(z);
    t2 := t1 / sin(y);
    x := t2 + 16;
    w := t1;
    u := fun (t2);
    ```
4. **Usuwanie wyrażeń niezmienniczych**: Metoda ta polega na jednokrotnym wyliczeniu przed pętlą wartości wyrażenia, którego wartość nie zależy od zmiennej sterującej, ani od innych zmiennych modyfikowanych w pętli. Przykładowo konstrukcję:
    ``` turbo pascal
    for i := 1 to 100 do
    a[i] := sin(y) * a[i];
    ```
    zastępujemy przez:
    ``` turbo pascal
    t1 := sin(y);
    for i := 1 to 100 do
    a[i] := t1 * a[i];
    ```
5. **Rozszczepianie**: Metoda ta stosowana jest w przypadku, gdy wewnątrz pętli występuje instrukcja warunkowa, a wartość warunku nie zależy od zmiennej sterującej pętli. Przykładowo konstrukcję:
    ``` turbo pascal
    for i := 1 to 100 do
    if dodaj 
    then a := a + n
    else a := a - n;
    ```
    zastępujemy przez:
    ``` turbo pascal
    if dodaj then
    for i := 1 to 100 do
    a := a + n
    else
    for i := 1 to 100 do
    a := a - n
    ```
6. **Rozwijanie pętli**: Rozwijanie polega na zwiększeniu kroku pętli w celu zmniejszenia liczby sprawdzeń warunku zakończenia pętli. Przykładowo ciąg instrukcji:
    ``` turbo pascal
    i := 0;
    while i < 300 do
    begin
    a[i] := 0;
    i := i + 1;
    end;
    ```
    zastępujemy przez:
    ``` turbo pascal
    i := 0;
    while i < 300 do
    begin
    a[i] := 0;
    a[i+1] := 0;
    a[i+2] := 0;
    i := i + 3;
    ```
7. **Łączenie pętli**: Przykładowo ciąg instrukcji:
    ``` turbo pascal
    for i := 1 to 100 do
    a[i] := 0;
    for i := 1 to 100 do
    b[i] := 0;
    ```
    zastępujemy przez:
    ``` turbo pascal
    for i := 1 to 100 do
    begin
    a[i] := 0;
    b[i] := 0;
    end;
    ```

Niemal każda z opisanych metod optymalizacji może ograniczyć zrozumiałość programu, a nawet pogorszyć jakość konstrukcji całego programu. Do skrajnych przykładów należy podyktowana względami sprawnościowymi redukcja zmiennych lokalnych na rzecz zmiennych globalnych. Najlepszym rozwiązaniem jest stosowanie nowoczesnych kompilatorów optymalizujących, które wprowadzają wiele spośród opisanych metod optymalizacji w sposób automatyczny, reorganizując wynikowy kod programu. Chroni to nie tylko przed pomyłkowym lub wynikającym z braku doświadczenia nieuwzględnieniem możliwości optymalizacji kodu. Programista może też świadomie unikać pewnych optymalizacji, zachowując większą przejrzystość kodu programu i jednocześnie wiedząc, że stosowna optymalizacja zostanie wprowadzona automatycznie przez kompilator.

Należy zauważyć, że bardzo często znacznie lepszy skutek uzyskujemy stosując wydajniejszy algorytm (np. zmniejszanie liczby iteracji), podczas gdy drobne usprawnienia, jak usuwanie wyrażeń niezmienniczych czy rozwijanie pętli, mają mniejsze znaczenie dla sprawności programu. 


### 6. Zdefiniuj pojęcie wzorca projektowego. Przedstaw implementację wzorca Singleton w wybranym języku programowania.

#### Definicja

Wzorzec projektowy to schemat uniwersalnego rozwiązania programistycznego, opisujący często pojawiające się i powtarzalne problemy projektowe.

To abstrakcyjny opis rozwiązań programistycznych. Opisuje rozwiązanie problemu, który często występuje podczas tworzenia oprogramowania. Pokazuje zależności pomiędzy klasami i obiektami oraz korzyści wynikające z tych zależności. Przedstawia sposoby projektowania oraz zachowanie poszczególnych klas i obiektów.  Celem stosowania wzorców projektowych jest ułatwienie tworzenia, a następnie modyfikacji i utrzymania kodu źródłowego.

#### Implementacja wzorca Singleton w Java:

``` java
public final class Singleton {
    private static Singleton instance;
    public String value;

    private Singleton(String value) {
        // The following code emulates slow initialization.
        try {
            Thread.sleep(1000);
        } catch (InterruptedException ex) {
            ex.printStackTrace();
        }
        this.value = value;
    }

    public static Singleton getInstance(String value) {
        if (instance == null) {
            instance = new Singleton(value);
        }
        return instance;
    }
}
```

### 7. Przedstaw różnice w projektowaniu interakcji człowiek-komputer dla urządzeń mobilnych i komputerów stacjonarnych.

- Ekran komputera stacjonarnego może pomieścić dużo więcej elementów, niż ekran urządzenia mobilnego, zatem sposób prezentowania treści powinien być różny. Na ekranie komputera elementy interfejsu mogą sąsiadować ze sobą w poziomie, gdyż jest na to miejce. Taki układ na urządzeniu mobilnym wymagałby od użytkownika przybliżania/oddalania strony i przesuwania (*scrollowania*) elementów w poziomie, czego należy unikać, gdyż jest to bardzo niewygodne. Te same elementy na urządzeniu mobilnym lepiej umieścić jeden pod drugim, a użytkownik przewijałby zawartość strony w poziomie. Takie rozwiązanie również ma wady - użytkownik, przewijając górną część strony, nie wie, jakie elementy znajdują się na samym dole. Często może stracić zainteresowanie zawartością strony, zanim zobaczy elementy na dole. Dlatego treści prezentowane na urządzeniach mobilnych powinny być ograniczone jedynie do tych najbardziej niezbędnych, a pozostałe treści mogą być dostępne po kliknięciu na pewien element interfejsu.
- Dla komputerów stacjonarnych istnieje możliwość użycia tzw zdarzenia *hover*, czyli obsługi najechania kursorem na wybrany element interfejsu. Zwykle wyświetlana jest wtedy informacja dotycząca wskazanego elementu, tzw. *tooltip*. Ta akcja nie jest przyjazna do wykorzystania przez użytkownika na urządzeniach mobilnych (wywoływana jest przy przytrzymaniu dotyku na ekranie, wraz z natywną akcją przeglądarki). Zatem należy unikać umieszczania jako *hover* istotnych elementów obsługi aplikacji.
- Na urządzeniu mobilnym możliwe jest niemal jednoczesne kliknięcie wielu elementów interfejsu, wieloma palcami naraz. Jest to wykorzystywane na przykład do obsługi powiększania na mapie. Ta akcja nie jest możliwa na komputerze, gdyż kursor jest jeden. Wtedy przybliżanie mapy może się odbywać przy wykorzystaniu kółka myszy i klawisza CTRL.


### 8. Na wybranym przykładzie omów zasady projektowania diagramów klas w notacji *UML*.

#### Zasady dotyczące klas

![](https://i.imgur.com/3c8vay0.png)

Klasa reprezentowana jest przez prostokąt podzielony na kilka części. W pierwszej z nich znajduje się nazwa klasy. W przykładzie jest to `Customer`. Następna sekcja zawiera atrybuty, kolejna metody.

Elementy, które są podkreślone oznaczają elementy statyczne. Na przykład atrybut `DEFAULT_PROMO_CODE` jest statycznym atrybutem klasy. Elementy pisane kursywą są abstrakcyjne (może dotyczyć także samej klasy), na przykład metoda `fetchPromoCode` jest abstrakcyjna.

Zarówno atrybuty jak i operacje mogą być poprzedzone symbolem. Dopuszczalne są między innymi:

- `+` element publiczny
- `#` element „chroniony” (może odpowiadać protected w języku Java)
- `-` element prywatny

Klasa w przykładzie ma cztery atrybuty. Trzy atrybuty instancji i jeden atrybut klasy (statyczny). Atrybuty zapisywane są w formacie nazwa:typ. Ta sama klasa ma trzy metody. Prywatną metoda `modifyOrderStats` i dwie metody publiczne. Metody mogą mieć określone typy parametrów i wartości zwracane.

W podobny sposób oznacza się interfejs. W odróżnieniu od klasy zawiera on tak zwany stereotyp `«interface»`. Na diagramie powyżej `NotificationPipe` jest interfejsem zawierającym dwie metody. Symbole określające dostępność metod są w tym przypadku pominięte.

#### Zasady dotyczące relacji między klasami

Pomiędzy klasami mogą występować relacje. Przykładem relacji jest dziedziczenie. Relacje reprezentowane są przez różne symbole. Na rysunku poniżej znajdują się możliwe relacje:

![](https://i.imgur.com/2BOGUvE.png)

Dla lewej kolumny:
- Pierwsza przerywana strzałka reprezentuje implementację. Jest używana do tego żeby pokazać jaki interfejs jest implementowany przez klasę.
- Druga oznacza dziedziczenie. W tym przypadku grot wskazuje klasę nadrzędną.

W prawej kolumnie znajdują się strzałki pokazujące relacje pomiędzy klasami inne niż implementacja czy dziedziczenie. Są posegregowane rosnąco według tego, jak silne są relacje przez nie opisywane.

Relacje ze strzałkami mogą być jednokierunkowe albo dwukierunkowe. W przypadku relacji jednokierunkowej strona bez grota używa strony, na którą pokazuje grot. W przypadku braku grota relacja jest dwukierunkowa. Trochę inaczej sprawa wygląda z rombami. Opiszę to jak poznasz rodzaje relacji.

- Najsłabszą relacją pomiędzy klasami jest zależność. Reprezentowana jest przez przerywaną linię. Zależność oznacza, że jedna klasa w pewnym momencie używa innej, na przykład jako parametr, czy wartość zwracana metody. W przypadku zależności klasa, od której zależymy nie jest zapisana jako atrybut. Przykładem zależności w bibliotece standardowej Javy może być zależność `Integer` od `String`, widać ją na przykład w metodzie `Integer.valueOf(String)`.
- Kolejnym rodzajem relacji jest asocjacja. W tym przypadku jest to zapis, który może zastąpić atrybut klasy – jeśli nie chcesz dodawać atrybut w prostokącie reprezentującym klasę, możesz użyć asocjacji. Przykładem asocjacji w bibliotece standardowej Javy może być `FileInputStream` i String. Klasa `FileInputStream` posiada atrybut typu `String` reprezentujący ścieżkę do pliku.
- Kolejną relacją jest agregacja. Agregacja wprowadza w relacji stronę, która jest „właścicielem”. Jedna klasa agreguje inną. Relacja tego typu oznaczona jest przez ciągłą linię z pustym rombem po stronie właściciela. W bibliotece standardowej tego typu relacja występuje pomiędzy `ArrayList` a klasą, której instancje przechowuje.
- Ostatnią relacją jest kompozycja. Kompozycja jest bardzo podobna do agregacji. Jest między nimi jedna znacząca różnica. W przypadku kompozycji „właściciel” jest odpowiedzialny za tworzenie (cykl życia) elementów, które grupuje. Przykładem kompozycji w bibliotece standardowej Javy może być implementacja `HashMap`, która zarządza elementami w kolekcji opakowując je w instancje `HashMap.Node`, które tworzy.

Na poniższym diagramie znajdują się przykładowe relacje pomiędzy klasami. Dla czytelności, atrybuty i operacje są pominięte.

![](https://i.imgur.com/H5EtIvZ.png)

- klasa `LargeItem` implementuje interfejs `Item` – implementacja,
- klasy `VIP` i `OrdinaryCustomer` dziedziczą po klasie abstrakcyjnej `Customer` – dziedziczenie,
- klasa `OrderCalculator` używa klasy `Basket` – zależność,
- klasa `Basket` wie o kliencie z którym jest powiązana (klasie `Customer`), odwrotne stwierdzenie także jest prawdziwe – asocjacja,
- klasa `Basket` może zawierać wiele instancji klasy `Item` – agregacja,
- klasa `VIP` zawiera wiele instancji klasy `BonusCode` i zarządza ich cyklem życia – kompozycja.

Strzałeczka oznacza kierunek relacji. Na przykład asocjacja pomiędzy `ItemBundle` a `Item` jest jednokierunkowa. `ItemBundle` wie o powiązanej klasie `Item`, `Item` zaś nie wie nic o `ItemBundle`. Jeśli strzałeczka nie jest umieszczona, oznacza to, że relacja jest dwukierunkowa – można „przejść” z jednej klasy do drugiej w obu kierunkach.

Trochę inaczej wygląda sprawa w relacjach agregacji i kompozycji. W tym przypadku romby oznaczają stronę, która agreguje drugą stronę relacji. Na powyższym przykładzie klasa `VIP` zarządza cyklem życia `BonusCode`. `BonusCode` nic nie wie o klasie `VIP`.

Nowością może być także komentarz do relacji (contains), który może ją opisywać. Nowe są także oznaczenia pokazujące liczność. W powyższym przykładzie jeden koszyk może zawierać wiele elementów (`0..*`).

#### Zasady dotyczące relacji dwukierunkowych

Dwukierunkowa relacja zależności została już pokazana wyżej, pomiędzy klasami `Customer` i `Basket`. W praktyce występują też bardziej zagmatwane przypadki. Przykłądowo, klasa reprezentująca książkę – `Book`. Książka ma autora – `Author`. Jedna książka może być napisana przez wielu autorów, a jeden autor może napisać wiele książek. To klasyczna relacja „wiele do wielu”.

Często tego typu relacje wprowadzają nową klasę, która reprezentuje samą relację. W tym przypadku byłoby to autorstwo – `Authorship`. Poniższy diagram pokazuje przykładowe sposoby przedstawienia sytuacji tego typu na diagramie UML.

![](https://i.imgur.com/p6LjEDK.png)

W pierwszym przypadku `Author` przechowuje kolekcję `Book` i zarządza ich cyklem życia. `Book` wie o liście swoich autorów.

W drugim przypadku `Author` przechowuje kolekcję swoich *„autorstw”*. Podobną kolekcję przechowuje także `Book`.

### 9. Porównaj metodyki Scrum i Kanban.

- Podstawą dla Scruma jest dzielenie zasobów na małe zespoły oraz dzielenie czasu i pracy na iteracje. Natomiast podstawą dla Kanbana jest podział i wizualizacja zadań pod kątem etapu prac (jak tablica w Trello) oraz limit ilości zadań będących w trakcie implementacji
- Obie z tych metodyk zakładają rozbijanie dużych zadań na mniejsze partie
- Scrum narzuca dzielenie pracy na iteracje, co nie występuje w przypadku Kanban. Dzięki temu Kanban daje większą dowolność planowania
- Scrum narzuca określanie osobom ról (Product Owner, Scrum Master, Zespół), co nie występuje w przypadku Kanban. Dzięki temu w Kanban przykładowo osoba będąca product ownerem może dokonywać zmian w tablicy zadań

## Języki skryptowe

### 1. Podaj przykłady prostych skryptów dla systemu Windows, typu zmiana nazwy okna, wyświetlenie pełnej struktury plików z katalogu, etc.

Zmiana nazwy okna:
```
title Nowe okno
```

Wyświetlenie pełnej struktury plików z katalogu (rozumiem, że drzewiastej):
```
tree "Nowy folder"
```

Wyświetlenie szczegółowej listy plików oraz podkatalogów z katalogu:
```
dir "Nowy folder"
```

Utworzenie nowego katalogu:
```
mkdir "Nowy folder 2"
```

Wyświetlenie dowolnego napisu:
```
echo Dowolny napis
```

Wyczyszczenie wcześniejszych napisów wyświetlanych w konsoli:
```
cls
```

Zmiana koloru tekstu w konsoli na jasnożółty:
```
color e
```

Wyświetlenie listy podstawowych komand wraz z którkim opisem każdej:
```
help
```

### 2. Scharakteryzuj automatyzację procesów zarządzania systemem poprzez skrypty: jakie są założenia i jakie wymogi powinien spełniać skrypt.

Założenia:

Celem tworzenia skryptów jest zautomatyzowanie wykonywania powtarzających się lub sekwencyjnych czynności, dzięki czemu wzrasta produktywność.

Wymogi:

- Skrypt powinien znajdować się w pliku tekstowym o rozszerzeniu `.bat`
- Skrypt powinien zawierać w osobnych liniach komendy do wykonania
- Skrypt dla systemu *Windows* powinien być uruchamiany przy użyciu powłoki *cmd.exe*

### 3. Omów zagadnienie kopiowania plików, zmiany nazwy, datowania i przesiewu informacji.

#### Kopiowanie plików:

Skopiowanie pojedynczego pliku:
``` cmd
xcopy C:\Folder1\Plik.txt C:\FolderDocelowy
```

Skopiowanie całego katalogu - kopiuje całą zawartość katalogu do innego katalogu, pomija puste katalogi, działa dla Windowsa Visty lub nowszego, działa lepiej od `xcopy` (bo wielowątkowo):
``` cmd
robocopy C:\FolderZrodlowy C:\FolderDocelowy
```

#### Zmiana nazwy pliku:

Zmiana nazwy pojedynczego pliku:
``` cmd
ren C:\Folder1\Plik.txt Plik2.txt
```

Grupowa zmiana nazw plików (w tym przypadku zmiana rozszerzeń):
``` cmd
ren C:\Folder1\*.txt *.json
```

#### Datowanie i przesiew informacji:

Jeśli chodzi o zmiany dat utworzenia/modyfikacji istniejącego pliku, to da się to zrobić w *PowerShell*u, ale natywnie nie znalazłem żadnego takiego przykładu, a i nie było go na zajęciach. Pewnie w treści zagadnienia chodzi o sprawdzanie daty przy odczycie plików.

Do przesiewu informacji przyda się `forfiles` (dostępne od Visty wzwyż)

Wyświetlenie wszystkich plików i folderów modyfikowanych w ciągu ostatnich 5 dni w bieżącym katalogu:
``` cmd
forfiles /D +5
```

Uwzględnienie również podfolderów:
``` cmd
forfiles /S /D +5
```

Ze wskazaniem konkretnej ścieżki:
``` cmd
forfiles /P C:\Folder1 /S /D +5
```

Wyświetlanie NIEmodyfikowanych plików:
``` cmd
forfiles /S /D -5
```

Użycie konkretnej daty (wyświetlanie plików modyfikowanych najwcześniej w tym dniu):
``` cmd
forfiles /S /D +08/06/2013
```

Wykonanie operacji na wybranych plikach (w tym przypadku usunięcie plików starszych niż rok):
``` cmd
forfiles /D +5 /C "cmd /C del @file"
```


### 4. Scharakteryzuj przeszukiwanie tekstu i danych poprzez skrypty.

W języku *batch* przeszukiwanie tekstu można realizować dzięki komendzie `findstr`.

Szukanie frazy w wybranym pliku tekstowym (zwraca wszystkie linie tekstu zawierające szukaną frazę):
``` cmd
findstr "szukana fraza" przeszukiwany-plik.txt
```

Można podać wiele plików do przeszukania (zwraca wyniki w postaci `nazwa-pliku:linijka-z-frazą`)
``` cmd
findstr "szukana fraza" przeszukiwany-plik-1.txt przeszukiwany-plik-2.txt
```

Szukanie frazy we wszystkich plikach `.txt` w bieżącym katalogu (zwraca wyniki w postaci `nazwa-pliku:linijka-z-frazą`):
``` cmd
findstr "szukana fraza" *.txt
```

Wypisanie tylko nazw plików, w których znaleziono podaną frazę:
``` cmd
findstr /m "szukana fraza" *.txt
```

### 5. Omów budowę systemów raportująco-analizujących w oparciu o skrypty.

System raportująco-analizujący musi składać się z części zbierającej dane o systemie oraz części przesyłającej wynik analizy lub zapisującej go do pliku. Skryptowe systemy raportująco-analizujące nie wprowadzają zmian podczas wykonywania (oprócz ewentualnego stworzenia pliku z raportem), korzystają jedynie z dostępnych komend odczytujących aktualny stan systemu operacyjnego/plików. Wykonywana analiza dotyczy zwykle aktualnego stanu systemu operacyjnego/plików, np. zliczając ilość plików i ich rozmiar, zapisując obciążenie systemu w trakcie wykonywania analizy w postaci listy wykonywanych procesów i ich zużycia zasobów czy zapisując konfigurację sprzętową w czasie wykonywania analizy. System raportująco-analizujący może generować raporty cząstkowe np. dziennie i grupować je w późniejszym okresie, np. generując raporty tygodniowe, z analizy raportów tygodniowych tworzyć raporty miesięczne itd.
Raport może zostać zapisany w przeznaczonej na raporty ścieżce lub zostać automatycznie przesłany np. wiadomością email do docelowych odbiorców raportu.
**Przykłady Batch**
Komenda zliczająca pliki w danym folderze: dir (listuje wszystkie pliki w katalogu, ale ostatnie dwie linie zawierają liczbę plików i ich rozmiar, oraz liczbę folderów i ich rozmiar.
Do rekurencyjnej analizy trzeba skorzystać z połączenia dir i find: dir wylistuje rekurencyjnie zawartość katalogów, a find zliczy ilość wystąpień adresów katalogów dir /a:d /s /b “Folder Path” | find /c “:\”.
Zapis treści do pliku: echo “treść” > ścieżka\nazwa_pliku.txt, dopisanie treści do pliku: echo “treść” >> ścieżka\nazwa_pliku.txt. 
Wysłanie email: START mailto:%adresEmail%?subject=”tytuł”&body=”treść”, komenda zostanie obsłużona przez domyślny program do wysyłki maili.
**Przykłady Bash**
Komenda zliczająca pliki w danym folderze i podfolderach: find i wc (find listuje wszystkie pliki w katalogu i podkatalogach, a wc zlicza ilość linii, generując tym samym liczbę znalezionych plików).
find ścieżka | wc -l, parametr -l spowoduje wypisanie ilości linii, domyślnie wc wypisuje też ilość znaków.
Zapis treści do pliku: echo “treść” > ścieżka/nazwa_pliku.txt, dopisanie treści do pliku: echo “treść” >> ścieżka/nazwa_pliku.txt. 
Wysłanie email: echo "treść" | mail -s "tytuł" "adres@email.com".


### 6. Scharakteryzuj pętlę w języku Python, warunek, operacje arytmetyczne i logiczne.

##### Pętla for 
Przykład:
``` python
pierwsze = [2,3,5,7]
for pierwsza in pierwsze:
    print pierwsza
```
Pętlę for możemy zrealizować z użyciem funkcji range lub xrange. Różnica między nimi polega na tym, że range zwraca nową tablicę liczb zawartych w określonym przedziale, natomiast xrange zwraca pojedyńczą liczbę, co czyni ją w pętlach bardziej wydajną. Python 3 używa funkcji range, która działa jak xrange. Funkcja xrange zwracając liczby zaczyna od 0.

``` python
# Wypisze liczby 0 1 2 3 4
for x in xrange(5):
    print x

# Wypisze 3 4 5
for x in xrange(3,6):
    print x
```

##### Instrukcje warunkowe

Python posiada specjalny typ danych logicznych, który jest używany w instrukcjach warunkowych i pętlach. Wartości logiczne True albo False są najczęściej zwracane, kiedy porównujemy ze sobą dwie wartości.
``` python
x = 2
print x == 2 # wypisze True
print x != 2 # wypisze False
print x == 3 # wypisze False
print x < 3  # wypisze True
```

##### Operatory logiczne
Operatory logiczne and i or (pol. "i" i "lub") pozwalają na budowanie kompletnych zdań logicznych.
Za pomocą operatora "in" możesz sprawdzić, czy konkretny obiekt znajduje sie w tablicy lub innym obiekcie gromadzącym inne obiekty.
W przeciwieństwie do ==, operator is nie sprawdza, czy zmienne mają taką samą wartość, ale czy wskazują na ten sam obszar w pamięci komputera.
Używając not przed wyrażeniem logicznym zmieniamy jego wartość na przeciwną.

##### Operatory arytmetyczne
Tak jak w innych językach programowania dostępne są operatory dodawania, odejmowania, mnożenia i dzielenia na liczbach.
Innym dostępnym operatorem jest modulo (%), który zwraca resztę z dzielenia (dzielna % dzielnik = reszta).

### 7. Omów kolekcje danych w języku Python (tabela, lista, krotka etc.): czym się różnią a jakie mają podobieństwa.

##### Lista
Lista jest najbardziej elastycznym typem obiektu uporządkowanej kolekcji.
Może zawierać różne typy danych - liczby, łańcuchy znaków, a nawet inne listy.

Tworzy się ją następująco:
``` python
lista = ["element 1" , "element 2" , "element 3"]
```

Można też utworzyć pustą listę:
`pusta = []`
Do poszczególnych elementów listy można się dostać przez indeks. Elementy listy są numerowane od zera.
Listy są typem modyfikowalnym, w związku z czym można dowolnie zmieniać ich elementy.
Listy nie mają z góry ustalonej wielkości.

##### Krotka (tuple)
Krotka jest ogólnie rzecz biorąc listą, której nie da się zmodyfikować. Obsługują one dowolne typy danych, zagnieżdżanie i zwykłe operacje na sekwencjach.
Krotki można konkatenować, konwertować na listę.

##### Słownik
Słowniki są bardzo podobne do list, z tą różnicą, że do poszczególnych elementów dostaje się za pomocą klucza zamiast indeksu. Klucz może być
stringiem lub liczbą.

``` python
d = { 'key1' : 1 , 'key2' : 2 , 'key3' : 3}
```

W słownikach wystepują pary klucz - wartość. Bardzo dobrze się sprawdzają
np. dla zbioru numerów telefonów, gdzie trzeba przyporządkować numer
do nazwiska lub przy stronach logowania, gdzie trzeba sparować nazwy
użytkowników z emailami. Do słowników można wstawiać listy.
Dostęp do wartości w słowniku po kluczu odbywa się tak samo jak dostęp
do wartości z listy po indeksie.
``` python
d ['key1'] # otrzymanie wartosci 1
```
Słowniki tak jak listy są modyfikowalne.
``` python
moj_slownik [nowy_klucz] = nowa_wartosc
```


### 8. Scharakteryzuj zagadnienie danych zewnętrznych i operacji na plikach w języku Python.

#### Dane zewnętrzne

Dane zewnętrzne - dane zlokalizowane np. w pliku, bazie danych, na serwerze.

#### Operacje na plikach

##### Odczyt danych z pliku

Metoda 1:

1. Otwarcie pliku, przykładowo: `plik = open('ścieżka/do/pliku.txt')`, ścieżka może być bezwzględna (cała ścieżka do pliku) lub może to być ścieżka z miejsca, w którym uruchomiony jest program
2. Odczyt danych z pliku, przykładowo: `print plik.read()` lub:
``` python
for linia in plik:
    print linia
```
3. Zamknięcie pliku: `plik.close()`, ponieważ może się zdarzyć, że niezamknięty plik ulegnie uszkodzeniu lub będzie blokowany gdy inny program będzie chciał mieć do niego dostęp

Metoda 2:

``` python
with open('plik.txt') as plik:
    for linia in plik:
        print linia.strip().split()
```

Dzięki temu mówimy, że chcemy wykonać coś wraz z otwarciem pliku i nie musimy przejmować się jego zamknięciem. To rozwiazanie ma też swoje wady - kiedy chcemy otworzyć w ten sposób wiele plików na raz, wcięcia w tekście robią się okropnie duże i ciężko ich pilnować. Jednakże do otwierania pojedyńczego pliku jak najbardziej warto tego używać, bo jasno widać gdzie dzieje się coś z plikiem i gdzie kończy się z nim coś dziać.

##### Zapis danych do pliku

Nadpisanie zawartości pliku:

``` python
plik = open('plik.txt','w')
plik.write("Jakiś napis\n")
plik.close()
```

Dopisanie danych do pliku:

``` python
plik = open('plik.txt','a')
plik.write("Jakiś napis\n")
plik.close()
```

W obu przypadkach, jeśli plik nie istnieje, zostanie utworzony.


##### Zamiana fragmentu tekstu w pliku
``` python
zrodlo = open('nazwa_pliku').readlines()
cel = open('nazwa_pliku', 'w')
for s in zrodlo:
	cel.write(s.replace("co zamienić", "na co"))
cel.close()
```
Kod ten otwiera plik źródłowy a następnie iterujemy go wierszami i każdy wiersz poddajemy działaniu metody replace a następnie zapisujemy do pliku docelowego.

##### Pobieranie określonego wiersza z pliku
``` python
import linecache
wiersz = linecache.getline('NAZWA PLIKU', NUMER_WIERSZA)
print wiersz
```

##### Zliczanie wierszy w pliku
Na małe pliki wystarczy prosty kod:
```
count = len(open('plik', 'rU').readlines())
print count
```

Lecz w przypadku większych plików znacznie wydajniejsze będzie:
``` python
count = -1
for count, wiersz in enumerate(open('plik', 'rU')):
	pass
count += 1

print count
```

### 9. Wyjaśnij zagadnienie przesiewu danych w języku Python, analizy plików i wykorzystania możliwości operacji na danych tekstowych.

TODO: przesiew danych

TODO: analiza plików


Sprawdzanie długości tekstu:
``` python
napis = "AAA BBB ..."
print len(napis) # 11
```

Dzielenie tekstu na słowa:
``` python
text = "Jakiś tekst do podziału".split()
print(text) # ['Jakiś', 'tekst', 'do', 'podziału']
```

Wycinanie fragmentu tekstu:
``` python
tekst = "Jakiś tekst przykładowy"[6:11]
print(tekst) # 'tekst'
```

Odwracanie tekstu:
``` python
print("Jakiś tekst do odwrócenia"[::-1]) # 'ainecórwdo od tsket śikaJ'
```





### 10. Omów podstawy tworzenia skryptów obliczeniowych: jakie są typy danych, jakie są działania arytmetyczne, jakie mamy operatory, etc.

Opisane operacje dotyczą Pythona 3 (w Pythonie 2 inaczej działa np dzielenie).

Typy danych liczbowych:

- `int` - przechowuje liczby całkowite, dowolnej wielkości. Należy podawać bez kropki, przykładowo `4` jest typu `int`, ale `4.0` już nie.
- `float` - przechowuje liczby rzeczywiste. Należy podawać z kropką, przykładowo `4.0`. Można korzystać z notacji wykładniczej, np `62E+6`
- `complex` - przechowuje liczby zespolone. Zapisywane jako suma części rzeczywistej i części urojonej z literą `j`, przykładowo `0 + 2j` lub `complex(0, 2)`. Można uzyskać dostęp do wartości rzeczywistej i urojonej: `zmienna.real` oraz `zmienna.imag`.

Działania arytmetyczne:

- dodawanie, operator `+`, rezultatem jest wartość typu `int` lub `float`
- odejmowanie, operator `-`, rezultatem jest wartość typu `int` lub `float`
- mnożenie, operator `*`, rezultatem jest wartość typu `int` lub `float`
- dzielenie, operator `/`, rezultatem jest wartość typu `float`
- dzielenie całkowitoliczbowe, operator `//`, rezultatem jest wartość typu `int`
- modulo, operator `%`, rezultatem jest wartość typu `int` lub `float`
- potęgowanie, operator `**` lub metoda `pow`, rezultatem jest wartość typu `int` lub `float`
- pierwiastkowanie, metoda `sqrt` (wymaga importu biblioteki `math`)
- wartość bezwzględna, metoda `abs`, np. `abs(-2)`
- wartość maksymalna ze zbioru, metoda `max`, np. `max(1,8,-10,5)`
- wartość minimalna ze zbioru, metoda `min`, np. `min(1,8,-10,5)`
- zaokrąglanie, metoda `round`, np. `round(3.1415)`, `round(3.1415, 2)`, `round(123.456, -2)`
- inne operacje matematyczne z biblioteki `math`


Kolejność operacji jest zgodną z tradycyjną notacją matematyczną. Np. dodawanie i odejmowanie mają priorytet niższy niż operacje mnożenia i dzielenia, z kolei te mają priorytet niższy od potęgowania. Przy tym samym priorytecie, działania wykonywane są w kolejności czytania tekstu „od lewej do prawej”.

Python lepiej się sprawdza przy wykonywaniu operacji arytmetycznych, niż inne języki programowania, bo:
- Typy liczbowe nie mają limitów - można bez obaw wykonywać operacje na dużych liczbach wykorzystując zwykłe operatory. Przykładowo w Javie trzeba by użyć klasy `BigDecimal`, co jest uciążliwe
- Operator dzielenia zwraca wartość rzeczywistą, a nie całkowitą
- Dostępne są operatory `**`, `%` i `//`
- Są dostępne potężne biblioteki matematyczne *SciPy* i *NumPy* 

## Programowanie I

### 1. Scharakteryzuj zmienne lokalne/globalne/statyczne w języku c++.

#### Zmienne lokalne

Są dostępne tylko w ramach danego bloku. Po ukończeniu wykonywania bloku, ich wartości są tracone.

W poniższym przykładzie istnieją dwie zmienne lokalne o nazwie `a`, każda dostępna w ramach swojego bloku. Zmiana wartości jednej z nich nie wpływa na wartość drugiej.

```
void f(int a)
{
    a++;
    cout << a;
}

int main()
{
    int a = 1;
    f(a);      // drukuje 2
    cout << a; // drukuje 1
    return 0;
}
```

#### Zmienne globalne

Są dostępne w całym programie i przez cały czas jego działania

W poniższym przykładzie zmienna `a` jest globalna, więc jest dostępna w każdym bloku kodu.

```
int a = 1;

void f()
{
    a++;
}

int main()
{
    cout << a; // drukuje 1
    f();
    cout << a; // drukuje 2
    return 0;
}
```

#### Zmienne statyczne

Są dostępne w ramach danego bloku, jak zmienne lokalne, jednak ich wartosć nie jest tracona po ukończeniu wykonywania bloku.

W poniższym przykładzie zmienna `a` jest zmienną statyczną, a jej wartość jest pamiętana pomiędzy wieloma wykonaniami bloku.

```
void f()
{
    static int a = 0;
    a++;
    cout << a;
}

int main()
{
    f(); // drukuje 1
    f(); // drukuje 2
    f(); // drukuje 3
    return 0;
}
```

### 2. Omów funkcje w języku c++ - ich deklarowanie, definiowanie, wywoływanie. Porównaj przekazywanie argumentów do funkcji przez referencję oraz wartość.

#### Delkaracja funkcji

Polega na określeniu:
- nazwy funkcji
- ilości argumentów oraz ich typów
- zwracanego typu

Przykład:

```
int oblicz(int, int);
```

#### Definicja funkcji

Polega na określeniu:
- nazwy funkcji
- ilości argumentów, ich typów oraz ich nazw
- zwracanego typu
- ciała funkcji

Przykład:

```
int oblicz(int a, int b)
{
    return a + b;
}
```

#### Wywołanie funkcji

Polega na użyciu funkcji w celu uzyskania rezultatu jej działania. Należy podać wartości wymaganych argumentów. Zwracaną wartość można przypisać do zmiennej.

Przykład:
```
int z, a = 1;
z = oblicz(3, a);
```

#### Przekazywanie argumentów przez referencję

Przekazując argument w ten sposób, otrzymujemy dostęp do zmiennej przekazanej z zewnątrz. W poniższym przykładzie, wykorzystującym przekazywanie przez referencję, wartość zmiennej utworzonej poza funkcją `f` zostanie zmieniona.

```
void f(int &a)
{
    a++;
}

int main()
{
    int a = 1;
    f(a);
    cout << a; // drukuje 2
    return 0;
}
```

#### Przekazywanie argumentów przez wartość

Przekazując argument w ten sposób, jego wartość jest kopiowana, zatem jego zmiana wewnątrz funkcji nie spowoduje zmiany wartości zmiennej zdefiniowanej poza funkcją. Poniższy przykład wykorzystuje przekazywanie przez wartość.

```
void f(int a)
{
    a++;
}

int main()
{
    int a = 1;
    f(a);
    cout << a; // drukuje 1
    return 0;
}
```

### 3. Scharakteryzuj pojęcie wskaźnika w języku c++. Podaj przykład zastosowania rzutowania wskaźników.

Wskaźnik jest zmienną przechowującą adres innej zmiennej w pamięci systemu. Pomaga w kontrolowaniu wartości innych zmiennych.

Przykład:

```
int main()
{
    int *a, b; // utworzenie wskaźnika a i zwykłej zmiennej b
    b = 1;     // przypisanie wartości do zmiennej b
    a = &b;    // ustawienie, żeby wskaźnik a wskazywał na zmienną b
    *a = 2;    // zmiana wartości zmiennej b poprzez wskaźnik
    cout << *a;
    
    return 0;
}
```

TODO: rzutowanie wskaźników :confused:

### 4. Omów tablice jednowymiarowe – ich definiowanie i przekazywanie do funkcji w języku c++. Podaj ich związek ze wskaźnikami.

#### Deklarowanie

Statyczne:

```
typ_elementów nazwa_tablicy[ilość_elementów];
```

Dynamiczne:

```
typ_elementów* nazwa_tablicy;
```

#### Inicjalizowanie:

Statyczne:

```
nazwa_tablicy[indeks_elementu] = wartość elementu;
```

lub

```
nazwa_tablicy = {element1, element2, ..., elementN};
```

Dynamiczne:

```
nazwa_tablicy = new typ_elementów[ilość_elementów];
nazwa_tablicy[indeks_elementu] = wartość_elementu;
```

#### Zwalnianie pamięci (dla dynamicznej alokacji)

```
delete [] nazwa_tablicy; 
```

#### Przekazywanie do funkcji:

Przykład:

```
void f(int liczby[]) { }
```

```
void f(int liczby[3]) { }
```

```
void f(int *liczby) { }
```

Kompilator wszystkie omówione zapisy zinterpretuje jako wskaźnik. Tablice przekazywane do funkcji nigdy nie są kopiowane.

Nazwa tablicy jest wskaźnikiem na jej pierwszy element. Oznacza to, że możemy odnieść się to tego elementu za pomocą nazwy tablicy i indeksu o wartości 0 lub poprzez ten wskaźnik.

Przykład:

```
nazwa_tablicy[0];
*nazwa_tablicy;
```

Po elementach tablicy możemy poruszać się przy użyciu indeksów podając kolejne numery komórek począwszy od 0, jak również przeskakując kolejne komórki tablicy z wykorzystaniem wskaźnika.

### 5. Omów tablice dwuwymiarowe – ich definiowanie i przekazywanie do funkcji w języku c++. Podaj ich związek ze wskaźnikami.

#### Deklarowanie

Statyczne (dla dwówymiarowej tablicy):

```
typ_elementów nazwa_tablicy[ilość_wierszy][ilość_kolumn];
```

Dynamiczne (dla dwówymiarowej tablicy):

```
typ_elementów** nazwa_tablicy;
```

#### Inicjalizowanie:

Statyczne:

```
nazwa_tablicy[indeks_wierszu][indeks_kolumny] = wartość elementu;
```

lub

```
nazwa_tablicy = {
{element11, element12, ..., element1M},
{element21, element22, ..., element2M},
...,
{elementN1, elementN2, ..., elementNM}
};
```

Dynamiczne:

```
nazwa_tablicy = new typ_elementów*[ilość_wierszy];
nazwa_tablicy[indeks_wiersza] = new typ_elementów[ilość_kolumn];
nazwa_tablicy[indeks_wiersza][indeks_kolumny] = wartość_elementu;
```

#### Zwalnianie pamięci (dla dynamicznej alokacji)

```
delete [] nazwa_tablicy[indeks_wiersza];
delete [] *nazwa_tablicy; 
```

#### Przekazywanie do funkcji:

Przykład:

``` c++
void f(int liczby[][]) { }
```

``` c++
void f(int liczby[3][4]) { }
```

``` c++
void f(int **liczby) { }
```

Kompilator wszystkie omówione zapisy zinterpretuje jako wskaźnik. Tablice przekazywane do funkcji nigdy nie są kopiowane.

Nazwa tablicy jest wskaźnikiem na jej pierwszy element. Oznacza to, że możemy odnieść się to tego elementu za pomocą nazwy tablicy i indeksu o wartości 0 lub poprzez ten wskaźnik.

Przykład:

``` c++
nazwa_tablicy[0][1];
**nazwa_tablicy;
```

Po elementach tablicy możemy poruszać się przy użyciu indeksów podając kolejne numery komórek począwszy od 0, jak również przeskakując kolejne komórki tablicy z wykorzystaniem wskaźnika.

### 6. Czym jest wskaźnik do funkcji w języku c++. Podaj przykład ilustrujący.

Wskaźnik do funkcji w języku C++ jest zmienną, która przechowuje jej adres w pamięci.

Deklaracja wskaźnika do funkcji:
``` c++
int (*f)(int);
zwracany_typ (*nazwa_funkcji) (typ_argumentu1, ..., typ_argumentuN);
```

Pominięcie nawiasu skutkowałoby zwróceniem wskaźnika na typ `int`.

### 7. Wyjaśnij dynamiczną alokację pamięci w języku c++. Podaj przykład ilustrujący.

Dynamiczna alokacja pamięci w C / C ++ odnosi się do ręcznego przydzielania pamięci przez programistę. Pamięć przydzielana dynamicznie jest alokowana na stercie, a zmienne niestatyczne i lokalne są przydzielane na stosie.

#### Zastosowania:
- Jednym z zastosowań pamięci przydzielanej dynamicznie jest przydzielanie pamięci o zmiennej wielkości, co nie jest możliwe w przypadku pamięci przydzielonej przez kompilator, z wyjątkiem tablic o zmiennej długości.
- Najważniejszym zastosowaniem jest elastyczność zapewniana programistom. Możemy przydzielać i zwalniać pamięć, kiedy tylko tego potrzebujemy i kiedy już nie potrzebujemy. Jest wiele przypadków, w których ta elastyczność pomaga. Przykłady takich przypadków to Lista połączona , Drzewo itp.

W przypadku zwykłych zmiennych, takich jak ```int a```, ```char str [10]``` itp., Pamięć jest automatycznie przydzielana i zwalniana. W przypadku pamięci alokowanej dynamicznie, takiej jak ```int * p = new int [10]```, odpowiedzialność za zwolnienie pamięci, gdy nie jest już potrzebna, spoczywa na programistach. Jeśli programista nie zwalnia pamięci, powoduje to wyciek pamięci (pamięć nie jest zwalniana do zakończenia programu).

C używa funkcji `malloc()` i `calloc()` do dynamicznego przydzielania pamięci w czasie wykonywania i używa funkcji `free()` do zwalniania dynamicznie przydzielanej pamięci. C++ obsługuje te funkcje, a także ma dwa operatory `new` i `delete`, które wykonują zadanie przydzielania i zwalniania pamięci.

### 8. Omów koncepcję stosu, podaj implementację w języku c++.

Stosy są rodzajem adapterów kontenerów z typem pracy LIFO (Last In First Out), w których nowy element jest dodawany na jednym końcu, a (na górze) element jest usuwany tylko z tego końca.
 
#### Funkcje powiązane ze stosem to:
- `empty()` - zwraca, czy stos jest pusty
- `size()` - zwraca rozmiar stosu
- `top()` - zwraca referencję najwyższy element stosu
- `push(g)` - dodaje element `g` na szczycie stosu
- `pop()` - usuwa najwyższy element stosu

``` c++
/* C++ program to implement basic stack 
operations */
#include <bits/stdc++.h> 

using namespace std; 

#define MAX 1000 

class Stack { 
	int top; 

public: 
	int a[MAX]; // Maximum size of Stack 

	Stack() { top = -1; } 
	bool push(int x); 
	int pop(); 
	int peek(); 
	bool isEmpty(); 
}; 

bool Stack::push(int x) 
{ 
	if (top >= (MAX - 1)) { 
		return false; 
	} 
	else { 
		a[++top] = x; 
		return true; 
	} 
} 

int Stack::pop() 
{ 
	if (top < 0) { 
		return 0; 
	} 
	else { 
		int x = a[top--]; 
		return x; 
	} 
} 
int Stack::peek() 
{ 
	if (top < 0) { 
		return 0; 
	} 
	else { 
		int x = a[top]; 
		return x; 
	} 
} 

bool Stack::isEmpty() 
{ 
	return (top < 0); 
} 

// Driver program to test above functions 
int main() 
{ 
	class Stack s; 
	s.push(10); 
	s.push(20); 
	s.push(30); 
	cout << s.pop(); 

	return 0; 
} 

```

### 9. Wyjaśnij koncepcję listy jednokierunkowej i dwukierunkowej – podaj implementację w języku c++.

#### Lista jednokierunkowa
- Składa się z pewnej liczby węzłów 
(elementów), z których każdy zawiera
- pewien zestaw danych
- adres następnego elementu
- Dodatkowo należy zapamiętać adres 
pierwszego węzła (tzw. głowa)
- Ostatni element wyróżnia się poprzez 
nadanie adresowi określonej wartości, którą
można odpowiednio zinterpretować, tzn. 
inaczej niż adres (w języku C: `NULL`)

Elementem listy jest najczęściej zmienna 
typu struktura
```#define MAX_ROZMIAR 50;
/* definicja elementu listy */
struct elem_tag {
 char wyraz[MAX_ROZMIAR+1];
 struct elem_tag *nast;
};
/* korzystamy ze zmiennych wskaźnikowych
 glowa – jeśli jest zmienna statyczna, jest
 inicjowana wartością 0, czyli NULL */
struct elem_tag *glowa;
```


Elementem listy dwukierunkowej jest  najczęściej zmienna typu struktura
```#define MAX_STR 50;
struct elem_tag
{
 /* dane */
 int pole1;
 char [MAX_STR+1] pole2;
 double pole3;

 /* adres następnego elementu listy */
 struct elem_tag *nast, *poprz;
} glowa_1, glowa_2
```

#### Składowe listy wielokierunkowej 
- Dane: wartości atrybutów
- Wskaźnik na następny element
- Wskaźnik na poprzedni element
- Dwa wskaźniki na elementy (pierwszy  i ostatni)

### 10. Omów kolejne etapy kompilacji w języku c++.

#### Etap 0: Start procesu
Procesowi kompilacji poddawany jest każdy plik źródłowy z osobna. Oznacza to, że każdy plik źródłowy staje się całkowicie odseparowaną jednostką kompilacji. Dopiero pod koniec całego procesu pliki są łączone ze sobą.

#### Etap 1: Preprocesor
Pierwszym etapem procesu kompilacji jest preprocessing. To w tym momencie zostają dołączane wszystkie wymagane przez plik źródłowy nagłówki. Realizowane jest to poprzez rekursywne zastępowanie wszystkich instrukcji #include zawartościami plików, do których te instrukcje nawiązują.

Dodatkowo, w tym kroku odbywa się rozwiązywanie prostych składniowo makr znajdujących się wewnątrz plików źródłowych (instrukcja #define). Makra dają nam takie możliwości jak sterowanie procesem kompilacji w zależności od aktualnego środowiska, dodatkowe informacje debugujące, definiowanie własnych etykiet (dla stałych wartości) oraz upraszczanie bardziej skomplikowanych fragmentów kodu bez dodatkowego kosztu wydajnościowego. Niestety, są to zwykłe operacje podmiany, zatem błędy związane z typowaniem nie są tutaj wykrywane.

#### Etap 2: Diagnostyka
Kod przygotowany przez preprocesor trafia do kompilatora, który uruchamia pierwszy z trzech procesów: front-end. To front-end sprawdza kod pod kątem poprawności składniowej oraz semantycznej. Na tym poziomie generowane są błędy związane z niepoprawną składnią. Podczas procesu diagnostyki kod jest również sprawdzany pod względem semantyki, czyli sprawdzane jest to, czy instrukcje zawarte w kodzie mają sens.

#### Etap 3: Optymalizacja
Drugim procesem uruchamianym przez kompilator jest middle-end. To w tym miejscu generowane są drzewa AST. 

To na podstawie drzew składniowych przeprowadzane są optymalizacje. Przekształcenia optymalizacyjne mają za zadanie wygenerować kod niezmieniający zachowania optymalizowanych fragmentów, próbując przy tym zaoszczędzić m.in. na takich zasobach jak: czas wykonania operacji, zużycie pamięci oraz wielkość pliku wynikowego.

#### Etap 4: Wygenerowanie kodu assemblera
Ostatnim zabiegiem przeprowadzanym przez kompilator jest przełożenie kodu reprezentacji wewnętrznej na kod assemblera (assembly). Jest to najniższego poziomu język programowania, w którym nie ma pętli ani klas. Są za to rejestry, w których możemy przechowywać wartości oraz przeprowadzać na nich bardzo proste operacje.

#### Etap 5: Wygenerowanie pliku obiektowego
Przedostatnim krokiem procesu kompilacji jest przetworzenie kodu assemblera na kod maszynowy, zrozumiały dla systemu operacyjnego. Na jego podstawie generowane są pliki obiektowe, będące wyjściem dla jednostek kompilacji. Są one zbliżone budową do plików wykonywalnych - z tą różnicą, że pliki obiektowe nie posiadają referencji do wykorzystywanych symboli. Na tym etapie poszczególne jednostki kompilacji nie wiedzą nic o sobie wzajemnie, dlatego nie mogą określić pochodzenia wykorzystywanych wewnątrz siebie symboli.

#### Etap 6: Linkowanie
Jest to ostatni krok podejmowany przez sterownik kompilacji. Na tym etapie wszystkie pliki obiektowe łączone są w jeden plik wynikowy. W tym miejscu łączone są ze sobą tablice symboli oraz podejmowane są czynności sprawdzające istnienie użytych wewnątrz kodu symboli. Jeżeli choć jeden użyty w kodzie symbol nie zostanie znaleziony w pozostałych jednostkach kompilacji, zostanie to zgłoszone jako błąd linkera. W tym miejscu podejmowane są również optymalizacje, których nie można było przeprowadzić ze względu na wiedzy jednostek kompilacji o sobie wzajemnie.

Błędy, na jakie możemy liczyć w tym momencie to błędy związane z nieodnalezioną referencją do funkcji lub symbolu.

## Programowanie II

### 1. Funkcja zaprzyjaźniona w języku C++. Omów czym są i jakie korzyści wynikają z możliwości zastosowania funkcji zaprzyjaźnionych w języku C++


Funkcja zaprzyjaźniona z klasą to funkcja, która mimo, że nie jest składnikiem klasy ma dostęp do wszystkich (nawet prywatnych) składników klasy.W pewnych sytuacjach może być korzystne by jakaś funkcja, spoza zakresu tej klasy, miała także dostęp do składników prywatnych. Wewnątrz definicji klasy wystarczy umieścić deklarację tej funkcji poprzedzoną słowem `friend`.

Korzyści:
- funkcja może być przyjacielem więcej niż jednej klasy, wtedy taka funkcja może mieć dostęp do składników prywatnych i chronionych składników kilku klas
- funkcja zaprzyjaźniona może na argumentach jej wywołania dokonywać konwersji zdefiniowanych przez użytkownika (np zamiana argumentu na obiekt danej klasy)
- dzięki deklaracji przyjaźni możemy nadać dostęp do prywatnych składników klasy nawet takim funkcjom, które nie mogłyby być funkcjami składowymi (np. napisane w innym języku)


### 2. Obsługa wyjątków w językach programowania, w szczególności w języku C++. Wyjaśnij czym jest i na czym polega obsługa wyjątków w językach programowania na przykładzie języka C++.

Obsługa wyjątków jest uwzględnianiem w programie jego niepoprawnego działania.

Przykładowo, próba otwarcia pliku może zakończyć się niepowodzeniem, jeśli plik nie istnieje. Jeśli błąd nie zostanie obsłużony, program przestanie działać. Zwykle oczekiwanym rezultatem w przypadku wystąpienia takiego błędu jest wyświetlenie informacji użytkownikowi, a nie zakończenie działania programu.

W jezyku C++ obsługa wyjątków realizowana jest przez tworzenie bloków `try`-`catch`:

```
try
{
  // instrukcje mogące spowodować błąd
}
catch(Typ_wyjątku nazwa_wyjątku)
{
  // instrukcje obsługujące wyjątek
}


```

Program nie zostanie zatrzymany, a sterowanie przeniesie się do bloku *catch*

### 3. Omów cykl życia obiektu w języku C++. Zwróć uwagę na operatory new i delete w języku C++.

#### Cechy obiektów storzonych operatorem `new`:
- Obiekty tak utworzone istnieją od momentu, gdy je utworzymy operatorem `new`. Do momentu, gdy je skasujemy operatorem `delete`. Inaczej mówiąc,to my decydujemy o czasie ich życia.
- Obiekt tak utworzony nie ma nazwy, można nim operować tylko za pomocą wskaźników. 
- Skoro obiekty takie nie mają nazw, obiektów tych (ich nazw) nie obowiązują zwykłe zasady o zakresie ważności. Jeśli tylko jest w danym momencie dostępny choćby jeden wskaźnik, który na taki obiekt pokazuje, to mamy do tego obiektu dostęp.
- Tylko statyczne obiekty wstępnie inicjalizowane są zerami, o ile nie określamy inaczej. Natomiast obiekty tworzone operatorem new nie są statyczne, wręcz przeciwnie, są dynamiczne. Dlatego zaraz po utworzeniu, tkwią w nich jeszcze śmieci. Musimy sami zadbać o zapisanie tam sensownych wartości. 


### 4. Konstruktory w języku C++: czym są i jaką pełnią rolę. Podaj przykłady.

Konstruktor w języku C++ jest specjalną funkcją składową. Charakteryzuje się tym, że nazywa się tak samo jak klasa. Jego rolą jest zainicjowanie pól klasy odpowiednimi wartościami.

Przykład:
```
class numer {
    int liczba;
public:
    // funkcje składowe
    numer(int k) { liczba = k; } // <- konstruktor

    void funkcja1(int n) { liczba = n; }
    int funkcja2() { return liczba; }
}
```

Konstruktor jest wywoływany automatycznie przy tworzeniu nowego obiektu danej klasy.

Dwa sposoby użycia konstruktora:
```
numer a = numer(15);
numer b(15);
```

### 5. Omów zagadnienie dziedziczenia w języku C++, zwróć uwagę na wielodziedziczenie i uwzględnij dziedziczenie konstruktorów.

Dziedziczenie to technika pozwalająca na definiowanie nowej klasy przy wykorzystaniu klasy już wcześniej istniejącej. Nowo utworzona klasa (tzw *klasa pochodna*) rozszerza funkcjonalność klasy, po której dziedziczy (tzw *klasy bazowej*).

Klasa może dziedziczyć po więcej niż jednej klasie. Takie dziedzicznie nazywamy wielodziedziczeniem.

Konstruktory nie są dziedziczone - jeśli w klasie pochodnej nie zdefiniowaliśmy konstruktora, to zostanie użyty konstruktor domyślny. Aby jednak powstał obiekt klasy pochodnej, musi być *najpierw* utworzony podobiekt klasy nadrzędnej, wchodzący w jego skład. On również zostanie utworzony za pomocą konstruktora domyślnego, który zatem musi istnieć. Konstruktory klas bazowych są wywoływane w kolejności, w jakiej występują na liście pochodzenia klasy pochodnej.

### 6. Czym jest i jaką rolę pełni konstruktor kopiujący w języku C++. Podaj przykład ilustrujący.

Konstruktorem kopiującym w danej klasie nazywamy konstruktor, który można wywołać z jednym argumentem poniższego typu.

`klasa::klasa(klasa &)`

Inne warianty konstruktora kopiującego to:
```
klasa::klasa(const klasa &)
klasa::klasa(volatile klasa &)
klasa::klasa(const volatile klasa &)
```

Konstruktor kopiujący służy do skonstruowania obiektu, który jest kopią innego, już istniejącego obiektu tej klasy.

Wywoływanie konstruktora kopiującego:
- jawnie
    - do konstruktora wysyłamy inny istniejący obiekt
- niejawnie
    - podczas przesyłania argumentów do funkcji, jeśli argumentem funkcji jest obiekt
    - podczas gdy funkcja jako rezultat zwraca (przez wartość) obiekt

### 7. Czym jest i jaką rolę pełni destruktor klasy w języku C++.

Destruktor to funkcja składowa klasy. Destruktor nazywa się tak samo jak klasa z tym, że przed nazwą ma znak ~ (wężyk). Podobnie jak konstruktor nie ma ona typu zwracanego, nie jest wywoływany z żadnymi argumentami, nie może być przeładowany. 

Funkcja jest wywoływana automatycznie zawsze gdy obiekt jest likwidowany.
Klasa nie musi mieć obowiązkowego destruktora. Destruktor nie likwiduje obiektu, ani nie zwalnia obszaru pamięci który obiekt zajmował.

Przy kończeniu pracy programu wszystkie istniejące wówczas obiekty są likwidowane, więc wtedy też automatycznie uruchamiane są ich destrutktory. 

Do obiektu, który kreowaliśmy operatorem `new`, destruktor wywoływany jest, gdy użyjemy operator `delete` wobec wskaźnika pokazującego na ten obiekt.

Destruktor można wywołać jawnie.
``` obiekt.~klasa(); ```
``` wskaźnik->~klasa(); ```

Jawne wywołanie konstruktora nie likwiduje obiektu.

Calling a destructor explicitly is seldom necessary. However, it can be useful to perform cleanup of objects placed at absolute addresses. These objects are commonly allocated using a user-defined new operator that takes a placement argument. The delete operator cannot deallocate this memory because it is not allocated from the free store (for more information, see The new and delete Operators). A call to the destructor, however, can perform appropriate cleanup. 

### 8. Czym jest wyciek pamięci. Podaj przykład ilustrujący. Jak nie dopuszczać do wycieków pamięci.

Programując w C++ sami możemy zarządzać rezerwowaniem i zwalnianiem pamięci. Kiedy tworzymy zwykłą zmienną, ma ona przydzielone swoje miejsce w pamięci. Miejsce to jest zwalniane kiedy wychodzimy poza zakres ważności zmiennej, czyli za blok ograniczony nawiasami klamrowymi {}. Tutaj nie ma ryzyka powstawania wycieków pamięci, ponieważ jest ona na bieżąco zwalniana. Język C++ daje nam jednak dużą swobodę i możemy także rezerwować pamięć dynamicznie, czyli wtedy kiedy potrzebujemy, tyle ile jej potrzebujemy i na ile ją potrzebujemy. Ostatnia cecha pamięci alokowanej dynamicznie oznacza, że sami musimy zadbać o to, żeby zwolnić pamięć kiedy nie będzie już potrzebna. Jeżeli tego nie zrobimy w odpowiednim momencie, powstaje wyciek pamięci.

Przykład:
``` wyciek
int fun() {
  int* wsk = new int; // zarezerwowane pamięci na liczbę typu int
  *wsk = 20; // wpisanie w zarezerwowane miejsce wartości
  return *wsk;
}
```

### 9. Omów czym są wzorce funkcji w języku C++. Podaj przykład.

zięki nim możemy napisać program, bez początkowego uwzględniania typów. Jest wykorzystywany w metaprogramowaniu. Aby użyć szablonu, trzeba najpierw zadeklarować jego instancję.

Przed każdą funkcją, strukturą czy klasą musimy dokonać konkretyzacji szablonu(tworzenie instancji - konkretyzacja to ten proces). Każdą konkretyzację zaczynamy od słówka template.

Przykład deklaracji szablonu:
`template < class klasa, int ilosc >`

Jak można się domyślić, deklarujemy w szablonie typ zwracanej zmiennej i ilość danego elementu. 

Przykład wzorca funkcji:
``` szablon funkcji
#include <iostream>
#include <cstdio>
using namespace std; //nasz kochany namespace :D
template < class klasa > //używamy szablonu
klasa pierwiastek( klasa liczba ) //nowa funkcja z typem zwracalnym, jaki podaliśmy w template
{
    klasa liczba_pierwiastka =( liczba * liczba ); //kto nie lubi liczenia ;)
    return liczba_pierwiastka;
}
template < class klasa > //znów template
klasa a_plus_b_minus_c( klasa a, klasa b, klasa c ) //nowa funkcja z typem zwracalnym, jaki podaliśmy w template
{
    klasa wynik =( a + b - c ); //nowa zmienna
    return wynik;
}
int main()
{
    double liczba1 = pierwiastek < double >( 3.9 ); //przypisujemy wartość zmiennej
    cout << liczba1 << endl;
    int liczba2 = a_plus_b_minus_c < int >( 9, 2, 1 ); //tu też
    cout << liczba2 << endl;
    getchar(); //funkcja odczytująca klawisz
}
```

### 10. Omów wzorzec singleton i podaj jego implementację w języku C++.

Singleton (ang. singleton) - wzorzec projektowy, który ogranicza maksymalną ilość instancji obiektu do jedności. Własności singletona:
- jest tworzony przy pierwszym odwołaniu się do niego
- jest niszczony po zakończeniu głównej funkcji programu
- w trakcie życia programu istnieje tylko i wyłącznie jego jedna instancja.

Singletony stosuje się zazwyczaj gdy chcemy posiadać globalny mechanizm dostępu do określonej funkcjonalności (np. logowania błędów).

Zaletą singletona w stosunku do zmiennej globalnej jest to, że zasoby zostaną jemu przydzielone dopiero wtedy, gdy zostanie on wywołany. Oznacza to, że:
- jeżeli w trakcie życia aplikacji nie zostanie wywołany singleton to nie zostanie on utworzony
- inicjalizacja obiektu nie jest skumulowana w jednym punkcie, tj. w chwili uruchomienia aplikacji.

W konsekwencji dwóch powyższych punktów uzyskujemy oszczędność zasobów i wzrost szybkości ładowania aplikacji.

Inną istotną zaletą singletona jest jasno określony sposób uzyskiwania dostępu do globalnego zasobu.


## Programowanie obiektowe i graficzne

### 1. Omów koncepcję tworzenia obiektów i interakcji pomiędzy nimi na przykładzie języka c#.

Aby uprościć inicjalizację obiektów, wszystkie jego dostępne pola i własności można ustawiać przez inicjalizator obiektu bezpośrednio po zakończeniu procesu konstrukcji.

#### Tworzenie obiektów

Tworzenie obiektów (instancji klas) w języku C# odbywa się poprzez wykorzystanie operatora `new` i konstruktora klasy.

Klasa:
```
public class User {
    public User() {
        
    }
}
```

Tworzenie obiektu:
```
User user = new User();
```

Jeśli konstruktor posiada argumenty, należy je podać przy tworzeniu obiektu.

Klasa:
```
public class User {
    private string name;
    
    public User(string name) {
        this.name = name;
    }
}
```

Tworzenie obiektu:
```
User user = new User("Bartek");
```


#### Interakcja pomiędzy obiektami

Jeśli pozwala na to modyfikator dostępu, można uzyskać dostęp do składowych już istniejącego obiektu w następujący sposób:

```
user.name = "Oliwia";
user.ToString();
```

Odpowiednie metody oraz przeciążone operatory mogą przyjmować jako argumenty instancje innych obiektów, co również można zaliczyć jako interakcje pomiędzy obiektami.



### 2. Omów trzy podstawowe filary programowania obiektowego na przykładzie języka c#: hermetyzacja, dziedziczenie i polimorfizm.

Język C# implementuje szeroki wachlarz technik obiektowych, takich jak hermetyzacja, dziedziczenie
i polimorfizm.

Hermetyzacja to technika polegająca na tworzeniu ograniczonych obiektów, których zewnętrzne (publiczne) mechanizmy są oddzielone od wewnętrznych (prywatnych) szczegółów implementacyjnych. 

Klasa może dziedziczyć zawartość innej klasy, aby ją rozszerzyć lub dostosować do indywidualnych
potrzeb. Dziedziczenie umożliwia wielokrotne wykorzystanie funkcjonalności klasy, dzięki czemu
nie trzeba za każdym razem pisać wszystkiego od nowa. Klasa może dziedziczyć tylko po jednej innej
klasie, ale sama może być wykorzystywana w tej roli przez wiele innych klas. W ten sposób powstaje
hierarchia klas. 

Poniżej znajduje się przykład definicji klasy Asset:
``` c#
public class Asset
{
 public string Name;
}
```
Następnie definiujemy klasy Stock i House dziedziczące po klasie Asset. Będą one zawierać wszystko to, co klasa Asset, oraz dodatkowe własne składowe:
``` c#
public class Stock : Asset // dziedziczy po Asset
{
 public long SharesOwned;
}

public class House : Asset // dziedziczy po Asset
{
 public decimal Mortgage;
}
```

Referencje są polimorficzne. Oznacza to, że zmienna typu x może się odnosić do obiektu typu będącego podklasą klasy x. Np.:
``` c#
public static void Display (Asset asset)
{
 System.Console.WriteLine (asset.Name);
}
```

Działanie polimorfizmu opera się na fakcie, że podklasy (Stock i House) mają wszystkie właściwości
klasy bazowej (Asset). Ale twierdzenie odwrotne nie jest prawdziwe. Gdyby metoda Display przyjmowała obiekty klasy House, nie można by było do niej przekazywać obiektów klasy Asset.

### 3. Omów szczegółowo pojęcie hermetyzacji. W jaki sposób jest realizowane w języku *C#* i odnieś je do dowolnego innego języka programowania.

Hermetyzacja (enkapsulacja) polega na decydowaniu jak pola i metody danego obiektu mają być widoczne dla innych obiektów.

Gdy dostęp do wszystkich pól danej klasy jest możliwy wyłącznie poprzez metody, lub inaczej mówiąc: gdy wszystkie pola w klasie znajdują się w sekcji prywatnej lub chronionej, to taką hermetyzację nazywa się hermetyzacją pełną.

W praktyce, hermetyzację realizuje się przy użyciu modyfikatorów dostępu, stawianych przed składowymi klasy. W języku *C#* dostępne modyfikatory dostępu to:
- `public` - dostęp do składniku klasy możliwy spoza niej
- `private` - dostęp do składniku klasy możliwy tylko dla obiektów tej samej klasy
- `protected` - dostęp do składniku klasy możliwy dla obiektów tej samej klasy i klas po niej dziedziczących
- `internal` - dostęp do składniku klasy możliwy dla obiektów klas znajdujących się w tej samej bibliotece
- `protected internal` - dostęp do składniku klasy możliwy tylko dla obiektów tej samej klasy oraz dla obiektów klas po niej dziedziczących tylko w obrębie danej biblioteki


W Javie również dostęp do danych możemy modyfikować za pomocą modyfikatorów dostępu:
- `public` - dostęp do składniku klasy możliwy spoza niej, czyli tak samo, jak w *C#*
- `private` - dostęp do składniku klasy możliwy tylko dla obiektów tej samej klasy, czyli tak samo, jak w *C#*
- `protected` - dostęp do składniku klasy możliwy dla obiektów tej samej klasy i klas po niej dziedziczących, czyli tak samo, jak w *C#*
- `brak modyfikatora` - dostęp do składniku klasy możliwy dla wszystkich obiektów klas znajdujących się w tym samym pakiecie


Zalety hermetyzacji:
- obiekt jest odizolowany, a więc nie jest narażony na celowe, bądź niezamierzone działanie ze strony użytkownika
- obiekt jest chroniony od niepożądanych referencji ze strony innych obiektów
- dzięki ukryciu wewnętrznej struktury obiektu, można uzyskać jego przenośność (zastosować definiującą go klasę w innym fragmencie kodu)
- uodparnia tworzony model na błędy polegające na przykład na błędnym przypisywaniu wartości oraz umożliwia wykonanie czynności pomocniczych
- lepiej odzworowuje model
- umożliwia rozbicie modelu na mniejsze elementy


### 4. Omów szczegółowo pojęcie dziedziczenia. W jaki sposób jest realizowane w języku *C#* i odnieś je do dowolnego innego języka programowania.



Dziedziczenie (ang. inheritance) umożliwia sprawne i łatwe wykorzystywanie już raz napisanego kodu oraz budowanie hierarchii klas przejmujących swoje właściwości.

W C# dziedziczenie jest wyrażane za pomocą symbolu dwukropka, a cała definicja schematycznie wygląda następująco:
``` c#
class KlasaPotomna : KlasaBazowa
{
    // wnętrze klasy
}
```
Zapis taki oznacza, że klasa potomna (inaczej: podrzędna, pochodna, ang. subclass,
child class) dziedziczy po klasie bazowej (inaczej: nadrzędnej, nadklasie, ang. base class,
superclass, parent class)


Dostępność składników klasy bazowej w klasie pochodnej jest zależna od ich modyfikatorów dostępu.


W języku *Java* w przeciwieństwie do *C#* nie występuje dziedziczenie wielokrotne. To znaczy, że klasa potomna może rozszerzać tylko jedną klasę bazową.
Aby rozszerzyć jakąś klasę należy użyć słowa kluczowego `extends` w nagłówku klasy:
``` java
class KlasaPotomna extends KlasaBazowa
{
    // wnętrze klasy
}
```

### 5. Omów szczegółowo pojęcie polimorfizmu. W jaki sposób jest realizowane w języku c# i odnieś je do dowolnego innego języka programowania.

W programowaniu obiektowym jest to jeden z paradygmantów, który najczęściej jest wyrażany jako „jeden interfejs, wiele funkcji”.
Polimorfizm może być statyczy i dynamiczny. W polimorfizmie statycznym odpowiedź funkcji jest określna w trakcie kompilowania. W polimorfizmie dynamicznym odpowiedź ta jest podejmowana w czasie wykonywania programu.

##### Polimorfizm statyczny
Mechanim łączenia metody z obiektem w trakcie kompilacji jest nazywany wczesnym wiązaniem lub też statycznym wiązaniem. C# udostępnia dwa sposoby implementowania statycznego polimorfizmu:
- przeciążanie metod
    - W tej samej definicji klasy może znajdować się wiele funkcji o tej samej nazwie. Definicja metod musi się różnić od siebie typem i/lub liczbą parametrów. Nie można przeciążyć metod, które różnią się tylko zwracanym typem.
- przeciązanie operatorów
    - C# pozwala na zmianę lub przeciążenie większości wbudowanych operatorów
    - programista może używać operatorów z typami zdefiniowanymi również przez użytkownika
    - przeciążone operatory to metody z nazwą, słowem kluczowym operator, po którym występuje symbol operatora, który chcemy zdefiniować
    - przeciążony operator ma typ zwracany oraz listę parametrów

##### Polimorfizm dynamiczny

C# pozwala tworzyć klasy abstrakcyjne, które następnie są implementowane w klasach pochodnych. Klasa taka zawiera abstrakcyjne metody, których implementacja zależy od wykorzystania w poszczególnych klasach pochodnych.

Lista zasad o których należy pamiętać tworząć klasy abstrakcyjne:
- nie można utworzyć instancji klasy abstrakcyjnej
- nie można zadeklarować metody abstrakcyjnej poza klasą abstrakcyjną
- kiedy klasa opatrzona jest modyfikatorem dostępu sealed nie może być dziedziczona
- klasa abstrakcyjna nie może być zdefinowana jakas sealed

Polimorfizm dynamiczny jest realizowany za pomocą **klas abstrakcyjnych** oraz **metod wirtualnych**. Metody te mogą mieć różne implementacje w klasach pochodnych, ale nie muszą. Jeżeli w klasie pochodnej nie będzie implementacji metody wirtualnej z klasy bazowej to użyta zostanie domyślna implementacja z klasy bazowej. Wybór odnośnie wywołania metody podejmowany jest w czasie wykonywania programu.


### 6. Rola abstrakcji w programowaniu obiektowym. Omów ją na przykładzie klas abstrakcyjnych i interfejsów.

Jeśli klasa jest abstrakcyjna, nie można tworzyć jej obiektów. Możliwe jest tylko tworzenie obiektów jej konkretnych podklas.
**Klasy abstrakcyjne** mogą zawierać definicje abstrakcyjnych składowych. Są one podobne do składowych wirtualnych, tylko nie mają domyślnej implementacji. Musi ona zostać podana w podklasie, chyba że podklasa również jest abstrakcyjna.

**Interfejsy** są podobne do klas, tylko zamiast implementacji składowych zawierają ich specyfikacje:
- Wszystkie składowe interfejsu są domyślnie abstrakcyjne. W klasach natomiast można definiować zarówno składowe abstrakcyjne, jak i konkretne, tzn. z implementacjami.
- Klasa (lub struktura) może implementować wiele interfejsów. Z drugiej strony klasa może dziedziczyć tylko po jednej innej klasie, a struktura nie może dziedziczyć w ogóle.
- 
Deklaracja interfejsu wygląda jak deklaracja klasy, tylko nie zawiera implementacji składowych, ponieważ wszystkie one są domyślnie abstrakcyjne. Ich implementacje zostaną dodane w klasach i strukturach implementujących dany interfejs. Interfejs może zawierać tylko metody, własności, zdarzenia i indeksatory, czyli te same składowe co klasy abstrakcyjne.


### 7. Omów rolę i znaczenie mechanizmu zdarzeń w języku c# w tworzeniu interaktywnego interfejsu graficznego użytkownika.

Podczas pracy z delegatami pojawiają się dwie typowe role: nadawcy i subskrybenta.

Typ delegacyjny definiuje rodzaj metody, jaki mogą wywoływać egzemplarze delegatu - określa typ zwrotny metody i typy jej parametrów.

**dawca** to typ zawierający pole delegacyjne. Decyduje on o tym, kiedy rozpocząć nadawanie poprzez wywołanie delegatu.
**Subskrybenci** to odbiorcy metody docelowej. Subskrybent decyduje, kiedy rozpocząć i zakończyć nasłuchiwanie, przez wywołanie operatorów += i -= na delegacie nadawcy. Subskrybent nie zna innych subskrybentów i nie wchodzi z nimi w żadne reakcje.

**Zdarzenia** to funkcja języka stanowiąca formalne ujęcie tego wzorca. Zdarzenie (event) to konstrukcja udostępniająca tylko część funkcjonalności delegatów potrzebną do realizacji modelu nadawca – subskrybent.

Głównym zastosowaniem zdarzeń jest zapobieżenie interakcji między subskrybentami.

Platforma .NET Framework definiuje standardowy wzorzec pisania zdarzeń. Ma on zapewnić spójność kodu użytkownika i samej platformy. Rdzeniem standardowego wzorca zdarzeń jest niemająca składowych (oprócz statycznej własności Empty) predefiniowana klasa System.EventArgs. Jest to klasa bazowa do przekazywania informacji dla zdarzenia. 

Metody dostępowe zdarzeń (ang. accessors) są implementacjami funkcji += i -=. Domyślnie są one implementowane niejawnie przez kompilator. 

Zdarzenia, podobnie jak metody, mogą być wirtualne, abstrakcyjne, przesłaniane i pieczętowane a także statyczne

Zdarzenia są często wykorzystywane w tworzeniu graficznych interfejsów przy interakcji z użytkownikiem. Gdy wykonuje on jakąś operację, wykonywane jest odpowiednie zdarzenie, które może zostać obsłużone. Przykładowe zdarzenia w interfejsie użytkownika to naciśnięcie przycisku lub wpisanie tekstu na klawiaturze.


### 8. Scharakteryzuj trójwarstwową architekturę tworzenia desktopowej aplikacji z interfejsem graficznym na przykładzie architektury MVP lub MVVM.

MVVM wprowadza architekturę składająco się z trzech elementów:
- modelu (model),
- widoku (view),
- viewmodelu (viewmodel).
 
**Data binding**
Cechą wzorca MVVM jest to, że dane przechowywane przez model są automatycznie odświeżane w widoku, w sytuacji gdy dojdzie do modyfikacji danych po stronie viewmodelu. Wystarczy tylko zaimplementować odpowiedni interfejs po stronie viewmodelu/modelu, a następnie trzymać się pewnych konwencji.

**Viewmodel**
Centralnym elementem wzorca MVVM jest viewmodel.
Trzyma referencję do modelu.
Wykształca mechanizm komend, które później są wykorzystywane w widoku do obsługi interakcji wywołanych przez użytkownika.
Jest „bindowany” jako kontekst danych dla strony/widoku/okna.
Powinien poprawnie implementować interfejs INotifyPropertyChanged.

**Model**
Model przechowuje pobrane/przetworzone przez viewmodel dane, które później zostaną „zbindowane” w widoku. Model powinien poprawnie implementować INotifyPropertyChanged i z założenia nie powinien posiadać żadnych elementów logiki biznesowej ani nawiązań do warstwy wizualnej projektu.

**View**
Ostatnim elementem jest widok. Jest odpowiedzialny za:
- utworzenie layoutu warstwy wizualnej,
- obsługę interakcji użytkownika za pomocą dedykowanych kontrolek, do których zostały „zbindowane” komendy utworzone po stronie kontrolera,
- ustanowienie kontekstu danych, do którego w poprawnej implementacji powinna zostać przypisana instancja viewmodelu,
- ustawienie za pomocą data binding pól zdefiniowanych w modelu jako właściwości poszczególnych kontrolek.
- 
Tak utworzona aplikacja zawiera jawną separację warstw. Logika zostaje oddzielona od warstwy prezentacji oraz klas typu DTO. Każdego z tych elementów dużo łatwiej jest reużyć w innym miejscu programu. Ponadto mechanizm data binding rozwiązuje problem odświeżania danych, który przez długi czas był charakterystyczny dla aplikacji okienkowych.

### 9. Porównaj ideę programowania obiektowego realizowaną w języku c# z c++, javą, kotlinem. Podaj podobieństwa i różnice.

![](https://i.imgur.com/O0BYoiG.png)


### 10. Podaj przykład i omów jeden wybrany wzorzec projektowy wykorzystywany w programowaniu obiektowym.

Np. singleton (jest już opisany)


