提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
前言
请跟我学习两种传递的不同,以下内容借鉴印度顶尖程序员,感谢他的无私分享和极其深刻的教学,让我深刻理解C语言中的指针
提示:以下是本篇文章正文内容,下面案例可供参考
一、内存的四个分区·
堆区(heap):一般由程序员手动分配释放(动态内存申请与释放),若程序员不释放,程序结束时可能由操作系统回收。
栈区(stack):由编译器自动分配释放,存放函数的形参、局部变量等。当函数执行完毕时自动释放。
全局区(global / stack):用于存放全局变量和静态变量, 里面细分有一个常量区,一些常量存放在此。该区域是在程序结束后由操作系统释放。
代码区(code / text):用于存放程序代码,字符串常量也存放于此。
二、
假设新手程序员Albert刚刚学习了关于函数的用法,写了这样的程序:
#include <stdio.h>
void Incremnet(int a){
a = a + 1;
}
int main(){
int a;
a = 10;
Incremnet(a);
printf("a = %d\n", a);
return 0;
}
在该程序中,Albert期望通过Increment()函数将a的值加1,然后打印出a = 11,但是,程序的实际运行结果却是a = 10。问题出在哪里呢?
实际上,这种函数调用的方式称为值传递call by value,这样在Increment()函数中,临时变量local variable a,会在该函数结束后立刻释放掉。也就是说Increment()函数中的a ,和main() 函数中的 a 并不是同一个变量。我们可以分别在Increment()和main()两个函数内打印变量a的地址:
printf("Address of a in Increment: %d", &a);
printf("Address of a in main: %d", &a); // 将这两句分别放在Increment函数和main函数中
输出:
Address of a in Increment: 2063177884
Address of a in main: 2063177908
这里两个地址的具体值不重要,重要的是他们是不一样的,也就是说我们在两个函数中操作的a变量并不是同一个,所以程序输出的是没有加1过的a的值。
笔者这里还是根据原视频作者的讲解,通过画出内存的形式来分析值传递。
程序会为每个函数创造属于这个函数的栈帧,我们首先调用main()函数,其中的变量a一直存储在main()函数自己的栈帧中。在我们调用Increment()函数的时候,会单独为其创造一个属于它的栈帧,然后main()函数将实参a=10传给Increment()作为形参,a会在其中加1,但是并没有被返回。在Increment()函数调用结束后,它的栈帧被释放掉,main()函数并不知道它做了什么,main()自己的变量值一直是10,然后调用printf()函数,将该值打印出来。
可以看到,局部变量的值的生命周期随着被调用函数Increment()的结束而结束了,而由于main()中的a和Incremet()中的a并不是同一个变量(刚才已经看到,二者并不在同一地址),因此最终打印出的值还是10。
三
那怎样才能实现Albert的预期呢?我们刚才已经看到,之所以最终在main()中打印的值没有加1,就是因为加1的变量和最终打印的变量不是同一个变量。那我们只要使得最终打印的变量就是在Increment()中加过1的变量就可以了。这要怎么实现呢?我们刚刚学过,通过指针可以指向某个特定的变量,并可以通过解引用的方式对该变量再进行赋值,而又由于在程序未执行结束时,main()函数里分配的空间均可以被其他自定义函数访问。因此我们可以将main()中的变量地址传给Increment(),在其中对该地址的值进行加一,这样最终打印的变量就会是加过1的了。
实现如下:
#include <stdio.h>
void Incremnet(int* p){
*p = *p + 1;
}
int main(){
int a;
a = 10;
Incremnet(&a);
printf("a = %d\n", a);
return 0;
}
这种传地址的方式我们称之为call by reference。
它可以在原地直接修改传入的参数值。另外,由于传的参数是一个指针,无论被指向的对象有多么大,这个指针也只占4个字节(32位机),因此,这种方式也可以大大提高传参的效率。