指针(Pointer)和引用(reference)二者看似不同但又仿佛有相似之处。只有从逻辑和底层彻底搞明白指针和引用,才能思路更清晰地编写代码。言简意赅两句干货:
指针本身是个变量,存储另一个变量的地址
引用表面是变量别名,底层是个const 指针
定义变量本质是申请空间,譬如:
int a;
表示申请一个32位(32/64位系统)的空间,该空间中的二进制码表示数量的规则是整型变量的方式。所以称a为整形变量,范围是-231~231-1(总数232,负数最高位是1,占总数一半,正数和0占另一半)。
那现在我在a变量里面存储b变量的地址,a变量就成为了一个指针,指向b变量。
int *a=b;
因为32/64位系统中寻址空间是232,所以a的长度是32位二进制,这样才可以不重不漏表示完所有的地址。指针也有变量类型,比如int型指针,double 型指针,这是为了语法的无歧义和程序安全考虑。可能各位会碰到各种绕来绕去的指针题目,这种题目不管叠加了多少指针多少*与&,只要一层层取下来就可以表示清楚了。*a就表示取出a中的数,&a表示取出a的地址。
可以看段代码应用一下:
int arr[] = {1,2,3,4,5,6,7,8};
int *p=arr;
*(p++)+=123;
printf("%d,%d\n", *p,*(++p));
第一行,定义数组arr,元素共八个。
第二行,定义指针p指向arr数组。
此时的*p 还是等于arr数组的第0个元素,也就是1。
第三行,p++里的++是最后才运算,所以先执行*p+=123,也就是arr的第0个元素被赋值为123。此时arr变成{123,2,3,4,5,6,7,8} ,然后是p++,此时*p已经是等于arr的第1个元素了,也就是2。
第四行,在执行printf时,括号里的参数是从右往左的顺序进行读取的,也就是说先执行 *(++p),也就是p先加一再指针,指向的是arr第2个元素3,然后在执行*p,还是3最后显示的内容就是:
3,3
注意:指针虽然是一个变量,但它是有类型的特殊变量。指针中存储的是地址,指针加1不代表该地址加1,比如0x00a2fd68 加1后不是0x00a2fd69,而是根据指针的属性去加。比如Int型是在地址上加4个字节,那就是0x00a2fd6B,这是为了方便移动指针。
void main()
{
int a[5]={1,2,3,4,5};
int *ptr=(int *)(&a+1);
printf(“%d,%d”,*(a+1),*(ptr-1));
}
正确答案是:
2,5
这段代码牵扯到另一个问题,数组与指针。第一个2很好出来,因为a为该数组的首地址,即a为a[0]的地址,那么a+1,就是a[1]的地址,按照该地址取内容就是a[1]=2。
第二个有点复杂,关键在于,a与&a的值一样类型不一样。这也是鄙人困惑的地方之一。在之前的叙述中,鄙人认为指针本身也是一个变量,那么a既然是一个指针就应该存在于内存当中,就应该有地址,a与&a肯定是不一样的。可以看到下图:
事实是,a与&a值是相同的,类型不一样,前者为数组元素指针,后者为数组指针。所以按照2.2中的叙述,前者加1偏移4个字节,后者加1偏移4*5=20个字节。所以将&a+1强制类型转换为*int 赋值给ptr后,ptr为*int类型,减1偏移4个字节,所以取内容是a[4]=5。
鄙人觉得这个语法用起来虽然方便,但难以理解a到底在哪,它的地址是什么。为什么a和&a一样的,为什么不能用通用的指针去理解a。唯一能想到的解释只能是:a单独出现时,表示数组首元素的指针,而&a中的a表示的是数组这个抽象的东西。还请看到本博客的各位大神赐教,指出计算机底层是怎么处理的,类似的问题看哪本书比较好。
学过c/c++的肯定都听过引用就是起别名,或者叫替身,有点像量子纠缠那样,替身和本体同时变化。当然啦,计算机才没有那么大本事,所以引用内部还是在用指针,其实都是一个对象。数据是对实体世界的一次抽象,引用和指针又是对数据的第二次抽象,这样才会让数据摆脱存储的桎梏,让数据在逻辑上具有很强的操作性。
先看一下引用怎么定义吧:
int a = 10;
int &b = a;
称b是a的引用,假如b的值改变了,那么a的值也会改变,即a,b是同体的。那么这是否与2.1中所说的申请变量是开辟内存空间相矛盾呢,其实不矛盾,因为引用是C++的一个障眼法,底层还是指针。请看下面的对比代码:
int i=10; int i=10;
int &j=i; int * const p=&i;
j=-1; *p=-1;
这两份代码的效果是一样的,实际上在C++内部就是这样来操作的,不过抽象出了一个引用的概念而已。从int * const p的角度看,理解引用的特性就非常简单了:
骗你的,没有代码,放个图片休息一下。
既然说到了引用,就不得不提const了,当它加入代码中,又会产生一些迷惑人的东西。
首先看这一段代码:
const int i=1;
i=2;
这是错误的,因为i被const 修饰了,表示i不能被更改。也就是说,被const修饰表示不能修改,那么指针呢。
const int * a; //指向整形常量 的指针,它指向的值不能修改
int *const b; //指向整形的常量指针 ,它不能在指向别的变量,但指向(变量)的值可以修改。
const int *const c; //指向整形常量 的常量指针 。它既不能再指向别的常量,指向的值也不能修改。
这三句话请反复琢磨三遍,其实无非是在限定俩东西,指针本身与指向的内容,换句话说就是指针本身作为变量能不能改,该变量作为地址找到的内存空间的数能不能改。
但是很容易记混乱吧,技巧是看const的右边是什么就限定了什么。第一个const右边是int ,那说明是内容不能改。第二个const右边是b,说明b本身不能改。第三个就是俩都限定了。
如此看来在3.1中,既然指针不能再指向其他的内容,那么引用初始化时肯定要挂在一个确定的变量上啊,而且不能改动。
const 还有一个知识是修饰成员函数时的语法,这个以后再说。