Czym jest zmienna
Zmienna to nazwany fragment pamięci, który przechowuje wartość. W C++ każda zmienna ma stały typ, wybierany przy deklaracji, i ten typ nigdy się nie zmienia. To właśnie czyni C++ językiem statycznie typowanym: kompilator zna typ każdej zmiennej, zanim program się uruchomi, i odmawia kompilacji kodu, który zapisuje wartość niewłaściwego rodzaju.
Deklaracja składa się z trzech części: typu, nazwy i (prawie zawsze) wartości początkowej.
Czytaj int age = 30; jako "utwórz int o nazwie age i wstaw do niego 30". Średnik kończy instrukcję, tak jak każdą instrukcję na stronie o komentarzach. Zauważ, że isActive wypisało się jako 1: bool domyślnie wyświetla się jako 1/0, do czego nawiązuje strona o typach danych.
Inicjalizacja a przypisanie
Te dwie rzeczy wyglądają podobnie, ale to różne operacje, a ta różnica to jedna z najważniejszych idei na tej stronie.
Inicjalizacja nadaje zmiennej pierwszą wartość w ramach deklaracji. Przypisanie zmienia wartość zmiennej, która już istnieje.
Możesz zadeklarować zmienną bez inicjalizacji i przypisać wartość później, ale uważaj:
To działa, bo score dostaje wartość przed odczytem. Niebezpieczna wersja to odczyt jako pierwszy, o czym dalej.
Pułapka niezainicjalizowanej zmiennej
To klasyczna pułapka C++, której w wielu innych językach nie ma. Odczyt zmiennej lokalnej, która została zadeklarowana, ale nigdy nie dostała wartości, to niezdefiniowane zachowanie: zmienna zawiera bajty, które akurat były w tej pamięci.
int score; // niezainicjalizowana
std::cout << score; // niezdefiniowane zachowanie: wypisze śmieci, "zadziała" albo się wysypie
Kompilator bez problemu to zbuduje. Może się wypisać 0, może 32766, a przy każdym uruchomieniu lub na każdym komputerze zachowanie może być inne, przez co takie błędy bardzo trudno wytropić. Dwie linie obrony:
- Zawsze inicjalizuj przy deklaracji.
int score = 0;nic nie kosztuje i usuwa cały problem. - Włącz ostrzeżenia. Kompilacja z
-Wall -Wextrasprawia, że kompilator zgłasza wiele odczytów niezainicjalizowanych zmiennych, zanim zdążą zaszkodzić.
Dla każdej zmiennej lokalnej wybieraj pierwszą opcję: nadaj jej sensowną wartość początkową w chwili utworzenia.
Style inicjalizacji: =, () i {}
C++ daje kilka sposobów zapisania wartości początkowej. W większości robią to samo, ale inicjalizacja klamrowa ma jedną dodatkową funkcję bezpieczeństwa, którą warto znać.
Powód, by sięgać po {}, jest taki, że odrzuca konwersje zawężające, czyli przypisania, które po cichu gubiłyby dane. Przy = wartość ułamkowa zostaje po cichu obcięta, a przy {} kompilator cię zatrzyma:
int x = 3.9; // kompiluje się: x po cichu staje się 3 (.9 jest odrzucane)
int y{3.9}; // błąd kompilacji: zawężanie z double do int jest niedozwolone
Wyłapanie tego w czasie kompilacji to dokładnie to, czego chcesz. Popularny współczesny nawyk to domyślne używanie {} dla tej dodatkowej kontroli i sięganie po = tylko wtedy, gdy inicjalizacja przez kopię czyta się naturalniej.
Zasady i konwencje nazewnictwa
C++ narzuca kilka twardych reguł, a na nie wszyscy nakładają konwencje. Reguły: nazwa może zawierać litery, cyfry i _, nie może zaczynać się od cyfry, nie może być zarezerwowanym słowem kluczowym (jak int czy return) i rozróżnia wielkość liter (age i Age to dwie różne zmienne). Unikaj nazw zaczynających się od podkreślenia i wielkiej litery oraz nazw z dwoma podkreśleniami z rzędu: są zarezerwowane dla implementacji.
Konwencje, których trzyma się większość kodu w C++:
- Zmienne zapisuje się w
snake_casealbocamelCase: wybierz jedno i trzymaj się go konsekwentnie,item_countalboitemCount. - Nazwy powinny opisywać wartość:
count, a niec;user_email, a niex.
Czytelne nazwy to nie ozdoba: dzięki nim za kilka miesięcy nadal zrozumiesz ten kod. total_price = item_count * price_per_item tłumaczy się samo, czego t = c * p nigdy nie osiągnie.
Zasięg zmiennej
Zmienna istnieje tylko wewnątrz bloku, czyli { ... }, w którym ją zadeklarowano, i jest niszczona przy klamrze zamykającej. To jej zasięg (scope). Zmienna zadeklarowana wewnątrz pętli lub bloku if jest poza nim niewidoczna:
Zarówno i, jak i square należą do pętli i znikają, gdy ta się kończy, a total jest zadeklarowane w bloku zewnętrznym, więc przetrwa. Blok wewnętrzny może też przesłonić nazwę z bloku zewnętrznego: nowa zmienna o tej samej nazwie tymczasowo ukrywa zewnętrzną, aż blok wewnętrzny się zamknie. To częste źródło zamieszania, więc unikaj ponownego używania nazw w zagnieżdżonych zasięgach.
Wniosek praktyczny: deklaruj każdą zmienną w najmniejszym bloku, który jej potrzebuje, i tam ją inicjalizuj. Wąski zasięg oznacza mniej nazw walczących o twoją uwagę i mniej okazji do odczytu wartości daleko od miejsca, w którym ją ustawiono.
Dalej: typy danych
Każda zmienna na tej stronie zaczynała się od typu: int, double, string, bool. Następna strona szczegółowo omawia typy danych w C++: typy podstawowe i ich rozmiary, liczby całkowite ze znakiem i bez znaku, float a double, char oraz to, jak dobrać właściwy typ do zadania.
Najczęściej zadawane pytania
Jak zadeklarować zmienną w C++?
Napisz typ, potem nazwę, a opcjonalnie nadaj wartość: int age = 30;. Typ (int) pozostaje stały przez całe życie zmiennej, a nazwa (age) służy do odwoływania się do niej. Możesz zadeklarować zmienną bez wartości, int age;, ale zmienna lokalna będzie wtedy zawierać śmieci, dopóki czegoś jej nie przypiszesz, więc zawsze inicjalizuj ją w miejscu deklaracji.
Jaka jest różnica między inicjalizacją a przypisaniem w C++?
Inicjalizacja nadaje zmiennej jej pierwszą wartość w ramach deklaracji: int x = 5; albo int x{5};. Przypisanie zmienia wartość już istniejącej zmiennej: x = 7;. To rozróżnienie ma znaczenie, bo odczyt zmiennej lokalnej, która została zadeklarowana, ale nigdy nie zainicjalizowana, to niezdefiniowane zachowanie.
Co się stanie, jeśli użyjesz niezainicjalizowanej zmiennej w C++?
Odczyt wartości niezainicjalizowanej zmiennej lokalnej to niezdefiniowane zachowanie: zmienna zawiera bajty, które akurat były w tej pamięci, więc program może wypisać losową liczbę, zadziałać przez przypadek albo się wysypać. Kompilator cię nie powstrzyma (choć wiele kompilatorów ostrzega przy -Wall), więc rozwiązaniem jest nadawanie zmiennym lokalnym wartości już przy deklaracji.