5.2 -- Literały

Literały to wartości wstawiane bezpośrednio do kodu. Na przykład:

return 5;                     // 5 is an integer literal
bool myNameIsAlex { true };   // true is a boolean literal
double d { 3.4 };             // 3.4 is a double literal
std::cout << "Hello, world!"; // "Hello, world!" is a C-style string literal

Literały są czasami nazywane stałymi literałami ponieważ ich znaczenia nie można przedefiniować (5 zawsze oznacza wartość całkowitą 5).

Typ literału

Tak jak obiekty mają typ, tak i wszystkie literały mają typ. Rodzaj literału określa się na podstawie wartości literału. Na przykład, literał będący liczbą całkowitą (np. 5) jest dedukowany jako typu int.

Domyślnie:

Wartość literałuPrzykładyDomyślny typ literałuUwaga
wartość całkowita5, 0, -3int
wartość logicznatrue, falsebool
wartość zmiennoprzecinkowa1.2, 0.0, 3.4double (nie float!)
znak’a’, ‘\n’char
ciąg w stylu C„Witaj, świecie!”const char[14]zobacz sekcję dotyczącą literałów łańcuchowych w stylu C poniżej

Przyrostki literałów

Jeśli domyślny typ literału nie jest zgodny z oczekiwaniami, możesz zmienić typ literału, dodając przyrostek. Oto niektóre z bardziej powszechnych sufiksów:

Typ danychSuffixZnaczenie
całkau lub Uunsigned int
całkal lub Llong
całkaul, uL, Ul, UL, lu, lU, Lu, LUunsigned long
całkall lub LLlong long
całkaull, uLL, Ull, ULL, llu, llU, LLu, LLUunsigned long long
całkaz lub ZPodpisana wersja std::size_t (C++23)
całkauz, uZ, Uz, UZ, zu, zU, Zu, ZUstd::size_t (C++23)
zmiennoprzecinkowyf lub Ffloat
zmiennoprzecinkowyl lub Llong double
ciągSstd::string
ciągsvstd::string_view

Za chwilę omówimy literały całkowe i zmiennoprzecinkowe oraz przyrostki.

W większości przypadków sufiksy nie są potrzebne (z wyjątkiem f).

Powiązana treść

Klasa s i sv sufiksów, których użycie wymaga dodatkowej linijki kodu. Omówimy je w dalszej części lekcji 5.7 — Wprowadzenie do std::string i 5.8 — Wprowadzenie do std::string_view.

Dodatkowe (rzadko używane) sufiksy istnieją dla liczb zespolonych i literałów chrono (czasowych). Są one udokumentowane tutaj.

Dla zaawansowanych czytelników

Z wyjątkiem sufiksu f , sufiksy są najczęściej używane w przypadkach, gdy występuje dedukcja typu. Patrz 10.8 -- Dedukcja typu dla obiektów korzystających z auto słowo kluczowe i 13.14 -- Przewodniki dedukcji i dedukcji szablonu klasy (CTAD).

Wielkość przyrostków

Większość przyrostków nie uwzględnia wielkości liter. Wyjątkami są:

  • s i sv musi być pisany małymi literami.
  • Dwa kolejne l lub L znaki muszą mieć tę samą wielkość liter (lL i Ll nie są akceptowane).

Ponieważ małe litery L mogą być stosowane. wyglądają jak numeryczne 1 w niektórych czcionkach niektórzy programiści wolą używać literałów wielkich liter, inni używają sufiksów małych liter, z wyjątkiem L.

Najlepsza praktyka

Preferują sufiks literalny L (wielkie litery) zamiast l (małe litery).

Literały całkowe

Zazwyczaj nie musisz używać sufiksów dla literałów całkowitych, ale tutaj są przykłady:

#include <iostream>

int main()
{
    std::cout << 5 << '\n';  // 5 (no suffix) is type int (by default)
    std::cout << 5L << '\n'; // 5L is type long
    std::cout << 5u << '\n'; // 5u is type unsigned int

    return 0;
}

W większości przypadków można używać literałów int bez przyrostka, nawet podczas inicjowania typów innych niż int :

int main()
{
    int a { 5 };          // ok: types match
    unsigned int b { 6 }; // ok: compiler will convert int value 6 to unsigned int value 6
    long c { 7 };         // ok: compiler will convert int value 7 to long value 7

    return 0;
}

W takich przypadkach kompilator przekonwertuje literał int na odpowiedni typ.

W pierwszym przypadku 5 jest już domyślnie int , więc kompilator może użyć tej wartości bezpośrednio do inicjalizacji int zmienną a. W drugim przypadku int wartości 6 nie pasuje do typu unsigned int b kompilator przekonwertuje wartość int 6 Do unsigned int wartości 6, a następnie użyje jej jako inicjatora dla b. W trzecim przypadku int wartości 7 nie pasuje do typu long c kompilator przekonwertuje wartość int 7 Do long wartości 7, a następnie użyje jej jako inicjatora dla c.

literałów zmiennoprzecinkowych

Domyślnie literały zmiennoprzecinkowe mają typ double. Aby zamiast tego utworzyć z nich float literały, należy użyć sufiksu f (lub F):

#include <iostream>

int main()
{
    std::cout << 5.0 << '\n';  // 5.0 (no suffix) is type double (by default)
    std::cout << 5.0f << '\n'; // 5.0f is type float

    return 0;
}

Nowy programiści są często zdezorientowani, dlaczego następujące ostrzeżenia powodują ostrzeżenie kompilatora:

float f { 4.1 }; // warning: 4.1 is a double literal, not a float literal

Ponieważ 4.1 nie ma przyrostka, literał ma typ double, nie float. Kiedy kompilator określa typ literału, nie obchodzi go, co z nim zrobisz (np. w tym przypadku użyjesz go do zainicjowania zmiennej float ). Ponieważ typ literału (double) nie odpowiada typowi zmiennej, która jest używana do inicjalizacji (float), wartość literału należy przekonwertować na float więc można go następnie użyć do inicjalizacji zmiennej f. Konwersja wartości z double do float może spowodować utratę precyzji, dlatego kompilator wyświetli ostrzeżenie.

Rozwiązanie jest jednym z poniższych:

float f { 4.1f }; // use 'f' suffix so the literal is a float and matches variable type of float
double d { 4.1 }; // change variable to type double so it matches the literal type double

Zapis naukowy dla literałów zmiennoprzecinkowych

Istnieją dwa różne sposoby zapisywania literałów zmiennoprzecinkowych.

  1. W notacji standardowej liczbę zapisujemy z ułamkiem dziesiętnym punkt:
double pi { 3.14159 }; // 3.14159 is a double literal in standard notation
double d { -1.23 };    // the literal can be negative
double why { 0. };     // syntactically acceptable, but avoid this because it's hard to see the decimal point (prefer 0.0)
  1. W notacji naukowej dodajemy e w celu przedstawienia wykładnika:
double avogadro { 6.02e23 }; // 6.02 x 10^23 is a double literal in scientific notation
double protonCharge { 1.6e-19 }; // charge on a proton is 1.6 x 10^-19

Literały łańcuchowe

W programowaniu ciąg to zbiór kolejnych znaków używanych do reprezentowania tekstu (takich jak nazwy, słowa i zdania).

Pierwszy napisany przez Ciebie program w C++ wyglądał prawdopodobnie mniej więcej tak:

#include <iostream>
 
int main()
{
    std::cout << "Hello, world!";
    return 0;
}

"Hello, world!" jest literałem łańcuchowym. Literały łańcuchowe są umieszczane między cudzysłowami, aby zidentyfikować je jako ciągi znaków (w przeciwieństwie do literałów znakowych, które są umieszczane między pojedynczymi cudzysłowami).

Ponieważ ciągi są powszechnie używane w programach, większość współczesnych języków programowania zawiera podstawowy typ danych w postaci ciągu znaków. Ze względów historycznych ciągi nie są podstawowym typem w C++. Mają raczej dziwny, skomplikowany typ, z którym trudno się pracuje (omówimy, jak/dlaczego w przyszłej lekcji, gdy omówimy więcej podstaw wymaganych do wyjaśnienia, jak działają). Takie ciągi są często nazywane ciągami C lub Ciągów w stylu C, ponieważ są dziedziczone z języka C.

Istnieją dwie nieoczywiste rzeczy, które warto wiedzieć o literałach ciągu w stylu C.

  1. Wszystkie literały ciągu w stylu C mają niejawny terminator zerowy. Rozważmy ciąg taki jak "hello". Chociaż wydaje się, że ten ciąg w stylu C ma tylko pięć znaków, w rzeczywistości ma ich sześć: 'h', 'e', 'l‘, 'l', 'o', I '\0' (znak z kodem ASCII 0). Ten końcowy znak „\0” jest znakiem specjalnym zwanym terminatorem zerowym i służy do wskazania końca łańcucha. Łańcuch kończący się terminatorem zerowym nazywa się ciągiem zakończonym zerem.

Dla zaawansowanych czytelników

To jest powód, dla którego ciąg "Hello, world!" ma typ const char[14] zamiast const char[13] -- ukryty terminator zerowy liczy się jako znak.

Powód użycia terminatora zerowego jest również historyczny: można go wykorzystać do określenia, gdzie kończy się ciąg znaków.

  1. W przeciwieństwie do większości innych literałów (które są wartości, a nie obiekty), literały łańcuchowe w stylu C są obiektami stałymi, które są tworzone na początku programu i mają gwarancję istnienia przez cały program. Fakt ten stanie się ważny podczas kilku lekcji, kiedy będziemy omawiać std::string_view.

Kluczowa informacja

Literały łańcuchowe w stylu C to obiekty stałe, które są tworzone na początku programu i mają gwarancję istnienia przez cały program.

W przeciwieństwie do literałów łańcuchowych w stylu C, std::string i std::string_view literały tworzą obiekty tymczasowe. Tych obiektów tymczasowych należy użyć natychmiast, gdyż ulegają zniszczeniu na końcu pełnego wyrażenia, w którym zostały utworzone.

Powiązana treść

Omawiamy std::string i std::string_view Literały w lekcji 5.7 — Wprowadzenie do std::string i 5.8 — Wprowadzenie do std::string_view .

Liczby magiczne

A liczba magiczna to literał (zwykle liczba), który albo ma niejasne znaczenie, albo może wymagać późniejszej zmiany.

Oto dwa stwierdzenia pokazujące przykłady magicznych liczb:

const int maxStudentsPerSchool{ numClassrooms * 30 };
setMax(30);

Co robią literały 30 oznaczają w tych kontekstach? W tym pierwszym przypadku można się zapewne domyślić, że jest to liczba uczniów w klasie, ale nie jest to od razu oczywiste. W tym drugim, kto wie. Musielibyśmy przyjrzeć się tej funkcji, żeby wiedzieć, co ona robi.

W złożonych programach bardzo trudno jest wywnioskować, co reprezentuje literał, chyba że istnieje komentarz wyjaśniający to.

Używanie liczb magicznych jest ogólnie uważane za złą praktykę, ponieważ nie tylko nie dostarczają kontekstu, do czego są używane, ale stwarzają problemy w przypadku konieczności zmiany wartości. Załóżmy, że szkoła kupuje nowe ławki, które pozwalają zwiększyć liczebność klas z 30 do 35 osób i nasz program musi to uwzględnić.

Aby to zrobić, musimy zaktualizować jeden lub więcej literałów z 30 Do 35. Ale które literały? 30 w inicjatorze maxStudentsPerSchool wydaje się oczywiste. Ale co z 30 użytym jako argumentem setMax()? Czy to 30 ma to samo znaczenie co drugie 30? Jeśli tak, warto go zaktualizować. Jeśli nie, należy to zostawić w spokoju, w przeciwnym razie możemy przerwać nasz program gdzie indziej. Jeśli wykonasz globalne wyszukiwanie i zamienianie, możesz przypadkowo zaktualizować argument setMax() kiedy nie powinien się on zmieniać. Musisz więc przejrzeć cały kod pod kątem każdego wystąpienia literału 30 (których może być setki), a następnie indywidualnie określić, czy należy go zmienić, czy nie. Może to być bardzo czasochłonne (i podatne na błędy).

Na szczęście zarówno brak kontekstu, jak i problemy związane z aktualizacją można łatwo rozwiązać za pomocą stałych symbolicznych:

const int maxStudentsPerClass { 30 };
const int totalStudents{ numClassrooms * maxStudentsPerClass }; // now obvious what this 30 means

const int maxNameLength{ 30 };
setMax(maxNameLength); // now obvious this 30 is used in a different context

Nazwa stałej zapewnia kontekst i wystarczy zaktualizować wartość w jednym miejscu, aby zmienić wartość w całym programie.

Zauważ, że liczby magiczne nie zawsze są liczbami — mogą być również tekstem (np. nazwy) lub inne typy:

int main()
{
    printAppWelcome("MyCalculator"); // bad: app name may be used in other places or change in the future
}

Literały użyte w oczywistych kontekstach, które prawdopodobnie nie ulegną zmianie, zazwyczaj nie są uważane za magiczne. Wartości -1, 0, 0.0, I 1 są często używane w takich kontekstach:

int idGenerator { 0 };         // okay: we're starting our id generator with value 0
idGenerator = idGenerator + 1; // okay: we're just incrementing our generator

Inne liczby również mogą być oczywiste w kontekście (i dlatego nie są uważane za magiczne):

int kmtoM(int km)
{
    return km * 1000; // okay: it's obvious 1000 is a conversion factor
}

Identyfikatory całkowe sekwencyjne również generalnie nie są uważane za magiczne:

int main()
{
    // okay: these are just sequential ids/counts
    printPlayerInfo(1); // `1` would not really benefit from being named `player1` instead
    printPlayerInfo(2);
}

Najlepsza praktyka

Unikaj magicznych liczb w swoim kodzie (zamiast tego używaj zmiennych constexpr, zobacz lekcję 5.6 -- Zmienne Constexpr).

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:  
405 Komentarze
Najnowsze
Najstarsze Najczęściej głosowane
Wbudowane opinie
Wyświetl wszystkie komentarze