Klasy, Programowanie, wykłady C++


Klasy (obiekty)

Wszystko co do tej pory zostało omówione, stanowi wiedzę podstawową. Jeśli umiesz biegle posługiwać się wszystkimi zagadnieniami jakie zostały do tej pory omówione, warto zacząć pogłębiać tajniki C++, ucząc się programowania obiektowego.

Jakie są zalety programowania obiektowego

W bardzo dużym uogólnieniu programowanie obiektowe umożliwia organizowanie kodu źródłowego w sposób bardziej przyjazny dla programisty. Jest to podstawowa własność programowania obiektowego, która generalnie sprowadza się do tego, aby zamknąć coś w 'pudełko' i posługiwać się pudełkiem, a nie porozrzucanymi częściami (funkcjami i zmiennymi) naszego programu.

Jeśli potrafisz wymyślić program, który coś będzie robił i jeśli ten program będzie wykorzystywał napisane przez Ciebie funkcje i stwierdzisz, że trochę niedopracowana jest organizacja kodu w C++, to będzie to najwyższa pora na to, żeby zacząć się uczyć programowania obiektowego.

Jak zbudowana jest klasa w C++

Klasa w bardzo dużym uogólnieniu przypomina definicję struktury. Jej zapis wygląda następująco:

class NazwaTwojejKlasy
{
 
//w tym miejscu piszemy definicje typów, zmienne  i funkcje jakie mają należeć do klasy.
};//tutaj średnik jest bardzo ważny!

Jak się korzysta z klasy

Klasy wykorzystuje się w praktyce tak samo jak struktury. Przykład:

class TwojaKlasa
{
};

int main()
{
  TwojaKlasa nazwaZmiennej;
 
return(0);
}

Prawa dostępu do klasy

Jeśli klasy deklaruje się tak samo jak struktury i jeśli wykorzystuje się tak samo jak struktury, możesz zacząć się zastanawiać czym tak naprawdę różni się klasa od struktury. Jeśli napiszemy następujący program:

class TwojaKlasa
{
 
int abc;
};

int main()
{
  TwojaKlasa nazwaZmiennej;
  nazwaZmiennej.abc=10;
 
return(0);
}

i spróbujemy go skompilować, otrzymamy następujący błąd kompilacji:


In function `int main()':
`int TwojaKlasa::abc' is private
within this context

Domyślnie dostęp do wszystkich elementów klasy jest ustawiony na prywatny. Dostęp prywatny oznacza, że nie można wykorzystywać zmiennych i funkcji zadeklarowanych w klasie 'na zewnątrz', czyli poprzez użyty tutaj zapis: nazwaZmiennej.abc=10;. Ze strukturami takiego problemu nie było, ponieważ przyjmują one domyślnie prawa dostępu publiczne dla wszystkich zadeklarowanych zmiennych wewnątrz struktury.

Słowa kluczowe private, protected, public

C++ umożliwia łatwe modyfikowanie praw dostępu do wybranych zmiennych i funkcji klasy. Służą do tego trzy słowa kluczowe: private, protected i public.

Słowo kluczowe private

Słowo kluczowe private oznacza dostęp do zmiennych i funkcji tylko z poziomu klasy. Nie są one widoczne poza klasą, więc użytkownik klasy nie będzie mógł uzyskać dostępu do zmiennej (lub funkcji) spoza klasy.

Słowo kluczowe protected

Słowo kluczowe protected ma takie same własności co słowo kluczowe private. Różnice pojawiają się tylko wtedy, gdy przyjdzie nam dziedziczyć klasy. Jeśli będziemy dziedziczyli klasę A do klasy B to zmienne oznaczone jako private w klasie A będą niewidoczne dla klasy B. Jeśli zmienne (i funkcje) klasy A będą miały prawo dostępu protected to takie zmienne (i funkcje) będą widoczne dla klasy B.

Słowo kluczowe public

Dostęp publiczny umożliwia uzyskiwanie dostępu do dowolnej zmiennej i funkcji z dowolnego miejsca w kodzie (jeśli oczywiście zachowana jest logika programowania w C++).

Jak się zmienia prawa dostępu w praktyce

Ustawianie praw dostępu jest bardzo prostą czynnością. Najprościej będzie to pokazać na przykładzie.

#include <string>
class TwojaKlasa
{
 
public:
 
double liczba;//prawo dostępu: publiczne
 
char tablica[20];//prawo dostępu: publiczne

 
private:
 
int abc;//prawo dostępu: prytatne
 
char znak;//prawo dostępu: prytatne
 
std::string napis;//prawo dostępu: prytatne
};

int main()
{
  TwojaKlasa nazwaZmiennej;
 
return(0);
}

Czy to koniec nauki o klasach

Rozdział ten stanowi tylko wstęp do programowania obiektowego. Kolejne rozdziały będą koncentrowały się już na praktycznym wykorzystaniu klas i będą pokazywały prawdziwe możliwości programowania obiektowego. Teoria, która została tu przedstawiona będzie wykorzystywana w każdym kolejnym programie wykorzystującym klasy.

Tworzenie funkcji w klasie

Cała znajomość programowania jaką poznałeś do tej pory sprowadzała się do programowania strukturalnego. Wszystkie funkcje, które pisałeś do tej pory miały zasięg globalny, czyli były widoczne z każdego miejsca w projekcie, co mogło zacząć sprawiać trudności z zapewnieniem unikatowych nazw funkcji dla całego projektu, który pisałeś. Często pewnie zdarzały Ci się również sytuacje, w których tworzyłeś funkcje, do jakich przeciętny użytkownik nie powinien mieć dostępu. Jeśli miałeś takie problemy lub chcesz ich uniknąć w przyszłości rozdział ten pokaże Ci jak można w bardzo prosty sposób je rozwiązywać.

Deklaracja funkcji wewnątrz klasy

Pierwszą, a zarazem najprostszą metodą na utworzenie funkcji w klasie jest napisanie jej wewnątrz klasy. Przykład:

class EksperymentCL
{
 
private:
 
int TwojaPrywatnaFunkcja(int a,int b)
 
{
   
return(a-b+2);
 
}
 
public:
 
int TwojaPublicznaFunkcja(int a,int b)
 
{
   
return((a+b)*2);
 
}
};

Funkcje, które zostały zadeklarowane jako publiczne, będą mogły być wywoływane spoza klasy. Funkcje prywatne mogą być wywołane tylko i wyłącznie wewnątrz klasy (we wszystkich funkcjach należących do klasy). Tworzenie funkcji prywatnych jest bardzo wygodne, ponieważ umożliwia nam dzielenie skomplikowanej, zazwyczaj publicznej funkcji na mniejsze elementy, które przy późniejszej analizie i nanoszeniu ewentualnych poprawek sprawia dużo mniej kłopotów niż modyfikacja jednego długiego kodu funkcji.

Definicja funkcji wewnątrz klasy, deklaracja poza klasą

Pierwsza metoda, która została przedstawiona jest bardzo wygodna w użyciu, jednak ma takie same minusy jakie miały funkcje pisane w bloku głównym - kompilator nie widzi funkcji, które są 'wyżej'. Jeśli więc chcielibyśmy wywołać z funkcji TwojaPrywatnaFunkcja(), funkcję TwojaPublicznaFunkcja () to kompilator wyrzuciłby nam błąd. Drugą wadą jest to, że czytelność kodu bardzo się obniża przez to, że nie widać czy dana funkcja należy do klasy czy też nie (nie wspominając o zmiennych). Aby temu zaradzić, ten sam kod co poprzednio można zapisać następująco:

class EksperymentCL
{
 
private:
 
int TwojaPrywatnaFunkcja(int a,int b);
 
public:
 
int TwojaPublicznaFunkcja(int a,int b);
};

int EksperymentCL::TwojaPrywatnaFunkcja(int a,int b)
{
 
return(a-b+2);
}

int EksperymentCL::TwojaPublicznaFunkcja(int a,int b)
{
 
return((a+b)*2);
}

Jak korzystać z funkcji będących wewnątrz klasy w praktyce

Aby skorzystać z funkcji, które są umieszczone wewnątrz klasy, musimy utworzyć sobie najpierw zmienną, a następnie za pomocą utworzonej zmiennej wywołujemy funkcje klasy. Dla powyższego przykładu wyglądałoby to tak:

#include <iostream>
#include <conio.h>

using namespace std;

class EksperymentCL
{
 
private:
 
int TwojaPrywatnaFunkcja(int a,int b);
 
public:
 
int TwojaPublicznaFunkcja(int a,int b);
};

int EksperymentCL::TwojaPrywatnaFunkcja(int a,int b)
{
 
return(a-b+2);
}

int EksperymentCL::TwojaPublicznaFunkcja(int a,int b)
{
 
return((a+b)*2);
}

int main()
{
  EksperymentCL zmienna;
  cout<<
"Wynik: "<<zmienna.TwojaPublicznaFunkcja(3,4)<<endl;
  getch();
 
return(0);
}

Zaprezentowany przykład jest niezbyt ciekawy, jednak pokazuje on jak działają klasy i funkcje w klasach oraz jak należy z nich korzystać.

Przykład

Poniższy przykład prezentuje praktyczne wykorzystywanie funkcji w klasach.

#include <iostream>
#include <iomanip>
#include <string>
#include <conio.h>

using namespace std;
class OsobaCL
{
 
protected:
 
string m_imie;
 
string m_nazwisko;
  OsobaCL* m_nastepnaOsoba;
 
public:
 
void Inicjuj();
 
void Sprzatnij();
 
void Wypelnij();
  OsobaCL* GetNastepnaOsoba();
 
void ShowDaneOsoby(int fImieWidth=12,int fNazwiskoWidth=20);
 
void DodajOsobeNaKoniec();
};

void OsobaCL::Inicjuj()
{
  m_nastepnaOsoba=NULL;
}

void OsobaCL::Sprzatnij()
{
 
if(m_nastepnaOsoba!=NULL)
 
{
    m_nastepnaOsoba->Sprzatnij();
   
delete m_nastepnaOsoba;
    m_nastepnaOsoba=NULL;
 
}
}

OsobaCL* OsobaCL::GetNastepnaOsoba()
{
 
return(m_nastepnaOsoba);
}

void OsobaCL::Wypelnij()
{
  cout<<
"Podaj imie: ";
  cin>>m_imie;
  cout<<
"Podaj nazwisko: ";
  cin>>m_nazwisko;
}

void OsobaCL::ShowDaneOsoby(int fImieWidth,int fNazwiskoWidth)
{
  cout<<setw(fImieWidth)<<m_imie<<
" "<<setw(fNazwiskoWidth)<<m_nazwisko<<endl;
}

void OsobaCL::DodajOsobeNaKoniec()
{
 
//Szukanie ostatniej osoby
  OsobaCL* tOstatniaOsoba=this;
//Zapisanie wskaźnika obecnej osoby do zmiennej
 
while(tOstatniaOsoba->m_nastepnaOsoba!=NULL) tOstatniaOsoba=tOstatniaOsoba->m_nastepnaOsoba;

 
//Utworzenie nowej osoby i zapisanie do ostatniej osoby
  OsobaCL* tNowaOsoba=
new OsobaCL;
  tNowaOsoba->Inicj
uj();
  tNowaOsoba->Wypelnij();
  tOstatniaOsoba->m_nastepnaOsoba=tNowaOsoba;
}

int main()
{
 
/*Utworzenie pierwszej osoby*/
  OsobaCL* tPierwszaOsoba=
new OsobaCL;
  tPierwszaOsoba->Inicjuj();

 
/*Wypełnienie pierwszej osoby*/
  tPierwszaOsoba->Wypelnij
();

 
/*Wczytywanie kolejnych osób*/
 
char tZnak;
 
do
 
{
    cout<<
"Czy chcesz dodac nowa osobe? (T/N) ";
   
do
   
{
      tZnak=getch();
   
}while((tZnak!='n')&&(tZnak!='N')&&(tZnak!='t')&&(tZnak!='T'));
    cout<<tZnak<<endl;
   
if((tZnak=='t')||(tZnak=='T')) tPierwszaOsoba->DodajOsobeNaKoniec();//Dodawanie nowej osoby
 
}while((tZnak!='n')&&(tZnak!='N'));

 
/*Wyświetlanie wszystkich osób*/
  OsobaCL* tWyswietlOsoby=tPierwszaOsoba;
 
while(tWyswietlOsoby!=NULL)
 
{
    tWyswietlOsoby->ShowDaneOsoby();
    tWyswietlOsoby=tWyswietlOsoby->GetNastepnaOsoba();
 
}

 
/*Zwalnianie pamięci*/
  tPierwszaOsoba->Sprzatnij();
 
delete tPierwszaOsoba;

  getch();
 
return(0);
}

Konstruktor, destruktor i konstruktor kopiujący klasy

Krótkie przytoczenie faktów

Bardzo często, a właściwie prawie zawsze tworząc klasę mamy potrzebę zainicjowania jej początkowymi wartościami. Do tej pory, aby to uczynić musiałeś tworzyć publiczną funkcję wewnątrz klasy, a następnie pamiętać o każdorazowym jej wywołaniu zaraz po zarezerwowaniu pamięci. Język C++ umożliwia dużo lepsze rozwiązanie niż to, które stosowaliśmy do tej pory.

Konstruktor klasy

Konstruktor jest specyficzną funkcją, która jest wywoływana zawsze gdy tworzony jest obiekt. Jeśli programista nie utworzy konstruktora dla klasy, kompilator automatycznie utworzy konstruktor, który nic nie będzie robił. Konstruktor nie pojawi się nigdzie w kodzie, jednak będzie on istniał w skompilowanej wersji programu i będzie wywoływany za każdym razem, gdy będzie tworzony obiekt klasy. Jeśli chcemy zmienić domyślne własności konstruktora jaki jest tworzony przez kompilator C++ wystarczy, że utworzymy własny konstruktor dla klasy. Poniższy przykład pokazuje jak zapisuje się konstruktor dla klasy.

class NazwaTwojejKlasy
{
 
public:
  NazwaTwojejKlasy();
//To jest definicja konstruktora
};

NazwaTwojejKlasy::NazwaTwojejKlasy()
{
 
//Tu inicjujemy wartości zmiennych klasy
}

Pierwsze co rzuca się w oczy w przypadku konstruktora, to brak zwracanego typu danych. Druga istotna własność konstruktora to jego nazwa. Konstruktor musi nazywać się tak samo jak nazwa klasy. Konstruktorom możesz przekazywać parametry tak samo jak funkcjom. C++ umożliwia również tworzenie kilku konstruktorów dla jednej klasy (muszą się one jednak różnić parametrami wejściowymi tak jak w przypadku funkcji).

Nadawanie domyślnych wartości zmiennym w chwili narodzin klasy

Gdy tworzymy klasę, wszystkie zmienne jakie są zadeklarowane wewnątrz niej są zainicjowane przypadkowymi wartościami, które w konstruktorze następnie ustawiamy na takie, jakie uważamy za stosowne. Przykład:

#include <iostream>
#include <conio.h>
using namespace std;
class JakasKlasa
{
 
int a;
 
char b;
 
std::string c;
 
public:
  JakasKlasa();
};

JakasKlasa::JakasKlasa()
{
  cout<<
"Klasa utworzona, wartosci zmiennych: "<<endl;
  cout<<
"a = "<<a<<endl;
  cout<<
"b = '"<<b<<"'"<<endl;
  cout<<
"c = \""<<c<<"\""<<endl;
  a=123;
  b=
'x';
  c=
"napis";
  cout<<
"Wartosci zmiennych po zainicjowaniu w konstruktorze: "<<endl;
  cout<<
"a = "<<a<<endl;
  cout<<
"b = '"<<b<<"'"<<endl;
  cout<<
"c = \""<<c<<"\""<<endl;
}

int main()
{
  JakasKlasa tZmienna;
  getch();
 
return(0);
}

Takie rozwiązanie jest oczywiście poprawne, niemniej jednak czasami zachodzi potrzeba zainicjowania zmiennej w trakcie tworzenia klasy, a nie po jej utworzeniu. Aby to zrobić, należy użyć następującego zapisu:

#include <iostream>
#include <conio.h>
using namespace std;
class JakasKlasa
{
 
int a;
 
char b;
 
std::string c;
 
public:
  JakasKlasa();
};

JakasKlasa::JakasKlasa():
a(123),
b(
'x'),
c(
"napis")
{
  cout<<
"Klasa utworzona, wartosci zmiennych: "<<endl;
  cout<<
"a = "<<a<<endl;
  cout<<
"b = '"<<b<<"'"<<endl;
  cout<<
"c = \""<<c<<"\""<<endl;
}

int main()
{
  JakasKlasa tZmienna;
  getch();
 
return(0);
}

Taki zapis ma kilka bardzo istotnych zalet:

Warto więc od początku wpajać sobie nawyk, który został tu zaprezentowany, ponieważ w przyszłości może Ci to oszczędzić dużo problemów, takich jak poniższe.

Problem nr 1.

class JakasKlasa
{
 
int& a;
 
public:
  JakasKlasa(
int& fA);
};

JakasKlasa::JakasKlasa(
int& fA)
{
  a=fA;
}

int main()
{
 
int zmiennaA;
  JakasKlasa tZmienna(zmiennaA);
 
return(0);
}

Komunikat:

C:\kurs\problem01.cpp    In constructor `JakasKlasa::JakasKlasa(int&)':
9    C:\kurs\problem01.cpp    uninitialized reference member `JakasKlasa::a'

Rozwiązanie nr 1.

class JakasKlasa
{
 
int& a;
 
public:
  JakasKlasa(
int& fA);
};

JakasKlasa::JakasKlasa(
int& fA):
a(fA)
{
}

int main()
{
 
int zmiennaA;
  JakasKlasa tZmienna(zmiennaA);
 
return(0);
}

Problem nr 2.

class JakasKlasa
{
 
const int a;
 
public:
  JakasKlasa(
int fA);
};

JakasKlasa::JakasKlasa(
int fA)
{
  a=fA;
}

int main()
{
  JakasKlasa tZmienna(55);
 
return(0);
}

Komunikat:

C:\kurs\problem2.cpp: In constructor `JakasKlasa::JakasKlasa(int)':
C:\kurs\problem2.cpp:9: error: uninitialized member `JakasKlasa::a' with `const' type `const int'
C:\kurs\problem2.cpp:10: error: assignment of read-only data-member `JakasKlasa::a'

Rozwiązanie nr 2.

class JakasKlasa
{
 
const int a;
 
public:
  JakasKlasa(
int fA);
};

JakasKlasa::JakasKlasa(
int fA):
a(fA)
{
}

int main()
{
  JakasKlasa tZmienna(55);
 
return(0);
}

Destruktor klasy

Często jest tak, że podczas życia klasy rezerwujemy pamięć, którą chcielibyśmy zwalniać zawsze przed usunięciem klasy. Pierwszym wariantem jest pamiętanie o wywołaniu funkcji, która będzie za to odpowiedzialna. Takie podejście jest jednak ryzykowne, ponieważ bardzo łatwo zapomnieć o wywoływaniu funkcji, która będzie zwalniała ewentualną zarezerwowaną dynamicznie pamięć.

Lepszym rozwiązaniem tego problemu jest wykorzystanie destruktorów. Destruktor jest specjalną funkcją, która jest wywoływana zawsze tuż przed zniszczeniem (usunięciem) klasy z pamięci.

Budowa destruktora wygląda następująco:

class NazwaTwojejKlasy
{
 
public:
  ~NazwaTwojejKlasy();
//To jest definicja destruktora
};

NazwaTwojejKlasy::~NazwaTwojejKlasy()
{
 
//Tu wykonujemy wszystkie operacje jakie mają się wykonać automatycznie tuż przed zwolnieniem pamięci zajmowanej przez klasę.
}

Destruktor, tak samo jak konstruktor nie posiada zwracanego typu. Drugą ważną cechą jest to, że destruktor musi być zawsze bezparametrowy. Trzecią, a zarazem ostatnią ważną cechą jest możliwość zdefiniowania tylko i wyłącznie jednego destruktora dla danej klasy.

Poniżej przedstawiam bardzo prosty przykład, demonstrujący działanie konstruktora i destruktora.

Przykład

#include <iostream>
#include <conio.h>
using namespace std;

class KlasaCL
{
 
public:
  KlasaCL();
  ~KlasaCL();
};

int main()
{
  KlasaCL* tKlasa;
  cout<<
"Rezerwuje pamiec za pomoca new"<<endl;
  tKlasa=
new KlasaCL;
  cout<<
"Wchodze do bloku {"<<endl;
 
{
    KlasaCL tKlasa;
 
}
  cout<<
"Wyszedlem z bloku }"<<endl;
  cout<<
"Zwalniam pamiec, ktora zostala zarezerwowana za pomoca new"<<endl;
 
delete tKlasa;
  getch();
 
return(0);
}

KlasaCL::KlasaCL()
{
  cout<<
"=> Konstruktor wywolany!"<<endl;
}

KlasaCL::~KlasaCL()
{
  cout<<
"=> Destruktor wywolany!"<<endl;
}



Wyszukiwarka

Podobne podstrony:
powtórzenie klasy, Programowanie, wykłady C++
Podstawy programowania (wykład III)
Program wykładów 1 sem
Program wykładu Fizyka II
zasady zaliczeń PP IG, Politechnika Białostocka, ZiIP (PB), Semestr 1, Podstawy programowania, Progr
Wskaźniki w C, Programowanie, wykłady C++
Program Wykładów I Semestru (30h) Starożytność str 1
program wykladow
00 program wykladu i literatura
Programowanie - wykłady, UE IiE ISIZ, programowanie
program wykładu z ps rozwoju SWPS, Ludwika Wojciechowska
Woroniecka Program wykładów z ekonomii, ekonomia _pochodzi od greckiego oiconomicos, oikos-dom, nomo
Program wykładów
Języki programowania wykłady
klasy, Programowanie, C++
Obsługa plików, Programowanie, wykłady C++
Program wykładów z pomp, IŚ Tokarzewski 27.06.2016, V semestr COWiG, PKM (Podstawy konstrukcji mecha
Nauka administracji - program wykładu dra R. Giętkowskiego, PRAWO ADMINISTRACYJNE

więcej podobnych podstron