5.2 -- Literały

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

return 5;                     // 5 to literał całkowity
bool myNameIsAlex { true };   // true jest literałem boolowskim
double d { 3.4 };             // 3,4 to podwójny literał
std::cout << "Hello, world!"; // „Witaj, świecie!” to literał łańcuchowy w stylu C

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 (bez przyrostka) wpisz int (przez domyślnie)
    std::cout << 5L << '\n'; // 5L do długiego pisania
    std::cout << 5u << '\n'; // 5u, aby wpisać 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: typy się zgadzają
    unsigned int b { 6 }; // ok: kompilator przekonwertuje wartość int 6 na wartość int bez znaku 6
    long c { 7 };         // ok: kompilator przekonwertuje wartość int 7 na wartość long 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 (bez przyrostka) jest typu double (domyślnie)
    std::cout << 5.0f << '\n'; // 5.0f, aby wpisać float

    return 0;
}

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

float f { 4.1 }; // ostrzeżenie: 4.1 to literał podwójny, a nie literał zmiennoprzecinkowy

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 }; // użyj przyrostka „f”, aby literał był zmiennoprzecinkowy i pasował do zmiennej typu float
double d { 4.1 }; // zmienić zmienną na typ double tak, aby odpowiadała typowi dosłownemu 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 to podwójny literał w notacji standardowej
double d { -1.23 };    // literał może być ujemny
double why { 0. };     // składniczo akceptowalne, ale unikaj tego, ponieważ trudno zobaczyć przecinek dziesiętny (preferowane 0,0)
  1. W notacji naukowej dodajemy e w celu przedstawienia wykładnika:
double avogadro { 6.02e23 }; // 6,02 x 10^23 to podwójny literał w notacji naukowej
double protonCharge { 1.6e-19 }; // ładunek protonu wynosi 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

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 }; // teraz oczywiste, co do 30 oznacza

const int maxNameLength{ 30 };
setMax(maxNameLength); // teraz oczywiste, że 30 jest użyte w innym kontekście

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"); // zły: nazwa aplikacji może zostać użyta w innym miejscu lub zmienić 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 };         // OK: zaczynamy nasz generator identyfikatorów z wartością 0
idGenerator = idGenerator + 1; // OK: właśnie zwiększamy nasz 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; // ok: to oczywiste, że 1000 to współczynnik konwersji
}

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

int main()
{
    // OK: do tylko kolejnych identyfikatorów/liczby
    printPlayerInfo(1); // „1” tak naprawdę nie zyskałby na nazwie „gracz1” zamiast tego
    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