Tematyka i cel ćwiczenia.
Celem ćwiczenia jest zapoznanie z podstawowym elementem języka C++ - klasą. Wprowadzone są następujące pojęcia: klasa, konstruktor(y) klasy, destruktor klasy, metody klasy, operatory new oraz delete wykorzystywane do alokacji i usuwania obiektów z pamięci oraz funkcje zaprzyjaźnione z klasą.
Wymienione wyżej pojęcia są zilustrowane na przykładzie klasy implementującej strukturę danych typu stos.
Wprowadzenie.
Celem wprowadzenia pojęcia klasy do języka C++ jest dostarczenie programiście narzędzia do tworzenia nowych typów, z których można korzystać tak samo wygodnie, jak z typów wbudowanych do języka. Typ zdefiniowany przez użytkownika nie powinien się różnić od typu wbudowanego sposobem używania, lecz tylko sposobem tworzenia go.
Deklaracja klasy zawiera jedno z trzech słów kluczowych class, struct lub union poprzedzających nazwę klasy i następnie deklarację poszczególnych pól danych i funkcji składowych (metod) zawartych w nawiasach klamrowych.
Na przykład:
struct abc {
int x;
double *d;
abc();
abc(int ax, double ad);
~abc();
void fun1(void);
int fun2(int x);
int fun2(double x);
};
W powyższym przykładzie zadeklarowano klasę o nazwie abc zawierającą dwa pola danych: jedno typu int, drugie typu double* oraz 6 metod:
abc() - konstruktor 1,
abc(int, double) - konstruktor 2,
~abc() - destruktor,
void fun1(void) - metoda fun1,
int fun2(int) - metoda 1. fun2,
int fun2(double) - metoda 2. fun2,
Szczególną rolę pełnią metody zwane konstruktorem klasy i destruktorem klasy.
Konstruktorem(ami) klasy jest metoda o nazwie identycznej z nazwą klasy. Jest ona wywoływana automatycznie przez kompilator w chwili tworzenia klasy i ma na celu jej zainicjowanie. Konstruktorów może być kilka (to znaczy mogą być przeciążane), przy czym muszą się one między sobą różnić typami argumentów (podobnie jak funkcje przeciążone). Konstruktor nie może zwracać żadnej wartości (nawet typu void).
Destruktorem klasy jest metoda o nazwie identycznej z nazwą klasy poprzedzoną znakiem „~”. W danej klasie może być tylko jeden destruktor. Musi być on metodą bez żadnych argumentów i nie może zwracać żadnej wartości (nawet typu void). Destruktor jest wywoływany automatycznie w chwili usuwania klasy z pamięci.
Jeżeli w definicji klasy zostanie użyte słowo kluczowe struct (tak jak w przykładzie), wtedy wszystkie elementy klasy (pola danych i metody) są domyślnie dostępne na zewnątrz klasy - inaczej mówiąc są publiczne.
Jeżeli w definicji klasy zostanie użyte słowo kluczowe class, wtedy wszystkie elementy klasy (pola danych i metody) nie są domyślnie dostępne na zewnątrz klasy - inaczej mówiąc są prywatne.
W celu zmiany praw dostępu do poszczególnych elementów klasy można użyć następujących słów kluczowych:
public - powodującego, że wszystkie następne elementy klasy są publiczne (dostępne na zewnątrz klasy i we wszystkich klasach pochodnych),
private - powodującego, że wszystkie następne elementy klasy są prywatne (nie dostępne na zewnątrz klasy i w klasach pochodnych),
protected - powodującego, że wszystkie następne elementy klasy są zastrzeżone (nie dostępne na zewnątrz klasy, ale są dostępne w klasach pochodnych).
Użycie wyżej wymienionych słów ilustrują przykłady poniżej.
struct abc1 {
int x; // public (domyślnie)
private:
double *d; // private
public:
abc1(); // public
abc1(int ax, double ad); // public
~abc1(); // public
void fun1(void); // public
private:
int fun2(int x); // private
public:
int fun2(double x); // public
};
class abc2 {
int x; // private (domyślnie)
double *d; // private (domyślnie)
public:
abc2(); // public
abc2(int ax, double ad); // public
~abc2(); // public
void fun1(void); // public
private:
int fun2(int x); // private
public:
int fun2(double x); // public
};
Funkcją zaprzyjaźnioną z daną klasą jest funkcja nie będąca funkcją składową danej klasy lecz mająca dostęp do prywatnych (private) i zastrzeżonych (protected) elementów klasy. Funkcję zaprzyjaźnione nie są składowymi klasy. W związku z tym nie mają one dostępu do wskaźnika this, dzięki któremu mogłyby identyfikować obiekt (instancję klasy), na której dana funkcja ma operować. Z tego powodu najczęściej jednym z argumentów funkcji zaprzyjaźnionych jest wskaźnik lub referencja do klasy, na której funkcja ma operować. Funkcja zaprzyjaźniona musi być zadeklarowana wewnątrz klasą, z którą ma być zaprzyjaźniona. Jej deklaracja jest poprzedzona słowem kluczowym friend.
Przykład deklaracji funkcji zaprzyjaźnionej jest pokazany poniżej.
class abc4 {
int x;
double *d;
public :
abc4();
abc4(int ax, double ad);
~abc4();
void fun1(void);
friend int fun2(abc4 &A, int x);
friend int fun2(abc4 &A, double x);
};
int fun2(abc4 &A, int x)
{
// definicja funkcji
}
int fun2(abc4 &A, double x)
{
// definicja funkcji
}
Do ćwiczenia należy zapoznać się z literatury z następującymi pojęciami:
klasa, definicja klasy, konstruktor(y) klasy, destruktor klasy, metody klasy, wskaźnik this, dostęp do składowych klasy,
funkcje zaprzyjaźnione,
operatory new i delete.
Z literatury można polecić następujące książki:
B. Stroustrup „Język C++”,
S. Lipmann „Podstawy języka C++”.
Program ćwiczenia
Wstęp
Stos (ang. stack) jest liniową strukturą danych dostępnych do zapisywania i odczytywania tylko z jednego końca. Z tego powodu stos jest nazywany strukturą typu LIFO (Last In First Out). W praktyce stos można zaimplementować za pomocą jednowymiarowej tablicy s oraz wskaźnika stosu sp wskazującego indeks kolejnej wolnej komórki tablicy. Implementacja taka jest przedstawiona na rysunku 1.
Operacja wprowadzania elementu na stos polega na przypisaniu wartości tego elementu komórce tablicy s wskazywanej przez wskaźnik sp, a następnie na zwiększeniu tego wskaźnika o 1 tak, aby wskazywał on następną wolną komórkę. Implementacja funkcji wprowadzania na stos nie może dopuścić do przepełnienia stosu tj. zapisywania poza granicami tablicy.
Pobieranie elementu ze stosu polega na zmniejszeniu wskaźnika sp o 1 i odczytaniu wartości komórki tablicy s wskazywanej przez wskaźnik stosu sp. Implementacja funkcji odczytującej kolejne elementy z wierzchołka stosu nie może zmniejszać wskaźnika stosu jeśli stos jest pusty.
Jeżeli wskaźnik stosu ma wartość n - oznacza to, że stos jest pełny.
Jeżeli wskaźnik stosu wskazuje element o indeksie 0 - oznacza to, że stos jest pusty.
Dany jest następujący szkielet programu:
#include <string.h>
#include <stdio.h>
class Stack {
private:
int *s;
int size, sp;
public:
Stack(int ASize);
Stack(Stack &As);
~Stack();
void push(int Ax);
int pop(void);
int free(void);
int empty(void);
int full(void);
};
/* -------------------------------------------------------- */
/*
tutaj należy zdefiniować kostruktory, destruktor i metody klasy Stack.
*/
/* -------------------------------------------------------- */
main()
{
class Stack ss1(10);
int x, koniec = 0;
char c;
while(!koniec) {
printf("%c STOS(%d) ", ss1.empty() ? 'E' : ss1.full() ?
'F' : ' ', ss1.free());
scanf("%c", &c);
switch(c) {
case '<' : scanf("%d", &x);
ss1.push(x);
break;
case '>' : if(!ss1.empty()) {
x = ss1.pop();
printf("x=%d\n", x);
}
break;
case 'Q' :
case 'q' : koniec = 1;
break;
}
fflush(stdin);
}
return 0;
}
Klasa Stack reprezentuje stos o zadanej wielkości. Poszczególne pola i metody tej klasy mają mieć następujące zadania:
int *s; - wskaźnik do tablicy reprezentującej stos.
int size; - rozmiar stosu,
int sp; - wskaźnik stosu wskazujący kolejną wolną komórkę w tablicy s,
Stack(int ASize); - konstruktor klasy Stack o zadanej wielkości stosu. Konstruktor ten musi zaalokować w pamięci tablicę s oraz odpowiednio zainicjować pozostałe zmienne klasy. Jeżeli wartość argumentu ASize jest niedodatnia to należy ją przyjąć równą 1.
Stack(Stack &As); - konstruktor kopiujący. Konstruktor ten służy do tworzenia klasy identycznej jak podana jako argument. Musi on zaalokować tablicę s o takiej samej wielkości jak w klasie As i skopiować wartości poszczególnych pól.
~Stack(); - destruktor klasy usuwający z pamięci tablicę s.
void push(int Ax); - metoda zapisująca wartość argumentu Ax na wierzchołek stosu. Należy pamiętać aby nie dopuścić do przepełnienia stosu.
int pop(void); - metoda zdejmująca element z wierzchołka stosu, jeżeli nie jest on pusty.
int free(void); - metoda zwracająca liczbę wolnych komórek na stosie.
int empty(void); - test czy stos jest pusty. Zwraca wartość różną od 0, jeśli stos jest pusty.
int full(void); - test czy stos jest pełny. Zwraca wartość różną od 0, jeśli stos jest pełny.
W funkcji main() zadeklarowany jest stos ss1 o zadanej wielkości oraz zmienne pomocnicze x, koniec oraz c. Definicja funkcji main() pozwala na wprowadzanie i zdejmowanie ze stosu liczb całkowitych.
Program po uruchomieniu zgłasza gotowość znakiem zachęty:
c STOS(n): _
Znak c informuje czy stos jest pusty (litera E), czy pełny (litera F). Jeśli nie jest wyświetlana ani litera E ani F, oznacza to, że stos nie jest ani pusty ani pełny. Liczba n informuje o ilości wolnego miejsca na stosie.
W celu wprowadzenia na stos liczby należy wpisać znak < oraz liczbę, którą chcemy umieścić na stosie i zatwierdzić klawiszem <Enter>. Na przykład:
< 123 Enter
W celu usunięcia z wierzchołka stosu liczby należy wpisać znak > i zatwierdzić to klawiszem <Enter>. Jeżeli stos nie jest pusty liczba zdjęta z wierzchołka stosu będzie wyświetlona na ekranie. Na przykład:
> Enter
W celu zakończenia pracy z programem należy wpisać literę q lub Q i zatwierdzić klawiszem <Enter>.
Zadania do wykonania.
Napisać definicje konstruktorów, destruktora oraz zadeklarowanych metod klasy Stack z uwzględnieniem uwag zawartych we wstępie programu ćwiczenia. Uruchomić tę część jako program nr 1.
Przerobić deklarację klasy Stack i odpowiednie definicje funkcji, aby funkcje push() i pop() były funkcjami zaprzyjaźnionymi z klasą Stack (a nie funkcjami składowymi). Uruchomić tę część jako program nr 2.
JEZYK C++, KLASY - POJECIA PODSTAWOWE 6
Katedra Automatyki Napędu i Urządzeń Przemysłowych AGH