您的位置:首页 > 其它

指针-形参与实参-函数传参

2014-10-01 11:15 267 查看
大部分内容来自博客:http://blog.csdn.net/a475701239/article/details/11179351

今天看到一篇博客(如上),里面的结果我居然也算错了,稍加整理发下来:

<span style="font-size:12px;">#include <stdio.h>
struct A {
int a;
};
struct A g_ta = {
.a = 1,
};
struct A g_tb = {
.a = 2,
};

void fun1(struct A * p1)
{
p1->a = 3;
}
void fun2(struct A * p2)
{
p2 = &g_tb;
}
void fun3(struct A ** p3)
{
*p3 = &g_tb;
}

int main()
{

printf("\t\tgta_addr=%p\n",&g_ta);		//将g_ta,g_tb的地址全部打印出来
printf("\t\tgtb_addr=%p\n\n",&g_tb);

struct A *p = &g_ta;
printf("p->a = %d\n",p->a);
printf("\t\tptr_addr=%p\n",p);

fun1(p);
printf("p->a = %d\n",p->a);
printf("\t\tptr_addr=%p\n",p);

fun2(p);
printf("p->a = %d\n",p->a);
printf("\t\tptr_addr=%p\n",p);

fun3(&p);
printf("p->a = %d \n",p->a);
printf("\t\tptr_addr=%p\n",p);

printf("\t\tptr_addr=%x\n",p);		//试一下%x,%08x,%u与%p打印的区别。
printf("\t\tptr_addr=%08x\n",p);
printf("\t\tptr_addr=%u\n",p);
return 0;
}</span>
打印结果:
<span style="font-size:12px;">                gta_addr=0x601020
gtb_addr=0x601024

p->a = 1
ptr_addr=0x601020
p->a = 3
ptr_addr=0x601020
p->a = 3
ptr_addr=0x601020
p->a = 2
ptr_addr=0x601024
ptr_addr=601024
ptr_addr=00601024
ptr_addr=6295588</span>
p->a第一行和第二行输出应该没问题,很简单的,是通过指针方式进行传值的,所以改变形参的值,实参也会跟着改变。
再看第三行输出,为什么经过fun2 后值不是p->a = 2,而是p->a = 3?fun2不也是通过指针方式来进行传值的吗?怎么没有改变p的值呢?
再看第四行输出,p->a = 2?怎么形参为指针的指针就对了呢?
 
带着疑问看下面:
首先不要觉得指针是个很神奇的东西,我之前一直对指针不理解,或者是理解不透彻,这两天在写电子书的代码,感觉对指针这个东西有了些顿悟,我可以告诉你指针就是一个变量,他和别的变量(比如int,char)没有什么本质的区别,都是一个内存A里存放变量的值!唯一区别就是指针被多设计了一个 * 号,该 * 号的意思就是将这个内存A里面的值当成另一个内存B的地址,并取出这个内存B的值,说到这里,这就是指针的全部!
下面是函数参数传递,回头看我说的一句话“通过指针方式进行传值的,所以改变形参的值,实参也会跟着改变”,这句话其实是障眼法,学C语言的时候老师就告我们,值传递方式,形参改变不能影响实参,而地址传递传递是可以的,这句话不能算错,但是不能说对,地址传递方式形参改变确实能影响实参,但是也是要看改变的是什么,影不影响实参是有道理的!
为了更好得说明,我将内存抽块象成如下小方块,一个方块代表一块内存。

另注,1. 我将一个结构体看成一块小内存,2. 变量的内存地址是我假设的。3.地址就是指此块内存的地址,每块内存都有自己地址。

变量



地   址

现在开始分析代码:
首先申明两个struct A全局变量,其内存模型如下,(地址是我假设的,以下都是,不再赘述)  

     g_ta

g_ta

1

0x10

 

 

      

     g_tb

g_tb

2

0x11

 

 

 

在main数里面申明了结构体A指针,其指向g_ta,其内存模型如下

        P

p

0x10

0x50

 

 

 

注意看,p的值就是 0x10,g_ta的地址,没什么特别的。

现在开始分析fun2,fun2弄懂了fun1自然就理解了,首先p经过了fun1之后,将g_ta里面的值变成了3,所以,现在他们内存情况如下:(这三块内存方格是分开的,贴到这里就连在一起了,以下同样,不再赘述。。。)

      g_ta            g_tb            p

g_ta

3

0x10

g_tb

2

0x11

p

0x10

0x50

 

      

 

调用fun2(p);

         | |

         \/ 

void fun2(struct A * p2)
{
        p2 = &g_tb;
}
进入函数fun2,会生成一个和形参类型相同副本,其将实参值拷贝,即结构体指针p2,其内存模型如下                     

        p                p2

p

0x10

0x50

p2

0x10

0xa0

 

 

 

可以看到,他和指针p的不同就是内存地址不同,而他们的值是相同的,都表示g_ta的地址。

p2 = &g_tb;

这句就是将g_tb的地址赋值给p2,那么p2内存模型就会变成如下:

         p                p2

p

0x10

0x50

p2

0x11

0xa0

 

 

 

好了,p2的值不是改变了吗,p2确实指向了g_tb了啊?怎么打印得不对呢?哈哈,你再看看,printf打印得是p啊,p指向的还是是g_ta,当然输出的是3了啊!p2在退出fun2时候就自动销毁了!

跟着内存模型来看,很容易理解吧,那下面继续来看fun3,fun3为什么就能正确地打印出g_tb的值呢?继续用内存模型来分析:

此时内存情况如下:

       g_ta          g_tb              p

g_ta

3

0x10

g_tb

2

0x11

  p 

0x10
0x50

 

      

  

执行函数fun3(&p);

                 | |

                 \/

void fun3(struct A ** p3)
{
        *p3 = &g_tb;
}
经过fun3,将p的地址 0x50传给了fun3,这个0x50没有什么特别得,他就是一个数值,对于fun3来说他根本不知道这个0x50代表什么意思!所以我们就要告诉fun3,这个0x50是个内存地址,这个地址内存里面的内容仍然是一个地址!转变成C语言来理解就是要设计一个形参,这个形参能够进行两次取内存值的操作,好,struct A ** p3就应运而生了!

 p3:指针的指针,感觉好复杂啊!其实一点都不复杂,说到底p3就是一个指针嘛,不过他指向的内存里面的数据也是指针类型罢了,既然p3还是一个指针那再来看看他的内存模型

     g_ta            g_tb            p                 p3

g_ta

3

0x10

g_tb

2

0x11

p

0x10

0x50

P3

0x50

0xa1

                    

 

      

进过fun3,实参传递了p的地址0x50给p3,所以p3的值就是0x50,看清楚了!

执行*p3= &g_tb;

*p3就是取地址为0x50内存的值,地址为0x50的内存里面存放是谁的值啊?

对!就是p的值,所以这条代码其实改变了的是p的值,将g_tb的值赋给了p!

    g_ta              g_tb              p                p3

g_ta

3

0x10

g_tb

2

0x11

p

0x11

0x50

P3

0x50

0xa1

                    

 

      

好了,基本都已经清楚了,退出fun3,p3销毁,打印p指向的值,就是p->a = 2
分析已经结束了,还有fun1,可以自己用这种内存模型方法来自己分析一下 :)

总结:想要改变指针指向的内容,就传指针给函数,想要改变指针的值,那就得传递指针的地址了,相应的函数参数就要设计成指针的指针了!

**************************

%p:

格式控制符“%p”中的p是pointer(指针)的缩写。指针的值是语言实现(编译程序)相关的,但几乎所有实现中,指针的值都是一个表示地址空间中某个存储器单元的整数。printf函数族中对于%p一般以十六进制整数方式输出指针的值,附加前缀0x。

%x:十六进制输出

%u:十进制输出,如6295588换算成十六进制就是601024

**********************************

关于实参和形参:

形参出现在函数定义中,在整个函数体内都可以使用, 离开该函数则不能使用。
实参出现在主调函数中,进入被调函数后,实参变量也不能使用。 
形参和实参的功能是作数据传送。发生函数调用时, 主调函数把实参的值传送给被调函数的形参从而实现主调函数向被调函数的数据传送
1.形参变量只有在被调用时才分配内存单元,在调用结束时, 即刻释放所分配的内存单元。因此,形参只有在函数内部有效。 函数调用结束返回主调函数后则不能再使用该形参变量。 
2.实参可以是常量、变量、表达式、函数等, 无论实参是何种类型的量,在进行函数调用时,它们都必须具有确定的值, 以便把这些值传送给形参。 因此应预先用赋值,输入等办法使实参获得确定值。 
3.实参和形参在数量上,类型上,顺序上应严格一致, 否则会发生“类型不匹配”的错误。

4.函数调用中发生的数据传送是单向的。 即只能把实参的值传送给形参,而不能把形参的值反向地传送给实参。 因此在函数调用过程中,形参的值发生改变,而实参中的值不会变化。
5.当形参和实参不是指针类型时,在该函数运行时,形参和实参是不同的变量,他们在内存中位于不同的位置,形参将实参的内容复制一份,在该函数运行结束的时候形参被释放,而实参内容不会改变
如果函数的参数是指针类型变量,在调用该函数的过程中,传给函数的是实参的地址,在函数体内部使用的也是实参的地址,即使用的就是实参本身。所以在函数体内部可以改变实参的值。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: