Co to jest klasa, Programowanie, wykłady C++


Co to jest klasa

Klasa to jedno z podstawowych pojęć każdego języka obiektowego, jakim również jest C++. Sens języków obiektowych skupia się wokół pojęcia "obiektu", którego znaczenie każdy chyba wyczuwa intuicyjnie. W klasycznym języku C odpowiednikami obiektów były zmienne, tablice, wskaźniki, struktury itd. Każdy "obiekt" należy do jakiejś klasy. Klasa jest więc pewnym rozszerzeniem klasycznych typów jak int czy double. Na początek warto myśleć o klasie jako o strukturze. Poniższa tabela grupuje główne podobieństwa między klasycznymi strukturami a klasami.

Struktura

Klasa

Definicja

struct osoba
{
  char imie[20];
  char nazwisko[30];
  int wiek;
};

class osoba
{
public :
  char imie[20];
  char nazwisko[30];
  int wiek;
};

Deklaracja instancji

struct osoba prezes;

osoba prezes;

Odwołanie

strcpy(prezes.imie,"Jan");
strcpy(prezes.nazwisko,"Kowalski");
prezes.wiek = 49;

strcpy(prezes.imie,"Jan");
strcpy(prezes.nazwisko,"Kowalski");
prezes.wiek = 49;

Jak widać, w najprostszym wydaniu posługiwanie się klasą jest analogiczne jak w przypadku struktur. Wyjątkiem tu jest słowo public, którego znaczenie stanie się jasne w kolejnych akapitach. Klasa to jednak dużo więcej niż struktura. W kolejnych akapitach zostaną przedstawione główne cechy klas, tak, żeby korzystanie z nich było możliwie bezbolesne.

Funkcje wewnątrz klas

Jest to pierwszy wyróżnik klasy. Klasy, oprócz pól składowych znanych typów, oferują również funkcje, które mogą operować na poszczególnych składnikach. Funkcje składowe klasy są również zwane metodami, jako, że ich istotą jest zazwyczaj wykonanie pewnej czynności na obiekcie. Przykład takiej funkcji podano poniżej.

class osoba
{
public:
  char imie[20];
  char nazwisko[30];
  int wiek;
  void przedstaw_sie()
  {
    cout << imie << " " << nazwisko << endl;
  }
};

Kiedy już mamy zadeklarowany obiekt klasy osoba, możemy wywołać na nim funkcję zdefiniowaną wewnątrz definicji klasy.

osoba prezes;

...

strcpy(prezes.imie,"Jan");
strcpy(prezes.nazwisko,"Kowalski");
prezes.wiek = 49;

Nietrudno zgadnąć co pojawi się na ekranie. Treść funkcji oczywiście nie musi znajdować się wewnątrz definicji klasy. W definicji wystarczy deklaracja funkcji (poprzez zamieszczenie nagłówka), zaś treść może znajdować się w dowolnym innym miejscu programu (poprzez operator ::). w praktyce wyglądałoby to następująco:

class osoba
{
public:
  char imie[20];
  char nazwisko[30];
  int wiek;
  void przedstaw_sie();
};

...

void osoba::przedstaw_sie()
{
  cout << imie << " " << nazwisko << endl;
}

...

strcpy(prezes.imie,"Jan");
strcpy(prezes.nazwisko,"Kowalski");
prezes.przedstaw_sie();

Na koniec drobna dygresja o miejscu zamieszczenia treści funkcji. Z pozoru jest to zupełnie obojętne czy treść funkcji znajduje się wewnątrz definicji klasy, czy poza nią. Dominują tutaj względy czytelności kodu, np. krótką funkcję warto zawrzeć wewnątrz definicji klasy. W rzeczywistości jednak istnieje pewna różnica, mianowicie funkcje zamieszczone w obrębie definicji klasy są typu inline. Oznacza to tyle, że ilekroć kompilator napotka wywołanie funkcji, zamieści jej kopię w miejscu wywołania. W przeciwnym wypadku w miejscu wywołania znajdzie się jedynie odesłanie do miejsca rzeczywistego wystąpienia funkcji. Fakt ten można wykorzystać w celu przyspieszenia działania programu (można jednak równie dobrze zamieścić słowo inline).

Zakres ważności składników klasy

Drugim, niezmiernie ważnym wyróżnikiem klas jest możliwość kontrolowania zakresu "widoczności" poszczególnych pól składowych. Odpowiedzialne są za to trzy słowa kluczowe, inicjujące nowe sekcje wewnątrz definicji klasy:

private :
public :
protected :

Przed podaniem definicji tych słów kluczowych, warto przyjrzeć się krótkiemu przykładowi:

class osoba
{
public :
  char imie[20];
  char nazwisko[30];
  void przedstaw_sie()
  {
    cout << imie << " " << nazwisko << endl;
  }
  int czy_dobry_pin(int pin2)
  {
    if (pin2 == pin) return(1); else return(0);
  }

protected :
  int wiek;

private :
  char numer_konta[50];
  int pin;
};

Krótka analiza powyższej definicji klasy osoba z pewnością rozwieje wiele wątpliwości. Użytkownik klasy może sprawdzić imię i nazwisko danej osoby. Może również poprosić ją o przedstawienie się. Na koniec może też sprawdzić czy podany PIN jest prawidłowy, nie ma jednak bezpośredniego dostępu do PINu, ani do numeru konta. Pomimo intuicyjności słów "private" i "public", potrzebne jest jednak doprecyzowanie znaczenia każdej sekcji.

Zakresy ważności

private

Składniki są widoczne tylko wewnątrz danej klasy. W praktyce oznacza to, że tylko funkcje danej klasy mogą czytać i zapisywać tego typu składniki. Funkcja wewnątrz tej sekcji może być wywołana tylko z innej funkcji aktualnej klasy (lub funkcji zaprzyjaźnionej, o czym będzie mowa w innych rozdziałach). Jeśli składnik klasy nie jest objęty żadną sekcją, to domyślnie należy właśnie do sekcji private.

public

Składniki są widoczne wszędzie gdzie jest użyty obiekt danej klasy.

protected

Składniki są dostępne na nieco bardziej rozszerzonych zasadach niż private. Zakres widoczności jest poszerzony również o klasy wywodzące się z aktualnej klasy (o tym również będzie mowa w kolejnych rozdziałach).

Na koniec krótka dygresja na temat stosowania sekcji public, private oraz protected. Wydawałoby się, że logicznie jest wszystkie składniki umieścić w sekcji public, bo przecież trudno jest przewidzieć do których danych i w którym miejscu potrzebny będzie dostęp. Sztuka stosowania klas polega jednak właśnie na umiejętnym blokowaniu danych do których użytkownik klasy nie potrzebuje dostępu. W sekcji public powinien znajdować się jedynie prosty "interfejs" służący wygodnemu posługiwaniu się obiektem danej klasy. Pozostałe "wnętrzności" powinny być niewidoczne z poziomu użytkowania klasy.

Konstruktor

Konstruktorom i destruktorom będzie poświęcony osobny rozdział, jednak nie można tego ważnego zagadnienia pominąć przy omawianiu głównych cech klas. Konstruktor jest funkcją wywoływaną automatycznie przy powołaniu do życia obiektu danej klasy. Jest to odpowiednik deklaracji zmiennej z wartością początkową:

double pi = 3.14;

Konstruktor jest funkcją o nazwie identycznej jak nazwa klasy. Przy powołaniu do życia instancji danej klasy, funkcja ta zostanie wywołana automatycznie. Najłatwiej prześledzić działanie konstruktora na konkretnym przykładzie klasy.

class ID3_info
{
public:
  char *title;
  char *artist;
  char *album;
  int year;
  char *genre;
  char *comment;
  ID3_info(char *s1, char *s2, char *s3, int n, char *s4, char *s5);
  void ID3_print(void);
};

ID3_info::ID3_info(char *s1, char *s2, char *s3, int n, char *s4, char *s5)
{
  title = (char *)malloc(strlen(s1));
  strcpy(title,s1);
  artist = (char *)malloc(strlen(s2));
  strcpy(artist,s2);
  album = (char *)malloc(strlen(s3));
  strcpy(album,s3);
  year = n;
  genre = (char *)malloc(strlen(s4));
  strcpy(genre,s4);
  comment = (char *)malloc(strlen(s5));
  strcpy(comment,s5);
}

void ID3_info::ID3_print(void)
{
  cout << "Title: " << title << endl;
  cout << "Artist: " << artist << endl;
  cout << "Album: " << album << endl;
  cout << "Year: " << year << endl;
  cout << "Genre: " << genre << endl;
  cout << "Comment: " << comment << endl;
}

Deklaracja obiektu może teraz wyglądać dwojako:

ID3_info x = ID3_info("Youth Of The Nation","P.O.D.","Satellite",2001,"Rock","Hot!");

lub:

ID3_info x("Youth Of The Nation","P.O.D.","Satellite",2001,"Rock","Hot!");

Wywołanie funkcji ID3_print wygląda oczywiście standardowo:

x.ID3_print();

Jak widać, konstruktor znakomicie wywiązuje się jako inicjalizator obiektu. Należy pamiętać o tym, że konstruktor nie może zwracać żadnej wartości. Natomiast nazwa konstruktora może być (i praktycznie zawsze jest) wielokrotnie przeładowana. O tym będzie jeszcze mowa w dygresjach.

Destruktor

Podobnie jak funkcja wywoływana automatycznie przy inicjalizacji obiektu (konstruktor), istnieje funkcja wywoływana automatycznie przy zakończeniu istnienia obiektu - destruktor. Destruktor jest deklarowany analogicznie jak konstruktor, z tym wyjątkiem, że nazwa jest poprzedzona znakiem ~. Rozszerzając poprzedni przykład:

class ID3_info
{
public:
  char *title;
  char *artist;
  char *album;
  int year;
  char *genre;
  char *comment;
  ID3_info(char *s1, char *s2, char *s3, int n, char *s4, char *s5);
  ~ID3_info(void);
  void ID3_print(void);
};

ID3_info::ID3_info(char *s1, char *s2, char *s3, int n, char *s4, char *s5)
{
  title = (char *)malloc(strlen(s1));
  strcpy(title,s1);
  artist = (char *)malloc(strlen(s2));
  strcpy(artist,s2);
  album = (char *)malloc(strlen(s3));
  strcpy(album,s3);
  year = n;
  genre = (char *)malloc(strlen(s4));
  strcpy(genre,s4);
  comment = (char *)malloc(strlen(s5));
  strcpy(comment,s5);
}

ID3_info::~ID3_info(void)
{
  free(title);
  free(artist);
  free(album);
  free(genre);
  free(comment);
}

void ID3_info::ID3_print(void)
{
  cout << "Title: " << title << endl;
  cout << "Artist: " << artist << endl;
  cout << "Album: " << album << endl;
  cout << "Year: " << year << endl;
  cout << "Genre: " << genre << endl;
  cout << "Comment: " << comment << endl;
}

Jak widać, destruktor w powyższym przykładzie uwalnia pamięć zaalokowaną przez konstruktor. Destruktor ten będzie wywołany automatycznie przy zakończeniu działania programu, jednak można go wywołać ręcznie:

ID3_info x("Youth Of The Nation","P.O.D.","Satellite",2001,"Rock","Hot!");

...

x.~ID3_info();

To była chyba ostatnia ważna cecha klas. Do wyjaśnienia pozostają pewne wątpliwości i często stosowane "chwyty". Zebrano je poniżej w postaci "dygresji".

Funkcje i klasy zaprzyjaźnione

Funkcja zaprzyjaźniona z klasą to taka, która nie jest składnikiem klasy, a ma dostęp do wszystkich jej elementów (tak jak funkcje składowa klasy). Deklarację przyjaźni składa klasa (a nie funkcja która chce się przyjaźnić) słowem kluczowym friend. Funkcje zaprzyjaźnione mają co nieco trudniejszy dostęp do składników klasy niż zwykłe funkcje składowe, bowiem nie ma domyślnego wskaźnika this. Dlatego trzeba zawsze przekazywać do tego typu funkcji wskaźnik lub referencję obiektu, na którym chcemy pracować. Poniższy przykład rozwieje wszelkie wątpliwości.

#include <iostream.h>

#include <math.h>

//--------------------------------------------------

class kwadrat

{

private:

int x1,y1,x2,y2;

public:

kwadrat(int xp1, int yp1, int xp2, int yp2);

friend int pole(kwadrat &wsk);

};

//--------------------------------------------------

kwadrat::kwadrat(int xp1, int yp1, int xp2, int yp2)

{

x1 = xp1;

y1 = yp1;

x2 = xp2;

y2 = yp2;

}

//--------------------------------------------------

int pole(kwadrat &ref)

{

return(abs(ref.x2-ref.x1)*abs(ref.y2-ref.y1));

}

//--------------------------------------------------

void main(void)

{

kwadrat a(1,1,5,5);

cout << "Pole = " << pole(a) << endl;

cin.get();

}

Również całe klasy mogą być zaprzyjaźnione. Wówczas wszystkie składowe i funkcje danej klasy są dostępne dla klasy, z którą zadeklarowano przyjaźń. Na koniec trzeba zapamiętać, że przyjaźń w języku C++ nie jest domyślnie obustronna. Oznacza to, że jeśli klasa A jest przyjacielem klasy B, to wcale nie oznacza, że klasa B jest od razy przyjacielem klasy A. Ten fakt trzeba zadeklarować. Przyjaźń również nie jest przechodnia, czyli jeśli klasa B jest przyjacielem klasy A, oraz klasa C jest przyjacielem klasy B, to klasa C nie jest od razu przyjacielem klasy A.

Dygresja 1: odwołanie się do obiektu

Istnieją zasadniczo trzy sposoby odwołania się do obiektu należącego do danej klasy:

Dygresja 2: słowo kluczowe this

Funkcja składowa danej klasy może odwoływać się bezpośrednio do pozostałych składników tej klasy. Jest oczywiste, o który składnik chodzi, nawet jeśli wywołanie jest na zewnątrz definicji klasy, np:

osoba z;

...

void osoba::przedstaw_sie()
{
  cout << imie << " " << nazwisko << endl;
}
...

z.przedstaw_sie();

Pozornie do funkcji przedstaw_sie nie został przekazany wskaźnik do obiektu z. W praktyce jednak przekazanie wskaźnika rzeczywiście ma miejsce, jednak "za naszymi plecami". Wskaźnik ten jest obecny wewnątrz funkcji przedstaw_sie pod nazwą this. Tak więc rzeczywista postać tej funkcji powinna być następująca:

void osoba::przedstaw_sie()
{
  cout << this->imie << " " << this->nazwisko << endl;
}

Oczywiście słowa this nie potrzeba pisać w kodzie, bowiem wskaźnik do bieżącego obiektu przyjmowany jest domyślnie, czasem jednak wiedza o adresie aktualnego obiektu może być użyteczna. W takim przypadku warto pamiętać o wskaźniku this.

Dygresja 3: przeładowanie nazwy funkcji

Bardzo często ma miejsce przeładowanie nazwy funkcji składowej dla klasy, tzn. ta sama funkcja jest definiowana dla różnego zestawu parametrów. Która funkcja zostanie wybrana zależy od sposobu jej wywołania. Najczęstszy przypadek przeładowania dotyczy konstruktorów (i destruktorów):

class plik
{
public:
  char nazwa[256];
  char sciezka[256];
  long rozmiar;
  plik(char *s1);
  plik(char *s1,char *s2);
  plik(char *s1,char *s2, long n);
  plik(char *s1,long n);
}

plik::plik(char *s1)
{
  strcpy(nazwa,s1);
}

plik::plik(char *s1, char *s2)
{
  strcpy(nazwa,s1);
  strcpy(sciezka,s2);
}

plik::plik(char *s1, char *s2, long n)
{
  strcpy(nazwa,s1);
  strcpy(sciezka,s2);
  rozmiar = n;
}

plik::plik(char *s1, long n)
{
  strcpy(nazwa,s1);
  rozmiar = n;
}

Sposób zadeklarowania obiektu będzie przesądzał który z przeładowanych konstruktorów zostanie użyty:

plik plik1("win.com");
plik plik1("win.com","c:\\windows\\");
plik plik1("win.com","c:\\windows\\",25495);
plik plik1("win.com",25495);

Dygresja 4: zasłanianie nazw

Podobnie jak zmienna lokalna w funkcji zasłania zmienną globalną, składnik klasy zasłania zmienną zewnętrzną o takiej samej nazwie.
Ponadto...
Składnik klasy może również zostać zasłonięty, jeśli wewnątrz funkcji składowej zostanie zdefiniowana zmienna o takiej samej nazwie.
Jednak...
Do składnika globalnego (lub względnie globalniejszego) można się "dostać" poprzez operator zakresu (::).
Ogólnie jednak technika zasłaniania nazw jest dosyć ryzykowna i należy ją stosować ze szczególną "ostrożnością".

Dygresja 5: słowa kluczowe static, const oraz volatile

Słowa te mają co nieco rozszerzone znaczenie dla klas niż w przypadku klasycznego języka C. Poniższa tabela pokrótce wyjaśnia sens stosowania tych technik.

static

Składnik statyczny ma sens w odniesieniu do danej składowej wewnątrz klasy, oraz w odniesieniu do funkcji pracujących na tego typu danych składowych. Wówczas będzie istniała wspólna zmienna składowa dla każdego obiektu danej klasy powołanego do życia. Oznacza to tyle, że niezależnie ile obiektów powołamy to życia, to odwołanie do składnika statycznego będzie zawsze dotyczyło tej samej komórki pamięci. Ma to zastosowanie np. w liczniku istniejących obiektów:

class obiekcik
{
public:
  static int licznik_obiektow;
  obiekcik(void) { licznik_obiektow++; }
  ~obiekcik(void) { licznik_obiektow--; }
}

const

Słowo to ma sens dla obiektu którego wartość jest ustalona raz na zawsze, np:

const osoba a("albert","einstein");

Na tego typu obiektach mogą być wykonywane tylko funkcje zadeklarowane jako const, które nie modyfikują żadnej składowej obiektu, np.

void osoba::przedstaw_sie() const
{
  cout << imie << " " << nazwisko << endl;
}

volatile

Obiekty i funkcje volatile to obiekty o "specjalnej trosce". Kompilator musi za każdym razem sięgać na nowo po wartość tego typu obiektów, choćby istniała aktualna kopia np. w cache'u. Ma to znaczenie dla obiektów aktualizowanych w sposób nieprzewidywalny, np. przez elementarne podzespoły komputera. Do tego typu funkcji i składowych odnoszą się te same zasady jak przy const.

0x01 graphic

Zadania do wykonania

Do wykonania na zajęciach jest projekt i realizacja własnej klasy w następującej kolejności:



Wyszukiwarka

Podobne podstrony:
Co to jest agroturystyka, Turystyka i rekreacja wykłady, Agroturystyka
wyklad 1 co to jest kultura popularna(1)
CO TO JEST PROGRAM PRZYJACIELE ZIPPIEGO
EDoc 6 Co to jest podpis elektroniczny slajdy
Co to jest seie
Co to jest teoria względności podstawy geometryczne
Co to jest widmo amplitudowe sygnału, SiMR, Pojazdy
CO TO JEST SORBCJA, Ochrona Środowiska
25. Co to jest metoda PCR i do czego służy - Kopia, Studia, biologia
Co to jest budzet panstwa, prawo, Finanse
CO TO JEST TEORIA, POLONISTYKA, 1
Str '1 rozdz. Co to jest umysł' Ryle, Filozofia UŚ
Co to jest wada wymowy, logopedia
Lekcja 2- Co to jest szkoła wyższa, studia różne
Co to jest REIKI, Rozwój duchowy, Reiki
Co to jest informacja geologiczna
Co to jest integracja sensoryczna
amortyzacja sposób olbiczenia i co to jest

więcej podobnych podstron