October 30, 2025, Thursday, 302

Języki Programowania 5

From MJanik

Revision as of 09:15, 28 October 2025 by Majanik (Talk | contribs)
Jump to: navigation, search

Zadanie 5 - Klasy, enkapsulacja, domyślne wartości, podział na pliki

Contents

Wstęp

Krotki opis pomysłu: Tworzymy oprogramowanie dla firmy. Firma ma pracowników zajmujących standardowo jedno z trzech stanowisk: informatyk, menadżer albo sprzątaczka. Firma posiada również flotę służbowych samochodów, nieprzyporządkowanych do pracowników. Chcielibyśmy posiadać "bazę danych" zarówno pracowników i samochodów, oraz posiadać łatwą możliwość obliczenia kosztów utrzymania wszystkiego.


1. Krok pierwszy: pola prywatne i publiczne, wskaźnik "this", tworzenie klas

Tworzymy potrzebne klasy, umożliwiające dodawanie i wypisywanie zarówno samochodów jak i pracowników. Jeśli czujesz, że potrzebujesz przećwiczyć tworzenie klas, możesz najpierw spróbować wykonać te przykładowe zadania. Ale zanim się do tego zabierzemy, zacznijmy od garści informacji:

Pola prywatne i publiczne:

Enkapsulacja (ang. encapsulation) to jedna z podstawowych zasad programowania obiektowego. Polega na ukrywaniu wewnętrznej budowy obiektu (czyli danych i sposobu ich przetwarzania) przed światem zewnętrznym oraz udostępnianiu tylko ściśle określonego interfejsu — zwykle w postaci metod publicznych. W praktyce oznacza to, że: • dane obiektu (pola klasy) są prywatne (private), • dostęp do nich odbywa się tylko przez publiczne metody (public), które kontrolują sposób odczytu i modyfikacji danych.

prywatne: private

Jest dostępne tylko dla funkcji składowych klasy (i funkcji zaprzyjaźnionych z daną klasą). Jeśli nie wyszczególnimy etykiety, to wszystkie składniki klasy będą domyślnie prywatne.

publiczne: public

Publiczne składniki mogą być używane zarówno we wnętrzu klasy, jak również spoza jej zakresu.


Wskaźnik *this

- Mówi: "teraz odwołasz sie do składowej >TEJ< klasy" (w której jesteś)

- Jego używanie nie jest obligatoryjne (przydaje sie, jeśli z jakiegoś powodu chcemy posiadać takie same nazwy dla argumentów funkcji jak i składników klasy)

Potrzebne klasy:

Klasa Pracownik:

pola prywatne:

string imie;
string nazwisko;  
int wiek;
double pensja;
string zawod;

metody publiczne:

void zapisz(string i, string n, int w, double p, string z);
void wypisz();

Klasa samochod:

pola prywatne:

 rodzaj marka;
 double spalanie_na_kilometr;
 double km;

metody publiczne:

 void zapisz(rodzaj marka, double spalanie_na_kilometr, double km); //uwaga, te same nazwy! żeby moc przyporządkować odpowiednie wartości - użycie wskaźnika this! "this->km = km;" - przyporządkuje do <składnika klasy km> wartość <argumentu funkcji km>)
 void wypisz();

By przetestować działanie programu, tworzymy w funkcji main() tablice pracowników i samochodów, oraz tworzymy np. 3 samochody i 3 pracowników. Wypisujemy ich na ekran.

2. Krok drugi: enum

Typy wyliczeniowe: enum. Jest to osobny typ dla wybranego zestawu stałych całkowitych.

Przydaje sie często - np. mamy ograniczana liczbę stanowisk, na jakich może być zatrudniony pracownik (w naszym przypadku: 3). Ale zapamiętywanie każdego zawodu jako "string" nie dość, ze zajmuje niepotrzebna bardzo dużo miejsca, to jeszcze sprzyja generowaniu problemów (np. jeden programista wpisze "Sprzataczka" drugi "sprzataczka", a trzeci, mający brata pracującego przy sprzątaniu wpisze "sprzatacz". A wtedy zaczynają sie problemy z porównywaniem składnika "zawod" z jakimś konkretnym słowem... Wiec myślimy: skoro mamy trzy zawody, to przypiszmy im numery. Informayk - 1, sprzątaczka - 2, manager - 3. I po problemie. Jednak zapamiętywanie, która cyferka odpowiada jakiemu zawodowi może również generować problemy, chociaż innego rodzaju - w dużych programach mamy tysiące linijek kodu. Wyobraźmy sobie, ze musimy dodać nowa funkcje. A kto będzie pamiętał po tygodniu, czy 3 to była sprzątaczka, czy manager...

Używamy wiec typu wyliczeniowego enum:

Przykład użycia: enum zawod{ informatyk=1, sprzataczka, manager};

W ten sposób, jeśli zadeklarujemy taki typ globalnie, to za każdym razem, jeśli napiszemy w kodzie "manager" to program będzie wiedział, ze chodzi o 3. A jeśli programista próbował by napisać np. "Manager" to od razu kompilator zgłosi błąd. Oczywiście powodów użycia typów wyliczeniowych możemy wymyślać dużo więcej.

Zamiast deklarować typu wyliczeniowego globalnie, możemy zrobić to również wewnątrz klasy. Takie postępowanie jest bardzo naturalne - niektóre wyliczenia wiążą sie tylko i wyłącznie z jedną, konkretną klasą. Wtedy możemy zadeklarować:

class Pracownik{
 public:
   enum zawod{ informatyk=1, sprzataczka, menadzer};
    ...
 }

Wtedy wewnątrz składników klasy używamy zwyczajnie slow "informatyk" tak, jakby był to dowolny int.

Na zewnątrz klasy musimy jednak określić, z jakiego zakresu słowo "informatyk" ma pochodzić: Pracownik::informatyk.

Wydawało by sie, ze łatwiej zapamiętać cyfrę 1, niż złożoną konstrukcję "Pracownik::informatyk". Ale to niekoniecznie prawda - jeśli przeglądamy kod jakiś czas po napisaniu programu i widzimy w nim masę 1, 2 i 3 - to niewiele jesteśmy w stanie z niego przeczytać. Jeśli natomiast mamy w takich miejscach "Pracownik::informatyk" kod staje sie dla nas dużo bardziej czytelny i przejrzysty - jesteśmy w stanie od reki odpowiedzieć na pytanie, czego dokładnie dotyczy dana linijka.

Zadanie:

- zmodyfikować markę samochodu tak, by była globalnym typem wyliczeniowym (przyjmijmy, ze firma kupuje jedynie ople, mazdy i toyoty).

- zmodyfikować zawód pracownika tak, by był typem wyliczeniowym (informatyk, sprzataczka, manager) w zakresie klasy Pracownik (oczywiście publicznym).

Wskazówka:

Zamiast: void zapisz(string i, string n, int w, double p, string z); Będziemy mieli: void zapisz(string i, string n, int w, double p, zawod z);

Zamiast: flota[0].zapisz("Opel",7,200); Będziemy mieli: flota[0].zapisz(Opel,7,200);

Zamiast: personel[0].zapisz("Jan","Kowalski",43,1000.0,"informatyk"); Będziemy mieli: personel[0].zapisz("Jan","Kowalski",43,1000.0,Pracownik::informatyk);

Sprawdzić czy działa!


3. Krok trzeci: Domyslne wartosci argumentow funkcji

To jest niesamowicie proste i przydatne. Powiedzmy, ze tworząc (zapisując) samochod, zazwyczaj nasza firma kupuje samochody spalajace 7 l/km, a gdy są nowe, to ich przebieg wynosi 0.

Modyfikujemy:

 void zapisz(rodzaj marka, double spalanie_na_kilometr, double km)

Dodając "=<jakaś liczba>" przy argumentach funkcji

 void zapisz(rodzaj marka, double spalanie_na_kilometr=7, double km=0)

Wtedy w funkcji main jesli tworzymy samochod, mozemy napisać:

zapisz(opel, 7, 0);

albo

zapisz(opel, 7); - wpisalismy jeden argument mniej

albo

zapisz(opel); - wpisalismy dwa argumenty mniej

albo tez zmienic argumenty:

zapisz(opel, 6, 5000);


Oczywiście, nie muszą być to liczby, tak samo zadziala to z stringiem czy innym typem. Wtedy pamiętamy: najmniej istotne parametry trafiają na koniec, gdyż potem możemy pomijać je tylko od końca - nie moglibyśmy wpisać zapisz(opel, 0) chcąc wyzerować składnik km - bo skąd nasz program ma wiedzieć, że nie chodzi nam o spalanie_na_kilometr? Zawsze będzie przypisywał parametry od początku: pierwszy - pierwszemu, drugi - drugiemu, itd.

Bardzo często i powszechnie stosowane. Zapamiętać!


4. Krok czwarty: Podział na pliki

Na koniec:

Podzielić nasz program na oddzielne klasy: główną (main.cpp), dla pracownika (pracownik.cpp, pracownik.h) oraz dla samochodu (samochod.cpp, samochod.h) - uwaga, pewne modyfikacje mogą być konieczne!


Dodatkowo: zapisywanie do pliku

Napisz funkcję składową, która zapisuje wszystkie informacje o samochodzie do pliku samochod.txt (w takim samym formacie jak funkcja wypisz()).