Serwis Edukacyjny w I-LO w Tarnowie ![]() Materiały dla uczniów liceum |
Wyjście Spis treści Wstecz Dalej Autor artykułu: mgr Jerzy Wałaszek |
©2025 mgr Jerzy Wałaszek |
Struktura jest typem danych, który służy do tworzenia zmiennych strukturalnych. Najpierw ten typ należy zdefiniować. Definicja typu struktury wygląda następująco:
struct nazwa_typu { typ pole_1; typ pole_2; ... typ pole_n }; |
Zwróć uwagę, że za definicją typu strukturalnego umieszcza się średnik. Typ strukturalny po zdefiniowaniu możesz używać do tworzenia zmiennych podobnie jak typy wbudowane int, double, float itp.
struct nazwa_typu strukturalnego nazwa_zmiennej. |
Na przykład:
struct TPunkt // definicja typu strukturalnego TPunkt { float x; float y; }; ... struct TPunkt a,b,c; |
W powyższym przykładzie zdefiniowany został typ strukturalny TPunkt. Możesz go traktować jako plan budowy przyszłych zmiennych. Każda struktura typu TPunkt zawiera w sobie dwa pola o nazwach x i y. W polach tych program może umieścić liczby zmiennoprzecinkowe typu float. Po zdefiniowaniu typu strukturalnego następuje definicja trzech zmiennych: a, b i c. Każda z tych zmiennych jest strukturą typu TPunkt, a zatem zawiera w sobie dwa pola x i y typu float.
Jeśli planujesz tylko jednorazowe użycie struktury w swoim programie, to nie musisz tworzyć jawnego typu strukturalnego. Zmienne tworzysz bezpośrednio po definicji struktury:
struct { float x; float y; } a,b,c; |
W takim przypadku powstają trzy zmienne a, b i c i każda jest strukturą, która zawiera dwa pola x i y. Nazwa typu struktury nie została tutaj nazwana. Nie polecam jednak tego sposobu (niemniej czasami jest to użyteczne). Definiowanie typu strukturalnego nic nie kosztuje – jest to tylko informacja dla kompilatora. Nie zwiększa ona wielkości programu wynikowego. Zdefiniowany typ strukturalny może być wykorzystywane w różnych funkcjach do tworzenia struktur. Polecam definiować typy strukturalne na samym początku programu i dobrze je opisywać.
Zmienną strukturalną można zainicjować podobnie jak tablicę przez podanie wartości jej pól w klamerkach. Dostęp do pola struktury, które jest normalną zmienną (chyba że poprzedziłeś definicję zmiennej słowem const), uzyskujemy poprzez nazwę zmiennej, operator kropka oraz nazwę pola. uruchom poniższy program:
/* Struktury (C)2016 mgr Jerzy Wałaszek Data utworzenia: 22.10.2016 */ #include <stdio.h> #include <stdlib.h> #include <locale.h> // Definicja typu strukturalnego struct TPracownik { char i[10]; char n[12]; int w,p; }; int main() { struct TPracownik p1 = {"Jan","Kowalski",44,2150}; struct TPracownik p2 = {"Grzegorz","Liska",52,1800}; struct TPracownik p3 = {"Anna","Matuszewska",31,12700}; setlocale(LC_ALL,""); printf("+--------------------------------------+\n" "|Firmowa baza danych pracowniczych 2016|\n" "+------------+----------+----+---------+\n" "|NAZWISKO |IMIĘ |WIEK|PENSJA |\n" "+------------+----------+----+---------+\n"); printf("|%-12s|%-10s| %2d |%5d PLN|\n", p1.n, p1.i, p1.w, p1.p); printf("|%-12s|%-10s| %2d |%5d PLN|\n", p2.n, p2.i, p2.w, p2.p); printf("|%-12s|%-10s| %2d |%5d PLN|\n", p3.n, p3.i, p3.w, p3.p); printf("+------------+----------+----+---------+\n"); return 0; } |
Co więcej, struktury mogą być elementami tablic, jak każde inne zmienne. Umożliwia to efektywne przetwarzanie danych w pętli. Uruchom poniższy program:
/* Struktury (C)2016 mgr Jerzy Wałaszek Data utworzenia: 22.10.2016 */ #include <stdio.h> #include <stdlib.h> #include <locale.h> // Definicja typu strukturalnego struct TPracownik { char i[10]; char n[12]; int w,p; }; int main() { struct TPracownik t[] = { {"Jan","Kowalski",44,2150}, {"Grzegorz","Liska",52,1800}, {"Anna","Matuszewska",31,12700}, {"Barbara","Nowakowska",35,1750}, {"Kazimierz","Grochowski",48,3600}, {"","",0,0} // ostatni rekord }; int i; setlocale(LC_ALL,""); printf("+--------------------------------------+\n" "|Firmowa baza danych pracowniczych 2016|\n" "+------------+----------+----+---------+\n" "|NAZWISKO |IMIĘ |WIEK|PENSJA |\n" "+------------+----------+----+---------+\n"); for(i = 0; t[i].w; i++) printf("|%-12s|%-10s| %2d |%5d PLN|\n", t[i].n, t[i].i, t[i].w, t[i].p); printf("+------------+----------+----+---------+\n"); return 0; } |
Zwróć uwagę na inicjalizację tablicy. Wbrew pozorom nie różni się ona od inicjalizacji tablic ze zwykłymi zmiennymi:
int t[] = {1, 7, 9, 3, 8}; |
Tutaj elementami tablicy są struktury. Zatem inicjalizacja każdego elementu wymaga inicjalizacji struktury. Dlatego wewnątrz klamerek inicjujących zawartość tablicy mamy dla każdej struktury osobne klamerki z danymi, które zostaną wstawione do jej pól. Zasada jest zatem taka sama jak dla zwykłych typów: inicjujemy poszczególne komórki-struktury.
Odwołanie do komórki o danym indeksie i jest odwołaniem do struktury, która w tej komórce się znajduje. Jeśli chcemy uzyskać dostęp do określonego pola tej struktury, używamy operatora kropka i nazwy pola:
Koniec tablicy oznaczamy strukturą, której pole w (wiek) ma wartość 0. W ten sposób pętla rozpoznaje koniec danych i kończy wyświetlanie zawartości tablicy.
Uruchom poniższy program:
/* Struktury (C)2016 mgr Jerzy Wałaszek Data utworzenia: 22.10.2016 */ #include <stdio.h> #include <stdlib.h> #include <math.h> #include <locale.h> // Definicja typu strukturalnego struct Txy { float x,y; }; int main() { struct Txy a; setlocale(LC_ALL,""); a.x = 1; while(a.x <= 6) { a.y = sqrt(a.x); printf("Pierwiastek kwadratowy z %5.3f wynosi %5.3f\n", a.x, a.y); a.x += 0.125; } return 0; } |
Oczywiście to samo uzyskalibyśmy za pomocą dwóch zmiennych typu x i y. Jednak tutaj te zmienne zostały ze sobą powiązane faktem, że znajdują się w jednej strukturze. Dzięki zgrupowaniu zmiennych uzyskujemy nad nimi większą kontrolę. Szczególnie wtedy, gdy struktura zawiera wiele pól. Na szczęście takie skomplikowane struktury danych nie są zbyt często stosowane w świecie małych mikrokontrolerów. Jednak warto coś wiedzieć na ich temat – a nóż przerzucisz się w niedalekiej przyszłości na potężne mikrokontrolery 32- lub 64-bitowe z mnóstwem pamięci, gdzie można uruchamiać zaawansowane oprogramowanie.
Jest to jedna z operacji, której nie da się przeprowadzić prosto przy pomocy operatorów porównań == , !=, <, >... Powodem jest to, iż operatory te działają tylko na typach prostych. W przypadku typów złożonych, a takimi są tablice i struktury, musisz przeprowadzić porównanie poszczególnych elementów obu struktur. Poza tym niektóre z tych operatorów nie mają nawet dobrze zdefiniowanego sensu dla struktur. Co to na przykład znaczy, że struktura a jest większa od struktury b (a > b ?).
Mówimy, że dwie struktury są równe, jeśli odpowiadające sobie pola w obu strukturach zawierają tę samą wartość. Inaczej struktury te są różne.
Porównanie wykonujemy za pomocą operatora == oraz koniunkcji logicznej &&:
(a.pole_1==b.pole_1)&&(a.pole_2==b.pole_2)&&...&&(a.pole_n==b.pole_n) |
Jeśli wynikiem tego wyrażenia jest 1
(prawda), to struktury a i b są równe. Jeśli wynikiem
jest 0 (fałsz), to struktury a i b są
różne.
Uruchom poniższy program:
/* Struktury (C)2016 mgr Jerzy Wałaszek Data utworzenia: 22.10.2016 */ #include <stdio.h> #include <stdlib.h> #include <locale.h> // Definicja typu strukturalnego struct Ts { int a,b; float x; }; int main() { struct Ts s1 = {1,8,3.141592}; struct Ts s2 = {1,8,3.141592}; setlocale(LC_ALL,""); printf("s1 %s s2\n", (s1.a==s2.a)&&(s1.b==s2.b)&&(s1.x==s2.x)?"==":"!="); return 0; } |
Zmień w programie dane inicjalizacyjne jednej ze struktur, skompiluj i uruchom program ponownie.
Struktury można kopiować tak samo jak zwykłe zmienne za pomocą operatora przypisania:
/* Struktury (C)2016 mgr Jerzy Wałaszek Data utworzenia: 22.10.2016 */ #include <stdio.h> #include <stdlib.h> #include <locale.h> // Definicja typu strukturalnego struct Ts { int a,b; float x; }; int main() { struct Ts s1 = {1,8,3.141591}; struct Ts s2; setlocale(LC_ALL,""); s2 = s1; printf("s2.a = %d\n" "s2.b = %d\n" "s2.x = %f\n", s2.a, s2.b, s2.x); return 0; } |
Struktura w pamięci komputera zajmuje pewien obszar, w którym są rozmieszczone jej pola. Kopiowanie polega na skopiowaniu bajt po bajcie zawartości obszaru jednej struktury do obszaru drugiej struktury, która musi być tego samego typu. W ten sposób pola drugiej struktury przyjmują dokładnie te same wartości co pola w pierwszej strukturze.
Należy postępować ostrożnie, gdy kopiujemy struktury zawierające wskaźniki. W takim przypadku zostaje skopiowany adres umieszczony we wskaźniku, natomiast wskazywany obiekt nigdzie nie jest kopiowany. W efekcie wskaźniki w obu strukturach wskazują na ten sam obiekt. Uruchom program:
/* Struktury (C)2016 mgr Jerzy Wałaszek Data utworzenia: 22.10.2016 */ #include <stdio.h> #include <stdlib.h> #include <locale.h> // Definicja typu strukturalnego struct Ts { int x; int *p; // wskaźnik!! }; int main() { struct Ts a,b; setlocale(LC_ALL,""); a.x = 10; a.p = &a.x; // pole p wskazuje na pole x w a b = a; // do struktury b kopiujemy strukturę a *b.p = 99; // zmieniamy zawartość danej wskazywanej przez p w b // Co się zmieniło? printf("a.x = %d\n" "b.x = %d\n", a.x, b.x); return 0; } |
Co robi ten program? Tworzy dwie struktury typu Ts o nazwach a i b. W strukturze typu Ts znajdują się dwa pola. Jedno typu int o nazwie x, a drugie będące wskaźnikiem do danej typu int i mające nazwę p. Po uruchomieniu program umieszcza w polu x struktury a liczbę 10. Następnie w jej polu p umieszcza adres pola x (użyty tutaj jest operator adresu &), czyli pole p wskazuje na pole a tej samej struktury. Teraz następuje skopiowanie struktury a do struktury b. Kopiowanie przenosi wartości obu pól. Zatem w b.x znajdzie się a.x, a w b.p znajdzie się b.p. Pole p struktury b nie wskazuje jednak na pole b.x lecz wciąż na a.x. Jeśli teraz do obiektu wskazywanego przez b.p wstawimy liczbę 99, to trafi ona do struktury a, a nie do b. Oba wskaźniki a.p i b.p wskazują ten sam obiekt w pamięci, ponieważ zawierają ten sam adres. W efekcie otrzymasz:
a.x = 99 b.x = 10 |
Strukturę można wyzerować przez wyzerowanie jej poszczególnych pól. Jednak przy dużej liczbie pól może to być żmudne. Istnieje prostszy sposób, który wykorzystuje wskaźnik. Zasada jest następująca:
Tworzymy wskaźnik typu void *. Tego rodzaju wskaźnik nie wskazuje żadnych konkretnych danych. Dlatego można mu przypisać adres dowolnego obiektu. We wskaźniku umieszczamy zatem adres zmiennej strukturalnej, którą chcemy wyzerować. Następnie w pętli, która wykonuje się tyle razy ile wynosi rozmiar struktury, zerujemy bajt pamięci wskazywany przez wskaźnik, po czym wskaźnik przesuwamy na adres następnego bajtu.Uruchom program:
/* Struktury (C)2016 mgr Jerzy Wałaszek Data utworzenia: 22.10.2016 */ #include <stdio.h> #include <stdlib.h> #include <locale.h> // Definicja typu strukturalnego struct Ts { char t[10]; int a,b; float x,y; }; int main() { struct Ts s = {"Hejka!",6,125,6.28,-15.44}; void * p = &s; // w p adres struktury s int i; setlocale(LC_ALL,""); printf("Przed:\n%s\n%d\n%d\n%f\n%f\n\n",s.t,s.a,s.b,s.x,s.y); for(i = sizeof(s); i; i--) *(char *)p++ = 0; printf("Po:\n%s\n%d\n%d\n%f\n%f\n\n",s.t,s.a,s.b,s.x,s.y); return 0; } |
Zwróć uwagę na przypisanie wewnątrz pętli:
*(char *)p++ = 0; |
Jak rozumieć ten zapis. W nawiasie mamy tzw. rzutowanie (ang. casting). Rzutowanie mówi kompilatorowi, aby potraktował następne wyrażenie jako wyrażenie podanego w rzutowaniu typu. Tutaj kompilator ma potraktować wskaźnik p tak, jakby wskazywał daną typu char, czyli po prostu jedną komórkę pamięci. Bez rzutowania kompilator zgłosi błąd. W komórce pamięci wskazywanej przez wskaźnik p zostaje umieszczone zero, po czym wskaźnik jest zwiększany i wskazuje na następną komórkę pamięci.
Gdy pętla for się zakończy, cały obszar struktury zostanie wyzerowany. A dlaczego pętla zlicza wstecz? Po prostu po to, aby w każdym obiegu nie obliczać rozmiaru struktury. Wewnątrz pętli nie korzystamy z wartości i. Istotna jest tylko liczba obiegów.
struct nazwa_struktury * wskaźnik; |
Dostęp do pól struktury uzyskujemy za pomocą operatora strzałki:
wskaźnik -> pole_struktury |
Uruchom program:
/* Struktury (C)2016 mgr Jerzy Wałaszek Data utworzenia: 22.10.2016 */ #include <stdio.h> #include <stdlib.h> #include <math.h> #include <locale.h> struct TPunkt { float x,y; char n; }; // Funkcja wyświetla współrzędne punktu //------------------------------------- void pp(struct TPunkt * p) { printf("Punkt %c:(%5.2f,%5.2f)\n", p->n, p->x, p->y); } // Funkcja oblicza odległość dwóch punktów float len(struct TPunkt * a, struct TPunkt * b) { float x = b->x - a->x; float y = b->y - a->y; return sqrt(x*x + y*y); } // Funkcja wyświetla wyniki void pr(struct TPunkt * a, struct TPunkt * b) { pp(a); pp(b); printf("Odleglosc od %c do %c = %5.2f\n\n", a->n, b->n, len(a, b)); } int main() { struct TPunkt a = {1,0,'A'}; struct TPunkt b = {2,1,'B'}; struct TPunkt c = {2,2,'C'}; setlocale(LC_ALL,""); pr(&a,&b); pr(&b,&c); pr(&c,&a); return 0; } |
union nazwa_typu_unii { typ pole_1; typ pole_2; ... typ pole_n; }; |
Po zdefiniowaniu typu unii, wykorzystujemy go do definicji zmiennych:
union nazwa_typu_unii zmienna; |
Zmienne można również definiować bez nazywania typu unii:
union { typ pole_1; typ pole_2; ... typ pole_n; } zmienna; |
W takim przypadku powstaje zmienna unii posiadająca podane w definicji pola.
Struktura pozwala na jednoczesne przechowywanie wielu danych w swoich polach, ponieważ każde z nich w pamięci jest umieszczone w innym miejscu. Unia pozwala na przechowywanie tylko jednej danej naraz, ponieważ wszystkie pola unii są umieszczone w tym samym miejscu w pamięci. Na przykład:
struct xx { char a; int b; float c; }; |
union yy { char a; int b; float c; }; |
|
![]() |
![]() |
W strukturze xx mamy trzy pola: a, b i c. W tych polach program może umieścić trzy różne dane i struktura będzie je przechowywała.
W unii yy również mamy trzy pola: a, b i c. Jednak zajmują one ten sam obszar pamięci, dlatego w danej chwili unia może przechowywać dane tylko w jednym z tych pól. Unia pozwala efektywnie wykorzystać pamięć, szczególnie wtedy, gdy jest jej mało. Dzięki niej istnieje możliwość wykorzystania tego samego obszaru pamięci do różnych celów.
W pamięci struktura zajmuje taki obszar, aby pomieścił wszystkie jej pola. Unia natomiast zajmuje taki obszar, aby pomieścił jej największe pole.
/* Unie (C)2016 mgr Jerzy Wałaszek Data utworzenia: 27.10.2016 */ #include <stdio.h> #include <stdlib.h> #include <locale.h> int main() { struct { char a; int b; float c; } s; union { char a; int b; float c; } u; setlocale(LC_ALL,""); printf("Rozmiar struktury: %2d B\n" "Rozmiar unii : %2d B\n", sizeof(s),sizeof(u)); return 0; } |
Wynikiem działania programu jest:
Rozmiar struktury: 12 B Rozmiar unii : 4 B |
Przeanalizujmy to. Zmienna s jest strukturą o trzech polach:
a typu char (1 bajt),
b typu
int (4 bajty) i c
typu float
(4 bajty). Jeśli zsumujesz rozmiary
tych pól, otrzymasz
Komórki 32-bitowe posiadają adresy podzielne przez 4. Komórki 16-bitowe posiadają adresy podzielne przez 2. Takie rozwiązanie pozwala mikroprocesorowi pobierać dane w jednym cyklu dostępu do pamiąci.
Typy int oraz float posiadają rozmiar 4 bajtów i są umieszczane w pamięci w komórkach o adresach podzielnych przez 4. Dlatego w strukturach mogą występować puste miejsca (niezajęte przez dane). Dla przykładu, struktura s z naszego programu jest rozmieszczona w pamięci w sposób następujący:
pole | a | puste | b | c | ||||||||
komórki 8b | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
komórki 32b | 1 | 2 | 3 |
Teraz rachunek nam się zgodzi. Ponieważ pierwsze pole a
jest typu char, to zajmuje w pamięci tylko jedną komórkę
8-bitową. Drugie pole b jest typu int i zajmuje
jedną komórkę 32-bitową. Z tego powodu nie może być umieszczone
zaraz za polem a, ponieważ się nie zmieści. Umieszczone
zostaje pod adresem podzielnym przez 4. Za polem a
powstają trzy niewykorzystywane w strukturze komórki 8-bitowe.
Dlatego cała struktura zajmuje
A teraz unia: otrzymujemy wynik 4, ponieważ jest to maksymalny rozmiar danych, które w unii będą przechowywane:
a | ||||
b | ||||
c | ||||
komórki 8b | 1 | 2 | 3 | 4 |
Unia przechowuje tylko zawartość jednego pola. Dostęp do pól odbywa się identycznie jak w strukturze: za pomocą operatora kropka.
/* Unie (C)2016 mgr Jerzy Wałaszek Data utworzenia: 27.10.2016 */ #include <stdio.h> #include <stdlib.h> #include <locale.h> int main() { union { char a; int b; float c; } u; setlocale(LC_ALL,""); u.c = 3.141592; printf("Pole a = %c\n" "Pole b = %d\n" "Pole c = %f\n\n", u.a, u.b, u.c); u.b = 1234567890; printf("Pole a = %c\n" "Pole b = %d\n" "Pole c = %f\n\n", u.a, u.b, u.c); u.a = 'A'; printf("Pole a = %c\n" "Pole b = %d\n" "Pole c = %f\n\n", u.a, u.b, u.c); return 0; } |
Program tworzy unię zawierająca trzy pola. Następnie wpisuje dane do kolejnych pól i wyświetla zawartość unii. Zwróć uwagę, że poprawnie będzie wyświetlone tylko to pole, do którego wprowadzono dane. Odczyt pozostałych pól daje jakieś przypadkowe wyniki.
Zapraszam do następnego rozdziału.
![]() |
Zespół Przedmiotowy Chemii-Fizyki-Informatyki w I Liceum Ogólnokształcącym im. Kazimierza Brodzińskiego w Tarnowie ul. Piłsudskiego 4 ©2025 mgr Jerzy Wałaszek |
Materiały tylko do użytku dydaktycznego. Ich kopiowanie i powielanie jest dozwolone pod warunkiem podania źródła oraz niepobierania za to pieniędzy.
Pytania proszę przesyłać na adres email:
Serwis wykorzystuje pliki cookies. Jeśli nie chcesz ich otrzymywać, zablokuj je w swojej przeglądarce.
Informacje dodatkowe.