C++ Primer 笔记四 初识指针和引用
引用
引用是某个已存在对象的别名,实现了对其他对象的间接访问。引用本身不是对象。其定义如下:
int ival = 1024;
int &refVal = ival;
一般初始化变量时,初始值被拷贝到新建对象中。而定义引用时,程序把引用和它的初始值绑定在一起,一旦初始化完成,将无法把引用重新绑定到另一个对象上,因此引用必须被初始化。
对引用进行操作,实际上是对引用所绑定的对象进行操作。
refVal = 2048; // 实际上是把2048赋值给refVal所绑定的对象ival
// 以引用作为初始值,实际上是把引用绑定的对象作为初始值
int ival2 = refVal; // 用ival初始化ival2
int &refVal2 = refVal; // refVal2绑定到ival上
因为引用本身不是对象,所以不能定义引用的引用,且引用只能绑定到对象上,而不能与某个字面值表达式的结果绑定在一起。引用的类型要和与之绑定的对象严格匹配。
double dval = 3.14;
// 下面的操作将引发错误
// error: invalid initialization of non-const reference of type 'int&' from an rvalue of tye 'int'
int &refVal3 = dval;
double &dRef = &dval;
dRef = refVal2; // 实际上是将ival的值赋值给dval,执行隐式转化
指针
指针是指向另外一种类型的复合类型,同样实现了对其他对象的间接访问。不同于引用的是,指针本身就是一个对象。指针的定义如下:
int ival = 44;
int *p1, *p2 = &ival; // 使用取地址符 & 获取对象的地址
因为指针本身就是一个对象,所以允许对指针赋值和拷贝,且在指针的生命周期内,指针可以先后指向几个不同的对象,对指针进行赋值就令指针指向了一个新的对象。并且,指针无需在定义时赋初值,块作用域内定义的指针若没有被初始化,将拥有一个不确定的值。
指针的值是一个地址,且应该是下面4种状态之一:
- 指向一个对象;
- 指向紧邻对象所占空间的下一个位置;
- 空指针,意味着指针没有指向任何对象;
- 不属于上面3种情况,为无效指针;
试图拷贝或者访问一个无效指针都将引发错误,且编译器并不检查此类错误。建议初始化所有的指针,未确定指向对象时应该初始化为nullptr
或者0
.
因为引用本身不是对象,没有实际地址,因此不能定义引用的指针。但指针是对象,所有存在对指针的引用。
指针的类型和它所指的对象严格匹配。把int
变量直接赋值给指针是错误的操作,即便int的值恰好为0。
int *iptr = &ival; // 正确,iptr指向ival
iptr = &ival2; // 现在iptr指向ival2
// 下面的操作将引发错误
// error: cannot convert 'double' to 'int*' in initialization.
int *iptr1 = &dval;
ival2 = 0;
// 下面的操作将引发错误
// error: invalid conversion from 'int' to 'int*'
int *iptr2 = ival2;
//error: invalid conversion from 'int' to 'int*'
iptr2 = 32;
// OK
iptr2 = 0;
使用别名可以直接访问引用所绑定的对象,而要访问指针指向的对象,必须使用解引用符(*
):
std::cout << *iptr << std::endl; // 输出iptr指向对象ival2的值
std::cout << iptr << std::endl; // 输出iptr指向对象ival2的地址
*iptr = 32; // 把32赋值给iptr指向的对象ival2
空指针表示不指向任何对象,可以使用下面的方法来生成空指针:
int *ptr = 0; // 直接将指针初始化为字面常量0
int *ptr1 = NULL; // 等价于 int *iptr1 = 0;
// NULL为预处理变量,定义在cstdlib头文件中,NULL的值为0
int *ptr2 = nullptr; // C++11引入的方法,nullptr是一个指针字面值
如果指针拥有一个合法值,则可以将指针用在条件表示式中。任何非0指针对应的条件值都为true
。
int ival = 1024;
int ival1 = 2048;
int *iptr = 0;
int *iptr1 = &ival;
if (iptr) { // iptr是空指针,因此条件的值为false
// TODO
}
if (iptr1) { // iptr2指向ival,因此条件的值为true
// TODO
}
对两个指针进行比较,若存放地址相同,则它们相等;反之不相等。
if (iptr == iptr1) { // 结果为false
// TODO
}
int *iptr2 = 0;
if (iptr1 == iptr2) { // 结果为true
// TODO
}
两个指针存放的地址相同,有下面三种情况:
- 它们都为空;
- 两个指针指向相同的对象;
- 两个指针指向同一个对象的下一个地址。
void*
指针是一类特殊的指针,可存放任意对象的地址,其地址的对象的类型是不确定的,因此不能直接操作void*
所指向的对象。void*
所能进行的操作有:
- 和别的指针进行比较;
- 作为函数的输入或输出;
- 赋值给另外一个
void
指针。
double dpi = 3.14;
double *dptr = &dpi;
void *vptr = &dpi; // dpi可以是任意类型的对象
vptr = dptr; // vptr可以存放任意类型的指针
需要注意的是,void
类型既无操作也无值,不能定义一个void
类型的变量,因此也不可以有void
类型的引用。