5. Typy złożone

5.3. Struktury

W przeciwieństwie do tablic struktury to zbiór elementów, które różnią się typem, natomiast powinny pozostawać ze sobą w związku logicznym. Ze względu na wprowadzenie w C++ pojęcia klasy, znaczenie czystych struktur zdecydowanie zmalało. W zasadzie w C++ można traktować strukturę jako zdegenerowaną klasę. Zdegenerowaną – bo pozbawioną kontroli dostępu, ze wszystkimi polami i metodami publicznymi. Napisałem metodami – i to nie jest przeoczenie. W C++ struktura może mieć metody, a nie tylko pola.

Struktury definiujemy przy wykorzystaniu słowa kluczowego struct, następnie w nawiasach klamrowych podajemy opis pól i metod. Istnieją dwa ogólne schematy definicji – struktura nazwana i nienazwana. Zastosowanie struktur nienazwanych ogranicza się do tworzenia okazjonalnych zmiennych strukturalnych – osobiście rzadko widzę potrzebę ich stosowania.

Przykłady definicji struktur:


// struktura nazwana
struct costam {
  int a;
  char b;
  double c;
} ct1;

costam ct2, ct3 =
   { 10, 'a', 3.5 };

// struktura nienazwana
struct {
  int a1, a2;
  double a3;
} ala;

// struktura nazwana z odwołaniem rekurencyjnym
struct struktura {
  double a;
  char nn[5];
  costam ct;
  struktura *nast;
};

Z punktu widzenia składni języka z definicją typów strukturalnych wiążą się pewne nie zawsze jasne aspekty. Po pierwsze – za nawiasem klamrowym zamykającym każdą strukturę musi znajdować się średnik – inaczej niż w przypadku innego stosowania nawiasów klamrowych. Dla każdej struktury zostanie automatycznie wygenerowany zestaw specyficznych metod: kontruktor domyślny, kopiujący i przenoszący, operator przypisania i przeniesienia oraz destruktor. O znaczeniu tych metod dowiecie się później.

Można mieszać ze sobą (osadzać jedne w drugich) struktury i tablice, lecz – uwaga – w przypadku osadzenia tablicy w strukturze domyślny, wygenerowany operator przypisania i konstruktor kopiujący przestanią działać prawidłowo! Wykonywane domyślnie kopiowanie jest płytkie - skopiowana zostanie jedynie wartość adresu tablicy, a nie sama tablica.

Kompilator musi mieć możliwość określenia wielkości każdego pola definiowanej struktury, żeby możliwe stało się obliczenie jej zapotrzebowania na pamięć. Lecz kto pomyśli, że rozmiar struktury jest równy sumie rozmiarów jej pól – może się srodze zawieść.

Rozmiar struktury nie zawsze musi być równy rozmiarowi jej pól i taki sam na różnych systemach operacyjnych

To niemiłe zachowanie jest spowodowane różnymi ograniczeniami i optymalizacjami dostępu do pamięci w różnych systemach operacyjnych.

Dostęp do pól jest możliwy poprzez operator wyłuskania (kropka) oraz desygnator nazwy pola. W przypadku stosowania wskaźników do struktur, obowiązują w zasadzie te same zasady i ograniczenia co w przypadku wskaźników do pól prostych. Zmienia się jedynie postać postać operatora wyłuskania na ->. Uważajcie na jeszcze jedną nieciekawą cechę - dwie struktury nie są sobie równoważne nawet jeśli mają identyczną definicję. Dla porządku jeszcze wspomnę, że nie ma jakiegokolwiek automatycznego rzutowania typów, ani z/na typy proste, ani z / na inne typy złożone, oraz że nazwy pól nie mogą się powtarzać.

Struktura może mieć natomiast pola statyczne – pole takie, podobnie jak w przypadku klas – jest jedno dla wszystkich instancji struktury. Możecie je traktować jako zmienną globalną zdefiniowaną w przestrzeni nazw struktury.