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

Dodane 576 bajtów ,  1 rok temu
brak opisu edycji
Znaczniki: Z urządzenia mobilnego Z wersji mobilnej
Nie podano opisu zmian
 
== 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.
== 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)
{
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.
 
 
Struktury definiuje się w następujący sposób:
<sourcesyntaxhighlight lang="c">
struct Struktura {
int pole1;
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>
== 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;
/* ... */
};
</syntaxhighlight>
</source>
 
Na przykład:
<sourcesyntaxhighlight lang="c">
union LiczbaLubZnak {
int calkowita;
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>
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:
== 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; };
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;
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ą:
 
Możemy też zrezygnować z pełnej nazwy struktury i pozostawić tylko skróconą:
<sourcesyntaxhighlight lang="c">
typedef struct {
int pole;
 
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;
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).
 
=== 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;
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>).
=== 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 */
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]].
# 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>
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:
# 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)
{
} /* 5 */
}
</syntaxhighlight>
</source>
 
Zastanówmy się teraz, jak powinien wyglądać kod, który dodaje do listy następny element. Taka funkcja powinna:
# 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)
{
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)
{
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>
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)
{
}
}
</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.