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:
#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';
}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í:
#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';
}Stisknutím tlačítka Spustit zkompilujete a spustíte tento příklad.
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.
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++:
#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};
}Stisknutím tlačítka Spustit zkompilujete a spustíte tento příklad.
- 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`?