Tablica przechowuje wiele wartości jednego typu. struct robi odwrotnie: przechowuje kilka wartości różnych typów i traktuje je jak jedną rzecz. Punkt to x i y. Pracownik to nazwisko, identyfikator i pensja. Bez struktur nosiłoby się je jako trzy luźne zmienne z nadzieją, że nigdy nie pomyli się, czyja pensja jest czyja.
Deklaracja typu struktury
Deklaracja struktury nazywa etykietę (tag) i wymienia pola w nawiasach klamrowych:
struct Point {
int x;
int y;
};
Średnik po nawiasie zamykającym jest wymagany. Zapomnienie o nim daje jeden z najbardziej mylących komunikatów o błędzie, jakie spotyka początkujący w C, bo kompilator obwinia następną linię.
To deklaruje typ, a nie zmienną. W C pełna nazwa typu to struct Point, razem ze słowem kluczowym:
Pola odczytuje się i zapisuje operatorem kropki, p.x. Zmienna strukturalna to zwykła zmienna: leży na stosie, można jej przypisać przez = inną strukturę tego samego typu i znika na końcu swojego zasięgu.
Pisanie wszędzie struct Point szybko męczy, dlatego prawie cały prawdziwy kod w C opakowuje deklarację w typedef, żeby typ nazywał się po prostu Point.
Inicjalizacja struktury
Strukturę można wypełnić w chwili deklaracji. Forma pozycyjna wymienia wartości w kolejności deklaracji pól:
struct Point p = {3, 4}; // x = 3, y = 4
Inicjalizator desygnowany zamiast tego nazywa każde pole, co jest czytelniejsze i bezpieczniejsze:
Trzy rzeczy warte uwagi:
- Forma desygnowana może wymieniać pola w dowolnej kolejności, więc późniejsze dodanie pola lub zmiana kolejności nie przesunie po cichu wartości w złe miejsca.
- Każde pominięte pole jest inicjalizowane zerem:
{0}to idiomatyczny sposób na wyzerowanie całej struktury. - Pole
char name[32]to prawdziwa tablica wewnątrz struktury, więc napis jest przechowywany w samej strukturze, a nie gdzieś indziej. Oznacza to też, że późniejszea.name = "Ada";jest niedozwolone i potrzebujeszstrcpy(zobacz funkcje napisowe).
Struktura bez inicjalizatora jest niezainicjalizowana, a odczyt jej pól przed zapisem to niezdefiniowane zachowanie. = {0} nic nie kosztuje i usuwa całą kategorię błędów.
Przypisywanie i porównywanie
Przypisanie struktury kopiuje każde pole:
a pozostaje nietknięte: b dostało własną kopię. Zwróć jednak uwagę, czego C nie daje: dla struktur nie ma ==. if (a == b) się nie skompiluje. Porównanie oznacza porównanie pól:
if (a.x == b.x && a.y == b.y) { /* rowne */ }
Nie sięgaj po memcmp jako skrót. Kompilatory wstawiają między pola niewidoczne bajty wyrównania (padding), a te bajty mogą zawierać śmieci, więc dwie struktury z identycznymi polami mogą się okazać różne przy porównaniu bajt po bajcie.
Tablice struktur
Struktura to typ jak każdy inny, więc można utworzyć ich tablicę. To standardowy sposób przechowywania tabeli rekordów.
staff[i].salary czyta się jako „element i, a potem jego pole salary”. Indeksowanie wiąże mocniej niż kropka, więc nawiasy nie są potrzebne.
Struktury są kopiowane do funkcji
To zasada, na której wszyscy się łapią. Gdy przekazujesz strukturę do funkcji, C ją kopiuje. Funkcja pracuje na własnej, prywatnej kopii:
p nadal wynosi (3, 4). Odczyt struktury przez wartość, jak robi to distance_from_origin, jest w pełni w porządku i przy małych strukturach często jest najczytelniejszym wyborem. Ale jeśli funkcja musi zmienić strukturę wywołującego albo struktura jest na tyle duża, że kopiowanie jest marnotrawstwem, przekaż zamiast tego wskaźnik. Tym zajmuje się strona o strukturach i wskaźnikach.
Zwracanie struktury przez wartość działa tak samo i jest całkowicie dozwolone:
struct Point make_point(int x, int y) {
struct Point p = {x, y};
return p;
}
Zagnieżdżone struktury
Pole struktury samo może być strukturą. Aby dostać się do środka, łącz kropki:
b.published.year wchodzi o jeden poziom na raz. Zagnieżdżanie to sposób budowania prawdziwych modeli danych w C, a wewnętrzna struktura jest przechowywana wewnątrz zewnętrznej, a nie wskazywana, więc cała Book to jeden ciągły blok pamięci.
Rozmiar, wyrównanie i kolejność pól
sizeof mówi, jak duża jest struktura, a odpowiedź często jest większa niż suma pól:
Na typowej maszynie Wasteful ma 12 bajtów, a Tidy 8, choć obie przechowują te same trzy pola. Kompilator wstawia wyrównanie, żeby każde pole leżało pod adresem, który odpowiada jego typowi: int zwykle chce wielokrotności 4. Umieszczanie większych pól przed mniejszymi zwykle daje ciaśniejsze upakowanie.
Ma to znaczenie rzadko (tablica z milionem elementów, układ pakietu sieciowego) i nigdy dla poprawności twojego własnego kodu. Wyjaśnia za to, dlaczego sizeof cię zaskakuje i dlaczego memcmp na strukturach to zły pomysł.
Najczęściej zadawane pytania
Jak zadeklarować strukturę w C?
Napisz struct, a po nim nazwę etykiety i listę pól w nawiasach klamrowych: struct Point { int x; int y; };. To deklaruje typ, a nie zmienną. Aby utworzyć zmienną, piszesz potem struct Point p;. Słowo struct jest w C częścią nazwy typu i dlatego większość kodu łączy je z typedef.
Czym różni się . od -> przy strukturach?
. od -> przy strukturach?Używaj ., gdy masz samą strukturę (p.x), a ->, gdy masz wskaźnik na nią (ptr->x). Strzałka to skrót od (*ptr).x. Szczegóły znajdziesz na stronie struktury i wskaźniki.
Czy struktury w C są przekazywane przez wartość czy przez referencję?
Zawsze przez wartość. Przekazanie struktury do funkcji kopiuje każde pole, więc zmiany wewnątrz funkcji nie wpływają na zmienną wywołującego. Aby zmienić oryginał albo uniknąć kopiowania dużej struktury, przekaż zamiast tego wskaźnik.
Jak zainicjalizować strukturę w C?
Albo pozycyjnie, struct Point p = {3, 4};, albo inicjalizatorami desygnowanymi, struct Point p = {.y = 4, .x = 3};. Forma desygnowana nazywa każde pole, więc przetrwa późniejszą zmianę kolejności pól w strukturze, a pola pominięte zostawia wyzerowane.