Na początku przepraszam wszystkich, że przez jakiś czas nie pisałem - miałem OI. Postaram się być systematyczniejszy ;)
1. KLASY
Klasy w C++ pozwalają zdecydowanie inaczej spojrzeć na programowanie. Do tej pory w programach korzystaliśmy z niezależnych od siebie (praktycznie) zmiennych i funkcji - wszystko było zapisywane oddzielnie. Klasy natomiast pozwalają na łączenie ich w jedną, przejrzystą (najczęściej) całość.
Klasę definiuje się poprzez podanie jej nazwy i wnętrza:
class jakas_nazwa
{
... // wnętrze
};
Aby stworzyć obiekt jakiejś klasy, należy podać jej nazwę, a następnie nazwę obiektu. Przykładowo:
class facet // definicja klasy
{
int wzrost, waga;
float zarobki;
};
facet ktos; // tworzenie obiektu klasy facet
W klasie generalnie mogą znajdować się zmienne oraz funkcje. Do poszczególnych elementów klasy odwołujemy się poprzez kropkę ( . ), np. tomek.zarobki = 1500;
Gdybyśmy jednak spróbowali skompilować program z taką instrukcją, kompilator stwierdziłby błąd. Dlaczego? Otóż należy wiedzieć, że zmienne definiowane w klasach dzieli się na 3 rodzaje: public, private oraz protected. Do zmiennych typu public (publiczne) mamy dostęp praktycznie w każdym miejscu w programie, natomiast od private i protected już nie. Jeśli nie określimy na początku rodzaju zmiennej, będzie ona traktowana jako private. Na pytanie, po co nam w takim razie te dwa ostatnie typy, skoro nie można z nich "normalnie" korzystać odpowiem za chwilę. Najpierw zmodyfikujmy odpowiednio naszą klasę, aby intrukcja wpisania czegoś do zmiennych była możliwa bezpośrednio z poziomu funkcji main():
class facet
{
int wzrost, waga;
public: // wszystkie zmienne ponizej naleza juz do public
float zarobki;
char inicjaly[2];
};
Jak więc zmienić wartość zmiennych private? Na początku powiedzieliśmy sobie, że w klasie możemy zdefiniować również funkcje, które (ze względu na swoje miejsce zdefiniowania) mogą posłużyć nam do dokonywania operacji na składnikach typu private. Zobaczmy przykład:
class facet
{
int wzrost, waga;
public:
float zarobki;
char inicjaly[2];
void przytyl(int ile_kilo) // funkcja public
{
waga += ile_kilo;
}
}
Wywoływanie funkcji jakieś klasy wygląda dokładnie tak samo, jak odnoszenie się do zmiennych, np.
obiekt.przytyl(25);
Oczywiście funkcje również mogą być typu private. Wtedy do ich wywołania możemy posłużyć się innymi funkcjami:
class facet
{
int wzrost, waga; // zmienne private
public: // ponizej 2 zmienne public
float zarobki;
char inicjaly[2];
private: // znowu private
void przytyl(int ile_kilo)
{
waga += ile_kilo;
}
public:
void wywolaj() // funkcja public
{
int ile;
cout << \"Uwaga! Zaraz zostanie wywolana funkcja przytyl(). Ile kilogramow? \";
cin >> ile;
przytyl(ile); // wywolanie funkcji private
cout << \"Funkcja przytyl() zostala wywolana\";
}
}; // koniec definicji klasy
Zapisywanie definicji wszystkich funkcji składowych bezpośrednio w klasie nie zawsze jest przydatne - dla lepszej czytelności kodu wypadałoby czasami umieścić definicję już poza klasą. Możemy uczynić to bardzo prosto:
typ_zwracany nazwa_klasy::nazwa_funkcji(argumenty);
Dla naszej klasy facet wyglądałoby to tak:
class facet
{
int waga;
void funkcja();
}
void facet::funkcja()
{
... // cialo funkcji
}
Tworząc klasę, tworzymy nowy typ danych. Po zdefiniowaniu obiektu klasy możemy z nim robić dokładnie to (a nawet więcej), co ze zwykłymi zmiennymi (np. tworzyć tablice obiektów). Należy jednak uważać. Przykładowo mamy funkcję, której argumentem jest obiekt klasy:
void wyswietl_dane(facet ktos) // argumentem jest obiekt ktos klasy facet
{
cout << ktos.zarobki << endl;
ktos.wywolaj()
}
Wszystko fajnie, ale jeśli klasa jest bardzo rozbudowana albo jeśli zostanie zastosowana rekurencja (wywołanie funkcji przez samą siebie), to przesyłanie argumentu przez wartość (jak wyżej) może okazać się niepotrzebnym marnowaniem pamięci. W takim wypadku powinniśmy korzystać raczej z przesyłania obiektu przez referencję (zapis z & - przypomnij sobie z lekcji o funkcjach):
void wysiwetl_dane(&facet ktos)
Definicję klasy można umieszczać również w osobnych plikach nagłówkowych. Przykładowo:
Plik klasa_facet.h:
class facet
{
... // wnetrze klasy
};
Plik programu - program.cpp:
#include "klasa_facet.h" // dolaczanie definicji do programu
main()
{
facet ktos; // mozemy stworzyc obiekt typu facet
... // instrukcje programu
}
Do zapamiętania z rozdziału:
- klasy pozwalają łączyć zmienne i funkcje w logiczną całość
- składniki klasy mogą być określone jako: public, private i protected (do tego ostatniego jeszcze dojdziemy)
- z "zewnątrz" klasy można się odnosić jedynie do składników public
- do składników klasy odnosimy się poprzez podanie nazwy obiektu oraz kropki
- klasy można zapisywać w osobnych plikach
Zadanie:
Stwórz dowolną klasę - zdefiniuj zmienne i napisz funkcję wyświetlającą ich wartości (przekaż obiekt klasy przez referencję). Jedną ze stworzonych przez Ciebie zmiennych powinien być obiekt jakiejś innej klasy - zastanów się, jak odwołać się do jego "podzmiennych".
2. KONSTRUKTOR i DESTRUKTOR - WSTĘP
W tym rozdziale jedynie wspomnimy o konstruktorach i destruktorach - ich omawianie będziemy kontynuować na lekcji następnej.
Co to takiego konstruktor? Jak sama nazwa wskazuje, ma on za zadanie konstruować (choć jak się za chwilę przekonamy, raczej pracować na skonstruowanym :P).
Konstruktor jakieś klasy będzie zatem obecny przy stwarzaniu jej obiektów. W praktyce okazuje się ona bardzo przydatny.
Jak zapewne pamiętasz, w wypadku zwyczajnych typów danych można było od razu (czyt. przy tworzeniu) przypisywać wartości zmiennym, np. int zmienna = 25;
Spróbujmy teraz w podobny sposób przypisać wartości składnikom klasy:
class facet
{
int waga = 100;
};
Przy próbie kompilacji zostanie zgłoszony błąd. Na chłopski rozum: nie wolno nam z góry zakładać, że każdy człowiek (każdy obiekt klasy facet) będzie ważył 100 kilogramow. Możnaby oczywiście dopisać funkcję, którą wywoływalibyśmy po stworzeniu każdego obiektu:
class facet
{
int waga;
public:
void tworzenie()
{
waga = 100;
}
};
Aby stworzyć w ten sposób obiekt musielibyśmy napisać 2 linijki kodu:
facet ktos;
ktos.tworzenie();
Konstruktor pozwala nam zaoszczędzić nieco klawiatury: jest on bowiem wywoływany przy tworzeniu każdego obiektu danej klasy. Definiuje się go podobnie, jak inne
funkcje składowe klasy, z tą jednak różnicą, że nazwa konstruktora jest zawsze identyczna, jak nazwa klasy. Nie posiada on również typu zwracanego. Zobaczmy przykład:
class facet
{
int waga;
public:
facet(int ile) // konstruktor
{
waga = ile;
cout << "Powstal wlasnie obiekt klasy FACET";
}
}
Aby wywołać konstruktor piszemy przy definicji obiektu:
facet ktos = facet(70);
Istnieje również drugi, krótszy zapis:
facet ktos(70);
Uwaga! Należy zawsze pamiętać, że zarówno konstruktor, jak i destruktor muszą należeć do sekcji public w klasie.
Ponieważ konstruktor jest funkcją, można jego definicję "wyrzucić" poza klasę:
class facet
{
int waga;
public: // public!
facet(int ile); // deklaracja konstruktora
} // koniec definicji klasy
facet::facet(int ile) // nazwa_klasy::konstruktor
{
waga = ile;
}
Analogicznie jest w wypadku destruktora - jest on wywoływany, gdy obiekt klasy zostaje usunięty. Jego definicja wygląda tak samo, jak definicja konstruktora, z tą jednak różnicą, że przed nazwą występuje tylda (~). Przykład:
class facet
{
int waga;
public:
~facet(int ile) // definicja destruktora
{
cout << "Zostal wywolany destruktor";
}
Temat destruktorów (podobnie jak i konstruktorów) szerzej poruszymy na następnej lekcji - na razie jedynie zasygnalizowaliśmy problem.
Do zapamiętania z rozdziału:
- konstruktor jest uruchamiany podczas tworzenia obiektu, a destruktor przy usuwaniu
- konstruktor i destruktor mają nazwę taką, jak nazwa klasy (destruktor ma dodatkowo znak tyldy)
Zadanie:
* Spróbuj napisać funkcję, która sztucznie (czyt. nie w momencie usuwania) wywoła destruktor dla danego obiektu (przekaż go do funkcji przez wartość).
Dygresja na temat struktur (struct)
W czystym C pojęcie klasy nie występuje. Zamiast tego jest struktura (możemy jej również używać w C++), która funkcjonuje nie inaczej, jak klasa ze wszystkimi składnikami publicznymi (public). Tworzy się ją również prawie identycznie:
struct facet
{
int waga;
char[2] inicjaly;
}
Definiowanie zmiennych (np. facet ktos) czy odwoływanie się do składowych (np. ktos.waga = 70) również jest takie samo, jak w klasach. Należy jednak pamiętać, że w strukturach występują jedynie zmienne (traktowane jako public) - nie mogą pojawiać się tu żadne funkcje.
W następnej części (oprócz omawiania konstruktorów i destruktorów) powiemy sobie o funkcjach zaprzyjaźnionych.