Menu

Struktury w C++ (struct): grupowanie powiązanych danych

Struktury w C++ w praktyce: jak połączyć powiązane zmienne w jeden typ, deklarować i inicjalizować obiekty struct, dodać do struktury funkcje składowe i konstruktory oraz czym struct naprawdę różni się od class.

Na tej stronie są działające edytory: edytuj, uruchamiaj i od razu zobacz wynik.

Po co grupować dane

Do tej pory każda zmienna istniała osobno: tu int, tam string. Prawdziwe programy operują jednak na rzeczach złożonych z kilku części: punkt ma x i y, a student ma imię, wiek i średnią ocen. Przekazywanie tego wszystkiego jako luźnych, osobnych zmiennych łatwo prowadzi do błędów: nic ich ze sobą nie wiąże, a funkcja, która potrzebuje wszystkich trzech, musi przyjmować trzy parametry.

Struktura (struct) rozwiązuje ten problem. Definiuje nowy typ, który łączy powiązane zmienne, czyli jego składowe, w jedną całość. Po zdefiniowaniu traktujesz całą paczkę jak jedną wartość, którą można przechowywać, kopiować i przekazywać do funkcji.

Do każdej składowej dostajesz się operatorem kropki (s.name, s.age). Średnik po zamykającym } definicji struktury jest obowiązkowy: jego pominięcie to jeden z najczęstszych błędów kompilacji u początkujących w C++.

Inicjalizacja struktury

Ręczne przypisywanie każdego pola działa, ale jest rozwlekłe i łatwo pominąć którąś składową. Czystszym rozwiązaniem jest inicjalizacja agregatowa: podajesz wartości w nawiasach klamrowych, w tej samej kolejności, w jakiej zadeklarowano składowe.

Uważaj na pustą deklarację: Point p; (bez klamer) zostawia w x i y śmieciowe wartości, bo składowe typów wbudowanych nie są automatycznie zerowane. Point p{}; inicjalizuje je wartością 0. Wybieraj klamry. Wartości domyślne możesz też wpisać od razu w definicji, dzięki czemu nawet Point p; startuje z czystym stanem:

Struktury jako parametry funkcji

Struktura to jedna wartość, więc funkcja może przyjąć jeden parametr zamiast trzech. Pamiętaj tylko, że przekazanie struktury przez wartość kopiuje wszystkie składowe. Przy czymkolwiek większym niż kilka intów przekazuj ją przez referencję const, żeby uniknąć kopii: to ta sama zasada, którą znasz ze strony o referencjach.

Zwracanie całej struktury z funkcji to idiomatyczny sposób na oddanie kilku wartości naraz, znacznie czytelniejszy niż żonglowanie wieloma referencjami wyjściowymi.

Funkcje składowe i konstruktory

Struktura nie ogranicza się do danych. Może zawierać funkcje składowe, które operują na jej własnych składowych, oraz konstruktor, który inicjalizuje obiekt w chwili jego utworzenia. Tu struktura zaczyna przypominać mały obiekt z własnym zachowaniem.

Wewnątrz funkcji składowej do składowych odwołujesz się po nazwie (width, height): oznaczają one kopię należącą do tego obiektu. Oznaczenie area() const mówi kompilatorowi, że funkcja tylko odczytuje obiekt, więc można ją wywołać także na wartościach const Rectangle. Konstruktory to osobny temat, łącznie ze składnią listy inicjalizacyjnej : width(w), a strona o konstruktorach omawia go dokładnie.

Struct a class: prawdziwa różnica

Często można usłyszeć, że "struktury są do danych, a klasy do obiektów". To konwencja, a nie reguła języka. W C++ struct i class to prawie to samo: jedyna wbudowana różnica to domyślny poziom dostępu.

struct S {
    int x;        // domyślnie public
};

class C {
    int x;        // domyślnie private
};

Składowe struct są public, chyba że oznaczysz je inaczej, a składowe class są od początku private. To wszystko: oba mogą mieć konstruktory, funkcje składowe, dziedziczenie i całą resztę. Do struktury możesz nawet dodać jawne specyfikatory dostępu, żeby ukryć składowe:

Wniosek praktyczny: używaj struct do przejrzystych paczek danych, w których każde pole można swobodnie zmieniać (Point, Color, rekord konfiguracji), a po class sięgaj wtedy, gdy chcesz ukryć stan wewnętrzny za publicznym interfejsem. Kompilator traktuje je tak samo, a słowo kluczowe jedynie sygnalizuje twoje intencje.

Tablice i wektory struktur

Struktura to zwykły typ, więc możesz umieścić wiele struktur w tablicy lub w vectorze i przechodzić po nich pętlą jak po każdej innej wartości.

Zwróć uwagę na zagnieżdżone klamry: każde {"Keyboard", 49.99} inicjalizuje agregatowo jeden Product, a zewnętrzne klamry budują wektor. Użycie const Product& w pętli zakresowej sprawia, że żadna struktura nie jest kopiowana w kolejnych iteracjach. Bez & każdy element byłby niepotrzebnie kopiowany.

Dalej: typy wyliczeniowe (enum)

Struktury pozwalają zbudować typ z kilku połączonych wartości. Następny rozdział idzie w przeciwną stronę: enum definiuje typ, który przechowuje dokładnie jedną wartość z małego, nazwanego zbioru. To idealne rozwiązanie dla takich rzeczy jak Color::Red, Direction::North czy stan maszyny stanów. Zobaczysz, jak enum class daje czytelne stałe z kontrolą typów, które naturalnie współpracują ze strukturami i klasami, które teraz budujesz.

Najczęściej zadawane pytania

Czym jest struktura (struct) w C++?

struct to typ zdefiniowany przez użytkownika, który grupuje kilka powiązanych zmiennych (nazywanych składowymi) pod jedną nazwą. Zamiast żonglować osobnymi zmiennymi string name, int age i double gpa, łączysz je w jeden typ Student i przekazujesz dalej ten jeden obiekt. Struktura może też mieć funkcje składowe i konstruktory: we współczesnym C++ to pełnoprawna klasa, której składowe są domyślnie public.

Jaka jest różnica między struct a class w C++?

Technicznie tylko domyślny poziom dostępu: składowe struct są public, dopóki nie zdecydujesz inaczej, a składowe class są domyślnie private. To samo dotyczy dziedziczenia. Cała reszta (konstruktory, funkcje składowe, metody, dziedziczenie) działa identycznie. Zgodnie z konwencją programiści sięgają po struct przy prostych paczkach danych, a po class przy typach z niezmiennikami i ukrytym wnętrzem.

Jak zainicjalizować strukturę w C++?

Najprościej przez inicjalizację agregatową w nawiasach klamrowych, w kolejności składowych: Point p{3, 4};. Możesz też przypisać każde pole ręcznie operatorem kropki (p.x = 3;), nadać składowym wartości domyślne w definicji (int x = 0;) albo napisać konstruktor, który sam przygotuje strukturę. Inicjalizacja klamrowa jest zalecana, bo nie zostawia po cichu niezainicjalizowanych pól.

Ilustracja języków programowania w Coddy

Ucz się programowania z Coddy

ZACZNIJ