Dr hab. inż. Lucyna Leniowska, prof. UR, Zakład Mechatroniki, Automatyki i Optoelektroniki, IT Programowanie obiektowe, wykład nr 6 Klasy i obiekty W programowaniu strukturalnym rozwój oprogramowania oparto o procedury/funkcje. Struktury danych odgrywają w nim rolę drugoplanową. Programowanie zorientowane obiektowo (OOP, ang. Object Oriented Programming) oferuje nowe podejście, przyznając strukturom rolę pierwszoplanową. Zakłada ono że żyjemy w świecie obiektów, oddziałujących ze sobą zgodnie ze swą naturą. Operacje jakim poddaje się obiekty są sprawą wtórną, ponieważ zależą od cech samych obiektów. Klasy są kluczowym pojęciem w programowaniu obiektowym. Oznaczają typy złożone, zawierające nie tylko dane, ale i funkcje operujące na tych danych (zwane metodami). Tak zdefiniowany typ może byd wzorcem jakiegoś rzeczywistego obiektu. W klasach można wyróżniad: 1. Kategorie widoczności elementów klasy 2. Pola (zawierają dane) 3. Metody ( funkcje) 4. Konstruktory 5. Destruktory Obiekt (ang. object) danej klasy jest reprezentowany przez zmienną typu zdefiniowanego przez tę klasę. Obiekty są rzeczywistymi egzemplarzami danej klasy złożonymi ze wszystkich składowych zadeklarowanych w tej klasie oraz ze składowych wszystkich jej przodków. Mechanizm dziedziczenia umożliwia tworzenie tzw. klas potomnych, które przejmują cechy swoich klas rodzicielskich (bazowych). Klasy pochodne definiują nowe własności i operacje oraz mogą zmieniad operacje odziedziczone (polimorfizm).
Przykład (abstakcja) Dla obiektu telefon klasa mogłaby zawierad składowe danych, takie jak: numer telefonu, typ połączeo (pulsowy lub tonowy) składowe metod (funkcje/procedury), takie jak: wybierz_numer, odpowiedz, rozłącz. Grupując w ten sposób dane i metody wykonujące operacje dla obiektu można uprościd programowanie i zwiększyd możliwości wielokrotnego wykorzystywania kodu. Definicja klasy (wzorca) class nazwa_klasy typ1 zmienna1; //Składowa danych typ2 zmienna2; //Składowa danych... void nazwa_1 (lista_parametrów); // metoda1 typ nazwa_2 (lista_parametrów); // metoda2 Przykład 6.1. class klasa_nowa int dana; //Składowa danych void pisz_skladowa (int a); //metoda Deklaracja obiektów (zmiennych klasy) Klasa_nowa obiekt_jeden, obiekt_dwa, obiekt_trzy; Przykład 6.2. class pracownik string imie_nazwisko; long identyfikator; void info_o_prac(void) cout << "Imię i nazwisko: " << imie_nazwisko << endl; cout << "Ident: " << ident_pracownika << endl;
cout << "Zarobki: " << zarobki << endl; Po zdefiniowaniu klasy w programie można deklarowad obiekty typu tej klasy (zmienne): pracownik szef, sekretarka; Ukrywanie informacji - dane prywatne i publiczne Niezależnie od miejsca zdefiniowania składnika wewnątrz klasy jego zakres ważności jest równy obszarowi całej klasy. Istnieją specyfikatory (etykiety), za pomocą których można określad dostęp do składników klasy. Ukrywanie informacji jest to proces udostępniania programowi tylko minimalnej części informacji, jaka jest potrzebna korzystającym z klasy. Podział danych na prywatne i publiczne pomaga realizowad w programach ukrywanie informacji. Dostęp do prywatnych składowych klasy mają jedynie funkcje składowe tej klasy. Tworząc klasy należy dzielid ich składowe na prywatne i publiczne w następujący sposób: class pewna_klasa int pewna_zmienna; void inicjalizuj_prywatne (int, float); void pisz_dane (void); private: int numer_klucza; float wartosc_klucza; Program ma dostęp do składowych publicznych przy użyciu operatora wyboru składowej (kropki): pewna_klasa obiekt; //Tworzy obiekt obiekt.pewna_zmienna = 10; obiekt.inicjalizuj_prywatne (2012, 1.2345); obiekt.pisz_dane;
Nie jest możliwe uzyskanie dostępu do prywatnych składowych przy użyciu operatora wyboru składowej ( kompilator zasygnalizuje błąd). class Moja_klasa public : // metody i dane dostępne na zewnątrz klasy protected : // metody i dane dostępne dostępne dla funkcji z danej klasy, funkcji //zaprzyjaźnionych i dla klas wywodzących się od tej klasy private : // metody i dane dostępne dostępne tylko dla funkcji z danej klasy //oraz dla tzw. funkcji zaprzyjaźnionych Dopóki w definicji klasy nie wystąpi żadna z etykiet zakłada się, że składniki mają dostęp private: Przykład 6.3. Domyślnie dostęp do wszystkich elementów klasy jest ustawiony na prywatny. Dostęp prywatny oznacza, że nie można wykorzystywad zmiennych i funkcji zadeklarowanych w klasie poza klasą. #include <iostream> class pracownik std::string imie_nazwisko; long identyfikator; void inf_o_prac(void) std::cout << "Imię i nazwisko: " << imie_nazwisko <<std::endl; std::cout << "Ident: " << identyfikator <<std::endl; std::cout << "Zarobki: " << zarobki << std::endl; int main()
pracownik szef; szef.imie_nazwisko = "Adam Kwiatek"; szef.identyfikator = 101; szef.zarobki = 5000.00; szef.inf_o_prac(); return 0; Uwaga: Jeśli nie dodamy etykiety to zapis: spowoduje błąd. szef.imie_nazwisko = "Adam Kwiatek"; Ze strukturami struc takiego problemu nie było, ponieważ przyjmują one domyślnie prawa dostępu dla wszystkich zadeklarowanych zmiennych wewnątrz struktury. Deklarowanie metod klasy na zewnątrz klasy W C++ można wstawiad do definicji klasy prototypy funkcji (metod), a definicje funkcji umieszczad na zewnątrz klasy. Nazwy funkcji definiowanych poza klasą należy poprzedzad nazwą klasy i operatorem widoczności (::). Przykład 6.4. #include <iostream> #include <string> class pracownik std::string imie_nazwisko; long identyfikator; void inf_o_prac(void); //prototyp void pracownik::inf_o_prac(void) std::cout << "Imię i nazwisko: " << imie_nazwisko <<std::endl; std::cout << "Ident: " << identyfikator <<std::endl; std::cout << "Zarobki: " << zarobki << std::endl; int main() pracownik sekretarka, szef;
sekretarka.imie_nazwisko ="Anna Kwiatek"; sekretarka.identyfikator = 12345; sekretarka.zarobki = 4000; szef.imie_nazwisko = "Jan Kowalski"; szef.identyfikator = 101; szef.zarobki = 5000.00; sekretarka.inf_o_prac(); szef.inf_o_prac(); return 0; Na ekranie zobaczymy: Imię i nazwisko: Anna Kwiatek Ident: 12345 Zarobki: 5000 Imię i nazwisko: Jan Kowalski Ident: 101 Zarobki: 5000 Przykład 6.5 Załóżmy, że obiekt generator używa składowej napiecie, która zawsze powinna mied wartośd od 150 do 155. Jeśli ta składowa jest publiczna, to program ma do niej bezpośredni dostęp i może zmienid jej wartośd: generator.napiecie = 1000; Jeśli natomiast zdefiniujemy tę składową jako prywatną, to należy utworzyd w klasie metodę przypisz_napiecie, która będzie nadawad wartośd tej składowej: int generator::przypisz_napiecie (int liczba) if ((liczba > 150) && (liczba < 155)) napiecie = liczba; return (0); else return(-1); //Poprawne przypisanie //Niepoprawna wartość
Konstruktory i destruktory klas Typową operacją przy tworzeniu obiektów w programach jest nadawanie wartości początkowych składowym obiektu. Aby uprościd proces inicjalizowania składowych danych klasy, C++ oferuje metodę konstruktor, która jest wywoływana dla każdego tworzonego w programie obiektu. Analogicznie istnieje funkcja destruktor, wykorzystywana do usuwania obiektów. Jeśli programista nie utworzy konstruktora dla klasy, kompilator utworzy go automatycznie. Metoda konstruktor ma taką samą nazwę jak klasa. Konstruktory nie zwracają żadnej wartości. Podczas usuwania obiektu wywoływana jest funkcja destruktor, która zwalnia pamięd zajmowaną przez obiekt. Nazwa funkcji destruktor składa się z falki (~) i nazwy klasy. Dodanie konstruktora class pracownik pracownik (std::string, long, float); // konstruktor void inf_o_prac (void); int zmien_zarobki (float); long podaj_id (void); private: std::string imie_nazwisko; long ident_pracownika; W programie należy definiowad funkcję konstruktor tak jak dowolną inną metodę klasy, np: pracownik::pracownik (std::string imie_nazwisko, long ident_pracownika, float zarobki) pracownik::imie_nazwisko = imie_nazwisko; pracownik::ident_pracownika = ident_pracownika; if (zarobki < 7000.00) pracownik::zarobki = zarobki; else pracownik::zarobki = 0.0;
Parametry dla funkcji konstruktor przekazywane są przy deklaracji obiektu: nazwa_klasy obiekt (wartosc1, wartosc2, wartosc3); np. pracownik informatyk ("Kazik Kowal", 301, 6000.0); W przypadku korzystania w programie z różnych obiektów klasy pracownik, nadawanie wartości początkowych (inicjalizacja) składowych każdego obiektu przy użyciu konstruktora wygląda następująco: pracownik sekretarka ("Anna Kowal", 101, 3000.0); pracownik ksiegowa ("Katarzyna Kwiatek", 200, 5000.0); Przykład 6.6 #include <iostream> class pracownik pracownik (std::string, long, float); // konstruktor void inf_o_prac (void); int zmien_zarobki (float); long podaj_id (void); private: std::string imie_nazwisko; long ident_pracownika; pracownik::pracownik (std::string imie_nazwisko, long ident_pracownika, float zarobki) pracownik::imie_nazwisko = imie_nazwisko; pracownik::ident_pracownika = ident_pracownika; if (zarobki < 7000.00) pracownik::zarobki = zarobki; else pracownik::zarobki = 0.0; void pracownik::inf_o_prac (void) std::cout << "Imię i nazwisko: " << imie_nazwisko <<std::endl; std::cout << "Ident: " << ident_pracownika << std::endl; std::cout << "Zarobki: " << zarobki << std::endl; int main(void) pracownik sekretarka ("Anna Kowal", 101, 3000.0); //Tworzenie obiektu1
pracownik ksiegowa ("Katarzyna Kwiatek", 200, 5000.0); //Tworzenie obiektu2 sekretarka.inf_o_prac (); ksiegowa.inf_o_prac (); Na ekranie zobaczymy: Imię i nazwisko: Anna Kowal Ident: 101 Zarobki: 3000 Imię i nazwisko: Katarzyna Kwiatek Ident: 200 Zarobki: 5000 Funkcja destruktor Funkcja destruktor jest wywoływana automatycznie za każdym razem, gdy program usuwa obiekt. Nazwa destruktora dla klasy składa się z falki (~) i nazwy klasy: ~nazwa_klasy (void) // Instrukcje W przeciwieostwie do konstruktorów, nie można przekazywad parametrów funkcjom destruktor. Przykład 6.7 #include <iostream> class pracownik pracownik (std::string, long, float); // konstruktor ~pracownik (void); // destruktor void inf_o_prac (void); int zmien_zarobki (float); long podaj_id (void); private: std::string imie_nazwisko; long ident_pracownika;
pracownik::pracownik (std::string imie_nazwisko, long ident_pracownika, float zarobki) pracownik::imie_nazwisko = imie_nazwisko; pracownik::ident_pracownika = ident_pracownika; if (zarobki < 7000.00) pracownik::zarobki = zarobki; else pracownik::zarobki = 0.0; //Podano niepoprawne zarobki // Destruktor pracownik::~pracownik (void) std::cout << "Usuwam obiekt " << imie_nazwisko <<std::endl; void pracownik::inf_o_prac (void) std::cout << "Imię i nazwisko: " << imie_nazwisko <<std::endl; std::cout << "Ident: " << ident_pracownika << std::endl; std::cout << "Zarobki: " << zarobki << std::endl; int main(void) pracownik sekretarka ("Anna Kowal", 101, 3000.0); //Tworzenie obiektu1 pracownik ksiegowa ("Katarzyna Kwiatek", 200, 5000.0); //Tworzenie obiektu2 sekretarka.inf_o_prac (); ksiegowa.inf_o_prac (); return 0; Na ekranie zobaczymy: Zarobki: 3000 Imię i nazwisko: Katarzyna Kwiatek Ident: 200 Zarobki: 5000 Usuwam obiekt Katarzyna Kwiatek Usuwam obiekt Anna Kowal