基本内置类型
类型 | 含义 | 最小尺寸 |
---|---|---|
bool | 布尔 | 未定义 |
char | 字符 | 8位 |
wchar_t | 宽字符 | 16位 |
char16_t | Unicode字符 | 16位 |
char32_t | Unicode字符 | 16位 |
short | 短整型 | 16位 |
int | 整型 | 16位 |
long | 长整型 | 32位 |
long long | 长整型 | 64位 |
float | 单精度浮点数 | 6位有效数字 |
double | 双精度浮点数 | 10位有效数字 |
long double | 扩展精度浮点数 | 10位有效数字 |
复合类型
引用
引用(reference)为对象起了另外一个名字,引用类型引用(refers to)另外一种类型,通过将声明符写成&d的形式来定义引用类型,其中d是声明的变量:
int ival = 1024;
int& refVal = ival; //refVal是ival的另一个名字
int& refVal2; //错误,引用类型必须初始化
一般在初始化变量时,初始值会被拷贝到新建的对象中。然而定义引用时,程序把引用和它的初始值绑定(bind)在一起,而不是将初始值拷贝给引用。一旦初始化完成,引用将和它的初始值对象一直绑定在一起。因为无法令引用重新绑定到另外一个对象,因为引用必须初始化。
引用只是为一个已经存在的对象另外取了一个名字。
指针
指针(pointer)是指向(point to)另外一种类型的复合类型。与引用类似,指针也实现了对其他对象的间接访问。然而指针与引用相比有有很多不同点。其一,指针本身就是一个对象,允许对指针赋值和拷贝,而且在指针的生命周期内它可以先后指向几个不同的对象。其二,指针无须在定义时赋初值。和其它内置类型一样,在块作用域内定义的指针如果没有被初始化,也将拥有一个不确定的值。
定义指针类型的方法将声明符写成*d的形式,其中d是变量名:
int* p = NULL; //也可以使用0
复合类型的声明
变量定义包括一个基本的数据类型(base type)和一组声明符。在同一行的定义域居中,虽然基本数据类型只有一个,但是声明符的形式却可以不同。也就是说,一条定义语句可能定义出不同类型的变量:
//i是一个int类型,p是一个int指针,r是一个int型的引用
int i = 1024, *p = &i, &r = i;
经常有一种观点误以为,类型修饰符(*和&)作用于本行定义的全部变量。造成这种错误看法的原因有很多,其中之一是我们可以把空格写在类型修饰符和变量名中间:
int* p = NULL;
感觉int*
可以修饰这条语句中的所有变量,其实恰恰相反,基本数据类型是int
,而不是int*
。*仅仅修饰了p而已,对该声明语句中的其它变量,它并不产生任何作用:
int* p1, p2; //p1是int类型的指针,p2是int
涉及指针或者引用的声明,一般那有两种写法。第一种把修饰符和变量标识符写在一起,这种形式强调变量具有复合类型:
int *p1, *p2; //p1和p2都是指向int的指针
另一种形式把修饰符和类型名写在一起,并且每条语句只定义一个变量,这种形式着重强调本次声明定义了一种复合类型:
int* p1;
int* p2;
我喜欢第二种写法!!!
指向指针的指针
一般来说,声明符中修饰符的个数并没有限制。当有多个修饰符连写在一起时,按照逻辑关系解释即可。以指针为例,指针是内存中的对象,像其它对象一样有自己的地址,因此允许把指针的地址放到一个一个指针中,通过*的个数可以区分指针的级别:
int ival = 1024;
int* pi = &ival; //pi指向int
int** ppi = π //ppi指向int类型的指针
引用指针的引用
引用本身不是一个对象,因此不能定义指向引用的指针。但指针是对象,所以可以有指针的引用:
int i = 42;
int* p = NULL; //p是指针
int*& r = p; //r是指针p的引用
r = &i; //r即p, 相当于p = &i
const修饰符
const和引用
可以把引用绑定到const对象上,就像绑定到其它对象一样,称为对常量的引用(reference to const)。与普通引用不同的是,对常量的引用不能用来修改它所绑定的对象:
const int ci = 1024;
const int& r1 = ci;
r1 = 42; //错误,r1所引用的对象是常量
int& r2 = ci; //错误,非常量引用不能绑定到常量对象上
经常把“对const的引用”简称为“常量引用”,这不是说引用是常量(因为不能更改引用所绑定的对象,在这个层面上所有的引用都是常量),而是说不能通过引用去改变所绑定的对象。为了对ci
进行引用,r1
必须有const
修饰符,否则就像和r2
一样了。const type&
这种形式主要使用在函数传参中。
下面的代码也是合法的:
int i = 42;
int& r1 = i;
const int& r2 = i;
r1 = 0;
r2 = 0; //错误
这里r2
同样有const
修饰符,所以不能通过r2
改变i
。
const和指针
指向常量的指针
和引用一样,也可以让指针指向常量或非常量。类似于常量引用,不能通过指向常量的指针(pointer to const)来改变指针所指向的对象。要想存放常量对象的地址,只能使用指向常量的指针:
const double pi = 3.14;
double* ptr = π //错误,pi是个常量,普通(非常量)指针不能指向常量
const double* cptr = π
*cpter = 42;
double dval = 3.14;
*cptr = &dval;
类似引用,因为pi
用const
修饰,所以cptr
必须有const
修饰,否则就和ptr
一样,可以通过指针来修改pi
的值了。指向常量的指针也可以指向非常量:
double dval = 3.14;
const double* cptr = &dval;
常量指针
因为指针是对象,所以可以把指针定义为const
的,即常量指针(const pointer),表示指针本身是常量。这种类型的指针必须初始化,而且一旦初始化完成,它的值(即存放在指针中的地址)就不能再改变了。把*
放在const
之前说明指针是一个常量,这样的写法表示指针本身的值不变而不是指针所指向的值不变:
int errNumb = 0;
int* const curErr = &errNumb; //写成int*的形式更容易理解,表示curErr是指针类型的常量
const double pi = 3.14;
const double* const pip = π
int*
的形式表示curErr
是指针类型的,const
表示它是常量,既然是某种类型的常量,那就不能修改,所以不能修改curErr
的值,它将一直指向errNumb
。
同样的,pip
是const double*
类型的,第二个const
表示pip
是常量,所以pip
是指向双精度浮点类型常量的常量指针。第二个const
表示pip
不能被修改,第一个const
表示pip
指向的值不能被修改。
顶层const
顶层const(top-level const)表示对象本身是常量,底层const(low-level const)表示所指向和引用的对象是常量,指针类型既可以是顶层const,也可以是底层const:
int i = 0;
int* const p1 = &i; //p1是顶层const,不能改变p1的值
const int ci = 42; //ci是顶层const,不能改变ci的值
const int* p2 = &ci; //p2是底层const,可以改变p2的值,但是不能通过p2修改所指向的对象
const int* const p3 = p2; //靠右的是顶层const,靠左的是底层const
const int& r = ci; //引用的const都是底层const
i = ci; //顶层const不影响拷贝
p2 = p3; //同上
int* p = p3; //错误,p3具有底层const,而p没有
p2 = &i; //int*可以转换为const int*
int& r = ci; //错误,int&不能引用const int
const int& r2 = i; //const int&可以绑定到const 上
实例分析
以const int* const& r
为例,r
为对指向常量的指针的常量引用。
这种复杂的复合类型一般采用从右往左的方式进行解读:
&r
表明r
是一个引用;
const& r
表明不能通过r
修改所引用的对象;
* const& r
表明r
是所引用的对象是一个指针;
int* const& r
表明指针指向int
类型;
const int* const& r
表明r
所引用的对象指向的是常量整型;
第二个const
表示r
是对常量的引用,即不能通过r
改变所引用的对象,第一个const
表示指针所指向的对象是常量,即不能通过r
来改变所指向的对象。简单来说r
是一个指针,第二个const
是顶层的,表示不能通过r
来改变指针的值,即r++
是错误的;第一个const
是底层的,表示不能通过r
来改变指针所指向的值,*r = 0
是错误的。