跳至内容

变量、类型与类型安全

学习 C++ 的类型系统 ── 基本类型、初始化形式、auto 推导、const 正确性、类型转换以及 C++ 如何在编译时防止类型错误。

C++ 是静态类型语言

在 C++ 中,每个变量都有一个在编译时确定且永远不会改变的类型。这与 Python 或 JavaScript 不同,在那些语言中变量可以在运行时持有任何类型。C++ 的类型系统是你的第一道防线 ── 编译器在程序运行之前就能捕获许多错误。

基本类型有:int(通常 32 位)、double(64 位浮点数)、char(单个字节/字符)、bool(true/false)。精确的大小取决于平台,但可以使用 中的固定宽度类型如 int32_t、uint64_t 来保证。

初始化形式

C++ 有多种初始化变量的方式。现代 C++ 推荐花括号初始化({})因为它可以防止窄化转换:

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';
}
输出

点击“运行”即可编译并运行这个示例。

auto 与类型推导

auto 让编译器从初始化器推导类型。它减少冗余,防止意外转换,并使代码适应未来的变化。但是 auto 永远不会推导出 const 或引用 ── 你必须显式写出 const auto&。

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';
}
输出

点击“运行”即可编译并运行这个示例。

const 与 constexpr

const 意味着「初始化后不可修改」。使用 const 既记录了意图又防止了意外修改。constexpr 更进一步:它意味着值在编译时已知。constexpr 变量是隐式 const 的并且必须从编译时表达式初始化。

一般来说:如果可以用 constexpr 就用 constexpr(编译时已知的值),否则使用 const(运行时确定但之后不变的值),只有在变量确实需要改变时才使用非 const。

C++ 中的类型转换

C++ 有四种命名转换运算符。每种都有特定用途。永远不要使用 C 风格转换((int)x)── 它没有任何安全检查:

常见陷阱

窄化转换会静默地丢失数据:int x = 3.14; 丢弃了 .14,而不会发出警告(使用圆括号初始化时)。花括号初始化可以防止这种情况 ── int x{3.14}; 是编译错误。

其他危险的隐式转换包括:unsigned 到 signed(大值变为负数)、int 到 float(大整数会丢失精度)、以及 size_t(无符号)到 int(有符号)的混合 ── 当你将 std::vector::size() 的返回值(size_t)与 int 比较时,后者经常出现。

编译时开启 -Wall -Wextra -Wpedantic 标志可以捕获大多数这类问题。

  • 花括号初始化({})可以防止窄化转换 ── 在新代码中始终使用
  • 注意有符号/无符号混合 ── size_t 与 int 的比较是常见的错误来源
  • 编译时使用 -Wall -Wextra -Wpedantic 来捕获隐式转换警告
  • 永远不要使用 C 风格转换 ── 使用 static_cast、const_cast、reinterpret_cast(谨慎使用)
关键要点

类型别名提高了可读性,并使日后更改类型更加容易。在现代 C++ 中,优先使用 using 而非 typedef:

关键要点
  • 每个 C++ 表达式都有一个在编译时确定的类型 ── 类型决定了大小、有效值和有效操作
  • 当精确大小很重要时,使用 中的固定宽度类型(int32_t、uint64_t)
  • 优先使用花括号初始化 {} ── 它能防止危险的窄化转换
  • 使用 nullptr(而非 NULL 或 0)表示空指针,以避免重载歧义
  • auto 从初始化器推导类型;decltype 查询表达式的类型而不求值
  • 使用 constexpr 定义编译时常量,使用 'using' 定义类型别名

测验 — 检验你的知识

(15 经验值)

1. `int x{3.14};` 和 `int x = 3.14;` 之间有什么区别?

2. 为什么 `auto x = 42;` 推导出 `int` 而不是 `const int`?

3. 什么时候应该使用 `static_cast<int>(x)` 而不是 `(int)x`?