Podstawy programowania 10

Dotychczas pracowaliśmy (z drobnymi wyjątkami w postaci obiektów cin i cout i zmiennych typu string) ze zmiennymi o prostych typach wbudowanych takich jak int, double, char czy wskaźnikami na nie. Jednak świat modelowany w programach jest często bardziej skomplikowany i przydaje sie możliwość tworzenia własnych typów danych, tzw. typów złożonych. Poznamy dwa rodzaje typów złożonych: struktury i unie.

Dodajmy, że jednym z założeń projektowych C++ jest aby typy złożone definiowane przez użytkownika mogły być używane tak jak typy wbudowane. Dla przykładu można definiować operatory dla samodzielnie zdefiniowanych typów. Nie będziemy się tym jednak tutaj zajmować.

Struktury

Struktura to definiowany przez programistę typ danych, który łączy razem wiele typów prostszych po jedną nazwą. Składowe struktury nazywamy polami. Do pól odwołujemy się używając operatora kropki. Dla przykładu, aby wygodnie przechowywać współrzędne punktów można stworzyć taką strukturę:

struct Point {
    double x;
    double y;
};

Używamy jej tak samo jak każdego innego typu. W szczególności możemy tworzyć zmienne typu Point, tablice i wskaźniki na obiekty typu Point:

Point p;
p.x = 1.0;  // Odwołanie do składowych
p,y = -2.0; // z użyciem kropki

Point q = {1.2, 3.4}; // Inicjalizacja w kolejności

Point v[10]; // Tablica 10-ciu punktów

Zmienne takie mogą być też parametrami metod. Pamiętajmy, że jeżeli nie zaznaczymy inaczej, to argumenty są kopiowane do funkcji. W przypadku dużych struktur lepiej używać przekazania poprzez referencję, co z reguły jest szybsze. Jeśli nie zmieniamy zmiennej przekazanej przez referencję, warto to zaznaczyć w parametrach funkcji dopisując const:

void drawLine(Point a, Point b) {
    // Kod rysujący linię z a do b
}

void drawCircle(const Point &a, double r) {
    // Kod rysujący okrąg o środku a i promieniu r
}

Nie ma żadnych przeciwwskazań, aby polami struktury były inne struktury. Polem struktury może być także wskaźnik na nią samą. Jest to często spotykane np. przy implementacji listy z dowiązaniami:

struct Node {
    Node *prev;
    Node *next;
    int payload;
}

Operator ->

Jak napisano wyżej, możemy też tworzyć wskaźniki do struktur. Ze względu jednak na kolejność operatorów, dostanie się do pól poprzez wskaźnik z użyciem kropki byłoby nieco kłopotliwe. Ponieważ kropka wiąże silniej niż gwiazdka, musimy użyć nawiasu:

Point p;
Point *q = &p;

p.x = 0.3;   // Zmiana pola wprost
(*q).y = 0.2 // Zmiana pola poprzez wskaźnik

Aby temu zaradzić w C wprowadzono kolejny operator, operator dostępu do składowej wskaźnika, oznaczany strzałką. Poniższe dwa wywołania są równoważne:

(*q).y = 0.2
q->y = 0.2

Unie

Unie to takie struktury, które są przechowywane w pamięci w sposób oszczędny, to znaczy pola zajmują (w miarę możliwości) ten sam obszar pamięci. Jest to raczej relikt z dawnych czasów, kiedy komputery miały mało pamięci i trzeba było stosować takie sztuczki, aby ją oszczędzać. Na dzisiejszym komputerach osobistych poza bardzo wyjątkowymi sytuacjami nie jest to potrzebne, chociaż może być użyteczne przy pisaniu kodu np. na mikrokontrolery.

Unię definiujemy jak strukturę, do składowych odwołujemy się potem również przez kropkę:

union FloatingPoint {
    float f;
    double d;
}

Zapisanie danych do pola f spowoduje, że pewne bity w d zostaną zamazane. Zapis do jednego z pól i późniejsze odczytywanie z innych pól prowadzi do niezdefiniowanego zachowania programu. Wynik takiej operacji zależy od sprzętu, kompilatora itp. i może to prowadzić do bardzo nieoczekiwanych wyników (np. kompilator przy optymalizowaniu kodu z reguły zakłada, że nie dochodzi do niezdefiniowanego zachowania programu).