Přeskočit na obsah

Types, Values & the Type System

Explore C++'s fundamental types, fixed-width integers, initialization forms, auto deduction, and the const system that enforces correctness at compile time.

The Type System: Your First Line of Defense

C++ je staticky typovaný jazyk — každý výraz má typ známý v době kompilace. Typový systém není jen vedení účetnictví; je to váš nejmocnější nástroj pro odhalování chyb dříve, než se váš kód vůbec spustí. Dobře napsaný program C++ nese své invarianty v samotném typovém systému, což znemožňuje celé třídy chyb.

Typ proměnné určuje tři věci: kolik paměti zabírá, jaké hodnoty může představovat a jaké operace jsou s ní platné. Správné typy jsou základem správného kódu C++.

Základní typy

C++ poskytuje sadu vestavěných typů, které mapují přímo možnosti hardwaru. Jejich přesné velikosti se liší podle platformy, ale standard zaručuje minimální rozsahy:

types.cppc++23
#include <iostream>
#include <cstdint>   // Fixed-width integer types
#include <limits>    // std::numeric_limits

int main() {
    // Boolean
    bool is_valid = true;           // true or false, typically 1 byte

    // Character types
    char letter = 'A';             // At least 8 bits, holds ASCII
    char8_t  u8ch  = u8'A';       // C++20: exactly 8 bits, UTF-8
    char16_t u16ch = u'\u00E9';   // At least 16 bits, UTF-16
    char32_t u32ch = U'\U0001F600'; // At least 32 bits, UTF-32

    // Integer types (sizes are minimums, typical x86-64 shown)
    short       s = 32000;         // At least 16 bits (16)
    int         i = 2'000'000;     // At least 16 bits (32) — digit separator C++14
    long        l = 100'000L;      // At least 32 bits (64 on Linux, 32 on Windows)
    long long  ll = 9'000'000'000'000'000'000LL;  // At least 64 bits

    // Unsigned variants
    unsigned int ui = 42u;         // Cannot be negative

    // Floating-point types
    float       f = 3.14f;         // ~7 decimal digits precision
    double      d = 3.141592653589793; // ~15 decimal digits precision
    long double ld = 3.14159265358979323846L;

    // Fixed-width types from <cstdint> — use these when size matters
    std::int32_t  exact = 42;      // Exactly 32 bits, guaranteed
    std::uint64_t big   = 18'446'744'073'709'551'615ULL; // Exactly 64 bits
    std::int_fast32_t fast = 42;   // At least 32 bits, fastest available
    std::size_t index = 0;         // Unsigned, suitable for sizes and indices

    std::cout << "int: " << sizeof(int) << " bytes, max = "
              << std::numeric_limits<int>::max() << '\n';
}
Výstup

Stisknutím tlačítka Spustit zkompilujete a spustíte tento příklad.

Inicializace: Správná cesta

C++ vyvinul několik inicializačních syntaxí. Uniformní (složená) inicializace{}je preferován v moderním C++, protože zabraňuje zúžení konverzí:

initialization.cppc++23
#include <iostream>
#include <string>

int main() {
    // Direct initialization
    int a(42);              // OK but can be confused with function declaration

    // Copy initialization
    int b = 42;             // OK, familiar from C

    // Uniform (brace) initialization — preferred in modern C++
    int c{42};              // Direct-list-initialization
    int d = {42};           // Copy-list-initialization

    // Brace initialization PREVENTS narrowing conversions
    // double to int loses precision — the compiler will ERROR:
    // int narrow{3.14};    // ERROR: narrowing conversion
    int ok = 3.14;          // Compiles! Silently truncates to 3 (dangerous)

    // Value initialization (zero for built-in types)
    int zero{};             // Guaranteed to be 0
    double dzero{};         // Guaranteed to be 0.0
    bool bfalse{};          // Guaranteed to be false

    // auto deduction
    auto x = 42;            // int
    auto y = 3.14;          // double
    auto z = 'A';           // char
    auto s = std::string{"hello"};  // std::string (not const char*!)

    // decltype — queries the type of an expression
    decltype(x) another = 100;  // same type as x → int
    decltype(auto) ref = (x);   // int& because (x) is an lvalue expression

    std::cout << "zero-initialized int: " << zero << '\n';
    std::cout << "auto-deduced string: " << s << '\n';
}
Výstup

Stisknutím tlačítka Spustit zkompilujete a spustíte tento příklad.

Osvědčený postup

V C je nulový ukazatel reprezentován makremNULL, které se obvykle rozšiřuje na0nebo(void*)0. To způsobuje nejednoznačnost v C++, protože0je také platnýint. Zvažte funkci přetíženou jak proint, tak pro typ ukazatele – předáníNULL(což je0) by vyvolalo přetíženíint, nikoli ukazatel.

C++11 představilo nullptr, klíčové slovo typustd::nullptr_t, které je implicitně převoditelné na jakýkoli typ ukazatele, ale ne na celá čísla. Pro nulové ukazatele vždy používejtenullptrmístoNULLnebo0.

Podobně upřednostňujte'\0'(nulový znak), když máte na mysli znak s hodnotou nula, před0, abyste jasně sdělili záměr.

  • Vždy používejte nullptr místo NULL nebo 0 pro null ukazatele
  • nullptr má svůj vlastní typ (std::nullptr_t) a zabraňuje nejednoznačnosti přetížení
  • NULL je makro C, které se rozšiřuje na 0 — je to celé číslo, nikoli ukazatel
  • Použijte '\0' pro znak null, abyste jej odlišili od celého čísla 0

konst a kvalifikátory typu

Kvalifikátorconstříká kompilátoru (a lidským čtenářům), že hodnota se po inicializaci nesmí měnit. Je to jeden z nejdůležitějších nástrojů v C++ pro psaní bezpečného, ​​samodokumentujícího kódu.

constse čte zprava doleva:const int*je ukazatel na const int (int nelze změnit, ukazatel ano).int* constje konstantní ukazatel na int (ukazatel nelze změnit, int ano).const int* constje ukazatel const na const int.

constexpr (C++11) jde ještě dále: znamená to, že hodnota musí být vypočitatelná v době kompilace. To umožňuje kompilátoru vyhodnotit výrazy během kompilace, čímž zcela eliminuje náklady na běh. Použijteconstexprpro konstanty, které jsou skutečně známé v době kompilace.

consteval (C++20) je ještě přísnější: označuje funkci, která musí být vyhodnocena v době kompilace – její volání za běhu je chyba kompilace.

Kvalifikátorvolatileříká kompilátoru, že hodnota proměnné se může změnit mimo kontrolu programu (např. hardwarové registry, paměťově mapované I/O). Zabraňuje určitým optimalizacím. Nepoužívejtevolatilepro vytváření vláken – místo toho použijtestd::atomic.

Úskalí

Jedna z nejzákeřnějších chyb v C a starém C++ pochází z zužujících konverzí – implicitních konverzí, které ztrácejí informace. Například přiřazenídoublekinttiše zkrátí zlomkovou část. Přiřazení velkéhointkshorttiše přeteče.

Inicializace složené závorky{}zabraňuje zúžení převodů v době kompilace. To je jeden z nejsilnějších důvodů, proč ji upřednostnit před inicializací=. Pokud napíšeteint x{3.14};, kompilátor vypíše chybu. Sint x = 3.14;se tiše zkrátí na 3.

Podobně míchání typů se znaménkem a bez znaménka v aritmetice způsobuje implicitní převody, které mohou přinést překvapivé výsledky. Zápornéintve srovnání sunsigned intbude převedeno jako první na nepodepsané, což se potenciálně stane velmi velkým kladným číslem.

  • Inicializace složené závorky {} zachytí zužující se konverze v době kompilace
  • Inicializace přiřazení (=) tiše umožňuje zúžení — data jsou ztracena bez varování
  • Míchání podepsaných a nepodepsaných typů způsobí implicitní převod na nepodepsané
  • Záporné int převedené na unsigned se stane velmi velkým kladným číslem

Typ Aliasy

Typové aliasy zlepšují čitelnost a usnadňují pozdější změnu typů. Preferujteusingpředtypedefv moderním C++:

aliases.cppc++23
#include <cstdint>
#include <vector>
#include <string>

// Modern style (using) — preferred
using Byte = std::uint8_t;
using StudentId = std::int32_t;
using Names = std::vector<std::string>;

// Old style (typedef) — equivalent but harder to read with complex types
typedef std::uint8_t OldByte;
typedef std::vector<std::string> OldNames;

// using works with templates; typedef does not easily
template <typename T>
using Vec = std::vector<T>;

int main() {
    Byte b{255};
    StudentId sid{12345};
    Names names{"Alice", "Bob", "Charlie"};
    Vec<double> values{1.0, 2.0, 3.0};
}
Výstup

Stisknutím tlačítka Spustit zkompilujete a spustíte tento příklad.

Klíčové poznatky
  • Každý výraz C++ má typ známý v době kompilace – typ určuje velikost, platné hodnoty a platné operace
  • Pokud na přesné velikosti záleží, použijte typy s pevnou šířkou z (int32_t, uint64_t).
  • Preferovat inicializaci složených závorek {} – zabrání nebezpečným zužujícím se konverzím
  • Pro nulové ukazatele použijte nullptr (ne NULL nebo 0), abyste se vyhnuli nejednoznačnosti přetížení
  • auto odvodí typ z inicializátoru; decltype se dotazuje na typ výrazu, aniž by jej vyhodnotil
  • Použijte constexpr pro konstanty v době kompilace a 'using' pro typové aliasy

Kvíz — Otestujte své znalosti

(15 XP)

1. Jaká je výhoda inicializace složené závorky`int x{42};`oproti inicializaci přiřazení`int x = 42;`?

2. Proč byste měli v C++ používat`nullptr`místo`NULL`?

3. Z čeho vyplývá`auto x = 42;`typ`x`?