PO lab 01

background image

Programowanie obiektowe; © Krzysztof Urbański 2014

1

Programowanie obiektowe

Lab.1. Struktury a klasy. Przejście od paradygmatu proceduralnego do

obiektowego

(c) Krzysztof Urbański 2014, wszelkie prawa zastrzeżone. Materiały te są chronione prawem autorskim. Żaden fragment
publikacji nie może być powielany lub rozpowszechniany w żadnej formie i w żaden sposób bez uprzedniego zezwolenia.
Wyrażam zgodę na użycie tych materiałów w trakcie realizacji kursów dydaktycznych na Wydziale Elektroniki
Mikrosystemów i Fotoniki Politechniki Wrocławskiej w semestrze letnim 2013/2014 r.

Zagadnienia do opanowania:

• Definicja struktury danych i używanie zmiennych strukturalnych.

• Referencje w argumentach funkcji: & .

• Dynamiczna alokacja i zwalnianie pamięci w C (malloc, calloc, free).

• Przeciążanie funkcji w C++.

• Definicja klasy w C++. Sekcje private, protected, public.

• Konstruktor i destruktor w klasie C++ - jak definiować, kiedy są wywoływane.

W poprzednim semestrze obowiązywał język C, a użycie mikrokontrolerów o niewielkich

zasobach pamięciowych wręcz wykluczało możliwość użycia języka C++. Język C zaliczany jest do

języków proceduralnych, co w uproszczeniu sprowadza się do programowania aplikacji w taki

sposób, aby wyraźnie wyodrębnić definicje i deklaracje typów danych (zwykle struktur danych), a

zupełnie osobno określić algorytmy przetwarzające te dane (podprocedury lub funkcje).

Z zupełnie innym podejściem zapoznamy się w tym semestrze w ramach kursu

Programowanie obiektowe. Problem nie będzie rozbijany na dane i algorytmy, zamiast tego

będziemy modelować rzeczywistość za pomocą klas i zależności między nimi. Takie możliwości

oferuje nam C++, dlatego zakładając projekt należy dodać do niego plik z rozszerzeniem .cpp (a nie

*.c) . Może to być np. main.cpp. Spowoduje to użycie kompilatora C++ zamiast C. Początkowo

różnice między tymi językami będą prawie niezauważalne, ale pod koniec zajęć pojawi się to coś,

czego nie było w języku C, a mianowicie klasy i obiekty.

background image

Programowanie obiektowe; © Krzysztof Urbański 2014

2

Zaczniemy zupełnie nieobiektowo, od definicji struktury danych, jaką jest Student:

struct Student
{

int nralb;

char imie[100];

//

nieeleganckie rozwiązanie

};

int main()
{

Student s;

printf("nralb: %d, imie: %s\n", s.nralb, s.imie);

return 0;

}

Wyniki:

nralb: -858993460, imie:

╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠

╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠

╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠

╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠

╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠

╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠

╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠

╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠

╠╠╠╠╠╠

╠╠╠╠╠╠

╠╠╠╠╠╠

╠╠╠╠╠╠ ╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠

╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠

╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠

╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠

ł ↕

1) Ponieważ używamy C++, nie jest konieczne każdorazowe dodawanie słowa kluczowego

struct, wystarczy sama nazwa typu strukturalnego (Student).

2) Kompilator C++ pozwala zastąpić wywołania funkcji printf() mniej ryzykownym obiektem

std::cout. Znajdź informacje (np. online), jak to zrobić.

3) Podobnie jak w C, zmienna lokalna w C++ nie ma określonej wartości początkowej, więc

należy zadbać o jej stan początkowy samodzielnie. Z drugiej strony, zmienne globalne

i statyczne są zerowane).

4) Warto przy okazji zdefiniować specjalizowaną funkcję do wyświetlania dowolnego studenta,

aby w przyszłości, wraz ze zwiększaniem liczby pól struktury, nie było konieczne

modyfikowanie wszystkich miejsc w kodzie, gdzie chcemy Studenta wyświetlić.

void Student_ustaw(Student &s, int nralb, char imie[100])
{

s.nralb = nralb;

strcpy(s.imie, imie);

//

ryzykowne – lepiej używać strncpy

}

void Student_wyswietl(Student s)
{

printf("nralb: %d, imie: %s\n", s.nralb, s.imie);

}

void main()
{

Student s;

Student_ustaw(s, 12345, "Jasiu");

Student_wyswietl(s);

}

Wyniki:

nralb: 12345, imie: Jasiu

background image

Programowanie obiektowe; © Krzysztof Urbański 2014

3

Lepiej, ale nie do końca.

Zwróć uwagę na referencję w funkcji Student_ustaw(…). Jest to trzeci sposób przekazywania

argumentów do funkcji. W C używaliśmy sposobu „przez wartość” oraz „przez wskaźnik”. C++

wprowadza bardzo przydatny mechanizm „przez referencję”. Używamy go wtedy gdy chcemy, aby

funkcja mogła bezpośrednio modyfikować przekazaną do niej zmienną, a nie tylko jej kopię.

To, co na pewno będzie wymagało poprawienia, to przekazywanie parametru

char imie[100]

do funkcji Student_ustaw(…). Nie jest też dobrym pomysłem użycie

Student_wyswietl(Student s)

– należałoby raczej posłużyć się wskaźnikiem albo referencją,

zmniejszyłoby to liczbę kopiowanych bajtów i pozwoliłoby zaoszczędzić pamięć stosu oraz

przyspieszyłoby działania kodu. Sama struktura Student też nie jest zbyt dobrze zaprojektowana,

pole imie zawsze zajmuje 100 bajtów niezależnie od tego, czy długość imienia wynosi 0, 10 czy 99

znaków. Co gorsza, gdybyśmy chcieli użyć imienia dłuższego niż 99 znaków, to nie będzie to

możliwe w tak zdefiniowanej strukturze. Lepiej byłoby użyć wskaźnika znakowego, zaś pamięć

potrzebną do przechowania imienia można alokować dynamicznie, dokładnie takiej wielkości, jaka

jest potrzebna, ani mniej, ani więcej.

Dynamiczna alokacja pamięci narzuca też na nas obowiązek zwalnianie tej pamięci, gdy już

nie będzie potrzebna. Pojawia się zatem kolejna funkcja Student_usun(…), która o to zadba.

struct Student
{

int nralb;

char *imie;

};

void Student_inicjalizuj(Student &s)
{

s.nralb = 0;

s.imie = NULL;

}

void Student_ustaw(Student &s, int nralb, char *imie)
{

s.nralb = nralb;

if(s.imie != NULL)

//jeśli student już posiada imię,

free(s.imie);

//to najpierw się go pozbywamy,

s.imie = strdup(imie);

//a następnie dynamicznie tworzymy

//duplikat (kopię) przekazanego argumentu wywołania funkcji

}

void Student_wyswietl(Student *s)
{

printf("nralb: %d, imie: %s\n", s->nralb, s->imie);

}

void Student_usun(Student *s)
{

printf("zwalniam pamiec studenta o nralb=%d\n", s->nralb);

background image

Programowanie obiektowe; © Krzysztof Urbański 2014

4

if(s->imie != NULL)

free(s->imie);

}

void main()
{

Student s;

Student_inicjalizuj(s);

Student_ustaw(s, 12345, "Jasiu");

Student_wyswietl(&s);

Student_usun(&s);

}

Powyższy kod powinien być zrozumiały, ale na oko widać, że jest nadmiernie rozwlekły.

Niepotrzebnie w identyfikatorach kolejnych funkcji został użyty przedrostek Student_. W języku C

byłoby to konieczne, aby uniknąć pojawienia się kilku funkcji o identycznych nazwach (np.

wyświetl), przeznaczonych do operowania na różnych argumentach. C nie pozwala na istnienie w

projekcie kilku definicji funkcji o identycznych nazwach (za wyjątkiem funkcji statycznych, ale to

przypadek szczególny)

W języku C++, w odróżnieniu od C, istnieje mechanizm przeciążania (przeładowania) funkcji.

Nie jest konieczne nazywanie tych funkcji aż tak rozwlekle – zamiast Student_inicjalizuj(s); można

krócej napisać inicjalizuj(s);. Nawet wtedy, gdy w naszym programie zdefiniujemy inną strukturę (np.

struct NapojGazowany {…}), i też zechcemy mieć funkcję inicjalizującą zmienne tego typu, to

możemy śmiało zapisać to tak:

struct NapojGazowany {

char *nazwa;

double cena, kalorie;

};

void inicjalizuj(NapojGazowany &n)
{

n.nazwa = NULL;

n.cena = b.kalorie = 0.0;

}

void inicjalizuj(Student &s)
{

s.nralb = 0;

s.imie = NULL;

}

void main()
{

Student s;

NapojGazowany n;


inicjalizuj(s);

inicjalizuj(n);

}

background image

Programowanie obiektowe; © Krzysztof Urbański 2014

5

Mamy 2 funkcje o identycznej nazwie

inicjalizuj(…)

, różniące się jednak typem

argumentów. To w zupełności wystarczy, aby kompilator C++ poradził sobie z odróżnianiem tych

funkcji.

Wyraźnie oddzielenie definicji typu danych (struktury danych) od algorytmów, które ich

używają (funkcji) niekoniecznie najlepiej odzwierciedla rzeczywistość. Ten sposób opisu działania

aplikacji sprawdza się nieźle w programach służących wyłącznie do przetwarzania danych, ale

trudniej opisywać w ten sposób grę komputerową, np. klasyczną strzelankę 3D.

Wracając do wcześniejszego przykładu, wydaje się, że bardziej naturalne byłoby przypisanie

studentowi nie tylko cech ilościowych (nr albumu, wiek, imię, nazwisko, oceny…), ale także

zdefiniowanie umiejętności, jakie student powinien posiadać (narodziny, zakuwanie, imprezowanie,

zgon). Od strony czysto praktycznej wygodniej by też było, gdy zmienne w programie same potrafiły

się samodzielnie inicjalizować (w chwili narodzin) oraz zwalniać pamięć automatycznie wtedy, gdy

już nie są nam one więcej potrzebne (gdy nastąpi zgon).

Takie możliwości daje nam użycie klasy zamiast struktury. Podstawowa różnica między

strukturą a klasą jest taka, że oprócz pól w tej drugiej można definiować także funkcje (w tym

przypadku będziemy je nazywać metodami). Tych różnic w przyszłości pojawi się dużo więcej.

class Student
{
public:

int nralb;

//

to jest pole

char *imie;

//

to jest pole


void ustaw(int nralb, char *imie)

//

to jest metoda


{

this->nralb = nralb;

if(this->imie != NULL)

free(this->imie);

this->imie = strdup(imie);

}


void wyswietl()

{

printf("nralb: %d, imie: %s\n", this->nralb, this->imie);

}


void usun()

{

printf("zwalniam pamiec studenta o nralb=%d\n", nralb);

if(imie != NULL)

free(imie);

}


void inicjalizuj()

{

nralb = 0;

background image

Programowanie obiektowe; © Krzysztof Urbański 2014

6

imie = NULL;

}

};


void main()
{

Student s;

s.inicjalizuj();

s.ustaw(9999, "Malgosia");

s.wyswietl();

s.usun();

}

Wyniki:

nralb: 9999, imie: Malgosia
zwalniam pamiec studenta o nralb=9999

Zdecydowanie lepiej! Czy można usprawnić nasz kod jeszcze bardziej? To, co najbardziej się

może przydać, to umiejętność automatycznego ustawiania (inicjalizacja) własnych pól

i automatyczne zwalnianie pamięci, którą zajęliśmy. Służą do tego konstruktory i destruktor. Każda

klasa w C++ może mieć 0 lub więcej konstruktorów i 0 lub 1 destruktor.

class Student
{
public:

Student()

//

a to jest specjalna metoda: konstruktor

{

printf("inicjalizacja pol obiektu klasy Student\n");

nralb = 0;

imie = NULL;

}

~Student()

//

specjalna metoda: destruktor

{

printf("zwalniam pamiec studenta o nralb=%d\n", nralb);

if(imie != NULL)

free(imie);

}

};

void main()
{

Student s;

s.ustaw(9999, "Malgosia");

s.wyswietl();

}

Wyniki:

inicjalizacja pol obiektu klasy Student
nralb: 9999, imie: Malgosia
zwalniam pamiec studenta o nralb=9999

background image

Programowanie obiektowe; © Krzysztof Urbański 2014

7

A gdyby tak jeszcze bardziej ułatwić życie programiście? W klasie możemy zdefiniować

dodatkowy konstruktor następującej postaci:

class Student {
public:


Student(int nralb, char *imie)

//to prawie to samo co metoda ustaw(…)

{

printf("tworze obiekt klasy Student (%d, %s)\n", nralb, imie);

this->nralb = nralb;

this->imie = strdup(imie);

}

};
void main()
{

Student s(9999, "Malgosia");

//krócej i ładniej się nie da

s.wyswietl();

}

Wyniki:

tworze obiekt klasy Student (9999, Malgosia)
nralb: 9999, imie: Malgosia
zwalniam pamiec studenta o nralb=9999

c.d.n.

background image

Programowanie obiektowe; © Krzysztof Urbański 2014

8

Zadanie do realizacji

1) Zbadaj zachowanie konstruktorów i destruktora, korzystając z wcześniejszych kodów

źródłowych. W którym momencie jest wywoływany każdy z nich? Jak zmienia się

działanie kodu, kiedy mamy do czynienia ze zmiennymi lokalnymi lub globalnymi?

2) Mamy dany poniższy fragment kodu. Co zostanie wyświetlone na ekranie? Jaki napis

pojawi się jako pierwszy? A jaki jako ostatni? DLACZEGO?

Student s(9999, "Malgosia");
void main()
{

printf("Witamy w funkcji main()!\n");

s.wyswietl();

}

3) Idąc tropem z punktu (2), zbadaj zachowanie aplikacji, kiedy masz do dyspozycji więcej

zmiennych obiektowych różnych typów (różnych klas). Co decyduje o kolejności

wykonania konstruktorów? Aby zrealizować ten podpunkt, przygotuj kompletną klasę

inną niż Student{}, może to być np. class SokOwocowy{} i utwórz kilkoro Studentów

oraz kilka SokówOwocowych.

4) Soki owocowe są na tyle zdrowe, że warto nauczyć Studenta, aby pił je (z umiarem) nawet

kilka razy w ciągu dnia. Pomyśl, w postaci jakich danych można reprezentować obiekt

klasy Studenta z plecakiem wypełnionym obiektami klasy SokOwocowy? Co jeszcze może

student nosić w plecaku? Czy są to tylko napoje? Jak najogólniej można zdefiniować klasę,

która będzie pasowała do dowolnego elementu wyposażenie studenta?

5) Celem tych zajęć jest zapoznanie się z pojęciem „model obiektowy” lub „programowanie

zorientowane obiektowo”. Zamodeluj obiektowo wycinek rzeczywistości, jakim jest ekipa

dobrze wyposażonych studentów wraz z opiekunami na najbliższym Rajdzie MTR. Użyj

języka UML i narysuj wybrany diagram (np. diagram zależności).


Wyszukiwarka

Podobne podstrony:
PO lab 5 id 364195 Nieznany
CMS Lab 01 Podstawy Joomla
pa lab [01] rozdział 1(2) 6NSOW2JJBVRSQUDBPQQOM4OXG5GLU4IBUS2XYHY
pa lab [01] rozdział 1(1) AV44KTWECPGV7P63OBNIPZBDRODKIVQ4A5KHZOI
LAB 7 01, Wyznaczanie ogniskowych soczewek za pomoc˙ ˙awy optycznej
PO lab 9
Lab 01 Wprowadzenie do systemu UNIX
CMS Lab
Lab 01
Systemy Lab 01
in lab 01
lab 01 id 258755 Nieznany
2011 Lab 01 bledy
lab 01
Lab 01 Introductin to UNIX System
PO 24.01.08 1 pomoc, Szkoła- pomoce naukowe ;P, Ściągi;)

więcej podobnych podstron