5.1 — Zmienne stałe (nazwane stałe)

Wprowadzenie do stałych

W programowaniu stała to wartość, której nie można zmieniać w trakcie działania programu wykonanie.

C++ obsługuje dwa różne rodzaje stałych:

  • Stałe nazwane są stałymi wartościami powiązanymi z identyfikatorem. Nazywa się je czasami stałymi symbolicznymi.
  • stałymi literałowymi są to wartości stałe, które nie są powiązane z identyfikatorem.

nasze omówienie stałych zaczniemy od przyjrzenia się nazwanym stałym. Następnie omówimy stałe dosłowne (w nadchodzącej lekcji 5.2 -- Literały).

Typy nazwanych stałych

Istnieją trzy sposoby definiowania nazwanych stałych w C++:

Zmienne stałe są najpopularniejszym typem nazwanych stałych, więc zaczniemy od tego.

Zmienne stałe

Jak dotąd wszystkie zmienne, które widzieliśmy, nie były stałe — to znaczy, że ich wartości można zmienić w dowolnym momencie (zwykle poprzez przypisanie nowej wartości). przykład:

int main()
{
    int x { 4 }; // x is a non-constant variable
    x = 5; // change value of x to 5 using assignment operator

    return 0;
}

Jednakże w wielu przypadkach przydatne jest zdefiniowanie zmiennych o wartościach, których nie można zmienić. Weźmy na przykład grawitację Ziemi (w pobliżu powierzchni): 9,8 metra na sekundę2. Prawdopodobnie nie zmieni się to w najbliższym czasie (a jeśli tak się stanie, prawdopodobnie będziesz mieć większe problemy niż nauka C++). omówimy to na kolejnych lekcjach.

Chociaż jest to dobrze znany oksymoron, zmienna, której wartości nie można zmienić po inicjalizacji, nazywa się zmienną stałą.

Deklarowanie zmiennej const

Aby zadeklarować zmienną stałą, umieszczamy słowo kluczowe const (tzw. „kwalifikator const”) obok typu obiektu:

const double gravity { 9.8 };  // preferred use of const before type
int const sidesInSquare { 4 }; // "east const" style, okay but not preferred

Chociaż C++ akceptuje kwalifikator const przed lub po typie. Znacznie częściej używa się kwalifikatora const przed typem, ponieważ lepiej jest zgodny ze standardową konwencją języka angielskiego, w której modyfikatory występują przed modyfikowanym obiektem (np. „zielona kulka”, a nie „zielona kulka”).

Na marginesie…

Ze względu na sposób, w jaki kompilator analizuje bardziej złożone deklaracje, niektórzy programiści wolą umieszczać const po typie (ponieważ jest nieco bardziej spójny). Ten styl nazywa się „east const”. Chociaż ten styl ma kilku zwolenników (i kilka rozsądnych punktów), nie przyjął się znacząco.

Najlepsza praktyka

Umieść const przed typem (ponieważ jest to bardziej konwencjonalne).

Kluczowa informacja

Typ obiektu zawiera kwalifikator const, więc gdy definiujemy const double gravity { 9.8 }; typ z gravity Jest const double.

Zmienne const muszą zostać zainicjowane

Zmienne const muszą być zainicjowane podczas ich definiowania, a następnie tej wartości nie można zmienić poprzez przypisanie:

int main()
{
    const double gravity; // error: const variables must be initialized
    gravity = 9.9;        // error: const variables can not be changed

    return 0;
}

Zauważ, że zmienne const mogą być inicjowane z innych zmiennych (w tym niestałych):

#include <iostream>

int main()
{ 
    std::cout << "Enter your age: ";
    int age{};
    std::cin >> age;

    const int constAge { age }; // initialize const variable using non-const value

    age = 5;      // ok: age is non-const, so we can change its value
    constAge = 6; // error: constAge is const, so we cannot change its value

    return 0;
}

W powyższym przykładzie zainicjuj zmienną stałą constAge ze zmienną inną niż stała age. Ponieważ age nadal nie jest stała, możemy zmienić jej wartość. Jednakże, ponieważ constAge jest stałą, nie możemy zmienić jej wartości po inicjalizacji.

Kluczowa informacja

Inicjator zmiennej stałej może być wartością inną niż stała.

Nazywanie stałej zmienne

Istnieje wiele różnych konwencji nazewnictwa stosowanych w przypadku zmiennych const.

Programiści, którzy przeszli z języka C, często wolą nazwy zmiennych const podkreślone i pisane wielkimi literami (np. EARTH_GRAVITY). W C++ częściej używa się nazw przeplatanych znakami z przedrostkiem „k” (np. kEarthGravity).

Jednakże, ponieważ zmienne const zachowują się jak normalne zmienne (z wyjątkiem tego, że nie można ich do nich przypisać), nie ma powodu, dla którego potrzebna jest im specjalna konwencja nazewnictwa. Z tego powodu wolimy używać tej samej konwencji nazewnictwa, której używamy w przypadku zmiennych innych niż stałe (np. earthGravity).

Stałe parametry funkcji

Parametry funkcji można przekształcić w stałe za pomocą opcji const słowem kluczowym:

#include <iostream>

void printInt(const int x)
{
    std::cout << x << '\n';
}

int main()
{
    printInt(5); // 5 will be used as the initializer for x
    printInt(6); // 6 will be used as the initializer for x

    return 0;
}

Należy pamiętać, że nie udostępniliśmy jawnego inicjatora dla naszego parametru const x -- wartość argumentu w wywołaniu funkcji zostanie użyta jako inicjator x.

Utworzenie stałej parametru funkcji wymaga pomocy kompilatora w celu zapewnienia, że ​​wartość parametru nie zostanie zmieniona wewnątrz funkcji. Jednak we współczesnym C++ nie tworzymy parametrów wartości const ponieważ generalnie jest nam obojętne, czy funkcja zmieni wartość parametru (ponieważ jest to tylko kopia, która i tak zostanie zniszczona na końcu funkcji). The const słowo kluczowe dodaje również niewielką ilość niepotrzebnego bałaganu do prototypu funkcji.

Najlepsza praktyka

Nie używaj const dla parametrów wartości.

W dalszej części tej serii tutoriali omówimy dwa inne sposoby przekazywania argumentów do funkcji: przekazywanie przez referencję i przekazywanie przez adres. Podczas korzystania z którejkolwiek z tych metod właściwe użycie const jest ważne.

Stałe wartości zwracane

Wartość zwracana przez funkcję może być również stała:

#include <iostream>

const int getValue()
{
    return 5;
}

int main()
{
    std::cout << getValue() << '\n';

    return 0;
}

W przypadku typów podstawowych, const kwalifikator typu zwracanego jest po prostu ignorowany (kompilator może wygenerować ostrzeżenie).

W przypadku innych typów (które omówimy później) zazwyczaj nie ma większego sensu zwracanie obiektów const według wartości, ponieważ są to kopie tymczasowe, które i tak zostaną zniszczone. Zwracanie wartości stałej może również utrudniać pewne rodzaje optymalizacji kompilatora (obejmujące semantykę przenoszenia), co może skutkować niższą wydajnością.

Najlepsza praktyka

Nie używaj const podczas zwracania według wartości.

Dlaczego zmienne powinny być stałe

Jeśli zmienną można ustawić jako stałą, generalnie należy ją ustawić jako stałą. Jest to ważne z kilku powodów:

  • Zmniejsza to ryzyko wystąpienia błędów. Ustawiając zmienną stałą, masz pewność, że wartość nie zostanie przypadkowo zmieniona.
  • Zapewnia kompilatorowi większe możliwości optymalizacji programów. Kiedy kompilator może założyć, że wartość się nie zmienia, jest w stanie wykorzystać więcej technik do optymalizacji programu, w wyniku czego skompilowany program jest mniejszy i szybszy. Omówimy to szerzej w dalszej części tego rozdziału.
  • Co najważniejsze, zmniejsza ogólną złożoność naszych programów. Próbując ustalić, co robi sekcja kodu lub próbując debugować problem, wiemy, że wartość zmiennej const nie może zostać zmieniona, więc nie musimy się martwić, czy jej wartość faktycznie się zmienia, na jaką wartość się zmienia i czy nowa wartość jest poprawna.

Kluczowa informacja

Każda ruchoma część systemu zwiększa złożoność i ryzyko defektu lub awarii. Zmienne inne niż stałe są częściami ruchomymi, podczas gdy zmienne stałe nie.

Najlepsza praktyka

Jeśli to możliwe, utrzymuj zmienne stałe. Przypadki wyjątków obejmują parametry funkcji według wartości i typy zwracane według wartości, które generalnie nie powinny być stałe.

Makra obiektowe z tekstem zastępczym

W lekcji 2.10 — Wprowadzenie do preprocesora, omówiliśmy makra obiektowe z tekstem podstawieniowym. Na przykład:

#include <iostream>

#define MY_NAME "Alex"

int main()
{
    std::cout << "My name is: " << MY_NAME << '\n';

    return 0;
}

Gdy preprocesor przetworzy plik zawierający ten kod, zostanie on zastąpiony MY_NAME (w linii 7) z "Alex". Zauważ, że MY_NAME jest nazwą, a tekst podstawienia jest wartością stałą, zatem makra obiektowe z tekstem podstawienia są również nazywane stałymi.

Preferuj zmienne stałe od makr preprocesora

Dlaczego więc nie użyć makr preprocesora dla nazwanych stałych? Istnieją (co najmniej) trzy główne problemy.

Największym problemem jest to, że makra nie przestrzegają normalnych reguł określania zakresu C++. Po #zdefiniowaniu makra wszystkie kolejne wystąpienia nazwy makra w bieżącym pliku zostaną zastąpione. Jeśli ta nazwa zostanie użyta gdzie indziej, otrzymasz podstawienie makra tam, gdzie tego nie chciałeś. Najprawdopodobniej doprowadzi to do dziwnych błędów kompilacji. Na przykład:

#include <iostream>

void someFcn()
{
// Even though gravity is defined inside this function
// the preprocessor will replace all subsequent occurrences of gravity in the rest of the file
#define gravity 9.8
}

void printGravity(double gravity) // including this one, causing a compilation error
{
    std::cout << "gravity: " << gravity << '\n';
}

int main()
{
    printGravity(3.71);

    return 0;
}

Podczas kompilacji GCC wygenerowało następujący mylący błąd:

prog.cc:7:17: error: expected ',' or '...' before numeric constant
    5 | #define gravity 9.8
      |                 ^~~
prog.cc:10:26: note: in expansion of macro 'gravity'

Po drugie, często trudniej jest debugować kod przy użyciu makr. Chociaż kod źródłowy będzie miał nazwę makra, kompilator i debuger nigdy nie zobaczą makra, ponieważ zostało ono już zastąpione przed uruchomieniem. Wiele debugerów nie jest w stanie sprawdzić wartości makra i często ma ograniczone możliwości podczas pracy z makrami.

Po trzecie, podstawienie makr zachowuje się inaczej niż wszystko inne w C++. W rezultacie można łatwo popełnić niezamierzone błędy.

Zmienne stałe nie mają żadnego z tych problemów: podlegają normalnym zasadom określania zakresu, mogą być widoczne dla kompilatora i debugera oraz zachowują się spójnie.

Najlepsza praktyka

Preferują zmienne stałe zamiast makr obiektowych z tekstem podstawienia.

Używanie stałych zmiennych w programie wieloplikowym

W wielu aplikacjach dana nazwana stała musi być używane w całym kodzie (nie tylko w jednym pliku). Mogą to być stałe fizyczne lub matematyczne, które się nie zmieniają (np. pi lub liczba Avogadro) lub wartości „dostrojenia” specyficzne dla aplikacji (np. współczynniki tarcia lub grawitacji). Zamiast definiować je na nowo za każdym razem, gdy są potrzebne, lepiej zadeklarować je raz w centralnej lokalizacji i używać ich tam, gdzie jest to potrzebne. Dzięki temu, jeśli kiedykolwiek będziesz musiał je zmienić, wystarczy, że zmienisz je tylko w jednym miejscu.

W C++ można to ułatwić na wiele sposobów — omawiamy ten temat szczegółowo w lekcji 7.10 — Współdzielenie stałych globalnych w wielu plikach (przy użyciu zmiennych wbudowanych).

Nomenklatura: kwalifikatory typu

A kwalifikator typu (czasami nazywany kwalifikator dla short) to słowo kluczowe stosowane do typu, które modyfikuje zachowanie tego typu. const używany do deklarowania zmiennej stałej nazywany jest kwalifikatorem typu const (lub kwalifikatorem const w skrócie).

Począwszy od C++23, C++ ma tylko dwa kwalifikatory typu: Kwalifikator const i volatile.

Odczyt opcjonalny

Klasa volatile służy do informowania kompilatora, że ​​wartość obiektu może zostać zmieniona w dowolnym momencie. Ten rzadko używany kwalifikator wyłącza pewne typy optymalizacji.

W dokumentacji technicznej kwalifikatory const i volatile są często określane jako kwalifikatory CV. W standardzie C++ używane są także następujące terminy:

  • A cv-unqualified typ to typ bez kwalifikatorów typu (np. int).
  • A cv-qualified typ to typ z zastosowanym jednym lub większą liczbą kwalifikatorów typu (np. const int).
  • A ewentualnie kwalifikowany do CV typ to typ, który może być niekwalifikowany lub kwalifikowany do CV.

Te terminy nie są używane zbyt często poza dokumentacją techniczną, więc są tutaj wymienione w celach informacyjnych, a nie jako coś, o czym musisz pamiętać.

Ale teraz przynajmniej możesz docenić ten żart JF Bastien:

  • P: Jak to zrobić wiesz, czy programista C++ ma kwalifikacje?
  • O: Patrzysz na jego CV.
guest
Twój adres e-mail nie zostanie wyświetlony
Znalazłeś błąd? Zostaw komentarz powyżej!
Komentarze związane z poprawkami zostaną usunięte po przetworzeniu, aby pomóc zmniejszyć bałagan. Dziękujemy za pomoc w ulepszaniu witryny dla wszystkich!
Awatary z https://gravatar.com/ są połączone z podanym adresem e-mail.
Powiadamiaj mnie o odpowiedziach:  
490 Komentarze
Najnowsze
Najstarsze Najczęściej głosowane
Wbudowane opinie
Wyświetl wszystkie komentarze