C++指针与引用

来源:互联网 发布:这样才能成为网络歌手 编辑:程序博客网 时间:2024/05/19 12:27

引言

C++中的指针沿用自C语言,这没有什么可说的。C语言中并没有引用这个概念,那么为什么C++中要引入这么一个概念呢?存在必有其道理,还是先来分析一下这两者的区别吧。

指针

提起指针那可是C语言中的一把大宝剑,进可杀人,退可伤己

它用法也是多种多样,比如:

  • 一个简单的交换函数

void Swap(int a, int b){int tmp = a;a = b;b = tmp;}
这个例子相比你已经屡见不鲜了,因为这里的两个形参只是实参的一份拷贝,所以达不到修改实参的目的。所以在C中我们会采用指针方式来实现:

void Swap(int *a, int *b){int tmp = *a;*a = *b;*b = tmp;}
这样我们再调用的时候可以将实参的地址传给形参(形参得到的依旧是一份拷贝,不过这次是地址的拷贝),然后函数内部就可以通过解引用(*指针名)的方式访问实参所在的空间,进而修改它的值。

  • strcpy库函数原型

char * __cdecl strcpy (char * dest, const char * source);
这里同上面的一点重要的区别就是const了。我们可以对不希望被修改的指针加上const关键字进行限定,防止它被意外的改掉。

对指针加const限定有一下几种情况(以int类型指针为例):

  1. const int * p1;
  2. int const * p2;
  3. int * const p3 = NULL;
  4. const int * const p4 =NULL;

不要感觉看着很乱,其实规则很简单。首先,前两种作用是一样的。我们以*为分隔符,将这个指针定义分为两部分,前两种情况都属于const在*左边的情况,那么这个const修饰代表的就是指针所指向的那块空间的内容不能通过这个指针来改变,即*p1和*p2的内容不能通过*p1和*p2来改变。第三种,依旧由*开,const在的右边,这是const修饰代表的是指针的值不能发生改变,即p3的值不能发生改变,也就是这个指针不能再指向其它的变量或是常量了。知道了这些,那么第四种情况也就很简单了,这两个const决定指针的值和它所指向的内存空间的内容都不能发生改变,双保险。(需要注意的一点:后两种情况在定义时必须初始化,我只是随便给了个值,因为指针的值不能再修改)

当然还有许多用法,如多级指针、指针作为返回值等,这里就不在一一列出。我们重点在引用。

引用

  • 引用的定义

int a = 1;int &b = a;

这里定义了a的一个引用b。通过对b的修改可以改变a。通过查看变量a和b的地址可以发现它俩拥有同一块空间,即并不为b分配新的内存空间(这里和指针不同)。我们为了简单理解可以把引用称之为取了一个别名。如这里b是a的一个别名,修改b就是修改a。

那么上面那个函数我们可以修改为引用版的:

void Swap(int &c, int &d){int tmp = a;a = b;b = tmp;}

我们将参数定义为引用,这样实参传递过来时就相当于为a和b分别取一个别名,我们直接通过c和d这两个别名进行操作,省去了指针中解引用的操作。让整个代码看起来清爽干净了许多。同样,C中需要用到多级指针的地方,我们同样可以用引用来代替。

  • 引用的特性
  1. 引用定义的同时必须初始化
  2. 已经定义的引用不能再引用其它值
  3. 非常量引用的初始值必须为左值

想必都已经听说过引用的底层实现是用指针的方式实现的。那么结合第一条与第二条,不难推出,引用其实相当于是上面加const指针中的第三种情况(int * const p = NULL;)。但再看第三条,非常量引用,即没有加const修饰的引用,初始值必须为左值,NULL是左值吗?显然不是,所以直接将一个普通引用初始化为NULL(这里可以是任意常量)是不可行的(如果可行的话,通过这个引用岂不是可以修改常量了么?这怎么可以,所以不行)。

  • const修饰的引用

如果非要引用一个常量怎么办,可以加const嘛!保证不修改常量:

const int &a = 0;
在看一个特殊点的例子:

double d = 3.14;int &i = d;
在VS2013下编译这条语句会得到这样的提示:

   无法用 "double" 类型的值初始化 "int &" 类型的引用(非常量限定)

那我加个const看看:

double d = 3.14;const int &i = d;
这回没有报错。这是为什么呢?看看反汇编:

                         

这里再运行到const int &i = d;语句时,(前两行生成一个d的临时值,后两行)并不是简简单单得到将i初始化为d的引用,而是产生了一个临时变量,而临时值是具有常属性,所以才可以赋给常引用 i 。

反观这个代码:

double d = 3.14;const double &i = d;
定义了一个常引用,使得 d 的值不能通过 i 来修改(这种方法经常用到)通常会用在函数传参中,传给只需要访问d而不需要修改d的值的函数,保障了 d 的安全性。

它的汇编代码:

                      

这里就没有多余的生成一个临时值的操作了。

  • 引用的大小

一个引用的大小与它所引用的对象的类型有关。

例如前一个例子中的引用:const double &i = 0; 那么sizeof(i)的大小将会是sizeof(double)的大小,在32位平台上,这个值是8。

  • 引用作为返回值

引用可以做返回值吗?看看下面的例子:

int & fun(){int a = 10;return a;}

比如我们再main函数中这样调用这个函数:int &b = fun();

有没有问题?仔细想想,可以发现,变量a的空间是在属于fun函数的栈桢上开辟的,变量a在fun函数返回后被销毁,此时它的值不在有效,也不能保证其值不变,返回这个变量的引用,相当于返回了栈空间的地址,那么b所引用的空间已被释放,它的值将会变得不可预料。当然指针的使用中也存在这样的错误例子(返回了栈空间地址)。

我们换个方式接受一下函数的返回值:int b = fun(); 这样依旧会存在一定的问题,函数的返回值是个引用(底层实现为指针),而函数返回值是通过寄存器实现的,则函数调用完成后,变量a的地址将被保存到寄存器中用以返回,然后函数栈桢销毁,再把这个地址的值赋给变量b。在此过程中也不能保证a所在空间的内容不发生改变,所以依旧有错。

2 0
原创粉丝点击