复合类型

我们前面看到的都是 基础类型(base type);而复合类型是构建在基础类型之上的,其中典型的是引用(reference)和指针(pointer)。

引用

新的标准引入了一种新的应用类型,叫 右值引用(rvalue reference)。严格的说,我们常见的引用都是 左值引用(lvalue reference)。

引用就是对象的别名

当我们初始化一个变量时,初始化的值会被复制到我们正在创建的对象;当我们定义一个引用时,我们不是复制初始化的值,而是绑定(bind)到初始化器的引用。一旦初始化,引用就始终绑定最初的对象。不能再次绑定到其他对象。因为我们不能再次绑定一个引用,所以引用必须初始化。

int ival = 1024;
int &ref = ival; // ref is another name for (refers to) ival.
int &ref2; // error, must be initialized
int &ref3 = ref; // ref3 is bound to the object which ref is bound, i.e. to ival

引用不是一个对象,只是一个已经存在的对象的别名。一旦引用被定义,所有在引用上的操作都是实际上在对引用所绑定对象进行操作。 因为引用不是对象,所以不能定义对引用的引用。(a reference to reference)。

引用的定义

如果在一个定义里定义多个引用;那每个标识符之前都得有&

int i = 1024, i2 = 2; 
int &r = i, r2 = i2; // r is reference to int; r2 is int
int i3 = i, r3 = &i3; // i3 is int; r3 is reference to int
int &r4 = i3, &r5 = i2; // both are reference to int

除了两个特殊情况(以后再讲!),引用的类型和该引用指向对象的类型必须完全匹配。并且,引用不能绑定到字面常量,只能绑定到对象,也不能是表达式。

int i = 9;
int &r = i + i; // error
int &r2 = 8; // error
double &r3 = i; // error

指针

指针是指向(point to)另一种类型的复合类型。类似引用,指针也是用作间接访问对象的方式。

  • 不同的是,指针就是对象,可以被赋值,复制。
  • 不同的是,指针没必要定义时去初始化,可以是未定义(undefined)的值。
  • 不同的是,一个指针在其生命周期内可以指向多个不同对象。

同样,如果一行定义多个指针,*必须重复。

接受对象的地址

指针持有另一个对象的地址。我们可以使用取地址符(&)来获取地址。

int ival = 42;
int *p = &ival; // p holds the address of ival; p is a pointer to ival

因为引用并不是对象,所以我们不能定义指向引用的指针

int i = 9;
int &r = i;
int *p = &r; // 指向的是i,并不是r。&r takes the address of the object refered to by r。

除了两个特殊情况(以后再讲!),指针的类型和该指向指向对象的类型必须完全匹配

指针的值

指针的值(就是地址),可以是下面四种情况之一:

  • 指向一个对象
  • 指向一个对象末尾的位置
  • 空指针,意味着不绑定任何对象
  • 不合法;不是前三种的值都是不合法

使用指针访问对象

使用解引用(dereference)符号(即 )来访问对象。

int ival = 42;
int *p = &ival;
cout << *p; // * yields object to which p points

我们也可以通过对解引用的结果赋值来对改对象赋值,如 *p = 0

一些符号可能有多种含义: 像 &,*要结合上下文才知道其含义。

空指针

空指针(null pointer)就是不指向任何对象。我们在使用指针前,一般需要检查是否为null。

下面的方法都能得到空指针:

int *p1 = nullptr;
int *p2 = 0;
int *p3 = NULL; // must #include cstdlib,可能你的编译器某个头文件已经包含它

其中,使用nullptr是C++ 11引入的,也是我们推荐的方法。而NULL只是个预处理变量(preprocessor variable), 值就是0,一般在老程序才会出现。

不能赋值一个int类型给指针,即使它可能碰巧是0

int zero = 0;
pi = zero; // error

建议初始化所以指针;即使目前没有对象与之绑定,就用nullptr来初始化。

指针与赋值

再强调一次,引用不是对象,因此,一旦定义,引用就不能去指向别的不同对象。

然而,指针是没有上面约束的,我们可以对其赋值。

其他指针操作

在条件语句中,如果指针是0,就是false;否则就是true。(注意:未定义的指针的行为也是未定义的)

指针可以执行==!=比较,甚至算术操作。

void* 指针

这是一种特殊的指针,可以接受任意对象的地址。

我们可以比较它,在函数中返回或传递它;但是,不能使用对它指向的对象直接操作,因为我们不知道该对象的 类型(类型决定操作)。

我们一般使用void*来用内存处理内存,而不是来访问其指向的对象。

理解复合类型的声明

单行语句可以定义多个不同类型的变量:

// i is an int, p is a pointer, r is a reference
int i = 1024, *p = &i, &r = i;

定义多个变量

一个常见的误区就是认为类型修饰符(*,&)会应用到单行定义的所有变量;

int* p; // legal, but misleading

记住,上面声明的基本类型(base type of this declaration)是int,不是int*是修饰p的。

指向指针的指针

**表示指向指针的指针;一般三个或更多没有意义。

int ival = 1024;
int *pi = &ival;
int **ppi = &pi; // ppi points to a pointer to an int

对于这种情况,如果要访问其底层(underlying)的对象,就需要使用两次解引用符号(**)。

对指针的引用

再次强调,没有对引用的指针;因为指针是对象,所以可以定义对指针的引用

int i = 42;
int *p;
int *&r = p; // is a reference to pointer p
r = &i; // &i to r makes p points to i
*r = 0; // also changes i to 0

我们来看r的定义:从右到左读。和变量名最近的标识对变量类型作用最大。因此,r是个引用。 余下的声明决定了r指向的类型;接下来的表明r指向一个指针类型。最后,是声明的基本类型, 表明r是对int指针的引用。