Laboratorium 3 #
Klasy #
Na tym laboratorium twoim zadaniem będzie przećwiczenie różnych funkcjonalności związanych z klasami, a także eksperymentowanie z inteligentnymi wskaźnikami. Głównym tematem laboratorium są funkcjonalności związane z piosenkami.
Skorzystaj z dostarczonego pliku main.cpp, w którym umieszczone zostały testy do pierwszych 3 etapów zadania. Po zakończeniu implementacji konkretnego etapu odkomentuje odpowiadającą mu część funkcji main i porównaj swój wynik z oczekiwanym (pamiętaj o załączeniu stworzonych przez ciebie plików w main.cpp). Dostarczony jest również startowy plik Makefile.
Etap 1: Implementacja klasy SongDuration
#
W pliku SongDuration.hpp
zaimplementuj klasę SongDuration
, która reprezentuje czas trwania utworu w minutach i sekundach.
Klasa powinna zawierać następujące elementy:
Prywatne pola:
unsigned int minutes
– liczba minut.unsigned int seconds
– liczba sekund.
Konstruktor:
- Zaimplementuj konstruktor przyjmujący liczbę minut i sekund. Jeśli liczba sekund przekracza 59, powinna być automatycznie przeliczona na minuty (np.
SongDuration(1, 65)
powinno skutkować2 minutami i 5 sekundami
).
Metody publiczne:
get_total_seconds()
– zwraca całkowity czas trwania w sekundach.get_formatted()
– zwraca string zawierający czas trwania w formacie"Xm Ys"
, gdzieX
iY
to odpowiednio liczna minut i sekund.
Oczekiwane wyjście etapu 1:
Duration 1: 2m 5s (125s)
Duration 2: 4m 38s (278s)
Etap 2: Implementacja klasy Song
#
W plikach Song.hpp
i Song.cpp
zaimplementuj klasę Song
, która reprezentuje pojedynczy utwór muzyczny.
Klasa powinna zawierać następujące elementy:
Prywatne pola:
std::string title
– tytuł utworu.std::string artist
– wykonawca utworu.SongDuration duration
– czas trwania utworu.
Konstruktory:
- Konstruktor bezargumentowy inicjalizuje tytuł jako
"Untitled"
, wykonawcę jako"Unknown"
, a czas trwania jako 0 sekund. - Konstruktor przyjmujący tytuł, czas trwania (
SongDuration
) oraz wykonawcę (wykonawca powinien mieć domyślną wartość"Unknown"
). - Konstruktor przyjmujący tytuł, czas trwania (w minutach i sekundach) oraz wykonawcę (sekundy powinny mieć domyślną wartość 0, a wykonawca
"Unknown"
).
Uwaga: Pamiętaj o używaniu list inicjalizacyjnych.
Metody publiczne:
print()
– wypisuje informacje o utworze w formacie:<tytuł>, Artist: <wykonawca> [<czas trwania>]
- gettery i settery do pól prywatnych (getter i setter do
duration
powinien przyjmować i zwracać tylko sumaryczną liczbę sekund)
Przypomnienie z wykładu: Gettery i settery to proste funkcje dostarczające kontrolowany dostęp do prywatnego stanu obiektu. Często są to jedno linijkowe funkcje zwracające bądź ustawiające wartość prywatnego pola. W przypadku pojawienia się dodatkowych wymagań dotyczących stanu obiektu, w łatwy sposób pozwala nam to kontrolować czy stan cały czas będzie poprawny poprzez dodatkową walidację w odpowiednich setterach.
Statyczne pole i metodę:
- Pole statyczne
unsigned int count_songs
powinno przechowywać aktualną liczbę stworzonych obiektów typuSong
. - Metoda statyczna
get_total_songs()
zwraca liczbę aktualnie istniejących obiektów typuSong
.
Uwaga: Żeby powyższy licznik działał poprawnie, musisz uwzględnić tę funkcjonalność w konstruktorach i zdefiniować odpowiedni destruktor.
Oczekiwane wyjście etapu 2:
Untitled, Artist: Unknown [0m 0s]
Recursion Anthem, Artist: Stack Overflow [4m 38s]
Binary Love, Artist: The Algorithms [3m 45s]
Segmentation Fault, Artist: Unknown [2m 5s]
Git Happens, Artist: Unknown [2m 0s]
Total songs: 5
Total songs: 3
get_title(): Polymorphic Dreams
get_artist(): Object-Oriented Orchestra
get_duration(): 300
Etap 3: Implementacja klasy Playlist
#
W plikach Playlist.hpp
i Playlist.cpp
zaimplementuj klasę Playlist
, która reprezentuje playlistę utworów.
Klasa będzie podobna w funkcjonalności do implementowanego przez was ostatnio w czasie laboratorium punktowanego wektora.
Piosenki powinny być przechowywane w dynamicznie alokowanej tablicy. Tym razem jednak będziemy pozwalać tylko na dodawanie elementów.
Klasa powinna zawierać następujące elementy:
Prywatne pola:
Song* songs
– wskaźnik na dynamiczną tablicę utworów.unsigned int capacity
– pojemność playlisty.unsigned int size
– aktualna liczba utworów w playliście.
Konstruktory, operatory przypisania i destruktor:
- Konstruktor przyjmujący początkową pojemność playlisty (domyślnie 10).
- Konstruktor kopiujący.
- Operator przypisania.
- Konstruktor przenoszący.
- Operator przypisania z przeniesieniem.
- Destruktor.
Z racji na to, że klasa Playlist
zarządza dynamiczną pamięcią, chcemy zdefiniować dla niej powyższe funkcjonalności zgodnie z regułą 5/0.
Metoda prywatna:
resize(unsigned int new_capacity)
– pomocnicza funkcja zmieniająca pojemność playlisty na podaną wartość.
Metody publiczne:
add_song(const Song& song)
– dodaje utwór do playlisty. Jeśli tablica jest pełna, zwiększ jej pojemność dwukrotnie.get_size()
– zwraca liczbę utworów w playliście.print()
– wypisuje wszystkie utwory w playliście w formacie (lubPlaylist is empty
jeżeli playlista jest pusta):1. <tytuł>, Artist: <wykonawca> [<czas trwania>] 2. <tytuł>, Artist: <wykonawca> [<czas trwania>] ...
Oczekiwane wyjście etapu 3:
Playlist size: 3
1. Polymorphic Dreams, Artist: Object-Oriented Orchestra [5m 0s]
2. Recursion Anthem, Artist: Stack Overflow [4m 38s]
3. Binary Love, Artist: The Algorithms [3m 45s]
Playlist copy (constructor):
1. Polymorphic Dreams, Artist: Object-Oriented Orchestra [5m 0s]
2. Recursion Anthem, Artist: Stack Overflow [4m 38s]
3. Binary Love, Artist: The Algorithms [3m 45s]
Playlist copy (assignment):
1. Polymorphic Dreams, Artist: Object-Oriented Orchestra [5m 0s]
2. Recursion Anthem, Artist: Stack Overflow [4m 38s]
3. Binary Love, Artist: The Algorithms [3m 45s]
Playlist moved (constructor):
1. Polymorphic Dreams, Artist: Object-Oriented Orchestra [5m 0s]
2. Recursion Anthem, Artist: Stack Overflow [4m 38s]
3. Binary Love, Artist: The Algorithms [3m 45s]
Moved from:
Playlist is empty
Playlist moved (assignment):
1. Polymorphic Dreams, Artist: Object-Oriented Orchestra [5m 0s]
2. Recursion Anthem, Artist: Stack Overflow [4m 38s]
3. Binary Love, Artist: The Algorithms [3m 45s]
Moved from:
Playlist is empty
Etap 4: Eksperymentowanie z inteligentnymi wskaźnikami #
W tej części zostawimy już tematykę piosenek ze sobą i będziemy eksperymentować z inteligentnymi wskaźnikami std::shared_ptr
i std::unique_ptr
.
Tę część wykonaj całkowicie w przygotowanej części na końcu funkcji main. Znajdziesz tam pomocniczą klasę, która pomoże obserwować, kiedy obiekt jest tworzony i niszczony.
1. Wykonaj polecenia oznaczone w kodzie jako [TODO 1]
- Oczekiwane wyjście:
A created
B created
C created
Clearing v1
A destroyed
Clearing v2
B destroyed
C destroyed
- Zastanów się, dlaczego dostaliśmy akurat taki wynik.
- Czy wynik zmieniłby się, gdybyśmy usunęli nawiasy
{ }
? Dlaczego? Sprawdź!
Jeżeli chcielibyśmy zrobić to samo tylko z wykorzystaniem std::unique_ptr
mielibyśmy problem, ponieważ jak nazwa wskazuje, może istnieć tylko jeden taki wskaźnik (nie można go kopiować).
Aby umieścić taki wskaźnik w wektorze, musimy skonstruować go od razu w środku wektora.
W tym celu można wykorzystać funkcję emplace_back, która przyjmuje argumenty konstruktora elementu i tworzy element już w wektorze.
1. Wykonaj teraz polecenia oznaczone w kodzie jako [TODO 2]
- Oczekiwane wyjście:
D created
E created
D destroyed
E destroyed