C++ 指针与引用
指针和引用的区别?
- 指针是实体,它指向⼀块内存,它的内容是所指内存的地址;引用是某块内存的别名
- 引用只能在定义时被初始化⼀次,之后不可变;指针可变
- 引用没有 const,指针有 const,const 的指针不可变;
- 引用不能为空,指针可以为空;
- “sizeof 引用”得到的是所指向的变量(对象)的大小,而“sizeof 指针”得到的是指针本身的大小,(32位下 即4个字节);
- 指针和引用的自增(++)运算意义不⼀样;
- 指针可以有多重指针,引用没有。
一、引用
C++ 11中新增了一种引用: 所谓的"右值引用(rvalue rederence)"
严格来说,当我们使用术语"引用"时,指的时"左值引用"
引用: 为对象起了另外一个名字,引用类型引用另一种类型,通过将声明写成&d
的形式来定义引用类型,其中d是声明的变量名。
int ival = 1024;
int &refVal = ival; // refVal指向ival
int &refVal2; //报错:引用必须被初始化
1. 引用必须被初始化
一般在初始化变量时,初始值会被拷贝到新建的对象中。然而定义引用时,程序把引用和它的初始值绑定(bind)在一起,而不是将初始值拷贝给引用。一旦初始化完成,引用将和它的初始值对象一直绑定在一起。因为无法令引用重新绑定到另外一个对象,因此引用必须初始化。
2. 引用即别名
引用即别名:引用并非对象,相反的,它知识为一个已经存在的对象所起的另外一个名字
任何对引用的操作,其实就是对其所指对象的操作。因为引用本身不是一个对象,所以不能定义引用的引用。
允许一条语句中定义多个引用,其中每个引用标识符都必须以符号&开头。
引用只能绑定在对象上,而不能与字面值或某个表达式的计算结果绑定在一起。
⭐ const引用
从右往左读,如
const int &r1
,&r1
表示是一个引用,const int
表明所指引用的类型
对常量的引用": 把引用绑定到const对象上,与普通引用不同的是,对常量的引用不能被用作修改它所绑定的对象:
const int ci = 1024;
const int &r1 = ci;//正确:引用及其对应的对象都是常量
r1 = 42; //错误:r1是对常量的引用
int &r2 = ci; //错误:试图让一个非常量引用 指向 一个常量对象
1.初始化
引用的类型必须与其所引用的对象的类型一致,但是又两个例外。
其一,在初始化常量引用时允许用任意表达式作为初始值,只要该表达式的结果能转换成引用的类型即可。尤其,允许为一个常量引用绑定非常量的对象、字面值,甚至是个别一般表达式。
int i = 42;
const int &r1 = i; //√
const int &r2 = 42; //√
const int &r3 = r1*2; //√
int &r4 = r1*2; // × ❗
要知道❗ int &r4 = r1 *2;
不对的原因,就需要知道当一个常量引用被绑定到了另外一种类型上时到底发生了什么
double dval = 3.14;
const int &ri = dval;
此处ri
引用了一个int
型的数。对ri的操作应该是整数运算,但dval
却是一个双精度浮点数。因此为了确保让ri
绑定一个整数,编译器把上述代码变成了如下形式:
const int temp = dval; //由双精度浮点数生成一个临时的整型常量
const int &ri = temp;//让ri绑定这个临时变量
这种情况下,ri
绑定了一个临时量对象。
临时量:当编译器需要一个空间来暂存表达式的求职结果时创建的一个未命名对象。
如果ri
不是常量,就会允许对ri赋值,这样就会改变ri所引用对象的值。注意,此时绑定的对象是一个临时量而非dval
。程序员既然让ri
引用dval
,就肯定想通过引用绑定到临时量上,C++也就把这种行为归为非法。
2. 对const引用可能引用一个并非const的对象
常量引用仅对引用可参与的操作做出限定,对于引用的对象本身是不是一个常量并未作限定。如果引用的对象是非常量,允许通过其他途径改变它的值,如i
可以通过r1
来更改
int i = 42;
int &r1 = i; // 引用r1绑定对象i
const int &r2 = i; // r2也绑定对象i,但是不允许通过r2修改i的值
r1 - 0;//r1并非常量,i的值修改为0
r2 = 0;//❌ r2是一个常量引用
二、指针
指针是指向另外一种类型的符合类型。与引用类似,指针也实现了对其他对象的间接访问。然而指针与引用相比又有许多不同点。其一,指针本身就是一个对象,允许对指针赋值和拷贝,而且在指针的生命周期内它可以先后指向几个不同的对象。其二,指针无须在定义时赋初值。和其它内置类型类型一样,在块作用域内定义的指针如果没有被初始化,也将拥有一个不确定的值。
定义指针: *d
, 其中d是变量名
double dp, *dp2; //dp2是指向double型对象的指针,dp是double型对象
指针存放某个对象的地址,想要获取该地址,需要使用取地址符&
int ival = 42;
int *p = &ival;//p存放变量ival的地址,或者说p是指向ival的指针
第二条语句把p定义为一个指向int的指针,随后初始化p令其指向名为ival的int对象。因为引用不是对象,没有实际地址,所以不能定义指向引用的指针。
除了两个例外情况,所指对象的类型与声明语句中指针的类型必须匹配。
double dval;
double *pd = &dval;//正确:初始值是double型对象的地址
double *pd2 = pd; //正确:初始值是指向double 对象的指针
int *pd = pd;//错误:指针pi的类型和pd的类型不匹配
pi = &dval; //错误:试图把double型对象的地址赋给int型指针
如果指针指向了一个对象,则允许使用解引用符*
来访问该对象
解引用操作仅适用于那些确实指向了某个对象的有效指针。
空指针
空指针(null pointer)不指向任何对象,在试图使用一个指针之前代码可以首先检查它是否为空。
得到空指针最直接的方法就是用字面值nullptr来初始化指针,这也是C++新标准刚引入的一种方法。nullptr是一种特殊类型的字面值,它可以被转换成任意其它的指针类型。
void
void*
是一种特殊的指针类型,可用于存放任意对象的地址。一个void*指针存放着一个地址,这一点和其它指针类似。不同的是,我们对该地址中到底是个声明类型的u第项并不了解。
利用void*指针能做的事特别有限:拿它和别的指针比较、作为函数的输入或输出,或者赋给另一个void * 指针。
int ival = 1024;
int *p1 = &ival; //pl指向一个int型的数
int **pp1 = &p1;//pp1指向一个int型的指针
指向指针的引用
引用本身不是一个对象,因此不能定义指向引用的指针。但指针式对象,所以存在对指针的引用:
int i = 42;
int *p; //p是一个int型指针
int *&r = p; //r是一个对指针p的引用
r = &i; //r引用了一个指针,因此给r赋值&i就是令p指向i
*r = 0; //解引用r得到i,也就是p指向的对象,将i的值改为0
⭐指针和const
指向常量的指针,不能用于改变其所指对象的值。要想存放常量对象的地址,只能使用指向常量的指针。
和常量引用一样,指向常量的指针没有规定其所指对象必须是一个常量。
指针是对象而引用不是,允许把指针本身定义为常量。常量指针必须初始化,而且一旦初始化完成,则它的值(也就是存放在指针中的那个地址)就不能再改变了。把*
放在const
之前用以说明指针是一个常量(不变的是指针本身的值,而非指向的那个值)。