1、基本内置类型
- 算术类型
 
(1) 整数类型:short、int、long、char、bool
(2) 浮点数类型:float、double- 空类型:void
 
1.1 内置类型的机器实现
对算术类型,C++标准只规定了各类型所占的最小尺寸,因此,在不同的机器上,同样的算术类型可能具有不同的尺寸。这一点和Java不同(Java规定了每种内置类型的具体尺寸,是平台无关的,这也是Java可移植性好的原因之一)。
1.2 选择类型的经验准则
和C语言一样,C++的设计准则之一也是尽可能地接近硬件,C++的算术类型必须满足各种硬件特质。一些选择类型的经验准则:
- 当明确知道数值不为负时,选择无符号类型
 - 使用 int 执行整数运算:实际应用中,short 往往太小;long 往往和 int 尺寸一样大。int 不够用时,使用 long long
 - 算术表达式中不要使用 char、bool:不同机器是编译器对 char 的处理可能不一样,有些是有符号的,有些是无符号的。如果必须使用 char,请明确指明其类型是 signed char 或者 unsigned char
 - 浮点数计算使用 double:float通常精度不够,而且单双精度浮点数的计算代价相差无几。long double 一般用不到。
 
1.3 基本算术类型的转换
- 非布尔类型的算术值赋给布尔类型时,非0为true,0为false;布尔值赋给非布尔类型时,true转为1,false转为0
 - 浮点数赋给整型时,只保留非小数部分;整型值转为浮点数时,小数部分记为0,如果该整数所占的空间超过浮点类型的容量,精度可能有损失。
 - 赋给无符号类型一个超出其范围的值时,结果是初始值对无符号类型表示数值总数取模后的余数。例如,8bit 大小的 unsigned char 可以表示0~255范围的值,如果赋给一个超出范围的值,结果是该值对256取模后的余数。因此,-1赋给8bit 大小的 unsigned char 的结果是255。注意: 切勿混用有符号类型和无符号类型。因为,如果表达式中既有带符号类型又有无符号类型,带符号类型会自动转换成无符号数,当带符号类型为负值时会出现异常结果。
 - 赋给有符号类型一个超出其范围的值时,结果是 未定义的(undefined) 。程序可能崩溃,也有可能产生垃圾数据。
 
1.4 字面值常量
    using namespace std;
    cout << "Hello World!" << endl;
    cout << 0.5 + 99 << endl;
上面代码片段中,"Hello World!" 和 0.5、99 就是所谓的字面值常量,指的是它们的值就是字面上呈现的样子,无法改变。
尽管我们没有为字面值常量明确指明其数据类型,但每个字面值常量都有对应的数据类型,否则计算机将不知道如何来存储这些字面值常量。
字面值常量的形式和值决定了它的数据类型:
- 十进制整型字面值的类型是在满足能容纳当前值的前提下,int、long、long long 中尺寸最小的那个
 - 浮点型字面值默认是 double 类型。可以在字面值后添加后缀来表示其它浮点类型
 - 单引号括起来的一个字符是 char 型字面值
 - 双引号括起来的0个或多个字符是 字符串型字面值 。字符串字面值的类型本质上是由常量字符构成的字符数组。编译器在每个字符串的结尾处添加一个空字符('\0') [注意它和字符0('0')的区别]。因此,字符串字面值的实际长度要比它的内容多1。
 - true 和 false 是布尔类型字面值
 - nullptr是指针字面值
 
2、变量
2.1 基本概念
变量的本质是一个具名的、可供程序操作的 存储空间。为了标识和操作这块存储空间,给它起个名字,这个名字叫做 变量名。
int a;    // 定义
extern int b;   // 声明
因此,上述代码中的 a 是变量名,它标识了一块存储空间,这块存储空间才是变量。但一般不说这么绕,直接说变量a。
- 变量定义 是一个 申请存储空间,并 将变量名和申请的存储空间关联起来 的过程。C++中,使用的变量必须先定义,否则没有空间存储数据。
 - 
变量声明 不申请存储空间,仅仅是告诉后面的程序,有这么一个某种类型的变量名。
- 如果所有程序都在同一个源文件中,我们只需要变量定义就够了,此时变量定义同时也起到了声明的作用;变量声明主要的作用在于支持C++的分离式编译,实现代码共享(一处定义,多处使用)。比如在一个头文件中定义了变量b,我们在其它文件中使用这个变量b时,就需要先声明,告诉程序我们要用的变量b是已经定义好的。此时,在变量名之前添加关键字 extern 即可。
 - 变量只能被定义一次,但可以被多次声明。
 
 - 
初始值是变量在定义时获得的一个特定值。此时我们说变量被 初始化 了。
- 
初始化和赋值是两个不同的概念。尽管很多时候我们使用 
=来初始化一个变量。强调:初始化不是赋值。初始化是创建变量时赋予其一个初始值;赋值是把变量的当前值擦除,然后用一个新值来替代。 - C++中初始化有多种不同形式,不仅仅是
=这一种。比如下面4条语句都能完成对变量的初始化:int t1 = 0; // =初始化 int t2 = {0}; // 列表初始化 int t3{0}; // 列表初始化 int t4(0); // 列表初始化 - 默认初始化:如果定义变量时没有指定初始值,则变量会被 
默认初始化,也就是被赋予一个默认值。内置类型在函数外(全局变量)被初始化为0;在函数内(局部变量)不被初始化,其值是未定义的。 
 - 
初始化和赋值是两个不同的概念。尽管很多时候我们使用 
 
3、复合类型
复合类型是在其它类型基础上定义的类型。
声明变量的语法: 基本数据类型   声明符列表
声明符为变量起了个名字(变量名),并指定该变量是和基本数据类型相关的某种类型。
因此,在基本内置类型的声明语句中,声明符就是变量名,此时变量的类型就是声明中的基本数据类型。而在复合类型的声明语句中,声明符更加复杂。下面介绍两种常用的复合类型,引用 和 指针。
3.1 引用(reference)类型
引用类型:引用另外一种类型。就像写论文时引用文献中的论据,一个引用类型的变量r(简称一个引用)引用另一个类型变量s中的内容,代表变量r的内容来源于另一个变量s。
本质上,引用就是为变量s起了一个别名。打个比方,就像一个人既有大名又有小名,但不论叫大名还是小名,最终指向的都是这个人。
强调:引用只是已有对象的别名。引用本身不创建对象。
引用必须初始化:
变量初始化时,初始值会被拷贝到新创建的对象中。而在声明引用时,由于引用并不创建对象,所以程序是把引用和它的初始值(即它引用的对象)绑定到一起,而不是拷贝初始值给引用。一旦初始化完成,引用将和它的初始值对象一直绑定在一起,无法重新绑定到另一个对象,因此引用必须初始化。
另:因为引用本身不是一个对象,所以不能定义引用的引用。
int ival = 1024;
int &refVal = ival;   //声明引用,并初始化。refVal引用ival
int &refVal2;         //报错 ‘refVal2’ declared as reference but not initialized:引用必须初始化
引用的定义:
- 为了和一般变量的定义区别开,引用的声明符在变量名之前加
 &前缀- 引用只能绑定在对象上,不能绑定在字面值和表达式的计算结果上
 
int &refVal1 = 10;     //错误:引用类型必须绑定在对象上
double val = 3.14;
int &refVal2 = val;    //错误:引用类型和绑定的对象的类型不匹配
3.2 指针(pointer)类型
与引用类似,指针也能实现对其他对象的间接访问。指针和引用的不同点在于:1.指针本身就是一个对象,因此可以对指针赋值和拷贝,从而指针可以先后指向不同的对象(非常量指针)。2.指针无须再定义时赋初值,在块作用域内如果指针未被初始化,也将拥有一个不确定的值。
指针的定义:
double *dp, dp2;
其中,声明符dp是一个整体*:dp是变量名,*说明这是一个指针,*只对dp生效;dp2是一个普通的double型变量。
在定义变量时,
*表示定义一个指针变量;而在访问指针指向的对象时,*表示解引用符。
空指针:
空指针(null pointer)不指向任何对象。生成空指针的方法:
// 下面3种方法本质上是等价的,都是给指针赋予一个初始值0
int *p1 = nullptr;      //使用字面值nullptr进行初始化
int *p2 = 0;            //将p2初始化为字面常量0
int *p3 = NULL;         //需要#include cstdlib
// 不能直接使用int变量给指针赋值,即使这个int变量的值为0
int zero = 0;
int *p4 = zero;         //报错:invalid conversion from ‘int’ to ‘int*’
void* 指针:
void*是一种特殊的指针类型,可存放任意对象的地址。
void*指针主要用于指针比较、函数的输入输出等。不能直接操作void*指针所指的对象,因为不知道这个对象的具体类型,从而也就无法确定能在这个对象上进行哪些操作。
3.3 理解复合类型的声明
变量定义:
基本数据类型 声明符列表
在同一条定义语句中,基本数据类型只有一个,但声明符的形式却可以不同,从而一条定义语句可以定义出不同类型的变量。
// 一条定义语句中,定义了int型变量a,int型指针b,int型引用c
int a = 1024, *b = &a, &c = a;
指向指针的指针:
int ival = 1024;
int *pi = &ival;    //pi指向一个int型数据
int **ppi = π    //ppi指向一个int型的指针
指向指针的引用:
指针本身是一个对象,因此可以定义对指针的引用。
#include <iostream>
using namespace std;
int main()
{
    int i = 42;
    int *p;
    //int &r = p;    //r是一个指向int型变量的引用,无法和指针绑定
    int *&r = p;     //r是一个对指针的引用
    cout << "r=" << r << ", p=" << p << endl;
    cout << "*r=" << *r << ", *p=" << *p << ", i=" << i << endl;
    cout << "---------------------------------\n"<< endl;
    r = &i;
    cout << "r=" << r << ", p=" << p << endl;
    cout << "*r=" << *r << ", *p=" << *p << ", i=" << i << endl;
    cout << "---------------------------------\n"<< endl;
    *r = 0;
    cout << "r=" << r << ", p=" << p << endl;
    cout << "*r=" << *r << ", *p=" << *p << ", i=" << i << endl;
    return 0;
}
结果是:
r=0x400c61, p=0x400c61
*r=1961723208, *p=1961723208, i=42
---------------------------------
r=0x7fff8bbdec74, p=0x7fff8bbdec74
*r=42, *p=42, i=42
---------------------------------
r=0x7fff8bbdec74, p=0x7fff8bbdec74
*r=0, *p=0, i=0
示例代码中的 r 是对指针 p 的引用,我们应该从右往左阅读 r 的定义:离变量名最近的符号对变量的类型有最直接的影响,因此&告诉我们 r 是一个引用。声明符的其余部分用以确定 r 引用的类型是什么,例子中的*说明 r 引用的是一个指针。最后,基本数据类型部分指出 r 引用的是一个int型指针。
指向引用的指针:
引用本身不是一个对象,因此不能定义指向引用的指针。
4、const限定符
const准确的含义是只读,即使用相同的值给一个const变量重新赋值也不行。const对象一旦创建后其值就无法改变,所以 const对象必须初始化。
const int i = 42;
const int j = i;
j = i;      //即使用相同的值重新赋值也会报错:assignment of read-only variable ‘j’
const int k;    //const变量没有初始化,报错:uninitialized const ‘k’
编译器在编译过程中,会把使用到const变量的地方都替换成const变量相应的值。const变量默认只在文件内生效,如果需要在多个文件之间共享const变量,必须在const变量定义之前添加extern关键字,其它文件内使用该变量时,在声明之前也添加extern关键字,用于表明该变量的定义在别的文件中。
4.1 const和引用
把引用绑定在 const 对象上,称之为对常量的引用(reference to const)。和普通引用不同,对常量的引用不能用于修改其绑定的对象。
    const int ci = 1024;
    const int &r1 = ci;
    r1 = 42;   //error: assignment of read-only reference ‘r1’
    int &r2 = ci; //error: binding ‘const int’ to reference of type ‘int&’ discards qualifiers
const 对象不能被赋值,所以也就不能通过引用去改变ci。用ci初始化r2报错的原因是,假如该初始化合法,那么可以通过r2来改变它引用对象的值,这显然是错误的。
对常量的引用(reference to const)在工作中也常简称为"常量引用"。严格地说,由于引用本身不是一个对象,我们无法让引用本身恒定不变,所以并不存在常量引用;C++中无法改变引用所绑定的对象,从这一层意义上看的话所有的引用又都算常量。无论引用的对象是不是常量,都不会影响引用和对象的绑定关系本身。
通常,引用的类型必须和其所引用对象的类型一致。但有两个例外:
其一:常量引用初始化时,允许任何可转化成引用的类型的表达式作为初始值。
    int i = 42;
    // 允许为常量引用绑定非常量对象、字面值、表达式 
    const int &r1 = i;    //绑定非常量对象
    const int &r2 = 42;   //绑定字面值
    const int &r3 = r1 * 2;  //绑定表达式
    
    int &r4 = r1 * 2;  //错误:r4是普通的非常量引用
    const int &r5 = 3.14;   //正确,double类型可转化为 const int 类型
4.2 const和指针
指向常量的指针(pointer to const):不能用于改变所指对象的值。要想保存常量对象的地址,只能使用指向常量的指针。但指向常量的对象可以指向一个非常量的对象。
    const double pi = 3.14;   //pi是常量,值不能改变
    double *ptr = π   //ptr是个普通指针,报错:invalid conversion from ‘const double*’ to ‘double*’
    const double *cptr = π  //正确
    *cptr = 3.1415926;   //cptr指向常量,报错:assignment of read-only location ‘* cptr’
    double dval = 10.24;
    cptr = &dval;  //正确,指向常量的指针可以指向一个非常量对象
const 指针:和引用不同,指针本身是对象,所以指针本身可以是常量,即常量指针(const pointer)。和一般 const 对象一样,常量指针也必须初始化,而且一旦初始化,值就不能再改变。
把
*放在const关键字之前,用以说明指针是一个常量,指明不变的是指针本身的值而非指向的那个值。
    int errNumb = 0;
    int *const curErr = &errNumb;  //curErr将一直指向errNumb
    const double pi = 3.14;
    const double *const pip = π //pip是一个指向常量对象的常量指针
如之前变量声明所述,声明的含义应该从右往左读:*const curErr是一个整体,可以理解为一个声明符。离变量名curErr最近的是符号是const,代表curErr本身是一个常量对象;其次是符号*,说明curErr是一个指针,合起来指明curErr是一个常量指针。最后,基本数据类型部分确定了curErr指向一个 int 对象。
按这样的套路来分析pip:声明符整体说明pip是一个常量指针;基本数据类型部分确定了pip指向一个 double 类型的常量。最终,我们确定,pip是一个指向常量的常量指针。
4.3 顶层/底层 const
- 顶层const:对象本身是常量,如const pointer
 - 底层const:指针、引用所指的对象是一个常量
 
指针本身是一个对象,指针既可以是顶层 const 也可以是底层 const;引用本身不是对象,因此引用只能是底层 const。
为什么要区分顶层/底层 const ?
在执行对象的拷贝操作时,顶层 const 不受什么影响(特指拷贝的对象是顶层 const,拷入的对象当然不能是顶层 const)。
另一方面,当拷贝对象时,拷入和拷出的对象必须具有相同的底层 const
资格,或两个对象的数据类型可以转换。通常,非 const 可以转换成 const,反之则不可。
    int i = 0;
    const int *const p = &i;  // 正确,非const可以转换成const
    int *p1 = p;     // 报错:p包含底层const的定义,而p1没有
    const int *p2 = p;   //正确,p和p2都是底层const,p的顶层const 部分不影响
    const int ci = 42;    //顶层const
    int &r = ci;          //非const不能绑定到const上
    const int &r2 = i;    //正确,非const可以转换成const
4.4 constexpr 和常量表达式
常量表达式(const expression):值不会改变并且在
编译过程中就能得到计算结果的表达式。
字面值是常量表达式,用常量表达式初始化的 const 对象也是常量表达式。
5、处理类型
5.1 类型别名
两种方式:
  (1). C风格方式
    typedef  旧类型名  新类型声明符([修饰符]类型名);
    如:
typedef double wages;   // wages 是 double 的同义词
typedef wages base, *p; // base 是 double 的同义词,p是 double * 的同义词
  (2). 新标准方式
    using 新类型 = 旧类型;
注意:
复合类型的别名的理解方式,如下代码:
typedef char *pstring;
const pstring cstr = 0;
const pstring *ps;
typedef将 pstring指定为指向 char 的指针类型的别名。因此,在后面使用过程中,pstring就应该始终被理解为指针类型。把类型别名机械地进行文本替换,这种理解方式是错误的!
类比const int a;,声明了int型的常量a,意味着a是int类型,它本身是不变的;同理,const pstring cstr;意味着cstr是pstring类型(指向char的指针),且本身是不变的,也就是说,这条语句指明cstr是一个指向char的常量指针。
类似地,ps是一个指向常量的指针,ps指向一个char型常量指针。
    typedef int *pstring;  //pstring是指针类型的别名
    const pstring *ps;    //ps是指向常量指针的指针
    int i = 42;
    int *p = &i;
    ps = &p;             //非const可以转换成const
    cout << "p=" << p << endl;           //p=0x7fff6fd55944
    cout << "*ps=" << *ps << endl;     //*ps=0x7fff6fd55944
    int j = 55;
    int *const q = &j;
    ps = &q;          //ps可以指向另一个对象,说明ps本身不是const的
    cout << "q=" << q << endl;         //q=0x7fff6fd55934
    cout << "*ps=" << *ps << endl;   //*ps=0x7fff6fd55934
    *ps = &i;   //报错:assignment of read-only location ‘* ps’
5.2 auto 类型说明符
C++新标准引入了auto类型说明符,可以让编译器通过初始值来推算变量的类型。因此,auto定义的变量必须有初始值。
auto会忽略顶层const (引用例外),保留底层const
auto i = 0, *p = &i;     //推算出i是整数,p是整型指针
auto sz = 0, pi = 3.14;   //错误,sz和pi类型不一致
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是指向整型常量的指针,对e而言,ci是底层const