这恐怕是对指针最通俗的解释了

有时候觉得自己什么学了很久的C了,什么都懂。真正碰到一个大家都说了几千遍的问题,你却还有点懵逼的时候,才觉得自己是个大傻叉。人傻就要多读书啊!

指针简介

指针的3三个重要的概念:

  • 指针的地址
  • 指针保存的地址
  • 指针保存的地址所对应的值

所谓大道至简,在复杂的东西也逃不过类比,指针也是如此。

理解指针之前,我们先将变量想做是一个盒子,那么:
1.每个盒子都有自己的序列号
2.每个盒子都有自己的空间,里面可以存放一个数。当这个数是另外一个盒子的序列号的时候,我们可以通过这个序列号可以找到另外一个盒子。
3.每一个程序中定义的变量都有自己专属的盒子。
OK,下来我们再来进行讨论。我们经常称int *p是一个指针变量,既然是变量也有自己的盒子了。那么我们的指针变量的地址就是盒子上的序列号。当盒子里存放了另外一个序列号(地址)的时候,我们就可以根据这个序列号去找另外一个盒子,进而查看另外一个盒子里面的东西。
那么对于以下情况:
1.int a对应的盒子里存放的就是a实际的值。
2.int *p对应的盒子里存放的是是一个地址,通过*的操作我们可以找到这个地址对应的盒子并获取盒子里面的内容。
道理就是这么简单啦~看图说话就更清晰啦~
pointer.png
很显然,我们定义了指针变量int *p,这个变量对应的盒子的序号是0000,这个盒子里面存放的是0003,我们根据这个地址就可以找到0003的盒子里面存放的值是15了。

所谓指针传递,值传递

对于所谓的值传递,本质上是把盒子里面的东西放到另外一个盒子里的操作。

对于这样的两个函数来说

void fun(int a)
{
}
void fun2(int *a)
{
}

事实上int a,int *a作为形参是一个新的变量,他们是拥有自己单独的盒子的,也就是拥有自己的独立空间。这个时候数据的相互传递的实质是把实参变量对应盒子里面存放的东西放到了形参变量对应的盒子里。

那么如何判断所谓大道至简,莫过于此。
因此对于通过传递指针来改变一个数的值情况来说,本质上都是通过获取到某个盒子的标号来改变盒子里的内容来实现的。
那么对于下面的程序会出现错误结果的原因,我们也能用我们的"火眼金睛"来看穿它的本质了。

void fun1(int *m)
{
    cout<<"fun1----"<<m<<"----"<<&m<<endl;
    m = (int *)malloc(sizeof(int));
    cout<<"fun1----"<<m<<"----"<<&m<<endl;
    *m = 15;
}
int main(void)
{
    int *p = NULL;
    cout<<p<<"----"<<&p<<endl;
    fun1(p);
    cout<<p<<"----"<<&p<<endl;
    return 0;
}
/*********************************************/
运行结果:

0----0x22feac
fun1----0----0x22fe90
fun1----0x6562f0----0x22fe90
0----0x22feac

可以看出形参是有自己专属的盒子(独立的内存空间)。在实参p向形参传递数据的时候,将自己盒子里面的内容复制了一份传给了形参。之后形参就对着自己盒子里面的东西一顿摆弄,然而实参盒子里面的东西变化了么?自然是没有变化的。所以p依然是一个空的指针,如果你去访问p存放的指针对应的值,就会崩~~~炸了(程序崩溃)。

那么我们如何改变P的值呢?最最关键的是改变P的盒子里面装的内容!
你可以这样:

int *fun2(int *m)
{
    cout<<"fun1----"<<m<<"----"<<&m<<endl;
    m = (int *)malloc(sizeof(int));
    cout<<"fun1----"<<m<<"----"<<&m<<endl;
    *m = 15;
    return m;    
}
int main(void)
{
    int *p = NULL;
    cout<<p<<"----"<<&p<<endl;
    p=fun2(p);
    cout<<p<<"----"<<&p<<"----"<<*p<<endl;
    return 0;
}
/*********************************************/
运行结果:
0----0x22feac
fun1----0----0x22fe90
fun1----0x5f62f0----0x22fe90
0x5f62f0----0x22feac----15

当然也可以这样:

void fun3(int **m)
{
    *m = (int *)malloc(sizeof(int));
    **m = 23;
}
int main(void)
{
    int *p = NULL;
    cout<<p<<"----"<<&p<<endl;
    fun3((int **)&p);
    cout<<p<<"----"<<&p<<"----"<<*p<<endl;
    return 0;
}
/*********************************************/
运行结果:
0----0x22feac
0x6462f0----0x22feac----23

引用传递

引用传递是一种完全的传递,是一个完整个体的传递,它传递的东西包括盒子的标号和盒子里面的内容。

对于变量引用来说

int main()
{
    int i = 10;
    int &j = i;
    cout<<&i<<endl<<&j<<endl;
    return 0;
 }     
/*********************************************/
运行结果:

0x22fea8
0x22fea8

对于函数形参引用传递-fun(int &i)类型

void fun4(int &i)
{
    cout<<"in fun4---";
    cout<<i<<"----"<<&i<<endl;
}
int main()
{
    int i = 10;
    int &j = i;
    cout<<"Before---"<<&i<<"----"<<&j<<endl;
    fun4(i);
    fun4(j);
    return 0;
 }     
/*********************************************/
运行结果:

Before---0x22fea8----0x22fea8
in fun4---10----0x22fea8
in fun4---10----0x22fea8

对于函数形参引用传递-fun(int *&i)

void fun3(int *&i)
{
    cout<<"In fun3----";
    cout<<i<<"----"<<&i<<endl;
}
int main()
{
    int i = 10;
    int &j = i;
    int *p = &i;
    cout<<"Before---"<<&i<<"----"<<&j<<endl;
    cout<<"P------"<<p<<"------"<<&p<<endl;
    fun3(p);//这里不能传递&i的值
    return 0;
 }     
/*********************************************/
运行结果:

Before---0x22fea8----0x22fea8
P------0x22fea8------0x22fea4
In fun3----0x22fea8----0x22fea4

引用传递是完全的传递在这里更能明显的表示出来。在fun3函数中我们可以看出传递进来的是p对应的变量盒子的整体,包括盒子的标号(p的地址)和盒子的内容(指针变量保存的地址),两者缺一不可,同时这也是传递&i会报错的原因(它只是传递了盒子的标号---变量的地址,这和形参的类型是不同的)。

常量指针

对于常量指针有这样的三种形式:
int * const p:这种情况下可以理解为当前盒子里面的内容是不能进行改变的。就像上了锁的保险箱,里面存放了另外一个箱子的标号,当前箱子里面存放的标号不能被更改,但是这个标号对应的箱子里面的东西你却可以随便更改。
举例如下:

int main()
{
    int m = 5;
    int n = 6;
    int * const p = &m;
    cout<<*p<<endl;//5
    m = n;
    cout<<*p<<endl;//6
    p = &n; //不能进行修改
    cout<<*p<<endl;
    return 0;
}

const int *p:这种情况下可以理解为当前盒子里面存放的标号所对应的盒子里面的内容是不能进行改变的。这种情况类似于士兵,士兵的任务就是负责保护盒子里面的内容不被改变,但是士兵可以被派去保护不同的盒子。
举例如下:

int main()
{
    int m = 5;
    int n = 6;
    const int * p = &m;
    cout<<*p<<endl; //5
    p = &n;
    cout<<*p<<endl;//6
    *p = n;            //错误,不能进行修改
    cout<<*p<<endl;
    return 0;
}

const int * const p:这种情况是上述两种情况的结合,即当前盒子不能改变,里面存放的东西也是不能动的。

总结

1.本质上值传递和指针传递是一种类型,它们传递的过程中只传输一个变量的一种属性(地址或者是改地址存放的值)。但是引用传递会传输一个完整的变量,包括地址和地址存放的内容。 2.对于值传递和指针传递来说,函数形参的来说都会单独分配一个独立的内存空间,但是引用传递不会,传递过来的是实参的实体。

额外的说明——数组名和指针的区别

对于函数的值传递和参数传递来讲,在上面我们已经很清楚的了解了它们的本质。但是最近对于数组又有了新的发现:

int main()
{
    int a = 5;
    int m[1]={a};
    printf(" m----%X\n",m);
    printf("&m----%X\n",&m);
    printf("*m----%X\n",&m[0]);
    printf("&a----%X\n",&a);
}     
运行结果:
 m----28FF04
&m----28FF04
*m----28FF04
&a----28FF08

数组名和指针是由区别的:数组名不是一个变量,它没有自己的独立空间(没有自己的盒子!!),编译器解析为数组第一个元素的首地址,即数组名的值和数组中第一个元素存放的地址相同。本质上数组名对应的地址是一个常量指针,因此不能直接对数组名进行自加减操作。同时因为数组名是一个地址,对它取地址实际上并没有什么意义。
需要注意的是,当数组名作为sizeof和&操作符的操作数时需要特别对待。

char array[] = {1,2,3,4};
printf("%d\n",sizeof(array));
printf("array----%X\n",array);
printf("array + 1----%X\n",array + 1);
printf("&array----%X\n",&array);
printf("&array + 1----%X\n",&array + 1);
运行结果:
array----28FF04
array + 1----28FF05
&array----28FF04
&array + 1----28FF08

显然我们可以得出这样的结论:
1.array指向的是元素的首地址,加一操作之后指向下一个元素 ,本质上是指向元素的指针
2.&array指向的是整个数组的地址,加一操作之后偏移了整个数组的长度,本质上是一个数组指针

一些非常好的参考文章:
1.[http://www.cnblogs.com/uniqueliu/archive/2011/07/14/2106681.html]
2.[http://blog.csdn.net/huqinwei987/article/details/50769096]
3.[https://stackoverflow.com/questions/2528318/how-come-an-arrays-address-is-equal-to-its-value-in-c]

文章版权:My-World - 勿于浮沙筑高台

本文链接:http://doachieveit.cn/index.php/archives/26.html

版权声明:本文为作者原创,转载请注明文章原始出处 !

添加新评论