Struktury typ definiowany przez uŝytkownika. Na tej lekcji nauczymy się, jak grupować dane róŝnych typów, umieszczając je w pojedynczej zmiennej. Nauczymy się tworzyć nowe, zdefiniowane przez siebie typy złoŝone, zwane strukturami. Zastosujemy zdefiniowane przez siebie struktury w swoich programach. Utworzymy małą, prostą bazę danych, w której wykorzystamy zdefiniowane przez siebie struktury. Struktura (typ strukturalny) jest złoŝonym typem danych słuŝącym do grupowania informacji opisujących jakiś obiekt. Dane te mogą być (i najczęściej są) róŝnych typów. Poszczególne dane zgrupowane w strukturze nazywamy polami lub składowymi struktury. Zacznijmy od przykładu: aby zapamiętać dane pewnej osoby, takie jak imię, nazwisko, wiek i płeć (zatem dane róŝnego typu), do tej pory musieliśmy umieścić je w kilku zadeklarowanych zmiennych. Chcąc wyświetlić informacje o tej osobie, musieliśmy wyświetlić wartości poszczególnych zmiennych. NaleŜało więc zapamiętać, Ŝe wszystkie te zmienne dotyczą tej samej opisywanej wielkości. MoŜna jednak zgrupować wszystkie informacje i umieścić w jednej zmiennej. Typ tej zmiennej to właśnie struktura. Aby korzystać z tego typu, musisz go najpierw zdefiniować. Potem moŝesz tworzyć zmienne zdefiniowanego przez siebie typu. Definicja struktury ma postać: struct nazwa_typu //nazwa typu podlega tym samym //regułom, co nazwy zmiennych typ_pola_l nazwa_pola_l; typ_pola_2 nazwa_pola_2;... typ_pola_n nazwa_pola_n; //definicja typu musi kończyć sie średnikiem Aby pokazać, jak informacje o osobie umieścić w jednej zmiennej, moŝemy przykładowo zdefiniować strukturę o nazwie człowiek: struct człowiek char imię[14]; char nazwisko[20] ; int wiek; char płec[10]; 1
Mając tak zdefiniowany typ, moŝemy z niego korzystać, deklarując zmienne strukturalne. Zmienne strukturalne są to zmienne, które sa typu struktury przez nas zdefiniowanej. Dla typu człowiek mogą być to zmienne mama, babcia, stryjek. Ich deklaracja wygląda następująco: człowiek mama, babcia, stryjek; Inicjalizacja zadeklarowanych zmiennych polega na nadaniu wartości poszczególnym polom. KaŜdej zmiennej przypiszemy wartość imienia, nazwiska, wieku i płci, odwołując się do pól zdefiniowanej struktury. Odniesienie się do poszczególnych pól struktury realizujemy za pomocą operatora odniesienia do pola struktury, który oznacza się kropką. mama.imie="anna"; mama.nazwisko="kowalska"; mama.wiek=40; mama.plec="kobieta"; babcia.imie="maria"; babcia.nazwisko="nowak"; babcia.wiek=70; babcia.plec="kobieta"; stryjek.imie="piotr"; stryjek.nazwisko="kawecki"; stryjek.wiek=51; stryjek.plec="mezczyzna"; KaŜdej zmiennej przypisaliśmy wartości pól zdefiniowanej struktury, na przykład zmiennej mama nadaliśmy wartości: Anna Kowalska, 40, kobieta. Są to zatem zmienne róŝnych typów (wiek jest liczbą całkowita a imię, nazwisko i płeć są ciągami znaków o róŝnych dozwolonych długościach). Zatem - w przeciwieństwie do tablicy, która będąc pojedynczą zmienną, przechowuje kilka, kilkanaście bądź więcej wartości tego samego typu - pola struktury mogą być róŝnych typów. Na rysunku przedstawiamy ilustrację zmiennych: mama, babcia, stryjek - są one zmiennymi typu człowiek. ZauwaŜ, Ŝe samo zdefiniowanie typu człowiek nie opisuje jeszcze Ŝadnej osoby: jest dopiero: modelem, mówiącym o tym, jakie cechy przypiszemy konkretnej osobie. Dopiero zainicjalizowane zmienne opisują rzeczywistych ludzi. 2
struct pacjent //definicja struktury "pacjent" int nr_badania; //elementy struktury char *imie ; //elementy struktury float koszt; //elementy struktury pt1,pt2; //deklaracje zmiennych strukturalnych Istnieje jeszcze jeden sposób deklaracji - przy pomocy słowa kluczowego typedef: typedef struct int nr_badania; //elementy struktury char *imie ; //elementy struktury float koszt; //elementy struktury pacjent; Tego typu deklaracja umoŝliwia tworzenie nowych nazw dla typów danych. Deklaracja zmiennych moŝe mieć postać: pacjent pt1,pt2; Pola zmiennej moŝemy oczywiście inicjalizowac wartościami podanymi przez uŝytkownika, jak w przykładzie poniŝej: #include <iostream.h> #include <conio.h> struct osoba char nazwisko[ 20 ] ; int wiek; 3
main() osoba uczeń; //definicja zmiennej typu osoba coutt«"podaj nazwisko ucznia "; cin»uczen.nazwisko; cout«"podaj wiek ucznia "; cin»uczen.wiek; cout«"informacje o uczniu: \n"; cout«uczen. imie«" "«uczen.nazwisko«" "«uczen.wiek«"lat"; getch(); I Na rysunku,obok uŝytego juŝ przykładu uczeń, przedstawiliśmy jeszcze dwie inne przykładowe wielkości, które warto zdefiniować za pomocą struktur: Jeśli chcemy zdefiniować strukturę, która opisuje cechy samochodów zgodnie z atrybutami zaznaczonymi na rysunku, to prawidłowa definicja wygląda następująco: struct samochód char marka [30]; //jeśli uznamy, ze jest to wystarczająca dlugosc int pojemnosc_silnika; int rok_produkcji; //przypominamy o średniku! Dwie przykładowe zmienne typu strukturalnego zadeklarujemy: samochód moje_auto, auto_sasiada; a wartości nadamy im podobnie jak w poprzednim przykładzie. Definicja struktury punkt w kartezjańskim układzie współrzędnych miałaby postać: struct punkt float wsp_x; float wsp_y; 4
PoniŜej przedstawiamy fragment kodu programu, w którym wykorzystana jest zdefiniowana struktura: main () punkt punktl, punkt2; //deklaracja zmiennych typu punkt punktl.wsp_x = 3; punktl.wsp_y = 5; punkt2.wsp_x = 3; punkt2.wsp_y = 7.4; if (punktl.wsp_x==punkt2.wsp_x) cout«"punkty wyznaczają prosta równoległa do osi OY"; getch(); Po zdefiniowaniu struktury punkt przejdźmy do kolejnej definicji matematycznej - tym razem dotyczącej wektorów. Przypomnijmy, Ŝe wektorem jest uporządkowana para dwóch punktów. Pierwszy z nich nazywamy początkiem wektora, drugi zaś jego końcem. Aby opisać wektory w kartezjańskim układzie współrzędnych, moŝemy zdefiniować strukturę do tego przeznaczoną: struct wektor float poczatek_wsp_x; //odcięta początku wektora float poczatek_wsp_y; //rzedną początku wektora float koniec_wsp_x; //odcięta końca wektora float koniec_wsp_y; //rzedną końca wektora Mając jednak zdefiniowaną strukturę punkt, na jej bazie zdefiniujemy strukturę wektor (ryc. poniŝej) struct wektor punkt początek; punkt koniec; 5
Teraz, w celu odwołania się do poszczególnych pól zmiennej wektor, zastosujemy dwukrotnie operator odniesienia do pola struktury. Dla przykładu: aby nadać wartości zmiennej moj_wektor naleŝącej do typu wektor, wykonamy kolejno instrukcje: moj_wektor.początek.wsp_x = 4.5; moj_wektor.początek. wsp_y = -6.1; moj_wektor.koniec. wsp_x = 2.8; moj_wektor.koniec.wsp_y = 4.9; Pamiętaj, Ŝe definicja struktury punkt musi się znajdować przed definicją Struktury wektor, gdyŝ druga z nich wykorzystuje pierwszą. Przeanalizuj krótki program, wykorzystujący obie struktury; jego zadaniem jest wyznaczenie współrzędnych środka wektora, którego krańce podajemy z zewnątrz: #include<iostream.h> #include<conio.h> struct punkt //definicja struktury punkt musi poprzedzać definicję struktury wektor I float wsp_x; hoat wsp_y; //pamiętaj o średniku kończącym definicje strukt struct wektor punkt początek; punkt koniec; //pamiętaj o średniku kończącym definicje strukt main () punkt p; //deklaracja zmiennej strukturalnej typu punkt; wektor w; //deklaracja zmiennej strukturalnej typu wektor; cout«"podaj odcięta początku wektora: "; cin»w.poczatek.wsp_x; cout«"podaj rzedną początku wektora: "; cin»w. poczatek.wsp_y; cout«"podaj odcięta końca wektora: "; cin»w. koniec.wsp_x; cout«"podaj rzedną końca wektora: "; 6
cin»w.koniec.wsp_y; p.wsp_x= 0.5*(w.poczatek.wsp_x+w.koniec.wsp_x); p.wsp_y= 0.5*(w.poczatek.wsp_y+w.koniec.wsp_y); cout«"oto współrzędne środka wektora: "«p.wsp_x«", "«p.wsp_y; getch(); JuŜ w tym krótkim programie moŝna zauwaŝyć, Ŝe duŝo łatwiej jest odnosić się do pojedynczego wektora za pomocą jednej zmiennej niŝ przeznaczać na jego opis aŝ cztery zmienne (dwie współrzędne początku i dwie współrzędne końca). Tablice o elementach typu strukturalnego #include <iostream.h> #include <conio.h> struct uczen char imie[20]; char nazwisko[20]; long numer_akt; uczen grupa[10]; cout<< Wypelniamy tablice danymi uczniow z naszej grupy: ; for(int i=0;i<10;i++) cout<< Podaj imie ucznia: ; cin>>grupa[i].imie; cout<< Podaj nazwisko ucznia: ; cin>>grupa[i].naziwsko; cout<< Podaj numer akt ucznia: ; cin>>grupa[i].numer_akt; 7
getch(); Unie Unia (ang. union) jest strukturą, której wszystkie składowe umieszczane są w tym samym obszarze pamięci. Kompilator ustala ilość potrzebnej pamięci wyliczając ilość pamięci potrzebnej do przechowania składowej o największym zapotrzebowaniu. W dowolnej chwili unia moŝe zawierać maksymalnie jeden obiekt (składową), poniewaŝ elementy unii dzielą ten sam obszar pamięci. Deklaracja unii jest podobna do deklaracji struktury, zamiast słowa kluczowego struci występuje słowo union. Przykładowa deklaracja unii moŝe mieć postać: union zmien int x1; char zn; Zmienne typu unii moŝna deklarować przez ich nazw na końcu definicji unii albo w oddzielnej instrukcji deklaracji. Aby zadeklarować zmienna wx1 jako typu zmień naleŝy napisać: union zmien wx1; Zarówno liczba całkowita x1 jak i znak zn zmiennej wx1 zajmują ten sam obszar pamięci ( zazwyczaj potrzebujemy dwóch bajtów dla zmiennej typu int i jednego bajtu dla zmiennej typu char). Unie umoŝliwiają oszczędne gospodarowanie pamięcią komputera. Dostęp do składowych unii jest analogiczny jak dla struktur. /*pm60.cpp unie i struktury */ #include <stdio.h> #include <conio.h> #include <string.h> // dla strcpy() int main(int) struct wx1 int tel; char nazwisko[50]; 8
char miasto[50]; union wx2 int tel; char nazwisko[50]; char miasto[50]; struct wx1 x1; union wx2 x2; Pola bitowe Język C/C++ umoŝliwia określenie liczby bitów, w których będą zapamiętywane składowe unsigned lub int struktury, klasy lub unii. Składowe takie nazywane są polami bitowymi ( ang. bit field). Typem pola bitowego musi być liczba całkowita ze znakiem lub bez. Pól bitowych uŝywa się w celu zaoszczędzenia pamięci. Bardzo często w programach musimy mieć dane reprezentujące wartości binarne. Przykładem jest reprezentacja stanu konkretnego przełącznika przełącznik moŝe być włączony lub wyłączony. Do opisania działania przełącznika potrzebujemy jednego bitu ( wartość 0 lub 1). Najmniejszym standardowym typem danych jest typ char. UŜywanie typu char do przechowywania wartości binarnych jest duŝym marnotrawieniem pamięci. Zwykle jednak uŝywa się typu int, który moŝe składać się z dwóch lub częściej z czterech bajtów. W wielu przypadkach do reprezentowania wartości zmiennej moŝemy potrzebować niewielkiej ilości bitów (2, 3). MoŜemy zebrać kilka nieduŝych zmiennych razem jako pola struktury. Składową struktury definiuje się jako pole przez podanie jej nazwy, a za nią liczby potrzebnych bitów. UŜywając pól moŝemy zmieścić w typie char osiem wartości binarnych, a typie long trzydzieści dwie wartości. Na przykład, poniŝsza deklaracja tworzy strukturę pol_bit zawierającą trzy pola o rozmiarze 1 bitu: struct unsigned int nra : 1; unsigned int nrb : 1; unsigned int nrc : 1; pol_bit; Poszczególnym polom przypisanie wartości początkowych odbywa się klasycznie, z wykorzystaniem operatora kropki: pol_bit.nra = 1; pol_bit.nrb = 0; pol_bit.nrc = 1; 9
W tym przykładzie, kaŝde uŝyte pole ma długość 1 bitu, moŝemy więc przypisywać jedynie wartości 0 lub 1. Wykorzystanie pamięci nie jest imponujące w tym przykładzie. Zmienna pol_bit jest typu int, rezerwuje więc 16 (lub 32 w zaleŝności od implementacji) bitów, gdy tymczasem wykorzystywane są jedynie trzy bity. Pola bitowe mogą mieć róŝna długość: struct unsigned int nra : 2; unsigned int nrb : 2; unsigned int nrc : 8; pol_bit; W tym przykładzie mamy dwa pola 2-bitowe i jedno 8-bitowe. Dysponując szerszymi polami, moŝemy operować większymi wartościami, np. teraz zmienna nra moŝe być zainicjalizowana wartością 3. Musimy jednak pamiętać, aby przypisując wartości nie przekroczyć pojemności pola. JeŜeli całkowita ilość bitów przypisana poszczególnym polom przekroczy rozmiar typu int, rezerwowana jest kolejna jednostka o długości typu int. PoniewaŜ Ŝadne z pól nie moŝe znajdować się na granicy pomiędzy dwoma jednostkami, kompilator tak rozmieszcza pola aby znalazły się całkowicie w pierwszej 10