Sari la conținut

Tipuri, valori și sistemul de tipuri

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

Sistemul de tip: Prima ta linie de apărare

C++ is a statically typed language — every expression has a type known at compile time. The type system is not just bookkeeping; it is your most powerful tool for catching bugs before your code ever runs. A well-typed C++ program carries its invariants in the type system itself, making entire classes of errors impossible.

The type of a variable determines three things: how much memory it occupies, what values it can represent, and what operations are valid on it. Getting types right is the foundation of correct C++ code.

Fundamental Types

C++ provides a set of built-in types that map directly to hardware capabilities. Their exact sizes vary by platform, but the standard guarantees minimum ranges:

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';
}
Rezultat

Apasă Rulează pentru a compila și rula acest exemplu.

Initialization: The Right Way

C++ has evolved multiple initialization syntaxes. Uniform (brace) initialization{}is preferred in modern C++ because it prevents narrowing conversions:

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';
}
Rezultat

Apasă Rulează pentru a compila și rula acest exemplu.

Bună practică

In C, the null pointer is represented by the macroNULL, which typically expands to0or(void*)0. This causes ambiguity in C++ because0is also a validint. Consider a function overloaded for bothintand a pointer type — passingNULL(which is0) would call theintoverload, not the pointer one.

C++11 introduced nullptr, a keyword of typestd::nullptr_tthat is implicitly convertible to any pointer type but not to integers. Always usenullptrinstead ofNULLor0for null pointers.

Similarly, prefer'\0'(the null character) when you mean the character with value zero, rather than0, to communicate intent clearly.

  • Utilizați întotdeauna nullptr în loc de NULL sau 0 pentru pointerii nuli
  • nullptr are propriul tip (std::nullptr_t) și evită ambiguitatea supraîncărcării
  • NULL este o macrocomandă C care se extinde la 0 - este un număr întreg, nu un pointer
  • Utilizați „\0” pentru caracterul nul pentru a-l deosebi de întregul 0

const și calificative de tip

Calificatorulconstspune compilatorului (și cititorilor umani) că o valoare nu trebuie modificată după inițializare. Este unul dintre cele mai importante instrumente din C++ pentru scrierea unui cod sigur, auto-documentat.

consteste citit de la dreapta la stânga:const int*este un pointer către un const int (int nu poate fi schimbat, pointerul poate).int* consteste un pointer const către int (pointerul nu poate fi schimbat, int poate).const int* consteste un pointer const către un const int.

constexpr (C++11) merge mai departe: înseamnă că valoarea trebuie să fie calculată la timp de compilare. Acest lucru permite compilatorului să evalueze expresiile în timpul compilării, eliminând complet costul de rulare. Utilizațiconstexprpentru constantele care sunt cu adevărat cunoscute la momentul compilării.

consteval (C++20) este și mai strict: marchează o funcție care trebuie să fie evaluată în timpul compilării - apelarea acesteia în timpul execuției este o eroare de compilare.

Calificatorulvolatileîi spune compilatorului că valoarea unei variabile se poate modifica în afara controlului programului (de exemplu, registre hardware, I/O mapate în memorie). Previne anumite optimizări. Nu folosițivolatilepentru threading - folosițistd::atomicîn schimb.

Capcană

Una dintre cele mai insidioase erori din C și C++ de stil vechi provine din restrângerea conversiilor — conversii implicite care pierd informații. De exemplu, atribuirea unuidoubleunuiinttrunchiază în mod silențios partea fracțională. Atribuirea unuiintmare unuishortdepășește în tăcere.

Inițializarea brace{}previne restrângerea conversiilor în timpul compilării. Acesta este unul dintre cele mai puternice motive pentru a-l prefera față de inițializarea=. Dacă scriețiint x{3.14};, compilatorul va emite o eroare. Cuint x = 3.14;, trunchiază silențios la 3.

În mod similar, amestecarea tipurilor semnate și nesemnate în aritmetică determină conversii implicite care pot produce rezultate surprinzătoare. Unintnegativ în comparație cu ununsigned intva fi convertit mai întâi în nesemnat, devenind potențial un număr pozitiv foarte mare.

  • Inițializarea bretelor {} prinde conversiile înguste în timpul compilării
  • Inițializarea atribuirii (=) permite restrângerea - datele se pierd fără avertisment
  • Amestecarea tipurilor semnate și nesemnate determină conversia implicită în nesemnat
  • Un int negativ convertit în unsigned devine un număr pozitiv foarte mare

Tastați Aliasuri

Aliasurile de tip îmbunătățesc lizibilitatea și facilitează schimbarea tipurilor mai târziu. Preferațiusingfață detypedefîn C++ modern:

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};
}
Rezultat

Apasă Rulează pentru a compila și rula acest exemplu.

Idei cheie
  • Fiecare expresie C++ are un tip cunoscut în momentul compilării - tipul determină dimensiunea, valorile valide și operațiile valide
  • Folosiți tipuri de lățime fixă ​​de la (int32_t, uint64_t) atunci când dimensiunea exactă contează
  • Preferă inițializarea bretelor {} — previne conversiile de restrângere periculoase
  • Utilizați nullptr (nu NULL sau 0) pentru pointerii nuli pentru a evita ambiguitatea supraîncărcării
  • auto deduce tipul din initializator; decltype interoghează tipul unei expresii fără a o evalua
  • Utilizați constexpr pentru constantele de timp de compilare și „utilizarea” pentru aliasuri de tip

Test — Verifică-ți cunoștințele

(15 XP)

1. Care este avantajul inițializării acolade`int x{42};`față de inițializarea atribuirii`int x = 42;`?

2. De ce ar trebui să utilizați`nullptr`în loc de`NULL`în C++?

3. Ce deduce`auto x = 42;`tipul`x`?