CPP Primer - 变量与基本类型

原始内置类型

可运算类型

  • 最小的可寻址内存块被称为“字节”(byte),基本存储单元(通常是几个字节)被称为“”(word)。
  • C++虽然一共有3种字符类型(char, signed char, unsigned char),但只有2种表示方式:signedunsignedchar类型使用其中一种,具体是哪一种取决于编译器。
  • 当需要使用一个小整数时,显式地使用signed charunsigned char

类型转换

  • 如果向unsiged类型的变量a赋超过其表示范围的值b,结果是b % 变量a类型的存储范围;如果向siged类型的变量a赋超过其表示范围的值b,结果是未定义的
  • 如果在算术表达式中同时使用unsigned类型和singed类型,singed类型将被自动转换为unsinged类型。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    unsigned u = 10, u2 = 42;
    std::cout << u2 - u << std::endl; // 32
    std::cout << u - u2 << std::endl; // 4294967264

    int i = 10, i2 = 42;
    std::cout << i2 - i << std::endl; // 32
    std::cout << i - i2 << std::endl; // -32

    std::cout << i - u << std::endl; // 0
    std::cout << u - i << std::endl; // 0

字面量

  • 默认情况下,十进制字面量是signed类型,而八进制和十六进制字面量可以是signed类型也可以是unsigned类型。
  • 虽然整数字面量存储于signed类型,但十进制字面量的值永远不会是一个负数。例如,-42中,负号不是字面量的一部分。

  • 默认情况下,浮点型字面量的类型是double

  • 两个仅由空格、tab或者换行符分隔的字符串字面量会被拼接为一个字面量。

变量

变量定义

  • 初始化和赋值在C++中是两个不同的操作。

  • 可以用以下4种方法定义一个名为units_sold的变量,并将其初始化为0,其中使用{}的第2、3种称为列初始化(list initialization)。

    1
    2
    3
    4
    int units_sold = 0;
    int units_sold = {0};
    int units_sold{0};
    int units_sold(0);
  • 使用会产生信息丢失的值进行列初始化是非法的。

    1
    2
    3
    long double ld = 3.14159265356;
    int a{ld}, b = {ld}; //error: narrowing conversion required
    int c(ld), d = ld; //ok: but value will be truncated
  • 在函数体外定义的变量会被初始化为0,在函数内定义的变量是未初始化的(uninitialized)。

变量声明与定义

  • 为了支持分离编译,C++将声明与定义区分开。声明(declaration)使程序知道变量的存在,一个文件若想使用在别处定义的变量,则需在文件中包含对该变量的声明;定义(definition)创建相应的变量。

  • 使用extern关键字但不提供显式初始化则可获得一个声明而非定义。

    1
    2
    extern int i;    // declares but does not define i
    int j; // declares and define j
  • 任何包含显式初始化的声明都是一个定义,带有初始化的extern声明是一个定义。

    1
    extern double pi = 3.1416;    //definition

命名空间

  • 全局命名空间没有名字,因此,可用::val_name访问全局命名空间中的名字。

复合类型

引用

  • 引用的类型与引用所指向对象的类型必须匹配(有2个例外)。

指针

  • 指针的类型与指针所指向对象的类型必须匹配(有2个例外)。
  • int类型的变量赋给指针是非法的,即使该变量的值恰好为0。

  • 不使用一个void *对其指向的对象进行操作——因为我们无法知道对象的类型。

理解复合类型说明

  • 理解一个引用r最好的方式是,从右到左地读它的定义。
    1
    2
    3
    int i = 42;
    int *p; // p is a pointer to int
    int *&r = p; // r is a reference to the pointer p

const

  • 当我们在多个文件中用相同的名字定义一个const时,这就像在每一个文件中定义了一个不同的变量。

const修饰的引用

  • “引用的类型与引用所指向对象的类型必须匹配”的第一个例外是:我们可以用任何可转换为引用的类型的表达式来初始化一个const修饰的引用。
    1
    2
    3
    4
    5
    6
    7
    8
    int i;
    const int &r1 = i; // we can bind a const int& to a plain int object
    const int &r2 = 42; // ok: r2 is a reference to const
    const int &r3 = r1 * 2; // ok: r3 is a reference to const
    int &r4 = r * 2; // error: r4 is a plain, non const reference

    double dval = 3.14;
    const int &ri = dval; // ok

指针与const

  • 一个const变量的地址只能存储在用const修饰的指针中。
    1
    2
    3
    4
    const double pi = 3.14;    // pi is const; its value may not be changed
    double *ptr = &pi; // error: ptr is a plain pointer
    const double *cptr = &pi; // ok: cptr may point to a double that is const
    *cptr = 42; // error: cannot assign to *cptr
  • “指针的类型与指针所指向对象的类型必须匹配”的第一个例外是:我们可以用一个const修饰的指针指向一个非const的对象。
    1
    2
    double dval = 3.14;         // dval is a double; its value can be changed
    const double *cptr = &dval; // ok: but can't change dval through cptr

Top-Level const

  • Top-level const是指指针本身是constlow-level const是指指针可指向一个const对象。

constexpr与常量表达式

  • 常量表达式是值不能改变,并且值能够在编译时确定的表达式。
  • 一个由常量表达式初始化的const对象也是一个常量表达式。

  • 可以用constexpr函数初始化一个constexpr变量。

    1
    2
    3
    4
    const int max_files = 20;        // max_files is a constant expression
    const int limit = max_files + 1; // limit is a constant expression
    int staff_size = 27; // staff_size is not a constant expression
    const int sz = get_size(); // sz is not a constant expression
  • 可以在constexpr中使用的类型称为“字面类型”。

  • 在函数外定义的变量的地址是常量表达式,可用于初始化constexpr指针。
  • 当我们在一个指针声明中使用constexpr时,constexpr修饰符应用于指针本身,而不是指针所指向的变量。
    1
    2
    const int *p = nullptr;        // p is a pointer to a const int
    constexpr int *q = nullptr; // q is a const pointer to int

constexpr引入的是top-level const

类型处理

类型别名

  • 使用typedef定义类型别名:

    1
    2
    typedef double wages;    // wages is a synonym for double
    typedef wages base, *p; // base is a synonym for double, p for double*
  • 使用类型声名定义类型别名:

    1
    using SI = Sales_item;   // SI is a synonym for Sales_item

decltype

  • decltype是唯一处理引用本身,而不是引用指向对象的语法。

    1
    2
    3
    4
    const int ci = 0, &cj = ci;
    decltype(ci) x = 0; // x has type const int
    decltype(cj) y = x; // y has type const int& and is bound to x
    decltype(cj) z; // error: z is a reference and must be initialized
  • 通常,decltype对产生左值的表达式返回一个引用。

    1
    2
    3
    4
    // decltype of an expression can be a reference type
    int i = 42, *p = &i, &r = i;
    decltype(r + 0) b; // ok: addition yields an int; b is an (uninitialized) int
    decltype(*p) c; // error: c is int& and must be initialized
  • 如果对一个变量名加上一组或多组圆括号,编译器会将该操作数当作表达式,decltype会对这样的表达式产生一个引用。

    1
    2
    3
    4
    // decltype of a parenthesized variable is always a reference
    int i;
    decltype((i)) d; // error: d is int& and must be initialized
    decltype(i) e; // ok: e is an (uninitialized) int

定义自己的数据结构

  • 没有初始化器的类成员会被默认初始化。