第2.4章: 初始化

English

什么是对象?

在 C++ 中,对象是一块带有类型的存储区域。 每个对象都有一个类型,它决定了该对象能持有哪些值,以及哪些操作是合法的。 当我们声明一个对象时,我们为它预留了内存;当我们初始化它时,我们给它第一个值。 初始化确保对象从确定的状态开始,而不是含有不确定的数据。

初始化风格

C++ 提供了多种初始化风格。 在现代 C++ 中,我们应当优先使用 {} 的统一初始化,因为它一致,并能防止窄化转换。


#include <fast_io.h>
#include <cstddef>
#include <cstdint>
#include <stdfloat>

int main()
{
    using namespace ::fast_io::iomnp;

    // 1. Copy initialization with =
    ::std::size_t a = 42;            // object a initialized to 42
    println("a = ", a);              // prints 42

    // 2. Direct initialization with ()
    ::std::int_least32_t b(100);     // object b initialized to 100
    println("b = ", b);              // prints 100

    // 3. Uniform initialization with {}
    ::std::float64_t pi{3.14159};    // object pi initialized to 3.14159
    println("pi = ", pi);            // prints 3.14159

    // 4. Zero initialization with {}
    ::std::uint_least16_t u{};       // object u initialized to 0
    println("u = ", u);              // prints 0

    // 5. Copy initialization from another object
    ::std::size_t c = a;             // object c initialized with value of a
    println("c = ", c);              // prints 42

    // 6. Multiple objects initialized together
    ::std::size_t m = 1, n = 2, o = 3;
    println("m = ", m, " n = ", n, " o = ", o); // prints 1 2 3

    // 7. Boolean initialization
    bool flag = true;                // object flag initialized to true
    println("flag = ", flag);        // prints 1
    println("flag (boolalpha) = ", boolalpha(flag)); // prints true

    // 8. Typed integer literals with suffixes
    ::std::size_t sz = 30zu;         // literal 30 as ::std::size_t
    println("sz = ", sz);            // prints 30

    unsigned int ui = 42u;           // literal 42 as unsigned int
    println("ui = ", ui);            // prints 42

    long long big = 1000000ll;       // literal as long long
    println("big = ", big);          // prints 1000000

    // 9. Floating-point literals with suffixes
    float f = 3.14f;                 // literal as float
    double d = 3.14;                 // literal as double (default)
    long double ld = 3.14L;          // literal as long double

    println("f = ", f);              // prints 3.14
    println("d = ", d);              // prints 3.14
    println("ld = ", ld);            // prints 3.14
}

拷贝初始化 vs 直接初始化

乍看之下,拷贝初始化(=)与直接初始化(())很相似。 二者都给对象赋初值,但它们并不相同:


#include <fast_io.h>
#include <string>

int main()
{
    using namespace ::fast_io::iomnp;

    // Copy initialization allows implicit conversions
    ::std::string s1 = "Hello";   // const char* converted to std::string
    println("s1 = ", s1);

    // Direct initialization calls constructor directly
    ::std::string s2("World");
    println("s2 = ", s2);

    // Narrowing conversions
    int i1 = 3.5;   // copy initialization, allowed (truncated to 3)
    // int i2(3.5); // direct initialization, error: narrowing not allowed
}

要点:拷贝初始化更宽松,可能执行隐式转换。 直接初始化更严格。 在现代 C++ 中,我们应当优先使用 {} 的统一初始化,因为它一致,并能防止窄化转换。

使用宏表示带类型的常量

除了 zu 或 ll 这类后缀,C++ 还在 <cinttypes> 中提供了宏,用于创建精确整数类型的常量。 当你想保证字面量在跨平台时具有确定的类型时,这些宏很有用。


#include <fast_io.h>
#include <cinttypes>
#include <cstdint>

int main()
{
    using namespace ::fast_io::iomnp;

    // Constant of type ::std::uint_least32_t
    ::std::uint_least32_t x = UINT_LEAST32_C(20);
    println("x = ", x); // prints 20

    // Constant of type ::std::int_least64_t
    ::std::int_least64_t y = INT_LEAST64_C(1000000);
    println("y = ", y); // prints 1000000
}

这些宏会展开为类型正确的字面量。 例如,UINT_LEAST32_C(20) 保证该常量是 ::std::uint_least32_t 类型,与底层平台无关。

各种初始化风格对比

风格 语法 示例 说明
拷贝初始化 = int x = 5; 传统风格,可能允许隐式转换。
直接初始化 () int x(5); 构造函数风格,比拷贝更严格。
统一初始化 {} int x{5}; 现代 C++ 中首选,可防止窄化转换。
零初始化 {} int x{}; 初始化为 0(或等价物)。
从对象拷贝 = other int y = x; 用另一个对象的值初始化。
多个对象 int a=1, b=2; int a=1, b=2; 紧凑地声明多个对象。
布尔 = true bool flag = true; 初始化为 true/false。
带类型的整数字面量 zu, u, l, ll auto n = 30zu; 后缀指定确切的整数类型。
浮点字面量 f, L float f = 3.14f; 后缀指定 float 或 long double。
带类型的常量宏 UINT_LEAST32_C(...) auto x = UINT_LEAST32_C(20); 来自 <cinttypes> 的宏可跨平台保证确切的整数类型。