C/Typy złożone: Różnice pomiędzy wersjami

Usunięta treść Dodana treść
Znaczniki: Z urządzenia mobilnego Z wersji mobilnej (przeglądarkowej)
Nie podano opisu zmian
Linia 1:
== typedef ==
Jest to słowo kluczowe, które służy do definiowania typów pochodnych np.:
<sourcesyntaxhighlight lang="c">
typedef stara_nazwa nowa_nazwa;
typedef int mojInt;
typedef int* WskNaInt;
</syntaxhighlight>
</source>
 
od tej pory można używać typów mojInt i WskNaInt.
Linia 13:
== Typ wyliczeniowy ==
Służy do tworzenia zmiennych, które mogą przyjmować tylko pewne z góry ustalone wartości:
<sourcesyntaxhighlight lang="c">
enum Nazwa {WARTOSC_1, WARTOSC_2, WARTOSC_N };
</syntaxhighlight>
</source>
Na przykład można w ten sposób stworzyć zmienną przechowującą kierunek:
<sourcesyntaxhighlight lang="c">
enum Kierunek {W_GORE, W_DOL, W_LEWO, W_PRAWO};
enum Kierunek ruch = W_GORE;
</syntaxhighlight>
</source>
 
Gdzie "Kierunek" to typ zmiennej, wcześniej określony, a "ruch" nazwa zmiennej, o takim typie. Zmienną tę można teraz wykorzystać na przykład w instrukcji [[Programowanie:C:Instrukcje sterujące#switch|switch]]
<sourcesyntaxhighlight lang="c">
switch(ruch)
{
Linia 36:
printf("gdzieś w bok\n");
}
</syntaxhighlight>
</source>
 
Tradycyjnie przechowywane wielkości zapisuje się wielkimi literami (W_GORE, W_DOL).
 
Tak naprawdę C przechowuje wartości typu wyliczeniowego jako liczby całkowite (zakres typu signed int), o czym można się łatwo przekonać:
<sourcesyntaxhighlight lang="c">
ruch = W_DOL;
printf("%i\n", ruch); /* wypisze 1 */
</syntaxhighlight>
</source>
 
Kolejne wartości to po prostu liczby całkowite: domyślnie pierwsza to zero, druga jeden itp. Możemy przy deklarowaniu typu wyliczeniowego zmienić domyślne przyporządkowanie:
<sourcesyntaxhighlight lang="c">
enum Kierunek { W_GORE, W_DOL = 8, W_LEWO, W_PRAWO };
printf("%i %i\n", W_DOL, W_LEWO); /* wypisze 8 9 */
</syntaxhighlight>
</source>
 
Co więcej liczby mogą się powtarzać i wcale nie muszą być ustawione w kolejności rosnącej:
<sourcesyntaxhighlight lang="c">
enum Kierunek { W_GORE = 5, W_DOL = 5, W_LEWO = 2, W_PRAWO = -1 };
printf("%i %i\n", W_DOL, W_LEWO); /* wypisze 5 2 */
</syntaxhighlight>
</source>
 
Traktowanie przez kompilator typu wyliczeniowego jako liczby pozwala na wydajną ich obsługę, ale stwarza niebezpieczeństwa:
 
Można przypisywać pod typ wyliczeniowy liczby, nawet nie mające odpowiednika w wartościach, a kompilator może o tym nawet nie ostrzec:
<sourcesyntaxhighlight lang="c">
ruch = 40;
</syntaxhighlight>
</source>
Lub przypisać pod typ wyliczeniowy, np. liczby:
<sourcesyntaxhighlight lang="c">
enum Kierunek { W_GORE, W_DOL, W_LEWO = -1, W_PRAWO };
</syntaxhighlight>
</source>
Co spowoduje nadanie tej samej wartości 0 dla elementów W_GORE i W_PRAWO, a to może skutkować błędem kompilacji, np. w przytoczonym powyżej użyciu instrukcji switch.
 
Linia 74:
 
Struktury definiuje się w następujący sposób:
<sourcesyntaxhighlight lang="c">
struct Struktura {
int pole1;
Linia 80:
char pole3;
};
</syntaxhighlight>
</source>
gdzie "Struktura" to nazwa tworzonej struktury.<br />
Nazewnictwo, ilość i typ pól definiuje programista według własnego uznania.
 
Zmienną posiadającą strukturę tworzy się podając jako jej typ nazwę struktury.
<sourcesyntaxhighlight lang="c">
struct Struktura zmiennaS;
</syntaxhighlight>
</source>
 
 
Dostęp do poszczególnych pól uzyskuje się przy pomocy '''operatora wyboru składnika''': kropki ('.').
<sourcesyntaxhighlight lang="c">
zmiennaS.pole1 = 60; /* przypisanie liczb do pól */
zmiennaS.pole2 = 2;
zmiennaS.pole3 = 'a'; /* a teraz znaku */
</syntaxhighlight>
</source>
 
Struktury jako argumenty funkcji mogą być użyte na 2 sposoby:<ref>[https://stackoverflow.com/questions/10370047/passing-struct-to-function stackoverflow questions : passing-struct-to-function]</ref>
Linia 103:
== Unie ==
Unie to kolejny sposób prezentacji danych w pamięci. Na pierwszy rzut oka wyglądają bardzo podobnie do struktur:
<sourcesyntaxhighlight lang="c">
union Nazwa {
typ1 nazwa1;
Linia 109:
/* ... */
};
</syntaxhighlight>
</source>
 
Na przykład:
<sourcesyntaxhighlight lang="c">
union LiczbaLubZnak {
int calkowita;
Linia 118:
double rzeczywista;
};
</syntaxhighlight>
</source>
 
Pola w unii '''nakładają''' się na siebie w ten sposób, że w danej chwili można w niej przechowywać wartość tylko jednego typu. Unia zajmuje w pamięci tyle miejsca, ile zajmuje największa z jej składowych. W powyższym przypadku unia będzie miała prawdopodobnie rozmiar typu double czyli często 64 bity, a całkowita i znak będą wskazywały odpowiednio na pierwsze cztery bajty lub na pierwszy bajt unii (choć nie musi tak być zawsze). Dlaczego tak? Taka forma często przydaje się np. do konwersji pomiędzy różnymi typami danych. Możemy dzięki unii podzielić zmienną 32-bitową na cztery składowe zmienne o długości 8 bitów każda.
 
Do konkretnych wartości pól unii odwołujemy się, podobnie jak w przypadku struktur, za pomocą kropki:
<sourcesyntaxhighlight lang="c">
union LiczbaLubZnak liczba;
liczba.calkowita = 10;
printf("%d\n", liczba.calkowita);
</syntaxhighlight>
</source>
 
Zazwyczaj użycie unii ma na celu zmniejszenie zapotrzebowania na pamięć, gdy naraz będzie wykorzystywane tylko jedno pole i jest często łączone z użyciem struktur.
 
Przyjrzyjmy się teraz przykładowi, który powinien dobitnie zademonstrować działanie unii:
<sourcesyntaxhighlight lang="c">
#include <stdio.h>
Linia 157:
return 0;
}
</syntaxhighlight>
</source>
 
Zauważyłeś pewien ciekawy efekt? Jeśli uruchomiłeś ten program na typowym komputerze domowym (rodzina i386) na ekranie zapewne pojawił Ci się taki oto napis:
Linia 167:
== Inicjacja struktur i unii ==
Jeśli tworzymy nową strukturę lub unię możemy zaraz po jej deklaracji wypełnić ją określonymi danymi. Rozważmy tutaj przykład:
<sourcesyntaxhighlight lang="c">
struct moja_struct {
int a;
char b;
} moja = {1,'c'};
</syntaxhighlight>
</source>
 
W zasadzie taka deklaracja nie różni się niczym od wypełnienia np. tablicy danymi. Jednak standard C99 wprowadza pewne udogodnienie zarówno przy deklaracji struktur, jak i unii. Polega ono na tym, że w nawiasie klamrowym możemy podać nazwy pól struktury lub unii którym przypisujemy wartość, np.:
<sourcesyntaxhighlight lang="c">
struct moja_struct {
int a;
char b;
} moja = {.b = 'c'}; /* pozostawiamy pole a niewypełnione żadną konkretną wartością */
</syntaxhighlight>
</source>
 
== Wspólne własności typów wyliczeniowych, unii i struktur ==
 
Warto zwrócić uwagę, że język C++ przy deklaracji zmiennych typów wyliczeniowych, unii lub struktur nie wymaga przed nazwą typu słowa kluczowego [[C/Typy_złożone#typedef|typedef]]. Na przykład poniższy kod jest poprawnym programem C++:
<sourcesyntaxhighlight lang="c">
enum Enum { A, B, C };
union Union { int a; float b; };
Linia 198:
return e + u.a + s.a;
}
</syntaxhighlight>
</source>
Nie jest to jednak poprawny kod C i należy o tym pamiętać szczególnie jeżeli uczysz się języka C korzystając z kompilatora C++.
Częstym idiomem w C jest użycie <tt>typedef</tt> od razu z definicją typu, by uniknąć pisania <tt>enum</tt>, <tt>union</tt> czy <tt>struct</tt> przy deklaracji zmiennych danego typu.<ref>[[http://stackoverflow.com/questions/612328/difference-between-struct-and-typedef-struct-in-c Difference between 'struct' and 'typedef struct' in C++? ]]</ref>
<sourcesyntaxhighlight lang="c">
typedef struct struktura {
int pole;
Linia 210:
Struktura s1;
struct struktura s2;
</syntaxhighlight>
</source>
 
W tym przypadku zmienne s1 i s2 są tego samego typu, który ma 2 nazwy: pełną:
Linia 222:
 
Możemy też zrezygnować z pełnej nazwy struktury i pozostawić tylko skróconą:
<sourcesyntaxhighlight lang="c">
typedef struct {
int pole;
Linia 228:
 
Struktura s1;
</syntaxhighlight>
</source>
 
Jeśli chcemy utworzyć strukturę rekurencyjną to wewnątrz struktury możemy użyć tylko nazwy długiej, nie krótkiej:
<sourcesyntaxhighlight lang="c">
typedef struct Wezel {// długa nazwa typu
double re;
Linia 238:
struct Wezel *poprzedni; /* poprzedni węzeł */
} TWezel; // krótka nazwa typu
</syntaxhighlight>
</source>
 
Należy również pamiętać, że po klamrze zamykającej definicje '''musi''' następować średnik. Brak tego średnika jest częstym błędem powodującym czasami niezrozumiałe komunikaty błędów. Jedynym wyjątkiem jest natychmiastowa definicja zmiennych danego typu, na przykład:
<sourcesyntaxhighlight lang="c">
struct Struktura {
int pole;
} s1, s2, s3;
</syntaxhighlight>
</source>
lub też definicja nowego typu, jak w poprzednim bloku kodowym (TWezel).
 
Linia 252:
=== Wskaźnik na unię i strukturę ===
Podobnie, jak na każdą inną zmienna, wskaźnik może wskazywać także na unię lub strukturę. Oto przykład:
<sourcesyntaxhighlight lang="c">
typedef struct {
int p1, p2;
Linia 265:
return 0;
}
</syntaxhighlight>
</source>
 
Zapis <tt>wsk->p1</tt> jest (z definicji) równoważny <tt>(*wsk).p1</tt>, ale bardziej przejrzysty i powszechnie stosowany. Wyrażenie <tt>wsk.p1</tt> spowoduje błąd kompilacji (strukturą jest <tt>*wsk</tt> a nie <tt>wsk</tt>).
Linia 275:
=== Pola bitowe ===
Struktury mają pewne dodatkowe możliwości w stosunku do zmiennych. Mowa tutaj o rozmiarze elementu struktury. W przeciwieństwie do zmiennej może on mieć nawet 1 bit!. Aby móc zdefiniować taką zmienną musimy użyć tzw. '''pola bitowego'''. Wygląda ono tak:
<sourcesyntaxhighlight lang="c">
struct moja {
unsigned int a1:4, /* 4 bity */
Linia 282:
a4:3; /* 3 bity */
};
</syntaxhighlight>
</source>
 
Wszystkie pola tej struktury mają w sumie rozmiar 16 bitów, jednak możemy odwoływać się do nich w taki sam sposób, jak do innych elementów struktury. W ten sposób efektywniej wykorzystujemy pamięć, jednak istnieją pewne zjawiska, których musimy być świadomi przy stosowaniu pól bitowych. Więcej na ten temat w rozdziale [[C/Przenośność programów|przenośność programów]].
Linia 305:
# wskaźnik na kolejny element listy
Przyjmijmy, że szukając liczb pierwszych nie przekroczymy możliwości typu unsigned long:
<sourcesyntaxhighlight lang="c">
typedef struct element {
struct element *next; /* wskaźnik na kolejny element listy */
unsigned long val; /* przechowywana wartość */
} el_listy;
</syntaxhighlight>
</source>
 
Zacznijmy zatem pisać nasz eksperymentalny program, do wyszukiwania liczb pierwszych. Pierwszą liczbą pierwszą jest liczba 2 Pierwszym elementem naszej listy będzie zatem struktura, która będzie przechowywała liczbę 2. Na co będzie wskazywało pole next? Ponieważ na początku działania programu będziemy mieć tylko jeden element listy, pole next powinno wskazywać na NULL. Umówmy się zatem, że pole next ostatniego elementu listy będzie wskazywało NULL - po tym poznamy, że lista się skończyła.
<sourcesyntaxhighlight lang="c">
#include <stdio.h>
#include <stdlib.h>
Linia 338:
return 0;
}
</syntaxhighlight>
</source>
 
Na początek zajmiemy się wypisywaniem listy. W tym celu będziemy musieli "odwiedzić" każdy element listy. Elementy listy są połączone polem next, aby przejrzeć listę użyjemy następującego algorytmu:
Linia 346:
# Przesuń wskaźnik na element, który jest wskazywany przez pole next
# Wróć do punktu 2
<sourcesyntaxhighlight lang="c">
void wypisz_liste(el_listy *lista)
{
Linia 356:
} /* 5 */
}
</syntaxhighlight>
</source>
 
Zastanówmy się teraz, jak powinien wyglądać kod, który dodaje do listy następny element. Taka funkcja powinna:
Linia 365:
# w pole next ostatniego elementu listy wpisać adres nowo przydzielonego obszaru
Napiszmy zatem odpowiednią funkcję:
<sourcesyntaxhighlight lang="c">
void dodaj_do_listy (el_listy *lista, unsigned long liczba)
{
Linia 379:
wsk->next = nowy; /* 5 */
}
</syntaxhighlight>
</source>
I... to już właściwie koniec naszej funkcji (warto zwrócić uwagę, że funkcja w tej wersji zakłada, że na liście jest już przynajmniej jeden element). Wstaw ją do kodu przed funkcją main. Został nam jeszcze jeden problem: w pętli for musimy dodać kod, który odpowiednio będzie "badał" liczby oraz w przypadku stwierdzenia pierwszeństwa liczby, będzie dodawał ją do listy. Ten kod powinien wyglądać mniej więcej tak:
<sourcesyntaxhighlight lang="c">
int jest_pierwsza(el_listy *lista, int liczba)
{
Linia 404:
dodaj_do_listy (first,i);
}
</syntaxhighlight>
</source>
 
Podsumujmy teraz efekty naszej pracy. Oto cały kod naszego programu:
<sourcesyntaxhighlight lang="c">
#include <stdio.h>
#include <stdlib.h>
Linia 467:
return 0;
}
</syntaxhighlight>
</source>
 
Możemy jeszcze pomyśleć, jak można by wykonać usuwanie elementu z listy. Najprościej byłoby zrobić:
wsk->next = wsk->next->next
ale wtedy element, na który wskazywał wcześniej <tt>wsk->next</tt> przestaje być dostępny i zaśmieca pamięć. Trzeba go usunąć. Zauważmy, że aby usunąć element potrzebujemy wskaźnika do '''elementu go poprzedzającego''' (po to, by nie rozerwać listy). Popatrzmy na poniższą funkcję:
<sourcesyntaxhighlight lang="c">
void usun_z_listy(el_listy *lista, int element)
{
Linia 490:
}
}
</syntaxhighlight>
</source>
Funkcja ta jest tak napisana, by usuwała z listy wszystkie wystąpienia danego elementu (w naszym programie nie ma to miejsca, ale lista jest zrobiona tak, że może trzymać dowolne liczby). Zauważmy, że wskaźnik <tt>wsk</tt> jest przesuwany tylko wtedy, gdy nie kasowaliśmy. Gdybyśmy zawsze go przesuwali, przegapilibyśmy element gdyby występował kilka razy pod rząd.