Struktury
Struktura jest obiektem złożonym z jednej lub kilku zmiennych (mogą one być różnych typów) zgrupowanych dla wygody pod jedną nazwą. Struktura pozwala traktować grupę zmiennych, związanych ze sobą w jakiś logiczny sposób, jak jeden obiekt (jedną zmienną). Ogólna postać deklaracji struktury została przedstawiona poniżej
struct nazwaStruktury
{
typ1 skladowa1;
...
typN skladowaN;
};
Mówiąc o typie strukturalnym będziemy mieć na myśli nazwę oraz typy i porządek składowych pewnej struktury.
Jednym z najczęściej spotykanych przykładów struktury, jest struktura reprezentująca punkt w przestrzeni dwuwymiarowej
struct punkt
{
float x;
float y;
};
Zmienne strukturalne mogą być deklarowane równocześnie z deklaracją typu strukturalnego
struct punkt
{
float x;
float y;
} myPunkt;
Jeśli do tego typu nie będzi później odwołań (czyli w naszym przypadku nie będzie potrzeby użycia w programie nazwy
punkt to deklaracja typu nie musi zawierać
nazwyStruktury
struct
{
float x;
float y;
} myPunkt;
Nazwa strutury i nazwa zmiennej strukturalnej mogą być takie same
struct punkt
{
float x;
float y;
} punkt;
Zmienne strukturalne mogą być inicjalizowane za pomocą listy inicjalizatorów zawierającej wartości wszystkich składowych struktury
struct punkt punkt2d={1.0, 2.7};
Dostęp do konkretnej składowej struktury uzyskujemy za pomocą operatora . (kropka), którego argumentami jest zmienna strukturalna oraz składowa
zmiennaStrukturalna.skladowa
na przykład
punkt2d.x=1.75;
Dostęp do składowych struktury można również uzyskać za pomocą wskaźnika i operatora -> Po lewej stronie tego operatora znajduje się wskaźnik do struktury po prawej zaś nazwa pola
struct punkt *wsk;
wsk=&punkt2d;
wsk->x=1.75;
#include <stdio.h>
struct punkt
{
float wspX;
float wspY;
};
float dist(struct punkt p1, struct punkt p2)
{
return sqrt((p1.wspX-p2.wspX)*(p1.wspX-p2.wspX)+(p1.wspY-p2.wspY)*(p1.wspY-p2.wspY));
}
int main(void)
{
struct punkt pkt1={0,0},pkt2={5,0},pkt3;
pkt3.wspX=0;
pkt3.wspY=3;
printf("Odleglosc punktu (%.2f,%.2f) od (%.2f,%.2f) wynosi %.2f\n",
pkt1.wspX,pkt1.wspY,pkt2.wspX,pkt2.wspY,dist(pkt1,pkt2));
printf("Odleglosc punktu (%.2f,%.2f) od (%.2f,%.2f) wynosi %.2f\n",
pkt1.wspX,pkt1.wspY,pkt3.wspX,pkt3.wspY,dist(pkt1,pkt3));
return 0;
}
|
|
Listing 4.1
|
Odleglosc punktu (0.00,0.00) od (5.00,0.00) wynosi 5.00
Odleglosc punktu (0.00,0.00) od (0.00,3.00) wynosi 3.00
|
|
Output 4.1
Efekt działania programu z listingu 4.1
|
#include <stdio.h>
struct osoba
{
char *imie;
char wiek;
};
int main(void)
{
struct osoba o1={"Piotr",26},
o2={"Michal",24};
struct osoba *wsk;
wsk=&o1;
printf("%s ma lat %d\n",(*wsk).imie,(*wsk).wiek);
// mozna taz inaczej i tak robimy to najczesciej
wsk=&o2;
printf("%s ma lat %d\n",wsk->imie,wsk->wiek);
return 0;
}
|
|
Listing 4.2
|
Piotr ma lat 26
Michal ma lat 24
|
|
Output 4.2
Efekt działania programu z listingu 4.2
|
#include <stdio.h>
#include <stdlib.h>
struct wezel
{
int wartosc;
struct wezel *l;
struct wezel *r;
};
void WypiszDrzewo(struct wezel *wezel)
{
if(wezel->l!=NULL)
WypiszDrzewo(wezel->l);
printf("%d ",wezel->wartosc);
if(wezel->r!=NULL)
WypiszDrzewo(wezel->r);
}
int main(void)
{
/*
1
/ \
/ \
2 5
/ \ / \
3 4 6 7
/ \
8 9
*/
struct wezel w1={1,NULL,NULL},
w2={2,NULL,NULL},
w3={3,NULL,NULL},
w4={4,NULL,NULL},
w5={5,NULL,NULL},
w6={6,NULL,NULL},
w7={7,NULL,NULL},
w8={8,NULL,NULL},
w9={9,NULL,NULL};
struct wezel *korzen=&w1;
w1.l=&w2;w1.r=&w5;
w2.l=&w3;w2.r=&w4;
w5.l=&w6;w5.r=&w7;
w7.l=&w8;w7.r=&w9;
WypiszDrzewo(korzen);
return 0;
}
|
|
Listing 4.3
|
|
Output 4.3
Efekt działania programu z listingu 4.3
|
typedef
W języku C wprowadzono mechanizm pozwalający na tworzenie nowych nazw typów danych. Na przykład deklaracja
typedef int calkowite;
tworzy dla typu int synonim calkowite. Z nazwy calkowite można korzystać w deklaracjach, rzutowaniach itp; ogólnie mówiąc, wszędzie tam, gdzie możemy korzystać z typu int
calkowite iloscDanych, iloscKonczyn;
Należy wyraźnie zaznaczyć, że deklaracja typedef w żadnym razie nie tworzy nowego typu. Nadaje ona po prostu nową nazwę dla typu już istniejącego. Powracając do przykładu ze strukturą reprezentującą punktu, możemy teraz napisać
typedef struct
{
float x;
float y;
} punkt;
int main(void)
{
punkt p1,p2;
...
p1.x=p2.y;
...
return 0;
}
#include <stdio.h>
#include
struct pkt1
{
float x;
float y;
};
typedef struct
{
float x;
float y;
} pkt2;
float dist1(struct pkt1 p1, struct pkt1 p2)
{
return sqrt((p1.x-p2.x)*(p1.x-p2.x)+(p1.y-p2.y)*(p1.y-p2.y));
}
float dist2(pkt2 p1, pkt2 p2)
{
return sqrt((p1.x-p2.x)*(p1.x-p2.x)+(p1.y-p2.y)*(p1.y-p2.y));
}
int main(void)
{
struct pkt1 p11={0,0},p12={3,0};
pkt2 p21={0,0},p22={3,0};
printf("Odleglosc punktu (%.2f,%.2f) od (%.2f,%.2f) wynosi %.2f\n",
p11.x,p11.y,p12.x,p12.y,dist1(p11,p12));
printf("Odleglosc punktu (%.2f,%.2f) od (%.2f,%.2f) wynosi %.2f\n",
p21.x,p21.y,p22.x,p22.y,dist2(p21,p22));
return 0;
}
|
|
Listing 4.4
|
Odleglosc punktu (0.00,0.00) od (3.00,0.00) wynosi 3.00
Odleglosc punktu (0.00,0.00) od (3.00,0.00) wynosi 3.00
|
|
Output 4.4
Efekt działania programu z listingu 4.4
|
Unie
Unie pozwalają na odwoływanie się do tego samego obszaru pamięci za pośrednictwem różnych typów. Deklaracja unii różni się od deklaracji struktury wyłącznie słowem kluczowym; piszemy union zamiast struct
union unijka
{
char x;
int y;
float z;
};
W odróżnieniu jednak od struktury, wszystkie składowe unii rozpoczynają się pod tym samym adresem. Tak więc wielkość unii jest rozmiarem jej największej składowej. W praktyce oznacza to, że w danym fragmencie kodu możemy operować tylko jedną składową
#include <stdio.h>
union _u
{
int xi;
float xf;
} u;
int main(void)
{
u.xi=10;
printf("%d %f\n",u.xi,u.xf);
u.xf=10;
printf("%d %f\n",u.xi,u.xf);
return 0;
}
|
|
Listing 4.5
|
10 0.000000
1092616192 10.000000
|
|
Output 4.5
Efekt działania programu z listingu 4.5
|
Pola bitowe
Pola bitowe są stałymi składającymi się z określonej liczby bitów; wchodzą one w skład struktur lub unii. Deklaracja pola bitowego ma następującą postać
typ opcjonalnyIdentyfikator: dlugosc;
gdzie typ jest typem unsigned int lub signed int a dlugosc liczbą bitów zajmowanych przez pole bitowe w pamięci. Długość pola nie może być większa niż długość słowa maszynowego (zazwyczaj sizeof(int)). Jeżeli niewielkie pola bitowe pozostawiają dostatecznie dużo miejsca dla kolejnych pól to mogą one zostać umieszczone w tej samej jednostce pamięci. Pole bitowe o zerowej długości wskazuje, że następne pole bitowe powinno zostać umieszczone w nowej jednostce pamięci bez względu na to czy w bieżącej jednostce pamięci pozostało jeszcze miejsce. Pola bitowe typu unsigned int interpretowane są jako liczby bez znaku, natomiast signed int mogą przyjmować wartości ujemne i są kodowane w systemie uzupełnieniowym do dwóch. W stosunku do pól bitowych nie można użyć operatora adresu (można natomiast zrobić to w stosunku do zmiennej struktralne takie pole zawierającej). Należy mieć na uwadze, że niektóre sposoby wykorzystania pól bitowych mogą powodować kłopoty z przenośnością jako że interpretacja bitów tworzących słowo może zależeć od rodzaju używanego sprzętu.
struct
{
unsigned int pole_1: 2;
unsigned int pole_2: 4;
signed int pole_3: 3;
} s;
/*
s.pole_1 : pole 2-bitowe mogace przechowac liczby z przedzialu od 0 do 3
s.pole_2 : pole 4-bitowe mogace przechowac liczby z przedzialu od 0 do 15
s.pole_3 : pole 3-bitowe mogace przechowac liczby z przedzialu od -4 do 3
*/