1.指针与引用

1.1指针和引用的区别

1.指针有自己的一块空间,而引用只是一个别名;

2.使用sizeof看一个指针的大小是4或8,而引用则是被引用对象的大小;

3.指针可以被初始化为NULL,而引用必须被初始化且必须是一个已有对象的引用;

4.作为参数传递时,指针需要被解引用才可以对对象进行操作,而直接对引用的修改都会改变引用所指向的对象;

5.可以有const指针,但是没有const引用;

6.指针在使用中可以指向其它对象,但是引用只能是一个对象的引用,不能被改变;

7.指针可以有多级指针(**p),而引用只有一级;

8.指针和引用使用++运算符的意义不一样;

9.如果返回动态内存分配的对象或者内存,必须使用指针,引用可能引起内存泄露。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <iostream>
using namespace std;

int main(int argc, char const *argv[])
{
int i = 3;
int *pt = &i, &ri = i;
cout << pt << endl; //pt = 0x7ffee43d269c
cout << ri << endl; //ri = 3

cout << sizeof(pt) << endl; // sizeof(pt) = 8
cout << sizeof(ri) << endl; // sizeof(ri) = 4

int *ptt; // 合法,但ptt为野指针,需要小心使用
int *pttt = NULL;
//int &rii; // 错误:引用必须初始化
//int &riii = 1; // 错误:非const引用不能指向const
//上面语句实际的操作是分两步
//const int tmp = 1;
//int &riii = tmp; // 这一步错误

ri++;
cout << i << endl; // i = 4
(*pt)++;
cout << i << endl; // i = 5
//*pt++;
//cout << pt << endl; // pt = 0x7ffee43d26a0
return 0;
}

2.引用

2.1引用概念

引用(reference)为对象起了另外一个名字,引用类型引用(refers to)另外一种类型。通过将声明符写成&d的形式来定义引用类型,其中d是声明的变量名:

1
2
3
int ival = 1024;
int &refVal = ival; // refVal指向ival(是ival的另一个名字)
int &refVal2; // 报错:引用必须被初始化

一般在初始化话变量时,初始值会被拷贝到新建的对象中。然而定义引用时,程序吧引用和它的初始值绑定(bind)在一起,而不是将初始值拷贝给引用。一旦初始化完成,引用将和它的初始值对象一直绑定在一起。因为无法令引用重新绑定到另外一个对象,因此引用必须初始化。

引用即别名

引用并非对象,相反的,它只是为一个已经存在的对象所起的另外一个名字。

定义一个引用之后,对其进行的所有操作都是在与之绑定的对象上进行的:

1
2
refVal = 2;	// 把2赋给refVal指向的对象,此处即是赋给了ival
int ii = refVal; // 与ii = ival执行结果一样

为引用赋值,实际上是把值赋给了与引用绑定的对象。获取引用的值,实际上是获取了与引用绑定的对象的值。同理,以引用作为初始值,实际上是以与引用绑定的对象作为初始值:

1
2
3
4
// valid:refVal3绑定到了那个与refVal绑定的对象上,这里就是绑定到ival上
int &refVal3 = refVal;
// 利用与refVal绑定的对象的值初始化变量i
int i = refVal; //valid:i被初始化为ival的值

因为引用本事不是一个对象,所以不能定义引用的引用。

2.2引用的定义

允许在一条语句中定义多个引用,其中每个引用标识符都必须以符号&开头:

1
2
3
4
5
6
7
8
9
10
11
int i = 1024, i2 = 2048;	// i和i2都是int
int &r = i, r2 = i2; // r是一个引用,与i绑定在一起,r2是int
int i3 = 1024, &ri = i3; // i3是int,ri是一个引用,与i3绑定在一起
int &r3 = i3, &r4 = i2; // r3和r4都是引用

int &refVal4 = 10; // 错误:引用类型初始值必须是一个对象
double dval = 3.14;
int &refVal5 = dval; // 错误:此处引用类型的初始值必须是int型对象

const int &refVal4 = 10; // valid
const int &refVal5 = dval; // valid

2.3左值引用

严格来说,当我们使用术语“引用(reference)”时,指的其实就是“左值引用(lvalue reference)”。

常规引用,一般表示对象的身份

2.4右值引用

敬请期待。。。

3.指针

3.1指针概念

指针(pointer)是“指向(point to)”另外一种类型的复合类型。

与引用类似,指针也实现了对其他对象的间接访问。然而指针与引用相比又有很多不同点。

  1. 指针本身就是一个对象,允许对指针赋值和拷贝,而且在指针的生命周期内它可以先后指向几个不同的对象。
  2. 指针无须在定义时赋值。和其他内置类型一样,在块作用域内定义的指针如果没有被初始化,也将拥有一个不确定的值。

3.2指针值

指针的值(即地址)应该属于下列4种状态之一:

  1. 指向一个对象。
  2. 指向紧邻对象所占空间的下一个位置。
  3. 空指针,意味着指针没有指向任何对象。
  4. 无效指针,也就是上述情况之外的其他值。

试图拷贝或以其他方式访问无效指针的值都将引发错误。编译器并不负责检查此类错误,这一点和试图使用未经初始化的变量是一样的。访问无效指针的后果无法估计,因此程序猿必须清楚任意给定的指针是否有效。

尽管第2、3种形式的指针是有效的,但其使用同样受到限制。显然这些指针没有指向任何具体对象,所以试图访问此类指针(假定的)对象的行为不被允许。如果这样做了,后果也无法预计。

3.3空指针

空指针(null pointer)不指向任何对象,在试图使用一个指针之前代码可以首先检查它是否为空。

1
2
3
4
5
6
7
int *p1 = nullptr;	// 等价于int *p1 = 0;
int *p2 = 0; // 直接将p2初始化为字母常量0
int *p3 = NULL; // 等价于int *p3 = 0;
cout << p1 << endl; // 0x0
cout << p2 << endl; // 0x0
cout << p3 << endl; // 0x0
cout << (long)p1 << endl; // 0

在效验一个指针是否为一个有效指针时,我们应该倾向于

1
if (ip != NULL)

而不是

1
if (ip)

为什么有人会用if(ip)这种方式校验一个指针非空,而且在C++中不会出现错误呢?而且现在很多人都会这样写。
原因是这样的,

1
2
3
4
5
6
7
8
/* Define   NULL   pointer   value   */
#ifndef NULL
# ifdef __cplusplus
# define NULL 0
# else
# define NULL ((void *)0)
# endif
#endif // NULL

思考一道腾讯面试题:

1
long a = (long)(((int *)0)+4);

a = 8

1
long b = (long)(((int *)8)+4);

b = 24

3.4野指针

野指针不是空指针,是一个指向垃圾内存的指针。

形成原因

  1. 指针变量没有初始化。

    任何指针变量被刚创建时不会被自动初始化为NULL指针,它的缺省值是随机的。所以,指针变量在创建的同时应当被初始化,要么将指针设置为NULL,要么让它指向合法的内存。例如

    1
    2
    char* p = NULL;  
    char* str = (char*)malloc(1024);
  1. 指针被free或delete之后,没有设置NULL,让人误以为是合法指针。

    freedelete只是把指针所指向的内存给释放掉,但并没有把指针本身给清理掉。这时候的指针依然指向原来的位置,只不过这个位置的内存数据已经被毁尸灭迹,此时的这个指针指向的内存就是一个垃圾内存。但是此时的指针由于并不是一个NULL指针(在没有置为NULL的前提下),在做如下指针校验的时候:

    1
    if (p != NULL)

    会逃过校验,此时的p不是一个NULL指针,也不指向一个合法的内存块,造成会面程序中指针访问的失败。

  2. 指针操作超越了变量的作用范围。

    由于C/C++中指针有++操作,因而在执行该操作的时候,稍有不慎,就容易指针访问越界,访问了一个不该访问的内存,结果程序崩溃
    另一种情况是指针指向一个临时变量的引用,当该变量被释放时,此时的指针就变成了一个野指针,如下

    1
    2
    3
    4
    5
    6
    A *p;	// A为一个自定义对象  
    {
    A a;
    p = &a; // 注意 a 的生命期 ,只在这个程序块中(花括号里面的两行),而不是整个test函数
    }
    p->Func(); // p是“野指针”

4.相关文章