点击链接加入群【C语言】:http://jq.qq.com/?_wv=1027&k=2H9sgjG


    

在结束了C之后,我们再来探讨一下C++,至于它们之间有哪些区别,我就不再讲了,需要在结束本系列之后再做一些简略总结。

我们采用的教才是C++ PRIMER 第四版 ,其实大家看一下那本书,很多基础知识点我们都在C系列中讲过啦,所以后面讲的都是C中没有讲过的或者漏讲的啦。

今天我们就讨论一个问题哦,什么问题呢?引用,这是C++中一个最基本的内容啦。

引用是什么呢?引用也是一种类型 哦,哦,又是类型,哈哈,这个是一个复合类型,很强很给力。

举个例子

int main()

{

char c = 'A';

char & rc = c;  //引用可以用变量进行初始化,这是非常重要的性质哦

size_t rc_size = sizeof rc;

int val =12;

int & rval = val;

size_t rval_size = sizeof rval;

return 0;

}

上面的代码定义了两个引用变量  rc 和 rval ,一个绑定到了c,另一个绑定到了val,我们可以看到 它们的大小分别是 1 和4 ,可以看到对引用的操作其实就相当于对变量本身操作,引用是变量的别名。我们再来看一个例子

int main()

{

int val =12;

int & rval = val;

rval = 13;

return 0;

}

这个操作大家要理解好,就能理解引用的作用啦,我们从反汇编的角度来分析问题

1:  int main()

2:  {

00401010  push    ebp      //保存环境

00401011  mov     ebp,esp    //设置基址指针

00401013  sub     esp,48h    //申请栈空间

00401016  push    ebx      //保存环境

00401017  push    esi      //保存环境

00401018  push    edi      //保存环境

00401019  lea     edi,[ebp-48h]

0040101C  mov     ecx,12h

00401021  mov     eax,0CCCCCCCCh

00401026  rep stos  dword ptr [edi]  //以上四步是初始化申请的栈空间为CC

3:    int val =12;

00401028  mov     dword ptr [ebp-4],0Ch  // ebp-4对应的是val

4:    int & rval = val;

0040102F  lea     eax,[ebp-4]

00401032  mov     dword ptr [ebp-8],eax  //ebp - 8 记录了val的首地址,这是引用操作

5:    rval = 13;

00401035  mov     ecx,dword ptr [ebp-8]  //当我们访问rval的时候,其实是间接 通过 ebp-8记录的val的首地址,访问到了val

00401038  mov     dword ptr [ecx],0Dh    // [ecx] = [ebp-8] ,这就是在访问val啦,怎么样,明白了引用是怎么回事了吗

6:    return 0;

0040103E  xor     eax,eax

7:  }

00401040  pop     edi

00401041  pop     esi

00401042  pop     ebx

00401043  mov     esp,ebp

00401045  pop     ebp

00401046  ret

上面是我们的main函数对应的反汇编代码,在代码中我已经做了介绍了,下面我们再来看一个例子

void swap(int &ra,int &rb)

{

int tem = a;

a = b;

b = tem;

}

int main()

{

int a=1,b=2;

swap(a,b);  //还记得上面写过 引用可以通过变量进行初始化吗,记得栈内存空间中是有参数空间的,所以这里就是用a初始化参数列表中的 int & ra

return 0;

}

这段代码大家应该是非常熟悉的啦,C中第六小节中,当时我们是以指针的方式来进行访问滴,现在是引用啦,你会发现我们使用引用不用再写那么多*号啦,因为引用将这个*地址的操作给封装起来了,也就是简化了我们对指针的操作啦,其实引用就是通过 记录 它要绑定的对象的地址,在访问引用的时候就是通过这个地址访问原对象,所以说,引用的功能还是非常强大的哦。举一个例子,我们要求一个数组的和,结果可以用返回值带出来,也可以传递一个参数过去,

void ArraySum(int arr[],int len,int & sum)

{

sum = 0;

for (int i=0;i<len;i++)

{

sum+=arr[i];

}

}

int main()

{

int arr[4]={1,2,3,4};

int sum ;

ArraySum(arr,4,sum);

return 0;

}

这个例子就是通过引用将结果带出来的哦,怎么样是不是很好理解呢。


在书2.5小节中主要是关于 引用的,这部分有几个重要的知识点总结的很好,我在这里再为大家总结一下:


引用必须初始化!  int & rval; 这是不允许的,因为我们知道它是通过指针进行访问的,所以它没有指针是肯定不行滴。

引用初始化完成后,不能再更改其指向! 就是说 int & rval = valA ; 这样就完成了初始化,不能再执行如下操作, rval = valB;


如下的定义:

int a=1,b=2;

int & ra = a,rb = b; 这个要注意一下,rb在这里还是一个整数,正确定义多个的作法如下

int & ra = a,&rb = b; 其实指针也是这样的哦,我们没有讲,在这里补充一下

int * pa = &a,pb = &b; 这个pb编译都通不过,因为pb是整型,而&b是int *类型 ,正确作法如引用一样

int * pa = &a,* pb = &b; 这是正确做法。


还记得之前讲指针与const一起使用的情况吗,在这里我们再给它们加两个名字:常量指针和指针常量

怎么样,这两个名字是不是很容易混淆呢,那么我来讲一下怎么理解

常量指针:指向常量的指针

指针常量:这个指针是常量

如果你把上面这两句话理解好,你也就知道它们之间的对应关系啦

常量指针,是指向常量的指针,所以,指针本身可以改变,const int * p = &val; 或int const * p = &val;

指针常量,是这个指针是常量,所以,指针本身不可以改变, int * const p = &val;


简单回顾了一下const与指针的关系,既然引用是通过对指针的*号操作的封装,那么我们来看一下const 与 引用 一起使用是怎么样的呢?


引用分为 const 引用 和 非const引用:const引用不能修改原值,而非const引用可以修改原值

const引用是指向const对象的引用

const int val = 12;

const int & rval = val; //这个rval 就是一个const引用。

其实大家想想就应该可以明白了,val是不能修改的,所以如果用非const引用绑定到val上,是肯定的不行,因为非const引用可以修改原值,而原值又是不能修改的,所以这是矛盾的,既然它们是矛盾的,就直接不要让非const引用绑定到const对象上吧,这就是标准啦。


再想像一下,const int & rval = 12; 12本身就是常量对象,所以,我们可以这样写哦。因为它符合我们的标准呀。


所以关于const和引用关联使用的标准记住两条就可以了


非const引用只能绑定到与该引用同类型的对象。//回想一下*号运算符就可以理解啦,如果char val = 'A'; int & rval = val;那么访问rval的时候,就是对 *((int*)&val),这是不合法的,因为会改写其它3B的数据,所以这种语法就不被允许啦,也就是非const引用只能绑定到与该引用同类型的对象上。


const引用则可以绑定到不同但相关的类型的 或 绑定到右值(就是右边的值,如12)。//因为用const引用绑定到的对象只需要能提供读取功能就行,所以,非const变量及常量都可以用来初始化const引用如

double val = 12.123;

const double rval = val; //可以,但是只能通过 rval 来读val,但不能通过 rval来修改val

const double rb = 12.33; //也可以,原因同上


const 引用也可以绑定到不同类型的对象上,但是要对象类型 需要能转换到引用类型,如

double val = 12.23;

const int rval = val; //这里会产生临时值(12), 然后rval 绑定到了临时值上,但是这在语法上是没有什么问题的


说了这么多,那么我们真正工作中,一般不会用到不同类型之间的绑定,更多的是用来限制参数值,还有就是用引用可以节省栈空间,因为我们用引用传递的参数是4字节的地址,而如果是对象,则可能会占N个字节啦,比如一个Person对象,一般在参数中加入const 表示不希望这个值被修改。比如

void push_back(const T& x);

这是一个函数,用于向栈中插入数据,也就是我们讲过的压栈啦,压栈这个操作,我们不希望对的压入的数据造成修改,所以加上const,至于T是什么东西,我们以后再讲吧。

好了,这一节就介绍这么多,其实大家不要感觉到麻烦,书上讲的许多东西是为了帮助我们理解但并不实用的东西,因为它是C++标准中定制的内容,所以书中要讲到它们,但是我们在用的时候一般就是做参数限制就行了,以后还会有引用的介绍呢,暂时就了解这么多吧。


本节重点1:理解引用是对 () 的一种封装,()里面是 "对 *一个地址(指针) 这个操作"  ,整句话中是有两个 【对】字的,*一个地址,我们C中讲过哦,指针就是记录地址用的。

本节重点2:理解const 引用的用处,用于限制参数被修改,还可以降低对栈空间的使用,主要体现在参数的传递上。


上面两个重点理解了,本书中的其它细节随着深入就会慢慢理解啦。