Skip to content

Latest commit

 

History

History
168 lines (153 loc) · 13.2 KB

第二章 变量和基本类型.md

File metadata and controls

168 lines (153 loc) · 13.2 KB

第二章 变量和基本类型

  1. 算术类型

    类型 含义 最小尺寸
    bool 布尔类型 未定义
    char 字符 8位
    wchar_t 宽字符 16位
    char16_t Unicode字符 16位
    char32_t Unicode字符 32位
    short 短整型 16位
    int 整型 16位
    long 长整型 32位
    long long 长整型 64位
    float 单精度浮点数 6位有效数字
    double 双精度浮点数 10位有效数字
    long double 扩展精度浮点数 10位有效数字
    • 从上表可以看出char16_t和char32_t分别最少有16位和32位,他们的值没有符号,属于无符号整形。可以用sizeof表达式得到编译器中他们的实际大小。char16_t是用来支持国际通用字符集(Universal Character Set,也叫Unicode)的UCS-2/UTF-16编码,char32_t是用来支持Unicode的UCS-4/UTF-32编码。和char一样,C++标准并没有规定wchar_t是有符号的还是无符号的,wchar_t至少有16位,他是用来支持执行宽字符集(execution wide character set)的。不严格的说,C++中的执行宽字符集所支持的字符需要囊括源代码中可能用到的所有字符。wchar_t和char16_t不同的是,执行宽字符集(即wchar_t)可以是Unicode,但也可以不是,是编译器自定义的行为。
    • 你也许好奇为什么这些命名和前面的int,char之类的不太一样,后面都跟着一个 _t,这其实是C语言的传统。C的程序员习惯把用户自定义的类型都加上 _t这个后缀,表示类型(type)。char16_t,char32_t和wchar_t 算是C语言标准规定的用户自定义类型,但他们只算“二等公民”。他们其实分别和某一个前面提到过的整型(“一等公民”)是同一种类型。C++中直接将这些类型提升为“一等公民”,他们在C++中和其他的整型属于不同的类型。但为了保持C的兼容,所以C++中也用 _t这个后缀。
    • 宽字符字面值常量wchar_t也可以文本输出,但输出前要定义正确的编码方式。以Windows为例,虽然wchar_t采用Unicode编码,Windows输出时采用的编码并不是Unicode,而是你在Windows设置中设置的本地化的语言编码。如果是中文,就是GB18030编码(GB18030兼容GB2312和GBK,这些编码和Unicode编码不同,即同一个汉字所映射的整数值不同),程序会自动将Unicode转换成本地化的语言编码。
    • char16_t和char32_t并不能像wchar_t一样直接用wcout作文本输出,他们是用来支持Unicode的各种编码,解码以及文档读取和存储等操作的,并不适合作文本输出(但他们可以用二进制形式输出)。宽字符的文本形式输出是和编译器内部的执行字符编码以及用户设置的本地化编码方式紧密相关的,所以用wchar_t更自然一些,因为wchar_t本身就是实现自定义的行为,不同的编译器可以用不同的执行字符编码以及采用不同的执行字符编码到用户设置的本地化编码的转换。
    • 字符型分为三种:char,signed char和unsigned char。 类型char和signed char 不一样:类型char实际上会表现为上述两种形式中的一种,具体是哪种由编译器决定。
    • 8 bit的signed char理论上应该可以表示-127至127区间的值,大多数现代计算机将实际的表示范围定为-128至127。
    • 如何选择类型:
      • 当明确知晓数值不可能能为负时,选用无符号类型。
      • 使用int执行整数运算。在实际应用中,short常常显得太小而long一般和int有一样的尺寸。如果你的数值超过了int的表示范围,选用long long。
      • 在算术表达式中不要使用char 或bool,只有在存放字符或布尔值时才使用它们。因为char在一些机器上是有符号的,而在另一些机器上又是无符号的,所以如果使用char进行运算特别容易出问题。如果你需要使用一个不大的整数,那么明确指定它的类型是unsigned char或者signed char。
      • 执行浮点数运算选用double,这是因为float通常精度不够而且双精度和单精度浮点数的计算代价相差无几。事实上,对于某些机器来说,双精度运算甚至比单精度还快。long double提供的精度在一般情况下是没有必要的,况且它带来的运行时消耗也不容忽视。
  • 类型转换

    bool b = 42;               //b为真
    int i = b;                 //i的值为1
    i = 3.14;                  //i的值为3
    double pi = i;             //pi的值为3.0
    unsigned char c = -1;      //假设char占8 bit,c的值为255
    signed char c = 256;       //假设char占8 bit,c的值是未定义的
    • 当在程序的某处使用了一种算术类型的值而其实所需的是另一种类型的值时,编译器同样会执行上述类型转换:

      int i = 42;
      if(i)         //if条件的值将为true
          i = 0;
    • 当一个算术表达式中既有无符号数又有int(或其它数值类型)值时,那个int值就会转换成无符号数。把int转换成无符号数的过程和把int直接赋给无符号亦是一样:

      unsigned u = 10;
      int i = -42;
      std::cout << i + i << std::endl;    //输出-84
      std::cout << u + i << std::endl;    //如果int占32位,输出4294967264
      • 把负数转换成无符号数类似于直接给无符号数赋一个负值,结果等于这个负数加上无符号数的模。
    • 当从无符号数中减去一个值时,不管这个值是不是无符号数,我们都必须确保结果不可能是一个负值:

      unsigned u1 = 42, u2 = 10;
      std::cout << u1 - u2 << std::endl;    //正确:输出32
      std::cout << u2 - u1 << std::endl;    //正确:不过,结果是取模后的值(如果int占32位,输出4294967264)
  • 字面值常量

    • 默认情况下,十进制字面值是带符号数,八进制和十六进制字面值既可能是带符号的也可能是无符号的。十进制字面值的类型是int、long和long long中尺寸最小的那个(例如,三者当中最小是int),当然前提是这种类型要能容纳下当前的值。八进制和十六进制字面值的类型是能容纳其数值的int、unsigned int、long、unsigned long、long long和unsigned long long中的尺寸最小者。类型short没有对应的字面值。
    • 严格来说,十进制字面值不会是负数。如果我们使用了一个形如-42的负十进制字面值,那个负号并不在字面值之内,它的作用仅仅是对字面值取负值而已。
    • 默认的,浮点型字面值是一个double。
  • 字符和字符串字面值

    • 编译器 在每个字符串的结尾处添加一个空字符('\0'),因此,字符串字面值的实际长度要比它的内容多1。
    • 如果反斜线\后面跟着的八进制数字超过3个,只有前3个数字与\构成转义序列。
  • 列表初始化

    • 作为C++11新标准的一部分,用花括号来初始化变量得到了全面应用。现在,无论是初始化对象还是某些时候为对象赋新值,都可以使用这样一组由花括号括起来的初始值了。
    • 当用于 内置类型 的变量时,这种初始化形式(列表初始化)有一个重要特点:如果我们使用列表初始化且初始值存在丢失信息的风险,则编译器将报错:
    long double ld = 3.1415926536;
    int a{ld}, b = {ld};    //错误:转换未执行,因为存在丢失信息的危险
    int c(ld), d = ld;      //正确:转换执行,且确实丢失了部分值
  • 默认初始化

    • 如果是内置类型的变量未被显式初始化,它的值由定义的位置决定。定义于任何函数体之外的变量被初始化为0。 一种例外情况是,定义在函数体内部的内置类型变量将不被初始化。
  • 变量声明和定义的关系

    • 我们能给由extern关键字标记的变量赋一个初始值,但是这么做也就抵消了extern的作用。extern语句如果包含初始值就是再是声明了,而变成定义了:
    extern double pi = 3.1416;    //定义
    • 在函数体内部,如果试图初始化一个由extern关键字标记的变量,将引发错误。
    • 变量能且只能被定义一次,但是可以被多次声明。
  • 嵌套的作用域

    • 作用域中一旦声明了某个名字,它所嵌套着的所有作用域中都能访问该名字。同时, 允许在内层作用域中重新定义外层作用域已有的名字
  • 引用

    • 引用只能绑定在对象上,而不能与字面值或某个表达式的计算结果绑定在一起。
  • 指针

    • 引用不是对象,没有实际地址,所以不能定义指向引用的指针。
  • 指向指针的引用

    int i = 42;
    int *p;        //p是一个int型指针
    int *&r = p;   //r是一个对指针p的引用
    
    r = &i;        //r引用了一个指针,因此给r赋值&i就是令p指向i(引用即别名)
    *r = 0;        //解引用r得到i,也就是p指向的对象,将i的值改为0
  • const限定符

    • 默认状态下,const对象仅在文件内有效。
    • 如果想在多个文件之间共享const对象,必须在变量定义前添加extern关键字。
    • const行为类似rust中的无mut绑定:
    const int ci;
    const int &r1 = ci;
    int &r2 = ci;        //错误:试图让一个非常量引用指向一个常量对象。
  • 字面值类型

    • constexpr只支持字面值类型声明。
    • 字面值类型包括算数类型、引用、指针、字面值常量类(7.5.6节,P267)、枚举类型(19.3节,P736)。
    • constexpr指针初始值必须是nullptr或0。
  • 处理类型

    • C++11规定类型别名的新方法:using SI = Sales_item;
    • 指针、常量和类型别名:
    typedef char *pstring;
    const pstring cstr = 0;    //cstr是指向char的常量指针,而不是指向常量char的指针,const修饰是的pstring,等价于char *const cstr = 0;
    • auto语句能在一条语句中声明多个变量,但所有变量的初始基本数据类型必须是一样的。
    • auto会忽略顶层const,保留底层const:
    int i = 0, &r = i;
    auto a = r;
    const int ci = i, &cr = ci;
    auto b = ci;        //b是一个整数(ci的顶层const特性被忽略掉了)
    auto c = cr;        //c是一个整数(cr是ci的别名,ci本身是一个顶层const)
    auto d = &i;        //d是一个整型指针(整数的地址就是指向整数的指针)
    auto e = &ci;       //e是一个指向整数常量的指针(对常量对象取地址是一种底层const)
      如果希望推断出的auto类型是一个顶层const,需要明确指出:
    
    const auto f = ci;
  • decltype

    • 如果decltype使用的表达式是一个变量,则decltype返回该变量的类型(包括顶层const和引用在内):
    const int ci = 0, &cj = ci;
    decltype(ci) x = 0;    //x的类型是const int
    decltype(cj) y = x;    //y的类型是const in&,y绑定至变量x
    decltype(cj) z;        //错误:z是一个引用,必须初始化
    • decltype和引用 *

      //decltype的结果可以是引用类型
      int i= 42, *p = &i, &r = i;
      decltype(r + 0) b;    //正确:加法的结果是int(此时decltype中的是值而不是变量),因此b是一个(未初始化的)int
      decltype(*p) c;       //错误:c是int&,必须初始化。解引用指针可以得到指针所指的对象,而且还能给空上对象赋值。因此decltype(*p)的结果类型就是int&,而非int。(因为解引用出来的并不是变量本身,而是值相同且能改变原变量值的东西,所以只能是别名,即引用。)
      • 如果给变量加上了一层或多层括号,编译器就会把它当成是一个表达式。变量是一种可以作为赋值语句左值的特殊表达式,所以这样的decltype就会得到引用类型:
      //decltype的表达式如果是加上了括号的变量,结果将是引用
      decltype((i)) d;    //错误:d是int&,必须初始化
      decltype(i) e;      //正确:e是一个(未初始化的)int
  • 自定义数据类型

    • 头文件即使(目前还)没有被包含在任何其他头文件中,也应该设置保护符。头文件保护符很简单,程序员只要习惯性地加上就可以了,没必要太在乎你的程序到底需不需要。