Wyjaśnij pojęcie struktury jako metoda przechowywania powiązanych informacji w programowaniu w języku C++
Język C++ jest językiem programowania "o wielu paradygmatach", m.in. również obiektowo zorientowanym. Stworzony w latach osiemdziesiątych XX wieku przez Bjarne Stroustrupa. Na język C++ wpływ miały, poza językiem C, jako jego podstawą, przede wszystkim Simula, z której zaczerpnął właściwości obiektowe, a także języki takie, jak Algol, Ada, ML i Clu.
Początkowo język C++ był dostępny w takim standardzie, w jakim opracowano ostatnią wersję kompilatora Cfront (tłumaczący C++ na C), później opublikowano pierwszy nieformalny standard zwany ARM (Annotated Reference Manual), który sporządzili Bjarne Stroustrup i Margaret Ellis. Standard języka C++ powstał w 1998 roku (ISO/IEC 14882-1998 "Information Technology - Programming Languages - C++"). Standard ten zerwał częściowo wsteczną zgodność z ARM w swojej bibliotece standardowej; jedyne, co pozostało w stanie w miarę nienaruszonym to biblioteka iostream.
Początkowo najważniejszą rzeczą wprowadzoną w C++ w stosunku do C było programowanie obiektowe, później jednak wprowadzono do niego mnóstwo ulepszeń, czyniąc ten język wygodniejszym i bardziej elastycznym od swojego pierwowzoru. Nie od rzeczy jest też wspomnieć, że niektóre zmiany w standardzie języka C były w większości zainspirowane językiem C++ (np. słowo const).
Nazwa języka została zaproponowana przez Ricka Mascitti w 1983 roku, kiedy to po raz pierwszy użyto tego języka poza laboratorium naukowym. Odzwierciedla ona fakt, że język ten jest rozszerzeniem języka C. Wcześniej używano nazwy "C z klasami".
Pierwsze kompilatory języka C++, podobnie jak Cfront, były wyłącznie translatorami na język C. Kompilatory takie dostępne są i dziś, ale niestety nie oferują one wszystkich właściwości języka C++. Pierwszym kompilatorem natywnym (produkującym od razu kod asemblerowy) dla języka C++ był g++ z pakietu GCC, którego pierwszym autorem był Michael Tiemann, założyciel Cygnus Solutions.
Struktura to złożony typ danych zbudowany z elementów innych typów (mogą to być typy wbudowane w język (int) jak i zdefiniowane przez programistę). Spójrzmy na kod poniżej :
struct punkt {
float x;
float y;
};
Definicję struktury rozpoczyna słowo struct po nim następuje nazwa struktury(w moim przykładzie jest to słowo 'punkt'). Nazwa wykorzystywana jest do deklarowania zmiennych strukturalnych. W nawiasach klamrowych zdefiniowane są składowe struktury. W moim przykładzie mamy dwie składowe typu float o nazwach x i y. Struktura nie może zawierać egzemplarza siebie samej. W strukturze możemy przechowywać wskaźnik do typu struktury(co jest bardzo przydatne we wszelkich dynamicznych strukturach), np.:
struct lacze {
int element;
lacze *nastepnyElement;
};
Składowe struktury muszą mieć różne nazwy, co oznacza że nie możemy zdefiniować struktury w ten sposób :
struct punkt {
float x;
float x;
};
Natomiast dwie różne struktury mogą mieć składowe o tych samych nazwach. Możemy więc zdefiniować sobie taką strukturę :
struct punktWPrzestrzeni {
float x;
float y;
float z;
};
Zmienne strukturalne są tworzone dokładnie w taki sam sposób jak inne zmienne. Jeśli chcemy utworzyć egzemplarz struktury typu punkt piszemy :
punkt A;
Jeśli chcemy utworzyć całą tablicę punków deklarujemy ją w ten sposób :
punkt Punkty[100];
Jeśli chcemy mieć wskaźnik do struktury punkt deklarujemy go w ten sposób :
punkt *APtr;
Dostęp do zmiennych składowych uzyskujemy dzięki dwóm operatorom :
operatorowi kropki oraz operatorowi ->('strzałka'). Kropką posługujemy się wówczas gdy, mamy zmienną lub referencję do struktury np. :
punkt A;
A.x=1.01;
A.y=23.12;
cout<<A.x<<' '<<A.y<<endl;
&ARef=A;
ARef.x=10;
ARef.y=9;
cout<<A.x<<' '<<A.y<<endl;
W powyższym kodzie najpierw tworzymy zmienną typu punkt. Następnie jej składowej z przypisujemy wartość 1.01, a składowej y wartość 23.12. Wyświetlamy wartości poszczególnych zmiennych składowych. Kolejnym krokiem jest stworzenie referencji do zmiennej A. Później posługujemy się tą referencją do przypisania zmiennej x wartości 10, a zmiennej y wartości 9. Na końcu ponownie wyświetlamy wartości zmiennych składowych. Ponieważ w C++ bardzo intensywnie wykorzystywane są wskaźniki, dlatego aby uprościć zapis dostępu do składowych :
punkt A, *APtr;
APtr=&A;
(*APtr).x;
stworzono operator strzałki (->). Teraz mając wskaźnik do struktury typu punkt piszemy :
APtr->x=20;
APtr->y=30;
W C++ stosuje się praktykę inicjowania zmiennych, a nie tylko ich alokowania. Do inicjacji zmiennych służą konstruktory. Konstruktor to związana automatycznie z daną strukturą funkcja o nazwie identycznej z nazwą struktury. Konstruktory uruchamiane są automatycznie w chwili tworzenia egzemplarza struktury. Konstruktory pomagają unikać błędów związanych z niezainicjowanymi danymi.
#include <iostream.h>
#include <assert.h>
struct punkt{
float x;
float y;
punkt (float xx, float yy)
{
x=xx;
y=yy;
}
};
int main()
{
punkt *aPtr=new punkt(10,20);
assert(aPtr!=0);
cout<<aPtr->x<<' '<<aPtr->y<<endl;
delete aPtr;
aPtr=0;
return 0;
}
Linie :
punkt (float xx, float yy)
{
x=xx;
y=yy;
}
to konstruktor struktury. Składowym x i y struktury przypisujemy wartości podane w momencie tworzenia struktury. W powyższym programie użyte zostały dwa nowe operatory new i delete. Operator new tworzy automatycznie obiekt o odpowiedniej wielkości ( w moim przykładzie jest to obiekt wielkości struktury point), wywołuje odpowiedni konstruktor i zwraca wskaźnik właściwego typu ( w moim przykładzie jest to wskaźnik do struktury typu point). Jeśli z jakiś powodów nie można zaalokować wystarczającej ilości pamięci to (w zależności od wieku kompilatora) albo zwracany jest wskaźnik o wartości 0 (NULL), albo generowany jest wyjątek bad_alloc [1]. Jeśli niepotrzebujemy już pamięci zarezerwowanej za pomocą operatora new możemy ją zwrócić systemowi operacyjnemu za pomocą operatora delete. Bardzo ważne jest właściwe korzystanie z tych operatorów. Jeśli alokujemy pamięć dla tablicy np.:
int *intPtr=new int [100];
należy ją zwolnić korzystając z operatora delete w ten sposób :
delete [] intPtr;
W przeciwnym razie zachowanie programu może być nieprzewidywalne. Dzieje się tak dlatego, ponieważ operator delete wywołuje odpowiedni destruktor. Jeśli zrobilibyśmy to w ten sposób :
delete intPtr;
to dla tablicy jakiś bardziej skomplikowanych obiektów niż int, nie mielibyśmy pewności czy wywołano jeden destruktor czy 100 destruktorów.
Makro assert służy sprawdzeniu, czy wartość wskaźnika zwracanego przez operator new jest inna niż NULL.
4