复合类型
我们前面看到的都是 基础类型(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 = π // 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
指针的引用。