[转]深入理解双指针(上)

转载:http://blog.csdn.net/feiyinzilgd/archive/2010/02/09/5302369.aspx

 对于C语言的参数传递都是值传递,当传传递一个指针给函数的时,其实质上还是值传递,除非使用双指针。

在讲双指针之前,还是先讲讲关于C语言函数调用的本质。
函 数调用操作包括从一块代码到另一块代码之间的双向数据传递和执行控制转移。数据传递通过函数参数和返回值来进行,包括局部变量的空间分配与回收,都是通过 栈来实现的。绝大多数CPU上的程序实现使用栈来支持函数调用操作。栈被用来传递函数参数、存储返回信息、临时保存寄存器原有值以备恢复以及用来存储局部 数据。当函数A调用函数B的时候,会把A的变量和参数压入到栈中,然后接着将B的变量和参数,局部变量压入到栈中。然后当A调用B是,B其实是在栈中取得 A传递的参数和值,从而达到值传递的效果。

以一个交换2个数的值的函数调用为例。

void
swap ( int *a, int *b ){
int c;
c = *a;
*a = *b;
*b = c;
}
int
main(int argc, char **argv){
int a,b;
a = 16;
b = 32;
swap( &a, &b);
return ( a - b );
}

那么,这段代码编译成汇编语言之后,除了会有代码段,数据段,堆栈,那么在调用的时候,会把main函数的参数变量压入main函数的栈帧,然后接着会压入swap函数的局部变量和参数

那么按照刚才上述理论,编译成汇编语言以后,这个图就是函数调用的时候内存形态。

有了上面的图和理论基础,再来讨论双重指针的问题。当定义的时候,只有一个*号的时候,我们叫它一级指针。**个星号的叫二级指针。

当我们使用一级指针的时候,我们试图使用下述错误代码来实现2个数交换

void
swap ( int *a, int *b ){
int *temp;
temp = NULL;
temp = a;
a = b;
b = temp;
}
int
main ( int argc, char **argv ){
int a,b;
a = 16;
b = 32;
swap(&a, &b);
return ( a - b );
}

这种方式按照理论上来说,是想通过调用swap函数,在swap函数内部,实现将交换&a,&b,即交换a和b的地址来达到目的。这样绝对不可以。因为当把a,b的地址传到swap函数之后,按照上述栈帧图的结构来看,最终swap函数值通过栈指针来实现的,当swap使用的时候,还是把 a,b的地址复制到寄存器中才能运算。那么,大家也许就明白了,swap把a,b的地址复制到寄存器中,然后运算,相当于抱着a,b的副本跑了,然后去操作,这些所有针对a,b副本的操作管main函数中的a,b什么事? 当swap返回之后,这些寄存器或者是栈空间随着swap的然会而释放了,而 main函数的a,b没发生任何变法。所以上述代码是错误的,无法实现你想要的功能。
   
   当我们用二级指针来实现上述功能的时候有就可以达到效果。

void
swap ( int **a, int **b ){
int *tmp = NULL;
tmp = *a;
*a = *b;
*b = tmp;
}
int
main ()
....
....

这个时候,你会发现就能实现达到交换的目的。
    这就是双指针神奇的功能,突破C语言传值的概念。那么,双指针是如何达到效果的呢?
    当我们申明 **a之后,其实双指针变量a其实已经存在了。那么在内存中的效果如下图 

那么,p中放的是中间桥梁bridge的地址&bridge,则*p就是中间桥梁bridge的内容即是目标操作数的地址&income,从而**p就是目标操作数

再来看这个图,p就是这里**a种的a.当我们申明**p之后,p就已经存在了。其实这个bridge也已经存在了,那么我们要做的就是bridge中放 我们要操作的数的地址。也就是&incom;那么,其实这样操作*bridge就是操作&incom也就是&b啊,这个一级指针 没什么区别啊。
    请注意,对于一级指针,我们要操作的是就是b,那么按照汇编语言的规则,就要把b放到寄存器或者栈中去操作,我们相当于复制了一个副本去操作,等我们操作完了,返回函数,这些寄存器,栈等都释放了,main中什么也没发生。但是如果我们用双指针,二级指针就不一样了。我们操作的是bridge.我们只是机械的复制一个bridge的内容到寄存器或者到栈中,而没有实际的去复制imcom的内容,我们只是告诉bridge,你要指向一个叫incom的地址, 也就是说,bridge的内容就是incom的地址,即&incom,通过bridge这个中间桥梁,所以,就达到目的了。所以,双指针让参数传递具有穿透力。
    双指针主要用在但我们想向一个A函数传递参数的时候,但是我们希望在A内部对参数做任何修改都能保存起来,那么就是用双指针吧。
    举个例子;
    我们在做链表的时候,我们肯定希望在用一个函数creatLink(...)函数来增加链表节点。那么我们可以有2种方法来实现
    第一种,用一级指针

typedef struct node{
...
...
}list;
node *create(list *l){
list *head;
head = l;
malloc...//为节点申请内存空间
...
...
//操作
return head;
}
int main(...){
...
...
list *listhead
createList(listhead);
....
//以后的任何操作,我们都要考虑,我们是否拿到的是链表头指针,到底哪个是链表波的头指针,我们是否要renturn下来返回链表头指针??等。。。。
}

这样做可以达到删除增加节点的目的,但是,在任何情况下,我们的操作都得死死地抓住头指针,也即是我们增加删除节点后,任何对链表长度的修改,我们都要 链表头指针返回,即 return head;所以,我们要通过这个函数最后获得头指针,抓住他,死死地抓住他,然后操作。
    
    第二种方法:用双指针,也即是二级指针。

typedef struct node{
...
...
}list;
void
create(list **l){
list *head;
head = l;
malloc...//为节点申请内存空间
...
...
//操作

}
int
main(...){
...
...
list *st
createList(st)
....
..
//以后的任何操作,不管是删除还 是插入,我们不需要考虑,我们是否已经return head了,不需要,我们在任何情况下,对链表的操作都只需要使用st来完成,因为,st就是链表的头指针,不变,因为在申明st的时候,已经为st分配 一个地址空间,它是存在的,一直存在,直到main函数结束
}

时间: 2024-10-12 19:03:15

[转]深入理解双指针(上)的相关文章

Android 进阶学习:事件分发机制全然解析,带你从源代码的角度彻底理解(上)

http://blog.csdn.net/guolin_blog/article/details/9097463 事实上我一直准备写一篇关于Android事件分发机制的文章,从我的第一篇博客開始,就零零散散在好多地方使用到了Android事件分发的知识.也有好多朋友问过我各种问题,比方:onTouch和onTouchEvent有什么差别,又该怎样使用?为什么给ListView引入了一个滑动菜单的功能,ListView就不能滚动了?为什么图片轮播器里的图片使用Button而不用ImageView?

理解流方式上传和form表单上传

流方式上传: $post_input = 'php://input'; $save_path = dirname( __FILE__ ); $postdata = file_get_contents( $post_input ); if ( isset( $postdata ) && strlen( $postdata ) > 0 ) { $filename = $save_path . '/' . uniqid() . '.jpg'; $handle = fopen( $filen

JVM理解(上):classloader加载class文件的原理和机制

转自:https://www.jianshu.com/p/52c38cf2e3d4 JVM理解(上):classloader加载class文件的原理和机制 安东尼_Anthony关注 12018.11.10 10:16:40字数 4,361阅读 3,731 1 JVM架构整体架构 在进入classloader分析之前,先了解一下jvm整体架构: JVM架构 JVM被分为三个主要的子系统 (1)类加载器子系统(2)运行时数据区(3)执行引擎 1. 类加载器子系统 Java的动态类加载功能是由类加载

Spark笔记:复杂RDD的API的理解(上)

本篇接着讲解RDD的API,讲解那些不是很容易理解的API,同时本篇文章还将展示如何将外部的函数引入到RDD的API里使用,最后通过对RDD的API深入学习,我们还讲讲一些和RDD开发相关的scala语法. 1)  aggregate(zeroValue)(seqOp,combOp)  该函数的功能和reduce函数一样,也是对数据进行聚合操作,不过aggregate可以返回和原RDD不同的数据类型,使用时候还要提供初始值. 我们来看看下面的用法,代码如下: val rddInt: RDD[In

理解RHEL上安装oracle的配置参数

无论安装什么版本的oracle,在安装之前,都需要配置 /etc/pam.d/login   /etc/profile   /etc/security/limits.conf这三个文件 那这三个文件究竟是做什么用的呢?答案就是设置对oracle用户的shell limits.下面就仔细分析一下这几个文件 首先需要编辑/etc/security/limits.conf文件(用户限制配置文件) oracle soft nproc 2047 oracle hard nproc 16384 oracle

三十分钟理解计算图上的微积分:Backpropagation,反向微分

神经网络的训练算法,目前基本上是以Backpropagation (BP) 反向传播为主(加上一些变化),NN的训练是在1986年被提出,但实际上,BP 已经在不同领域中被重复发明了数十次了(参见 Griewank (2010)[1]).更加一般性且与应用场景独立的名称叫做:反向微分 (reverse-mode differentiation).本文是看了资料[2]中的介绍,写的蛮好,自己记录一下,方便理解. 从本质上看,BP 是一种快速求导的技术,可以作为一种不单单用在深度学习中并且可以胜任大

理解RHEL上安装oracle的配置参数 :/etc/security/limits.conf, /etc/profile, /etc/pam.d/login

无论安装什么版本的Oracle,在安装之前,都需要配置 /etc/pam.d/login   /etc/profile   /etc/security/limits.conf这三个文件 那这三个文件究竟是做什么用的呢?答案就是设置对oracle用户的shell limits.下面就仔细分析一下这几个文件 首先需要编辑/etc/security/limits.conf文件(用户限制配置文件) oracle soft nproc 2047 oracle hard nproc 16384 oracle

Android 源码系列之<二>从安全的角度深入理解BroadcastReceiver(上)

提起BroadcastReceiver大家都很熟悉,它和Activity,Service以及ContentProvider并称为Android的四大组件(四大金刚),可见BroadcastReceiver的重要性,今天我们主要从安全的角度来讲解称为四大组件之一的BroadcastReceiver.可能有的童靴看到这里会有疑问,BroadcastReceiver有啥好讲的,不就是先定义自己的广播接收器然后在manifest.xml文件中注册,在需要发送广播的地方调用Context的sendBroa

『PyTorch』第五弹_深入理解autograd_上:Variable

一.Variable类源码简介 class Variable(_C._VariableBase): """ Attributes: data: 任意类型的封装好的张量. grad: 保存与data类型和位置相匹配的梯度,此属性难以分配并且不能重新分配. requires_grad: 标记变量是否已经由一个需要调用到此变量的子图创建的bool值.只能在叶子变量上进行修改. volatile: 标记变量是否能在推理模式下应用(如不保存历史记录)的bool值.只能在叶变量上更改.