Temat:
1. LISTY INDEKSOWANE
Lista, do której nowe elementy są wstawiane już na samym początku konsekwentnie w określonym porządku, służy oprócz swojej podstawowej roli gromadzenia danych, także do ich porządkowania. W sytuacji gdy istnieje tylko jedno kryterium sortowania (np. w kierunku wartości niemalejących pewnego pola x), to możemy mówić o ideale. Cóż jednak mamy począć, gdy elementy listy mają bardziej skomplikowaną strukturę?
Poruszony powyżej problem był na tyle charakterystyczny dla wielu programów, że zostało do jego rozwiązania wymyślone pewne „sprytne” rozwiązanie.
Pomysł polega na uproszczeniu i na skomplikowaniu zarazem tego, co poznaliśmy wcześniej. Uproszczenie polega na tym, że elementy zapamiętywane w liście nie są w żaden sposób wstępnie sortowane. Obok listy danych będziemy ponadto dysponować kilkoma listami wskaźników do nich. List tych będzie tyle, ile sobie życzymy kryteriów sortowania.
Ideę indeksowania przedstawia poniższy rysunek:
Poniżej przytoczono program indeksowania listy jednokierunkowej przechowującej nazwiska i uposażenie osób wg nazwisk alfabetycznie (plik PROG71.CPP).
/***************************************************************************************/
/* Program tworzy listę jednokierunkową bez sortowania elementów. */
/* Element roboczy listy przechowuje wartość typu całkowitego */
/* oraz wskaźnik na następny element. */
/* Utworzona lista jest ukierunkowana "do przodu". */
/* Program umożliwia również wyświetlenie kolejnych elementów */
/* listy, a także wyświetlenie listy alfabetycznie wg nazwisk. */
/***************************************************************************************/
#include <iostream.h>
#include <conio.h>
#include <string.h>
typedef struct El // struktura elementu roboczego
{ char Nazwisko[30];
int Uposazenie;
struct El *Nastepny; // wskaźnik do następnego elementu
} ELEMENT;
typedef struct // struktura elementu informacyjnego
{ ELEMENT *Glowa1;
ELEMENT *Ogon1;
}INFO;
typedef struct L
{ ELEMENT *Adres;
struct L *Nastepny;
} INDEKS;
typedef struct
{ INDEKS *Glowa2;
INDEKS *Ogon2;
} IND_INFO;
int i,n;
char Nazwisko[30];
int Uposazenie;
INFO *Info1; // wskaźnik do elementu informacyjnego
IND_INFO *Info2;
void Do_Listy( char Nazwisko[ ], int Uposazenie );
void Pisz_Liste( );
void Tworz_Indeksy( );
void Indeksuj_Liste( );
void Sortuj_Liste( );
void main ()
{
clrscr();
cout << "Podaj długość listy: ";
cin >> n;
Info1 = new INFO;
Info1->Glowa1 = NULL;
Info1->Ogon1 = NULL;
cout << "Wprowadź kolejne elementy listy:\n";
for (i=1; i <=n; i++)
{
cout.width(2);
cout << i << ". ";
cout << "Nazwisko: ";
cin.width(30);
cin >> Nazwisko;
gotoxy(40, wherey() -1);
cout << "Uposażenie: ";
cin >> Uposazenie;
Do_Listy(Nazwisko, Uposazenie);
}
cout << "\nOto lista:\n " << endl;
Pisz_Liste();
Tworz_Indeksy();
Indeksuj_Liste();
cout << "\nLista posortowana:\n";
Sortuj_Liste();
while (!kbhit())
{ }
}
//---------------------------------------------------------------------------------
void Do_Listy(char Nazwisko[], int Uposazenie)
/* Funkcja dołącza nowy element do listy - na koniec listy */
{
ELEMENT *Nowy;
Nowy = new ELEMENT;
if (Info1->Glowa1 == NULL) // dołączenie nowego elementu
{ // do listy pustej
Info1->Glowa1 = Nowy;
Info1->Ogon1 = Info1->Glowa1;
}
else // dołączenie nowego elementu
{ Info1->Ogon1->Nastepny = Nowy; // na końcu listy niepustej
Info1->Ogon1 = Nowy;
}
strcpy(Nowy->Nazwisko, Nazwisko);
Nowy->Uposazenie = Uposazenie;
Nowy->Nastepny = NULL;
}
//---------------------------------------------------------------------------------
void Pisz_Liste()
// Funkcja wyświetla elementy listy w kierunku od początku do końca
{
ELEMENT *Biezacy;
int i = 0;
int x,y;
x = wherex(); y = wherey();
gotoxy(x+3, y); cout << "Nazwisko:";
gotoxy(40, wherey()); cout << "Uposażenie:\n" << endl;
Biezacy = Info1->Glowa1;
if (Info1->Glowa1 == NULL)
cout << "Lista jest pusta\n";
else while (Biezacy != NULL)
{
i++;
cout.width(2);
cout << i << ". " << Biezacy->Nazwisko;
gotoxy(40, wherey());
cout << Biezacy->Uposazenie << endl;
Biezacy = Biezacy->Nastepny;
}
}
//---------------------------------------------------------------------------------
void Tworz_Indeksy()
// Funkcja umożliwia utworzenie listy indeksów
{
ELEMENT *Biezacy;
INDEKS *Ind, *Przed;
Biezacy = Info1->Glowa1;
if (Biezacy == NULL)
cout << "Lista jest pusta\n";
else
{
Ind = new INDEKS;
Info2->Glowa2 = Ind;
do { Ind->Adres = Biezacy;
Przed = Ind;
Info2->Ogon2 = Ind;
Biezacy = Biezacy->Nastepny;
Ind = new INDEKS;
Przed->Nastepny = Ind;
}
while (Biezacy != NULL);
Info2->Ogon2->Nastepny = NULL;
}
}
//---------------------------------------------------------------------------------
void Indeksuj_Liste()
// Funkcja sortuje indeksy w kolejności alfabetycznej nazwisk.
// Jako algorytm sortujący wykorzystano sortowanie bąbelkowe.
{
int Zamiana;
INDEKS *Przed, *Po, *Tym;
do
{ Przed = Info2->Glowa2;
Po = Przed->Nastepny;
Zamiana = 0;
while ( Po != NULL)
{ if (strcmp(Przed->Adres->Nazwisko, Po->Adres->Nazwisko) > 0)
{ Zamiana = 1;
Tym = new INDEKS;
Tym->Adres = Przed->Adres;
Przed->Adres = Po->Adres;
Po->Adres = Tym->Adres;
}
Przed = Po;
Po = Przed->Nastepny;
}
}
while (Zamiana == 1);
}
//---------------------------------------------------------------------------------
void Sortuj_Liste()
// Funkcja wyświetla nazwiska z listy w kolejności alfabetycznej
{
INDEKS *Biezacy;
int i=0;
Biezacy = Info2->Glowa2;
do { i++;
cout.width(2);
cout << i << ". ";
cout << Biezacy->Adres->Nazwisko;
gotoxy(40, wherey());
cout << Biezacy->Adres->Uposazenie << endl;
Biezacy = Biezacy->Nastepny;
}
while (Biezacy != NULL);
}
W przypadku, gdy należy posortować listę wg kilku kryteriów, należy utworzyć tyle list indeksów, ile będzie kryteriów sortowania. W związku z powyższym celowe jest użycie tablicy, której elementami będą struktury przechowujące adres początku i końca poszczególnych list indeksowych. Przykład programu umożliwiającego utworzenie listy jednokierunkowej nazwisk i uposażenia osób oraz wyświetlenia tej listy wg nazwisk alfabetycznie oraz wg uposażenia malejąco, przedstawiono poniżej (plik PROG72.CPP):
/***************************************************************************************/
/* Program tworzy listę jednokierunkową bez sortowania elementów. */
/* Element roboczy listy przechowuje wartość typu całkowitego */
/* oraz wskaźnik na następny element. */
/* Utworzona lista jest ukierunkowana "do przodu". */
/* Program umożliwia również wyświetlenie kolejnych elementów */
/* listy, a także wyświetlenie listy alfabetycznie wg nazwisk */
/* oraz wg uposażenia - malejąco. */
/***************************************************************************************/
#include <iostream.h>
#include <conio.h>
#include <string.h>
typedef struct El // struktura elementu roboczego
{ char Nazwisko[30];
int Uposazenie;
struct El *Nastepny; // wskaźnik do następnego elementu
} ELEMENT;
typedef struct // struktura elementu informacyjnego
{ ELEMENT *Glowa1;
ELEMENT *Ogon1;
}INFO;
typedef struct L
{ ELEMENT *Adres;
struct L *Nastepny;
} INDEKS;
typedef struct
{ INDEKS *Glowa2;
INDEKS *Ogon2;
} IND_INFO;
int i,n;
char Nazwisko[30];
int Uposazenie;
INFO *Info1; // wskaźnik do elem. inform. listy danych
IND_INFO *Info2[2]; // tablica wskaźników do elem. inf. list indeksów
void Do_Listy( char Nazwisko[], int Uposazenie);
void Pisz_Liste();
void Tworz_Indeksy_Nazwisk();
void Indeksuj_Liste_Nazwisk();
void Tworz_Indeksy_Upos();
void Indeksuj_Liste_Upos();
void Sortuj_Liste(IND_INFO *ind);
void main ()
{
clrscr();
cout << "Podaj długość listy: ";
cin >> n;
Info1 = new INFO;
Info1->Glowa1 = NULL;
Info1->Ogon1 = NULL;
cout << "Wprowadź kolejne elementy listy:\n";
for (i=1; i <=n; i++)
{
cout.width(2);
cout << i << ". ";
cout << "Nazwisko: ";
cin.width(30);
cin >> Nazwisko;
gotoxy(40, wherey() -1);
cout << "Uposażenie: ";
cin >> Uposazenie;
Do_Listy(Nazwisko, Uposazenie);
}
cout << "\nOto lista:" << endl;
Pisz_Liste();
Tworz_Indeksy_Nazwisk();
Indeksuj_Liste_Nazwisk();
cout << "\nLista posortowana wg nazwisk:\n";
Sortuj_Liste(Info2[0]);
Tworz_Indeksy_Upos();
Indeksuj_Liste_Upos();
cout << "\nLista posortowana wg uposażenia (malejąco):\n";
Sortuj_Liste(Info2[1]);
while (!kbhit())
{ }
}
void Do_Listy(char Nazwisko[ ], int Uposazenie)
/* Funkcja dołącza nowy element do listy - na koniec listy */
{
ELEMENT *Nowy;
Nowy = new ELEMENT;
if (Info1->Glowa1 == NULL) // dołączenie nowego elementu
{ // do listy pustej
Info1->Glowa1 = Nowy;
Info1->Ogon1 = Info1->Glowa1;
}
else // dołączenie nowego elementu
{ Info1->Ogon1->Nastepny = Nowy; // na końcu listy niepustej
Info1->Ogon1 = Nowy;
}
strcpy(Nowy->Nazwisko, Nazwisko);
Nowy->Uposazenie = Uposazenie;
Nowy->Nastepny = NULL;
}
//---------------------------------------------------------------------------------
void Pisz_Liste()
// Funkcja wyświetla elementy listy w kierunku od początku do końca
{
ELEMENT *Biezacy;
int i = 0;
Biezacy = Info1->Glowa1;
if (Info1->Glowa1 == NULL)
cout << "Lista jest pusta\n";
else while (Biezacy != NULL)
{
i++;
cout.width(2);
cout << i << ". " << Biezacy->Nazwisko;
gotoxy(40, wherey());
cout << Biezacy->Uposazenie << endl;
Biezacy = Biezacy->Nastepny;
}
}
//---------------------------------------------------------------------------------
void Tworz_Indeksy_Nazwisk()
// Funkcja umożliwia utworzenie listy indeksów nazwisk
{
ELEMENT *Biezacy;
INDEKS *Ind, *Przed;
Biezacy = Info1->Glowa1;
if (Biezacy == NULL) cout << "Lista jest pusta\n";
else
{
Ind = new INDEKS;
Info2[0]->Glowa2 = Ind;
do { Ind->Adres = Biezacy;
Przed = Ind;
Info2[0]->Ogon2 = Ind;
Biezacy = Biezacy->Nastepny;
Ind = new INDEKS;
Przed->Nastepny = Ind;
}
while (Biezacy != NULL);
Info2[0]->Ogon2->Nastepny = NULL;
}
}
//---------------------------------------------------------------------------------
void Indeksuj_Liste_Nazwisk()
// Funkcja sortuje indeksy w kolejności alfabetycznej nazwisk.
// Jako algorytm sortujący wykorzystano sortowanie bąbelkowe.
{
int Zamiana;
INDEKS *Przed, *Po, *Tym;
do
{ Przed = Info2[0]->Glowa2;
Po = Przed->Nastepny;
Zamiana = 0;
while ( Po != NULL)
{ if (strcmp(Przed->Adres->Nazwisko, Po->Adres->Nazwisko) > 0)
{ Zamiana = 1;
Tym = new INDEKS;
Tym->Adres = Przed->Adres;
Przed->Adres = Po->Adres;
Po->Adres = Tym->Adres;
}
Przed = Po;
Po = Przed->Nastepny;
}
}
while (Zamiana == 1);
}
void Tworz_Indeksy_Upos()
// Funkcja umożliwia utworzenie listy indeksów uposażenia
{
ELEMENT *Biezacy;
INDEKS *Ind, *Przed;
Biezacy = Info1->Glowa1;
if (Biezacy == NULL)
cout << "Lista jest pusta\n";
else
{
Ind = new INDEKS;
Info2[1]->Glowa2 = Ind;
do { Ind->Adres = Biezacy;
Przed = Ind;
Info2[1]->Ogon2 = Ind;
Biezacy = Biezacy->Nastepny;
Ind = new INDEKS;
Przed->Nastepny = Ind;
}
while (Biezacy != NULL);
Info2[0]->Ogon2->Nastepny = NULL;
}
}
//---------------------------------------------------------------------------------
void Indeksuj_Liste_Upos()
// Funkcja sortuje indeksy wg uposażenia (malejąco).
// Jako algorytm sortujący wykorzystano sortowanie bąbelkowe.
{
int Zamiana;
INDEKS *Przed, *Po, *Tym;
do
{ Przed = Info2[1]->Glowa2;
Po = Przed->Nastepny;
Zamiana = 0;
while ( Po != NULL)
{ if (Przed->Adres->Uposazenie < Po->Adres->Uposazenie )
{ Zamiana = 1; Tym = new INDEKS;
Tym->Adres = Przed->Adres;
Przed->Adres = Po->Adres;
Po->Adres = Tym->Adres;
}
Przed = Po; Po = Przed->Nastepny;
}
}
while (Zamiana == 1);
}
void Sortuj_Liste(IND_INFO *ind)
// Funkcja wyświetla listę posortowaną wg zadanego kryterium
{
INDEKS *Biezacy;
int i=0;
Biezacy = ind->Glowa2;
do { i++;
cout.width(2);
cout << i << ". ";
cout << Biezacy->Adres->Nazwisko;
gotoxy(40, wherey());
cout << Biezacy->Adres->Uposazenie << endl;
Biezacy = Biezacy->Nastepny;
}
while (Biezacy != NULL);
}
2. KLASY
W C++ dane mogą być powiązane z funkcjami. Oznacza to, że kompilator nie dopuści do tego, żeby do funkcji oczekującej argumentu typu temperatura wysłać argument typu uposażenie. Takim typem w C++ definiującym nie tylko jedną lub kilka zebranych razem danych ale również sposób ich zachowania się jako całości, jest klasa.
Klasa - to inaczej mówiąc typ. Definicja klasy ma następującą postać:
class identyfikator_typu
{
ciało klasy
};
gdzie ciało klasy zawiera deklaracje składników klasy.
Składnikami mogą być różnego typu dane (np. int, float, struct itd.). Nazywamy je danymi składowymi aktualnej klasy. Przykładowo:
class Okrag
{
public:
int x, y, r;
…
};
Aby odnieść się do składników obiektu, możemy posługiwać się jedną z poniższych notacji:
obiekt . składnik
wskaźnik -> składnik
referencja . składnik
Przykładowo:
Okrag Zielony; // definicja egzemplarza obiektu klasy Okrag
Okrag * Wskaz; // definicja wskaźnika do obiektu typu Okrag
Okrag & Moj = Zielony; // definicja referencji do obiektu klasy Okrag
Do składnika r okręgu zielonego możemy odwołać się w sposób następujący:
Zielony . r = 100; // poprzez nazwę obiektu
Wskaz = & Zielony;
Wskaz -> r = 100; // poprzez wskaźnik do obiektu Zielony
Moj . r = 100; // poprzez referencję
Składnikami klasy mogą być też funkcje. Funkcje te nazywamy funkcjami składowymi. Za ich pomocą pracujemy zwykle na danych składowych. Oto klasa Okrag wyposażona w funkcje:
class Okrag
{
public:
int x, y, r;
void Inicjuj ( int x, int y, int r);
void Rysuj ( int x, int y, int r);
};
W ogólnym przypadku deklaracji funkcji mogą być pomieszane z deklaracjami danych. Niezależnie od miejsca zdefiniowania składnika wewnątrz klasy - składnik jest znany w całej definicji klasy. Mówimy, ze nazwy deklarowane w klasie mają zakres ważności równy obszarowi całej klasy.
W ciele klasy są dane i funkcje. Jest to bardzo ważny fakt, bowiem w tej definicji, jak w kapsule, zamknięte są dane oraz funkcje do posługiwania się nimi. Takie zamknięcie nazywamy enkapsulacją ( od ang. encapsulation).
Funkcje składowe klasy mają zakres klasy - ich deklaracje tkwią wewnątrz nawiasu klamrowego, który wyznacza lokalny zakres ważności.
Zakresem ważności jednej funkcji Nastaw jest klasa Czajnik, natomiast zakresem ważności innej funkcji Nastaw jest klasa Zegarek. Te funkcje mogą co najwyżej się wzajemnie zasłaniać.
Definicja klasy sprawia, że dane i funkcje, które dawniej mieliśmy luźnie rozrzucone w programie - teraz zamknięto w kapsule. Dzięki temu, w momencie definicji pojedynczego egzemplarza obiektu takiej klasy otrzymujemy realizację danej kapsuły. Kapsułą jest łatwiej się posługiwać niż rozsypanymi elementami.
2.1. Ukrywanie informacji
Skoro składniki klasy zamknięte są w kapsule, to kapsuła może być przezroczysta lub nie.
Oto przykład klasy:
class Moj_Typ
{
private:
int Liczba; // prywatne dane składowe
float Temperatura;
char Komunikat [80];
int Czy_Gotowe( ); // prywatna funkcja składowa
public:
float Predkosc; // publiczna dana składowa
int Pomiar( ); // publiczna funkcja składowa
}
W definicja klasy można wyróżnić dwie grupy składowych występujące po etykietach private i public.
Etykieta private oznacza, że deklarowane po niej składniki (dane i funkcje) są dostępne tylko z wnętrza klasy. W przypadku danych składowych oznacza to, że tylko funkcje będące składnikami klasy mogą te prywatne dane odczytywać lub do nich zapisywać. W przypadku funkcji oznacza to, że mogą one zostać wywołane tylko przez inne funkcje składowe tej klasy.
Etykieta public oznacza, że zadeklarowane po niej składowe mogą (dane i funkcje) mogą być używane wewnątrz klasy, a także spoza zakresu klasy.
Są trzy etykiety, za pomocą których można określać dostęp do składników klasy:
private:
protected:
public:
Są trzy rodzaje dostępu do składnika klasy.
Składnik private: jest dostępny tylko dla składowych danej klasy. Jeżeli zależy nam na ukryciu informacji, to wówczas składnik powinien być deklarowany jako prywatny.
Składnik protected: jest dostępny tak, jak składnik private, ale dodatkowo jest dostępny dla klas wywodzących się z danej klasy.
Składnik public: jest dostępny bez ograniczeń. Zwykle składnikami takimi są wybrane funkcje składowe. Za ich pomocą dokonujemy z zewnątrz operacji na danych prywatnych.
Etykiety można umieszczać w dowolnej kolejności, mogą też się powtarzać.
Domniemanie: zakłada się, że dopóki w definicji klasy nie wystąpi żadna z tych etykiet, składniki przez domniemanie mają dostęp private.
Należy podkreślić, że sterowanie dostępem jest dobrodziejstwem, które chroni byśmy czegoś w danym obiekcie przez nieuwagę nie zepsuli.
2. Klasa a obiekt
Definicja klasy to jakby projekt techniczny nowego typu zmiennej. Mając zdefiniowaną klasę możemy stworzyć kilka egzemplarzy obiektów danej klasy, tak jak w przypadku typu (klasy) int możemy stworzyć kilka obiektów typu int:
int x, y, cena, kolor;
Rozpatrzmy klasę:
class Osoba
{
char Nazwisko[40];
int Wiek;
public:
void Zapamietaj (char *, int);
void Pisz ( );
};
Osoba Student1, Student2, Asystent; // definicja 3 obiektów klasy Osoba
Sama definicja klasy nie definiuje żadnych obiektów. Klasa to typ obiektu, ale nie sam obiekt.
Warto uzmysłowić jeszcze jedną rzecz: jeśli w naszym przykładzie zdefiniowaliśmy trzy obiekty klasy Osoba, to w pamięci utworzone zostały 3 różne komplety danych składowych (Nazwisko i Wiek). Natomiast funkcje składowe są zapamiętane w pamięci tylko jednokrotnie. Jest to zrozumiałe, bo przecież na każdym komplecie danych funkcje te powinny wykonać takie same działanie.
3. Funkcje składowe
Funkcje składowe mają pełny dostęp do wszystkich składników swojej klasy, to znaczy: do danych (mogą z nich korzystać) i do innych funkcji (mogą je wywoływać). Do składnika swojej klasy funkcje odwołują się przez podanie jego nazwy .
3.1. Wywołanie funkcji składowych
Nazwa_Obiektu . Nazwa_Funkcji_Składowej ( argumenty );
Przykładowo:
Student1 . Zapamietaj (″Kowalski″, 21);
Możemy także wywołać funkcję składową dla danego obiektu, pokazując na niego wskaźnikiem lub za pomocą referencji np.:
Osoba * Wsk; // definicja wskaźnika
Wsk = &Asystent; // ustawienie wskaźnika na obiekcie Asystent
Osoba &Belfer = Asystent; // definicja referencji
Wsk -> Zapamietaj (″Kowalski″, 21);
Belfer. Zapamietaj (″Nowak″, 30);
3.2. Definiowanie funkcji składowych
Definicja funkcji składowej może znaleźć się w dwóch miejscach:
Pierwszy sposób: wewnątrz definicji klasy. Przykładowo:
class Osoba
{ char Nazwisko [80]; // składniki prywatne
int Wiek;
public: // składniki publiczne
//-----------------------------------------------------
void Zapamietaj (char * Napis, int Lata )
{
strcpy (Nazwisko, Napis);
Wiek = Lata;
}
//-----------------------------------------------------
void Pisz ( )
{
cout << Nazwisko << ″, lat: ″ << Wiek << endl;
}
};
Drugi sposób: w definicji klasy umieszcza się tylko same deklaracje funkcji składowych, natomiast definicje są napisane poza ciałem klasy. Przykładowo:
class Osoba
{
char Nazwisko [80]; // składniki prywatne
int Wiek;
public: // składniki publiczne
//------------deklaracje funkcji składowych
void Zapamietaj (char * Napis, int Lata );
void Pisz ( );
};
//--------------------------------- koniec definicji klasy
void Osoba::Zapamietaj (char * Napis, int Lata )
{
strcpy (Nazwisko, Napis);
Wiek = Lata;
}
//-----------------------------------------------------
void Osoba::Pisz ( )
{
cout << Nazwisko << ″, lat: ″ << Wiek << endl;
}
//-----------------------------------------------------
Ponieważ funkcje znajdują się teraz poza definicją klasy, dlatego ich nazwa została uzupełniona nazwą klasy, do której mają należeć. Służy do tego operator zakresu ::.
Funkcja zdefiniowana poza klasą ma dokładnie taki sam zakres, jakby była zdefiniowana wewnątrz klasy.
Jednakże sposób definiowania funkcji wewnątrz, czy na zewnątrz definicji klasy stanowi różnicę dla kompilatora. Jeśli bowiem funkcję składową zdefiniowaliśmy wewnątrz definicji klasy, to kompilator uznaje, że chcemy, aby ta funkcja była typu inline.
Należy teraz wyjaśnić, co to jest funkcja typu inline, np.:
inline int Zaokr ( float Liczba);
{
return (Liczba + 0.5);
}
Zdefiniowanie funkcji typu inline powoduje to, że ile razy umieścimy w programie wywołanie funkcji Zaokr, kompilator umieści dosłownie jej ciało w wierszu, w którym to wywołanie nastąpiło. Nie będzie więc żadnych akcji związanych z wywoływaniem i powrotem z tej funkcji. W rezultacie kod taki będzie wykonywał się szybciej. Funkcje typu inline zostały pomyślane dla naprawdę krótkich funkcji, i tylko wtedy mają sens. Nie należy ich nadużywać.
Powracając do definicji funkcji składowej poza definicja klasy, należy podkreślić, że taka funkcja nie jest automatycznie uznawana jako inline.
Powstaje pytanie: kiedy lepiej zdefiniować funkcję składową wewnątrz klasy, a kiedy poza klasą? Zalecenia są następujące:
Jeśli ciało funkcji składowej ma nie więcej niż dwie linijki, to funkcję tę definiujemy wewnątrz klasy. Jest ona automatycznie inline.
Jeśli funkcja składowa jest dłuższa niż dwie linijki, to definiujemy ją poza definicją klasy.
Wobec tego, czy funkcja składowa zdefiniowana poza definicją klasy nie może być nigdy typu inline? Może, tylko trzeba to wtedy wyraźnie zaznaczyć pisząc słowo inline, np.:
inline void Osoba :: Pisz ( )
{
// ciało funkcji
}
Przykład prostego programu z użyciem klasy Osoba (plik PROG56.CPP):
#include <iostream.h>
#include <conio.h>
#include <string.h>
//------------------------ definicja klasy ------------------
class Osoba
{
char Nazwisko [50];
int Wiek;
public:
void Czyt_Dane(char *Napis, int Wiek);
//------------------------------------
void Pisz_Dane ()
{
cout << "\t" << Nazwisko << " , lat - "
<< Wiek << endl;
}
};
//---------------------- koniec definicji klasy ------------
void Osoba::Czyt_Dane (char *Napis, int L)
{
strcpy (Nazwisko, Napis);
Wiek = L;
}
//---------------------------------------------------------
void main ()
{
Osoba Student1, Student2, Asystent, Profesor;
clrscr ();
cout << "Dla informacji podaję, że jeden obiekt klasy Osoba\n"
<< " ma rozmiar: "
<< sizeof(Osoba)
<< " bajty. To samo inaczej: "
<< sizeof(Student1) << endl;
Student1.Czyt_Dane("Kowalski Jan", 20);
Student2.Czyt_Dane("Sikorski Piotr", 23);
Asystent.Czyt_Dane("Michalski Tomasz", 28);
Profesor.Czyt_Dane("Nowak Andrzej", 42);
cout << "\nSprawdzamy wpisaną informację do obiektów.\n";
cout << "\ndane z obiektu Profesor: \n";
Profesor.Pisz_Dane();
cout << "\ndane z obiektu Asystent: \n";
Asystent.Pisz_Dane();
cout << "\ndane z obiektu Student1: \n";
Student1.Pisz_Dane();
cout << "\ndane z obiektu Student2: \n";
Student2.Pisz_Dane();
cout << "\nPodaj nazwisko trzeciego studenta: ";
char Student[50];
cin >> Student;
cout << "Podaj wiek studenta: ";
int Ile_Lat;
cin >> Ile_Lat;
Student1.Czyt_Dane(Student, Ile_Lat);
cout << "\nOto dane, które teraz są zapamiętane w obiektach"
<< "\nStudent1 i Student2:\n";
Student1.Pisz_Dane();
Student2.Pisz_Dane();
while (!kbhit())
{ }
}
3.3. Odwołanie się do publicznych danych składowych
Rozpatrzmy następującą definicję klasy:
class Liczby
{
int L1:
public :
float L2;
void Fun ( );
};
Są tu dwie dane składowe. Składnik L1 jest prywatny (przez domniemanie). Jako prywatny może być dostępny tylko z zakresu klasy - czyli wewnątrz funkcji składowej Fun.
Natomiast składnik publiczny L2 oprócz tego, że może być dostępny w funkcji składowej Fun, dostępny jest także z zewnątrz klasy. Pracując jednak na nim z zewnątrz musimy podać, o który konkretnie obiekt chodzi, np.:
Liczba Temperatura, Cisnienie;
Temperatura . L2 = 18.6;
Ciscienie . L2 = 1003;
cout << ″Temperatura wynosi : ″ << Temperatura . L2 << ″ stopni C\n″;
cout << ″Ciśnienie wynosi: ″ << Cisnienie . L2 << ″ hPa\n″;
3.4. Zasłanianie nazw
Ponieważ nazwy składników klasy (danych i funkcji) mają zakres klasy, w obrębie klasy zasłaniają elementy o takiej samej nazwie leżące poza klasą.
Przykładowo, zmienna
int ile;
będąca składnikiem klasy zasłania wewnątrz klasy ewentualną zmienną ile o zasięgu globalnym lub lokalnym. Stają się one wtedy niedostępne. Można jednak, mimo wszystko, dostać się do zasłoniętej nazwy globalnej za pomocą operatora zakresu.
Możliwa jest również taka sytuacja: wewnątrz klasy definiujemy zakres lokalny, w którym zdefiniowana jest pewna nazwa. Nazwa ta zasłoni wówczas identyczną nazwę składnika klasy.
Rozpatrzmy następujący przykład ( plik PROG57.CPP):
#include <iostream.h>
#include <conio.h>
int Balkon = 50;
void Spiew ();
//------------------------- definicja klasy Opera ---------------
class Opera
{
public:
int n;
float Balkon;
void Funkcja();
void Spiew()
{
cout << "funkcja Spiew (z Opery): tra-la-la!\n";
}
};
//--------------------- koniec definicji klasy Opera ------------
void Opera::Funkcja()
{
cout << "Balkon (składnik klasy Opera) = "
<< Balkon << endl;
cout << "Balkon (zmienna globalna) = "
<< ::Balkon << endl;
//----- definicja zmiennej lokalnej dla danej funkcji --------
char Balkon = 'A';
cout << "\nPo definicji zmiennej lokalnej:\n";
cout << "Balkon (zmienna lokalna) = " << Balkon << endl;
cout << "Balkon (składnik klasy Opera) = " << Opera::Balkon << endl;
cout << "Balkon (zmienna globalna) = " << ::Balkon << endl;
//----- wywołanie funkcji Spiew -----------------------------
Spiew ();
int Spiew;
Spiew = 10;
// Spiew (); // błąd w trakcie kompilacji
// bo nazwa funkcji Spiew jest zasłonięta
cout << "Po zasłonięciu funkcję Spiew można wywołać "
<< "tylko w taki sposób - Opera::Spiew\n";
Opera::Spiew();
}
//**************************************************************
void main ()
{
Opera Halka;
clrscr();
Halka.Balkon = 2;
Halka.Funkcja();
Spiew();
while (!kbhit())
{ }
}
//*************************************************************
void Spiew()
{
cout << "zwykła funkcja Spiew (nie ma nic wspólnego "
<< "z klasą Opera)\n";
}
3.5. Przesyłanie do funkcji argumentów będących obiektami
Przez domniemanie zakłada się, że obiekt jest przesyłany do funkcji przez wartość, czyli tak samo jak to się odbywa z danymi typu int czy float. To znaczy, że obiekt śluzy do inicjalizacji swojej kopii wewnątrz funkcji.
Wynika z tego ważna konsekwencja: jeśli obiekt jest duży, to proces kopiowania może trwać długo. Wielokrotne wysyłanie przez wartość może wyraźnie wpłynąć na zwolnienie programu. Wysyłanie przez wartość nie jest więc dobrym rozwiązaniem.
Lepszym rozwiązaniem w takim przypadku jest przesyłanie przez referencję, np.:
void Prezentacja (Osoba &Ktos)
{
cout << ″Mam zaszczyt przedstawić Państwu,\n″
<< ″Oto we własnej osobie: ″ ;
cout << Ktos . Pisz_Dane ();
}
4. Konstruktor
Definicję obiektu i nadanie mu wartości można załatwić w jednej instrukcji. W tym celu należy posłużyć się specjalną funkcją składową zwaną konstruktorem. Charakteryzuje się ona tym, że nazywa się tak samo jak klasa. Oto przykład klasy wyposażonej w konstruktor:
class Numer
{
int Liczba;
public:
Numer ( int L ) { Liczba = L; } // konstruktor
void Schowaj ( int L ) { Liczba = L; }
int Zwracaj ( ) { return Liczba: }
} ;
Należy zwrócić uwagę, że konstruktor nie ma żadnego określenia typu wartości zwracanej. Nie może tam być nawet typu void. Oczywiście z faktu tego wynika, że nie może tam wystąpić instrukcja return zwracająca jakąkolwiek wartość.
Oto jak w programie posługujemy się konstruktorem klasy Numer:
Numer a = Numer(15);
lub
Numer b (15);
Rozpatrzmy przykład (plik PROG59.CPP ):
#include <iostream.h>
#include <conio.h>
#include <string.h>
//--------------- definicja klasy Numer ------------------------
class Numer
{
int Liczba;
char Nazwa[40];
public: //----- funkcje składowe ------
Numer (int L, char *Opis); // deklaracja konstruktora
void Schowaj(int L)
{
Liczba = L;
Melduj();
}
int Zwracaj() { return Liczba; }
void Melduj()
{
cout << Nazwa << Liczba << endl;
}
};
//-------------- koniec definicji klasy Numer -----------------
Numer::Numer(int L, char *Opis)
{
Liczba = L;
strcpy(Nazwa, Opis);
}
//*************************************************************
void main ()
{
Numer Samolot (1200, "Bieżąca wysokość: ");
Numer Atmosfera(920, "Ciśnienie atmosferyczne: "),
Kurs(63, "Kierunek lotu: ");
//---- wstępny raport -------------------
clrscr();
Samolot.Melduj();
Kurs.Melduj();
Atmosfera.Melduj();
cout << "\nKorekta lotu:\n";
//---- wzrost wysokości o 1 m ------------
Samolot.Schowaj(1201);
//---- zmiana kursu o 3 stopnie ----------
Kurs.Schowaj(Kurs.Zwracaj()+3);
//---- zmniejszenie ciśnienia o 2 hPa ---
Atmosfera.Schowaj(Atmosfera.Zwracaj()-2);
while (!kbhit())
{ }
}
Nazwa ″konstruktor″ może być nieco myląca. Konstruktor nie konstruuje obiektu, tylko nadaje mu wartość początkową. Konstruktor nie jest obowiązkowy.
5. Destruktor
Przeciwieństwem konstruktora jest destruktor - funkcja składowa wywoływana jest wtedy, gdy obiekt danej klasy ma być zlikwidowany.
Destruktor to funkcja składowa klasy. Destruktor nazywa się tak samo, jak klasa z tym, że przed nazwą ma znak ~ (tylda). Podobnie jak konstruktor - nie ma on określenia typu zwracanego.
Oto przykład klasy wyposażonej w destruktor (plik PROG60.CPP ):
#include <iostream.h>
#include <conio.h>
#include <string.h>
//------- definicja klasy Gadula -------------------------------
class Gadula
{
int Licz;
char Tekst[40];
public:
// konstruktor:
Gadula(int k, char *Opis);
// destruktor:
~Gadula(void);
// inne funkcje składowe:
int Zwracaj() { return Licz; }
void Schowaj(int x) { Licz = x; }
void CoTo()
{ cout << Tekst << " ma wartość: " << Licz << endl; }
};
//------- koniec definicji klasy Gadula -----------------------
Gadula::Gadula(int k, char *Opis) // konstruktor
{
strcpy(Tekst, Opis);
Licz = k;
cout << "Konstruuję " << Tekst << endl;
}
Gadula::~Gadula() // destruktor
{
cout << "Pracuje destruktor (sprząta) "
<< Tekst << endl;
}
//************************************************************
Gadula a(1, "obiekt a (GLOBALNY)" );
Gadula b(2, "obiekt b (GLOBALNY)" );
//************************************************************
void main()
{
clrscr();
a.CoTo();
b.CoTo();
{
cout << "Początek lokalnego zakresu:\n";
Gadula c(30, "obiekt c (lokalny)" );
Gadula a(40, "obiekt a (lokalny)" ); // zasłania globalny
cout << "\nCo teraz mamy: \n";
a.CoTo();
b.CoTo();
c.CoTo();
cout << "Do zasłoniętego obiektu globalnego można się jednak dostać\n";
::a.CoTo();
cout << "Kończy się lokalny zakres---------\n";
}
cout << "Już jestem poza blokiem\n";
a.CoTo();
b.CoTo();
cout << "Uruchamiam destruktor obiektu a\n";
a.Gadula::~Gadula();
cout << "K o n i e c p r o g r a m u\n";
while (!kbhit())
{ }
}
Oto przykład programu wykorzystującego klasę (plik OCENA4.CPP):
/**********************************************************************************/
/* Program umożliwia wystawienie oceny na podstawie liczby */
/* uzyskanych punktów według następującego kryterium: */
/* 0 - 49 pkt. 2 */
/* 50 - 59 pkt. 3 */
/* 60 - 69 pkt. 3+ */
/* 70 - 79 pkt. 4 */
/* 80 - 89 pkt. 4+ */
/* 90 - 100 pkt. 5 */
/* UWAGA: nazwiska i punkty są składnikami klasy */
/**********************************************************************************/
#include <iostream.h>
#include <conio.h>
#include <stdlib.h>
#include <ctype.h>
#include <fstream.h>
const int Max_Punkt = 100,
Min_Punkt = 0,
n = 20;
int Czyt_Int ( char Napisy[ ], int Ogr_d, int Ogr_g);
//------------- definicja klasy Osoba -----------------------------
class Osoba
{ public:
char Nazwisko[30];
int Punkty;
enum Stopnie {ndst, dst, dst_plus, db, db_plus, bdb};
void Wprowadz_Jedna_Osobe(char Nazwisko[], int &Lp,
int Ogr_d, int Ogr_g);
Stopnie Wystaw_Ocene ( int Lp );
void Wyswietl_Ocene ( Stopnie Ocena);
};
//------------- koniec definicja klasy Osoba ----------------------
void Osoba::Wprowadz_Jedna_Osobe(char Nazwisko[], int &Lp,
int Ogr_d, int Ogr_g)
/* Funkcja umożliwia wprowadzenie nazwiska i liczby punktów */
/* dla jednej osoby. */
/* UWAGA: po wprowadzeniu nazwiska nacisnąć <Enter>, */
/* wprowadzić liczbę punktów i nacisnąć <Enter>. */
{
cin >> Nazwisko;
gotoxy(32, wherey()-1);
Lp = Czyt_Int("", Ogr_d, Ogr_g);
}
//*****************************************************************
Stopnie Osoba::Wystaw_Ocene ( int Lp )
/* Funkcja umożliwia wystawienie oceny na podstawie */
/* liczby uzyskanych punktów. */
{
Stopnie Wynik;
Lp = Lp/10;
switch (Lp)
{
default : { Wynik = ndst; break; }
case 5 : { Wynik = dst; break; }
case 6 : { Wynik = dst_plus; break; }
case 7 : { Wynik = db; break; }
case 8 : { Wynik = db_plus; break; }
case 9 : { Wynik = bdb; break; }
case 10 : { Wynik = bdb; break; }
}
return Wynik;
}
//*****************************************************************
void Osoba::Wyswietl_Ocene ( Stopnie Ocena)
/* Funkcja umożliwia wyświetlenie oceny w postaci słownej */
{
switch (Ocena)
{
case ndst : { cout << "niedostateczna\n"; break; }
case dst : { cout << "dostateczna\n"; break; }
case dst_plus : { cout << "dość dobra\n"; break; }
case db : { cout << "dobra\n"; break; }
case db_plus : { cout << "ponad dobra\n"; break; }
case bdb : { cout << "bardzo dobra\n"; break; }
}
}
//************ koniec definicji funkcji składowych ****************
char Napis [] = {"Ile osób oceniasz?: "};
Osoba Dane[n];
int Ile_Osob;
void Pisz_Naglowek (char Napis1[ ], char Napis2[ ], char Napis3[ ]);
void Wprowadz_Wszystkie_Dane(Osoba Dane[],
int Ile_Osob, int Ogr_d, int Ogr_g);
void Wyswietl_Wszystkie_Oceny(Osoba Dane[ ],int Ile_Osob);
/*----------------------------------------------------------------*/
void main () // program główny
{
clrscr ();
Ile_Osob = Czyt_Int(Napis, 1,n);
Pisz_Naglowek("Nazwisko", "Liczba punktów", "");
Wprowadz_Wszystkie_Dane(Dane, Ile_Osob, Min_Punkt, Max_Punkt);
Pisz_Naglowek("Nazwisko", "Liczba punktów", "Ocena");
Wyswietl_Wszystkie_Oceny(Dane, Ile_Osob);
while (!kbhit())
{ }
} // koniec programu głównego
/*----------------------------------------------------------------*/
void Pisz_Naglowek (char Napis1[ ], char Napis2[ ], char Napis3[ ])
/* Funkcja umożliwia napisanie nagłówka składającego się */
/* z max trzech wyrazów */
{
clrscr();
cout << Napis1;
gotoxy (32, wherey());
cout << Napis2;
gotoxy (55, wherey());
cout << Napis3 << endl;
cout << endl;
}
/*---------------------------------------------------------------*/
int Czyt_Int ( char Napisy[], int Ogr_d, int Ogr_g)
/* Funkcja umożliwia wczytanie liczby całkowitej oraz */
/* kontrolowanie formalnej i logicznej poprawności liczby */
{
int x, y, Liczba, r;
enum Boolean {false, true} Poza_Zakresem, Powtorz;
cout << Napisy;
x = wherex();
y = wherey();
while (1)
{
cin >> Liczba;
Powtorz = (Liczba < Ogr_d) || (Liczba > Ogr_g) || (cin.fail());
if (cin.fail())
{
cin.clear(cin.rdstate() & ~ios::failbit);
char L[80];
cin >> L;
}
if (Powtorz)
{
gotoxy (x, y);
clreol ();
}
else break;
}
return Liczba;
}
/*----------------------------------------------------------------*/
void Wprowadz_Wszystkie_Dane(Osoba Dane[ ],
int Ile_Osob, int Ogr_d, int Ogr_g)
/* Funkcja umożliwia wprowadzenie nazwisk i punktów */
/* dla całej grupy */
{
int i;
for (i=0; i < Ile_Osob; i++)
Dane[i].Wprowadz_Jedna_Osobe(Dane[i].Nazwisko, Dane[i].Punkty,
Ogr_d, Ogr_g);
}
/*----------------------------------------------------------------*/
void Wyswietl_Wszystkie_Oceny(Osoba Dane[ ], int Ile_Osob)
/* Funkcja umożliwia wyświetlenie oceny w postaci słownej */
/* dla wszystkich osób. */
{
int i;
Stopnie Ocena;
for (i=0; i < Ile_Osob; i++)
{
Ocena = Dane[i].Wystaw_Ocene(Dane[i].Punkty);
cout << Dane[i].Nazwisko;
gotoxy(32, wherey ());
cout << Dane[i].Punkty;
gotoxy (55, wherey ());
Dane[i].Wyswietl_Ocene(Ocena);
}
}
23
NULL
Jan
Kowalski
37
2000
Michał
Zaleski
30
3000
Adam
Figurski
34
1200
adr1
adr2
adr3
adr3
adr1
adr2
adr2
adr3
adr1
adr3
adr1
adr2
NULL
NULL
NULL