Prezentowane materiały są przeznaczone dla uczniów szkół ponadgimnazjalnych. Autor artykułu: mgr Jerzy Wałaszek, wersja1.0 |
©2010 mgr
Jerzy Wałaszek |
W języku C++ możemy tworzyć dowolnie skomplikowane struktury danych za pomocą słowa kluczowego struct.
struct nazwa_struktury { pole danych; pole danych; ... };
pole danych określa element składowy struktury. Definiujemy go podobnie do zmiennej:
typ nazwa_pola;
Po zdefiniowaniu struktury możemy na jej podstawie tworzyć zmienne strukturalne jak każde inne zmienne. Typem będzie nazwa struktury.
nazwa_struktury nazwa_zmiennej;
Jeśli zmienna strukturalna jest zmienną statyczną, to dostęp do pól danych uzyskujemy za pomocą operatora kropka.
nazwa_zmiennej.nazwa_pola
Poniżej mamy prosty przykład struktury, która jest stosem.
// Struktura - stos // (C)2011 I LO w Tarnowie // KOŁO INFORMATYCZNE //------------------------ #include <iostream> #include <cstdlib> #include <time.h> using namespace std; const int LIMIT = 5; // długość stosu // Tutaj deklarujemy typ struktury struct stack { int w,s[LIMIT]; }; // Funkcja zapisuje na stos void Zapisz(stack & st, int x) { if(st.w < LIMIT) st.s[st.w++] = x; else cout << "STOS PELNY!!!" << endl; } // funkcja odczytuje ze stosu int Czytaj(stack & st) { if(st.w) return st.s[--st.w]; cout << "STOS PUSTY!!!" << endl; return -1; } // Program główny int main() { stack stos; int i,x; srand(time(NULL)); // inicjujemy generator pseudolosowy stos.w = 0; // inicjujemy wskaźnik stosu cout << "ZAPIS 7 LICZB NA STOS\n"; for(i = 1; i <= 7; i++) { x = rand(); cout << x << endl; Zapisz(stos,x); } cout << "\nODCZYT 7 LICZB ZE STOSU\n"; for(i = 1; i <= 7; i++) { x = Czytaj(stos); cout << x << endl; } return 0; } |
ZAPIS 7 LICZB NA STOS 24789 30828 19286 5754 30359 10767 STOS PELNY!!! 2799 STOS PELNY!!! ODCZYT 7 LICZB ZE STOSU 30359 5754 19286 30828 24789 STOS PUSTY!!! -1 STOS PUSTY!!! -1 |
Strukturę możemy utworzyć jako obiekt dynamiczny, podobnie jak tablicę. W tym celu tworzymy wskaźnik do struktury:
nazwa_struktury * wskaźnik_struktury;
W pamięci rezerwujemy odpowiedni blok pamięci i adres tego bloku umieszczamy we wskaźniku:
wskaźnik_struktury = new nazwa_struktury;
Dostęp do pól danych uzyskujemy za pomocą operatora ->.
wskaźnik_struktury -> nazwa_pola
Gdy struktura przestanie być potrzebna, usuwamy ją z pamięci:
delete wskaźnik_struktury;
Poniżej nasz program ze stosem przerobiony na strukturę dynamiczną:
// Struktura dynamiczna - stos // (C)2011 I LO w Tarnowie // KOŁO INFORMATYCZNE //------------------------ #include <iostream> #include <cstdlib> #include <time.h> using namespace std; const int LIMIT = 5; // długość stosu // Tutaj deklarujemy typ struktury struct stack { int w,s[LIMIT]; }; // Funkcja zapisuje na stos void Zapisz(stack * st, int x) { if(st -> w < LIMIT) st -> s[st -> w++] = x; else cout << "STOS PELNY!!!" << endl; } // Funkcja odczytuje ze stosu int Czytaj(stack * st) { if(st -> w) return st -> s[--st -> w]; cout << "STOS PUSTY!!!" << endl; return -1; } // Program główny int main() { stack * stos; // wskaźnik int i,x; stos = new stack; // tworzymy strukturę dynamiczną srand(time(NULL)); // inicjujemy generator pseudolosowy stos -> w = 0; // inicjujemy wskaźnik stosu cout << "ZAPIS 7 LICZB NA STOS\n"; for(i = 1; i <= 7; i++) { x = rand(); cout << x << endl; Zapisz(stos,x); } cout << "\nODCZYT 7 LICZB ZE STOSU\n"; for(i = 1; i <= 7; i++) { x = Czytaj(stos); cout << x << endl; } return 0; } |
Zwróć uwagę, iż funkcje obsługujące operacje na strukturze wymagają przekazania parametru będącego strukturą. W pierwszym przykładzie strukturę przekazywaliśmy przez referencję, dzięki czemu funkcje miały bezpośredni dostęp do pól struktury. W drugim przykładzie przekazywany był wskaźnik do struktury. Chociaż technicznie funkcje te wyglądają inaczej, to w rzeczywistości w obu przypadkach kompilator utworzył identyczne funkcje. W pierwszym referencja jakby ukryła przed nami fakt, iż faktycznie funkcja otrzymała wskaźnik do struktury. W drugim przypadku wskaźnik ten przekazaliśmy jawnie.
Klasa jest obiektem, z którym, oprócz danych, jak w strukturze, skojarzono funkcje operujące na tych danych. Funkcje klasy nazywamy funkcjami składowymi (ang. member functions). Stanowią one integralną część definicji klasy.
Powodem wprowadzenia klas było w pewnym sensie uproszczenie programowania. Wyobraźmy sobie telewizor. Chcę oglądać jakiś program - czy muszę koniecznie znać w najdrobniejszych szczegółach jego budowę? Oczywiście nie, wystarczy znać funkcje klawiszy sterujących, a to co w środku, to dla inżynierów. Podobnie jest z klasami. Klasa udostępnia programowi tzw. interfejs, poprzez który program może się komunikować z klasą. Cechy implementacyjne mogą być ukrywane. W tym celu definicja klasy zawiera tzw. część publiczną - dostępną dla programu, oraz część prywatną - na użytek samej klasy, do której zewnętrzne funkcje nie posiadają dostępu.
Oprócz pól danych klasa może zawierać funkcje składowe, będące jej integralną częścią. Funkcje te mogą być publiczne - wtedy program może z nich korzystać, lub prywatne - na potrzeby wewnętrzne klasy.
Klasa może zawierać specjalne funkcje zwane konstruktorami i destruktorami. Funkcja konstruktor ma taką samą nazwę jak nazwa klasy. Jej zadaniem jest odpowiednie zainicjowanie pól danych w czasie tworzenia klasy - w przypadku struktury inicjowaniem pól musiał się zajmować program. Konstruktorów może być kilka, ale muszą się różnić parametrami. Destruktor jest funkcją o nazwie klasy poprzedzoną tyldą ~. Zadaniem destruktora jest posprzątanie po klasie, gdy będzie ona usuwana z pamięci. Jest to istotne, jeśli klasa sama zawiera dynamiczne struktury - wtedy destruktor zwalnia wcześniej przydzieloną na nie pamięć. Konstruktor i destruktor jest wywoływany automatycznie, programista nic nie musi specjalnie robić w tym celu. Jeśli w klasie nie zdefiniujemy swojego konstruktora lub destruktora, to zostanie utworzony standardowy konstruktor i destruktor.
Definicja klasy wygląda następująco:
class nazwa_klasy
{
public:
// deklaracja pól i funkcji publicznych
pole_danych;
// pola dostępne dla programu
pole_danych;
...
nazwa_klasy(); //
konstruktor
~nazwa_klasy() //
destruktor
funkcja_składowa; // funkcje dostępne
dla programu
funkcja_składowa;
...
private:
// deklaracja pól i funkcji prywatnych
pole_danych;
// pola dostępne tylko dla funkcji składowych klasy
pole_danych;
...
funkcja_składowa; // funkcje dostępne
tylko wewnątrz klasy
funkcja_składowa;
};
Jeśli funkcje klasy są krótkie, to można je zdefiniować bezpośrednio w definicji klasy. W przypadku dłuższych funkcji definiujemy je na zewnątrz w sposób następujący:
Konstruktor - funkcja nic nie zwraca, brak w niej polecenia return:
nazwa_klasy::nazwa_klasy(ewentualne_parametry)
{
treść konstruktora
}
Destruktor - funkcja nic nie zwraca, brak w niej polecenia return:
nazwa_klasy::~nazwa_klasy()
{
treść destruktora
}
Funkcja składowa:
typ_wyniku
nazwa_klasy::funkcja_składowa(ewentualne_parametry)
{
treść funkcji składowej;
return wynik;
}
Funkcje składowe posiadają bezpośredni dostęp do danych klasy - jawnie nie musimy do nich przekazywać parametru będącego klasą. Niejawnie taki parametr jest zawsze przekazywany - wewnątrz każdej funkcji składowej nazywa się on this, lecz nie ma go na liście parametrów. Poniżej mamy nasz program ze stosem przerobiony na klasę. Sam stos jest tablicą dynamiczną, której rozmiar przekazujemy w trakcie tworzenia zmiennej - parametr ten trafia do konstruktora klasy.
// Klasa - stos // (C)2011 I LO w Tarnowie // KOŁO INFORMATYCZNE //------------------------ #include <iostream> #include <cstdlib> #include <time.h> using namespace std; // Tutaj definiujemy klasę class stack { public: // interfejs klasy dla programu stack(int n); // konstruktor stosu n-elementowego ~stack(); // destruktor void Zapisz(int x); // funkcja składowa int Czytaj(); // funkcja składowa private: // elementy prywatne do użytku wewnętrznego int w, * s, limit; }; // Definicja konstruktora stack::stack(int n) { limit = n; // zapamiętujemy rozmiar stosu s = new int[n]; // tworzymy tablicę dynamiczną w = 0; // zerujemy wskaźnik stosu cout << "KONSTRUKTOR - STOS UTWORZONY\n"; } // Definicja destruktora stack::~stack() { delete [] s; // usuwamy tablicę dynamiczną cout << "DESTRUKTOR - STOS USUNIETY\n"; } // Funkcja zapisuje na stos void stack::Zapisz(int x) { if(w < limit) s[w++] = x; else cout << "STOS PELNY!!!" << endl; } // Funkcja odczytuje ze stosu int stack::Czytaj() { if(w) return s[--w]; cout << "STOS PUSTY!!!" << endl; return -1; } // Program główny int main() { stack stos(5); // zmienna klasy, stos o rozmiarze 5 int i,x; srand(time(NULL)); // inicjujemy generator pseudolosowy cout << "ZAPIS 7 LICZB NA STOS\n"; for(i = 1; i <= 7; i++) { x = rand(); cout << x << endl; stos.Zapisz(x); } cout << "\nODCZYT 7 LICZB ZE STOSU\n"; for(i = 1; i <= 7; i++) { x = stos.Czytaj(); cout << x << endl; } return 0; } |
Klasy również mogą być tworzone dynamicznie. Procedura jest następująca:
nazwa_klasy * wskaźnik; // zmienna typu wskaźnik do klasy
W pamięci rezerwujemy odpowiedni blok pamięci i adres tego bloku umieszczamy we wskaźniku:
wskaźnik = new nazwa_klasy;
Dostęp do pól danych uzyskujemy za pomocą operatora ->.
wskaźnik -> nazwa_pola
Dostęp do funkcji składowych również uzyskujemy za pomocą operatora ->:
wskaźnik -> funkcja_składowa(parametry);
Gdy klasa dynamiczna przestanie być potrzebna, usuwamy ją z pamięci:
delete wskaźnik;
Poniżej nasz program z klasą dynamiczną:
// Klasa dynamiczna - stos // (C)2011 I LO w Tarnowie // KOŁO INFORMATYCZNE //------------------------ #include <iostream> #include <cstdlib> #include <time.h> using namespace std; // Tutaj definiujemy klasę class stack { public: // interfejs klasy dla programu stack(int n); // konstruktor stosu n-elementowego ~stack(); // destruktor void Zapisz(int x); // funkcja składowa int Czytaj(); // funkcja składowa private: // elementy prywatne do użytku wewnętrznego int w, * s, limit; }; // Definicja konstruktora stack::stack(int n) { limit = n; // zapamiętujemy rozmiar stosu s = new int[n]; // tworzymy tablicę dynamiczną w = 0; // zerujemy wskaźnik stosu cout << "KONSTRUKTOR - STOS UTWORZONY\n"; } // Definicja destruktora stack::~stack() { delete [] s; // usuwamy tablicę dynamiczną cout << "DESTRUKTOR - STOS USUNIETY\n"; } // Funkcja zapisuje na stos void stack::Zapisz(int x) { if(w < limit) s[w++] = x; else cout << "STOS PELNY!!!" << endl; } // Funkcja odczytuje ze stosu int stack::Czytaj() { if(w) return s[--w]; cout << "STOS PUSTY!!!" << endl; return -1; } // Program główny int main() { stack * stos; // wskaźnik do klasy int i,x; stos = new stack(5); // tworzymy klasę dynamicznie srand(time(NULL)); // inicjujemy generator pseudolosowy cout << "ZAPIS 7 LICZB NA STOS\n"; for(i = 1; i <= 7; i++) { x = rand(); cout << x << endl; stos -> Zapisz(x); // wywołanie funkcji składowej } cout << "\nODCZYT 7 LICZB ZE STOSU\n"; for(i = 1; i <= 7; i++) { x = stos -> Czytaj(); // wywołanie funkcji składowej cout << x << endl; } delete stos; // usunięcie klasy dynamicznej z pamięci return 0; } |
I Liceum Ogólnokształcące |
Pytania proszę przesyłać na adres email: i-lo@eduinf.waw.pl
W artykułach serwisu są używane cookies. Jeśli nie chcesz ich otrzymywać,
zablokuj je w swojej przeglądarce.
Informacje dodatkowe