Literatura: Pola Metody

Transkrypt

Literatura: Pola Metody
Klasy = pola + metody + specyfikatory dostępu
Definicja klasy
class nazwa_klasy //nagłówek klasy
{
W treści klasy wymienia się składowe klasy (pola
//treść klasy
i metody) oraz określa się sposób dostępu do nich.
};
Każda definicja klasy wprowadza do programu nowy typ danych, żeby z
niego skorzystać należy zdefiniować zmienną tego typu, którą
nazywamy obiektem.
Definicja obiektu:
nazwa_klasy nazwa_obiektu; //o ile brak w klasie konstruktora lub klasa
posiada konstruktor domyślny
nazwa_klasy nazwa_obiektu(lista argumentów aktualnych konstruktora);
W momencie definicji obiektu klasy, definicja klasy musi być znana.
Wykład 1
Literatura:
Bjarne Stroustrup Język C++.
Jerzy Grębosz Symfonia C++.
Jerzy Grębosz Pasja C++.
Bruce Eckel Thinking in C++ edycja polska.
Josee Liberty C++ dla każdego.
Stanley B. Lippman Istota języka C++.
Stanley B. Lippman Josee Lajoie Podstawy języka C++.
1
Pola
Pola klasy deklaruje się tak samo jak zmienne. Pola klasy
mogą być dowolnego typu (w tym też typu zdefiniowanego
przez użytkownika).
Jeśli
Stack pierwszy;
Przykład
Stack *wsk_drugi=&pierwszy;
Stack &ref_trzeci=pierwszy;
const MAX=10;
class Stack
to do pola top obiektu pierwszy
{
możemy odwołać się (o ile jest to
int table[MAX];
możliwe) w następujący sposób:
int top;
};
pierwszy.top
wsk_drugi->top
(*wsk_drugi).top
3
ref_trzeci.top
Czasami wystarczy klasę zadeklarować.
Deklaracja klasy to nagłówek klasy zakończony średnikiem
class nazwa_klasy;
Definiujemy raz, deklarować możemy wiele razy.
2
Metody
Metody klasy - funkcje określające to co z obiektem reprezentującym
klasę można zrobić. Metody deklaruje się w treści klasy. Deklaracja
metody wygląda tak jak deklaracja zwykłej funkcji.
const MAX=10;
class Stack
{
void push (int);
int pop ( );
void print ( );
int count ( );
int table[MAX];
int top;
};
Jeśli
Stack pierwszy;
Stack *wsk_drugi=&pierwszy;
Stack &ref_trzeci=pierwszy;
to wywołanie metody push, o ile będzie to
możliwe, dla obiektu pierwszy można
wykonać następująco:
pierwszy.push(10);
wsk_drugi->push(10);
(*wsk_drugi).push(10);
ref_trzeci.push(10);
4
Specyfikatory dostępu
Etykieta private poprzedza składowe prywatne (tzw. implementacja
klasy), które są dostępne w metodach klasy oraz w funkcjach,
metodach i klasach zaprzyjaźnionych. W sekcji prywatnej ukrywa się
najbardziej wrażliwe składowe klasy, zwykle pola.
Możliwość dostępu do składowych zależy od miejsca
zadeklarowania składowych w klasie.
Klasę można podzielić na następujące sekcje:
publiczna (zaczyna się od public:),
prywatna (zaczyna się od private:)
chroniona (zaczyna się od protected:).
Etykieta protected poprzedza składowe chronione, które są dostępne
tak jak składowe prywatne, a dodatkowo są dostępne w klasach
wywodzących (w klasach pochodnych) się z tej klasy.
Etykiety public, private, protected można umieszczać w dowolnej
kolejności, mogą też się powtarzać.
Etykieta public poprzedza składowe publiczne (tzw. interfejs
klasy), które są dostępne w każdym miejscu programu. Najczęściej
sekcja publiczna zawiera metody określające ogólnie dostępne
operacje wykonywane na obiektach klasy w programie.
Zakłada się, że dopóki w definicji klasy nie wystąpi żadna z tych
etykiet, to składniki przez domniemanie są poprzedzane etykietą
private.
5
Dodamy do klasy Stack etykiety:
Kolejność deklarowania pól i metod jest
class Stack
dowolna, ale pamiętać należy o
{
przejrzystości kodu.
public:
void push (int);
Z samą definicją klasy nie jest związany
int pop ( );
żaden obszar w pamięci z którego można
void print ( );
korzystać w programie. Dopiero definicja
int count ( );
obiektu klasy powoduje przydzielenie
pamięci.
private:
int main( )
int table[MAX];
{
int top;
Stack pierwszy;
};
pierwszy.top=10; //błąd, brak dostępu
pierwszy.table[2]=23; //błąd j.w.
pierwszy.print( );
return 0;
7
}
6
Mając definicję klasy Stack uda nam się skompilować poniższy
program
int main( )
int main( )
{
{
Stack pierwszy,
Stack pierwszy;
drugi;
pierwszy.print( );
pierwszy.print( );
Stack drugi;
drugi.print( );
drugi.print( );
return 0;
return 0;
}
}
Nie będziemy go mogli jednak uruchomić,
gdyż brakuje definicji metody print.
Każdy obiekt klasy ma własne egzemplarze pól klasy, natomiast
istnieje tylko jeden egzemplarz metod klasy.
Jak to dzieje się, że mimo, że mamy jeden egzemplarz metody, metoda
ta pracuje na danym konkretnym obiekcie?
Każda metoda posiada niejawny argument, stały wskaźnik this do 8
obiektu na rzecz którego ma pracować.
Treść metody print wygląda następująco:
Definicja metod klasy. Implementacja metod klasy.
Metodę możemy zdefiniować za treścią klasy, w której została
zadeklarowana, wtedy nazwę metody należy kwalifikować nazwą
klasy do której należy.
Metoda print
void Stack::print( )
{
if (top>0)
for (int i=top-1; i>=0; i--)
cout<<"[ "<<table[i]<<" ]"<<endl;
else
cout<<"*** stos pusty ***"<<endl;
cout<<endl;
}
void Stack::print ( )
{
if (this->top>0)
for (int i=this->top-1;i>=0;i--)
cout<<"[ "<<this->table[i]<<" ]"<<endl;
else
cout<<"*** stos pusty ***"<<endl;
cout<<endl;
}
Wskaźnik this sprawia, że
operacje przeprowadzane są
na składowych tego (this)
konkretnego obiektu, dla
którego tę metodę
wywołujemy.
9
Wskaźnik this
pojawia się w
definicjach metod
przed każdą nazwą
składowej klasy.
Można go
dopisywać ręcznie,
ale kompilator
oszczędza nasz czas
i wstawia go sam.
W momencie wywołania funkcji
oprócz swoich argumentów
metoda otrzymuje w pierwszym
niejawnym argumencie
wskaźnik do obiektu na rzecz
którego ma pracować, jest to
10
stały wskaźnik o nazwie this.
Program
Metody definiować możemy też w treści klasy, o ile treść metody
zawiera co najwyżej dwie linijki.
int main( )
{
Stack pierwszy;
pierwszy.print( );
Stack drugi;
drugi.print( );
return 0;
}
Definicja i deklaracja
metody count( )
class Stack
{
public:
inline int count ( ) {return top;}
//...
};
Metody definiowane w treści klasy
automatycznie otrzymują atrybut inline
Może działać dziwnie, gdyż pola obiektów nie zostały zainicjowane.
Chcielibyśmy nadać wartość zero składowej top.
W miejscu wywołania metody inline może zostać wstawiana jej treść.
11
12
W treści klasy nie można składowym nie statycznym przypisywać
wartości początkowych
class Stack
{
//...
private:
[C++ Error] Cannot initialize a class member here
int top=0;
};
Sposób znany nam ze struktur też nie jest dobrym rozwiązaniem jako, że
pola które chcemy zainicjować należą do części prywatnej klasy.
int main( )
{
Stack pierwszy={{0},0};
//...
return 0;
}
[C++ Error] Expression syntax
13
Dwa alternatywne sposoby definicji konstruktora.
Konstruktor z definicją
Konstruktor z definicją
za treścią klasy:
w treści klasy:
class Stack
class Stack
{
{
public:
public:
Stack( ); //deklaracja
Stack( ) {top=0;}
//...
//...
};
};
Konstruktor Stack( )
Stack::Stack ( ) //definicja
automatycznie zostanie
{
wywołany w liniach:
top=0;
Stack pierwszy;
}
Stack pierwszy=Stack( );
Stack *Wsk=new Stack;
Stack tablica[10];
Konstruktor, który można wywołać bez argumentów nazywamy 15
konstruktorem domyślnym.
Idealnie byłoby żeby w momencie definicji obiektu klasy nadać mu
wartość początkową. Do tego służy specjalna metoda zwana
konstruktorem.
Konstruktor klasy.
•Konstruktor jest specjalną metodą, której głównym zadaniem jest
nadanie wartości początkowych składowym obiektu (polom) klasy.
•Konstruktor nazywa się tak samo jak klasa do której należy.
•Konstruktor nie zwraca żadnej wartości (nawet typu void).
•Klasa może zawierać więcej niż jeden konstruktor, bądź może nie
mieć ich wcale.
•Konstruktor wywoływany jest automatycznie w momencie definicji
obiektu klasy, o ile konstruktor został zdefiniowany w części
publicznej klasy.
•Konstruktor tak jak wszystkie metody klasy musi zostać
zadeklarowany w treści klasy, a zdefiniowany za treścią klasy lub w
treści klasy (otrzymuje wtedy atrybut inline).
•Konstruktor nie może zostać wykonywany na rzecz konkretnego 14
obiektu, bo ten obiekt jeszcze nie istnieje.
Po tych modyfikacjach
int main( )
{
Stack pierwszy; // pracuje konstruktor
pierwszy.print( );
Stack drugi; // pracuje konstruktor
drugi.print( );
return 0;
}
na ekranie zobaczymy
*** stos pusty ***
*** stos pusty ***
16
Nazwę konstruktora można przeciążać.
class Stack
{
public:
Stack( );
Stack::Stack(int t) //definicja za klasą
Stack(int t); //deklaracja
{
//...
top=t;
};
if (t<0)
top=0;
else
if (t>MAX)
top=MAX;
for (int i=0; i<top; i++)
table[i]=i;
}
17
Konstruktor tak jak inne metody klasy może posiadać argumenty
domyślne. W naszym przypadku dwa konstruktory można zastąpić
jednym, z argumentem domyślnym o wartości 0.
class Stack
Wykład 2
{
public:
Stack(int t=0); //konstruktor domyślny, deklaracja
//...
Stack::Stack(int t)
};
{
top=t;
if (t<0)
top=0;
else
if (t>MAX)
top=MAX;
for (int i=0; i<top; i++)
table[i]=i;
19
}
Automatyczne wywołanie konstruktora z jednym argumentem będzie
się dokonywało w liniach:
Stack drugi(3);
Stack drugi=3;
Stack drugi=Stack(3);
Stack *wsk=new Stack(3);
Stack tablica[10]={Stack(2), Stack(4)};
A program
int main( )
{
Stack pierwszy;
pierwszy.print( );
Stack drugi(3);
drugi.print( );
return 0;
}
Wyprodukuje następujące wyniki
*** stos pusty ***
[2]
[1]
[0]
18
Destruktor – specjalna metoda, która może "coś" zrobić tuż przed
"zniszczeniem" obiektu.
Na przykład, gdy:
* konstruktor klasy zarezerwował pamięć operatorem new, to dobrze
byłoby żeby w destruktorze ta pamięć została zwolniona za pomocą
delete,
* zliczamy obiekty klasy, to w destruktorze licznik powinien się
zmniejszać o 1.
* gdy jest mierzony czas pracy z obiektem.
Destruktor nazywa się tak samo jak klasa, tylko że przed nazwą
umieszczamy znak ~.
Destruktor nie może zwracać żadnej wartości (nawet typu void).
Destruktor nie może mieć argumentów, tak więc nie można przeciążać
20
nazwy destruktora.
W przypadku klasy Stack destruktor będzie wypisywał tylko
informacje na ekranie.
Destruktor tak jak wszystkie metody klasy musi zostać
zadeklarowany w treści klasy, a zdefiniowany za treścią klasy lub
w treści klasy (tzn. może mieć atrybut inline).
Destruktor obiektu jest wywoływany automatycznie, w
momencie, gdy obiekt wychodzi ze swojego zasięgu, lub gdy
został użyty operator delete.
class Stack
{
public:
Stack(int t=0);
~Stack( ); //destruktor, deklaracja
//...
};
Destruktor może zostać wywołany na rzecz obiektu.
Stack::~Stack( ) //destruktor, definicja
{
cout<<"Destruktor stosu:"<<endl;
print( );
}
Zaleca się umieszczanie obok konstruktora, destruktora nawet
gdy nic nie robi.
21
Natomiast po wykonaniu programu
int main( )
{
Na ekranie zobaczymy
{
Stack pierwszy;
*** stos pusty ***
pierwszy.print( );
Stack drugi(3);
[2]
drugi.print( );
[1]
}
[0]
return 0;
}
Destruktor stosu:
[2]
[1]
[0]
Wtedy po wykonaniu programu
int main( )
{
Stack pierwszy;
pierwszy.print( );
Stack drugi(3);
drugi.print( );
return 0;
}
Na ekranie zobaczymy
*** stos pusty ***
[2]
[1]
[0]
22
23
Destruktor stosu:
*** stos pusty ***
24
Przyjaciel klasy – np. funkcja, która nie jest składową klasy, ale
może korzystać ze składowych prywatnych klasy z którą się przyjaźni.
Deklarację przyjaźni umieszcza się w dowolnym miejscu klasy.
class Stack
{
public:
Stack(int=0);
~Stack( );
void push (int);
int pop ( );
void print ( );
int count ( ) {return top;}
private:
int table[MAX];
int top;
friend void info(Stack &);
};
void info(Stack & arg)
{
if (arg.top==0)
cout<<"Stos jest pusty"<<endl;
else
if (arg.top==MAX)
cout<<"Stos jest pelny"<<endl;
else
cout<<"Na stosie zostalo: "
<<(MAX-arg.top)*sizeof(int)<<" B"<<endl;
}
Przyjaciela klasy
możemy zdefiniować
w miejscu deklaracji,
ale lepiej tego unikać,
ponieważ przyjaciel
nie jest składową
klasy.
Zwykle definiuje się
go poza treścią klasy.
25
Wtedy po wykonaniu programu
int main( )
{
Stack pierwszy;
pierwszy.print( );
info(pierwszy);
Stack drugi(3);
drugi.print( );
info(drugi);
return 0;
}
Na ekranie zobaczymy
Cały program:
*** stos pusty ***
Stos jest pusty
[2]
[1]
[0]
Na stosie zostalo: 28 B
26
27
#include <iostream.h>
const MAX=10;
class Stack
{
public:
Stack(int t=0);
~Stack( );
void push (int);
int pop ( );
void print ( );
int count( ) {return top;}
private:
int table[MAX];
int top;
friend void info(Stack &);
};
int main( )
{
Stack pierwszy;
pierwszy.print( );
info(pierwszy);
Stack drugi(3);
drugi.print( );
info(drugi);
return 0;
}
28
Stack::Stack(int t)
{
top=t;
if (t<0)
top=0;
else
if (t>MAX)
top=MAX;
for (int i=0; i<top;i++)
table[i]=i;
}
void Stack::print( )
{
if (top>0)
for (int i=top-1;i>=0;i--)
cout<<"[ "<<table[i]<<" ]"<<endl;
else
cout<<"*** stos pusty ***"<<endl;
cout<<endl;
}
void Stack::push(int element)
{
if (top<MAX)
table[top++]=element;
}
Stack::~Stack( )
{
cout<<"Destruktor stosu:"<<endl;
print( );
}
29
int Stack::pop( )
{
int element=0;
if (top>0)
element=table[--top];
return element;
}
void info(Stack & arg)
{
if (arg.top==0)
cout<<"Stos jest pusty"<<endl;
else
if (arg.top==MAX)
cout<<"Stos jest pelny"<<endl;
else
cout<<"Na stosie zostalo: "
<<(MAX-arg.top)*sizeof(int)<<" B"<<endl;
} ///koniec
30
Konstruktor ciąg dalszy.
Zajmiemy się klasą:
class Pacjent
{
public:
Pacjent (int _wiek, double _waga, double _wzrost);
void wyswietl ( );
private:
int wiek;
double waga;
double wzrost;
double wspolczynnik;
double oblicz ( ) { return waga/(wzrost*wzrost); }
};
31
32
Konstruktor z listą inicjalizującą (z listą inicjalizacji składowych).
składowych).
Lista inicjalizująca pojawia się tylko w definicji konstruktora.
Rozpoczyna się od : który stawiamy po nawiasie ) i kończy w
momencie otworzenia klamerki {.
Definicje metod klasy Pacjent mogą wyglądać następująco:
Pacjent::Pacjent (int _wiek, double _waga, double _wzrost)
{
wiek=_wiek;
waga=_waga;
wzrost=_wzrost;
wspolczynnik=oblicz ( ); //ważna kolejność
}
Pacjent::Pacjent(int _wiek, double _waga, double _wzrost)
: wiek (_wiek), waga (_waga), wzrost (_wzrost)
{
wspolczynnik=oblicz( );
}
void Pacjent::wyswietl ( )
{
cout<<"Waga="<<waga<<" kg"<<endl
<<"Wzrost="<<wzrost<<" m"<<endl
<<"Wspolczynnik="<<wspolczynnik;
}
33
• Składowa listy inicjalizującej to nazwa pola klasy dla którego w
nawiasach okrągłych podajemy wartość początkową składowej.
• Składowe listy inicjalizującej oddzielamy przecinkami.
• Na liście inicjalizującej składową klasy możemy inicjować tylko raz.
• Kolejność składowych listy inicjalizującej jest dowolna, kompilator
34
i tak wywołuje je zgodnie z kolejnością definicji w klasie.
Wykonanie konstruktora składa się z dwóch etapów:
– nadanie wartości początkowych polom klasy znajdujących się na
liście inicjalizującej konstruktora (inicjalizacja jawna lub
niejawna).
– wykonywanie treści konstruktora – faza obliczeń (przypisań).
Pacjent::Pacjent (int _wiek, double _waga, double _wzrost)
: wiek (_wiek), waga (_waga),
wzrost (_wzrost), wspolczynnik (oblicz ( ))
{}
Inicjalizacja a przypisanie.
Inicjalizacja jest operacją jednorazową w całym cyklu istnienia
obiektu (zmiennej), wykonywaną natychmiast po przydzieleniu
zmiennej potrzebnego obszaru pamięci i polega na nadaniu tej
zmiennej w tym szczególnym momencie pewnej wartości
początkowej.
Faza przydzielania pamięci oraz inicjalizowania odbywa się zanim
zmienna ta będzie mogła zostać rzeczywiście użyta.
Pacjent::Pacjent (int _wiek, double _waga, double _wzrost)
: wiek (_wiek), waga (_waga),
wzrost ((_wzrost>3)? _wzrost/100:_wzrost),
wspolczynnik (oblicz ( ))
{}
Wartość początkowa pojawiająca się w nawiasach może być
wielkością stałą, argumentem definiowanego konstruktora,
wyrażeniem, a nawet metodą klasy lub funkcją spoza klasy.
35
Przypisanie polega na zmodyfikowaniu wartości zmiennej;
operacja ta, w całym okresie istnienia zmiennej, może być
wykonywana dowolną ilość razy.
36
Konstruktor z listą inicjalizacji składowych zdefiniowany w treści klasy
Program testujący działanie metod klasy:
class Pacjent
{
public:
Pacjent (int _wiek, double _waga, double _wzrost)
: wiek(_wiek), waga(_waga),
wzrost((_wzrost>3)? _wzrost/100:_wzrost),
wspolczynnik(oblicz( )) { }
void wyswietl ( );
int main( )
{
Pacjent A(18,80,180);
A.wyswietl( );
return 0;
}
Na ekranie zobaczymy:
private:
int wiek;
double waga;
double wzrost;
double wspolczynnik;
double oblicz ( ) {return waga/(wzrost*wzrost);}
};
Waga=80 kg
Wzrost=1.8 m
Wspolczynnik=24.6914
37
Cały program:
#include <iostream.h>
class Pacjent
{
public:
Pacjent (int _wiek, double _waga, double _wzrost)
: wiek(_wiek), waga(_waga),
wzrost((_wzrost>3)? _wzrost/100:_wzrost),
wspolczynnik(oblicz( )) { }
void wyswietl ( );
private:
int wiek;
double waga;
double wzrost;
double wspolczynnik;
double oblicz ( ) {return waga/(wzrost*wzrost);}
};
38
int main( )
{
Pacjent A(18,80,180);
A.wyswietl( );
return 0;
}
void Pacjent::wyswietl ( )
{
cout<<"Waga="<<waga<<" kg"<<endl
<<"Wzrost="<<wzrost<<" m"<<endl
<<"Wspolczynnik="<<wspolczynnik;
}
// koniec programu
39
40
W praktyce plik nagłówkowy może zawierać:
Program możemy podzielić na moduły (pliki).
W naszym przypadku, mogą to być następujące moduły:
Pacjent.h - plik nagłówkowy z definicją klasy,
Pacjent.cpp - plik z implementacją metod klasy Pacjent,
test.cpp - plik z funkcją main.
Deklaracje zmiennych
extern int liczba;
Deklaracje funkcji
Definicje funkcji inline
[extern] int suma(int, int);
inline int suma(int a, int b)
{return a+b;}
const double pi=3.141593;
Definicje stałych
Definicje typów
Tylko jeden plik w programie wielomodułowym może zawierać
funkcję main.
Deklaracja typu
Makrodefinicje
class Punkt
{
double x,y;
};
class Punkt;
#define WERSJA 2
Dyrektywy kompilacji warunkowej #ifdef _cplusplus
Wyliczenia
enum kolor {niebieski, czarny};
41
#ifndef PACJENT_H
#define PACJENT_H
Plik Pacjent.h
class Pacjent
{
public:
Pacjent (int _wiek, double _waga, double _wzrost)
: wiek(_wiek), waga(_waga),
wzrost((_wzrost>3)? _wzrost/100:_wzrost),
wspolczynnik(oblicz( )) { }
void wyswietl ( );
private:
int wiek;
double waga;
double wzrost;
double wspolczynnik;
double oblicz ( ) {return waga/(wzrost*wzrost);}
};
43
#endif
42
Dyrektywa preprocesora define (bez argumentów).
#define NAZWA ciąg znaków // bez ;
Każde wystąpienie w programie słowa NAZWA jest zastępowane
przez ciąg znaków
Przykład:
#define BEGIN {
#define END }
W programie możemy napisać:
Kompilator widzi rozwiniętą
postać makrodefinicji
if (a>0)
BEGIN
a++;
b++;
END
if (a>0)
{
a++;
b++;
}
44
Dyrektywy kompilacji warunkowej
#ifdef (#if
(#if define),
define),
#ifndef (#if
(#if !define),
define),
#endif
Plik Pacjent.cpp
#include <iostream.h>
#include "Pacjent.h"
#ifndef NAZWA
//linie kodu
#endif
Jeśli NAZWA nie została zdefiniowana za
pomocą dyrektywy define to wykonywane
są linie kodu.
#ifdef NAZWA
//linie kodu
#endif
Jeśli NAZWA została zdefiniowana za
pomocą dyrektywy define to
wykonywane są linie kodu.
45
Plik test.cpp
#include "Pacjent.h"
int main( )
{
Pacjent A(18,80,180);
A.wyswietl( );
return 0;
}
47
void Pacjent::wyswietl ( )
{
cout<<"Waga="<<waga<<" kg"<<endl
<<"Wzrost="<<wzrost<<" m"<<endl
<<"Wspolczynnik="<<wspolczynnik;
}
46