`

从形参实参到堆内存与栈内存

阅读更多

一、运行程序看结果

有这样一段代码,你是否知道运行结果并作出合理的解释。

一个简单的实体类Person,里面只有一个name属性:

publicclass Person {

   public String name;

}

与之对应的一个PersonService,里面有两个方法,做同样一件事——改变Person实例的name值,只是实现方式不同:

publicclass PersonService {

   publicvoid changeName(Person p){

       p.name="alan";

   }

  

   publicvoid changeNameTwo(Person p){

       p=new Person();

       p.name="alan";

   }

}

在客户端中分别调用这两个方式,打印结果是什么?又是否会一致?

测试一:

publicclass Client {

  publicstaticvoid main(String[] args) {

    PersonService ser=new PersonService();

    Person p=new Person();

    p.name="chenyan";

    ser.changeName(p);

    System.out.println(p.name);

}

}

 

测试二:

publicclass Client {

  publicstaticvoid main(String[] args) {

    PersonService ser=new PersonService();

    Person p=new Person();

    p.name="chenyan";

    ser.changeNameTwo(p);

    System.out.println(p.name);

}

}

 

运行后输出结果分别为:

alan

chenyan

即第一个方式改变了实例对象pname值,而方式二却没有改变实例对象pname值。

对于此结果我们可以从形参和实参为入口来进行分析解释。

二、形参与实参

形参是定义方法的时候,该方法所携带的参数,比如说现在有一个方法

public void printInfo(String info){

Systemoutprintln(info);

}

此处info就是一个形参,它是String类型的。

实参是你在调用方法的时候,给这个方法传递的参数值,比如说有这么一个语句:

**printInfo("hello");(此处**表示printInfo方法所在类的一个对象),这里的"hello"就是一个实参,实现方法调用的时候,系统会吧实参"hello"的值赋予形参info变量,即info就指向了"hello",调用这个方法后,就会在屏幕上打印输出hello

搞清楚了什么是形参和实参,现在我们从内存的角度来分析一下调用PersonService两个相同方法不同实现结果不同的原因。

我们先来分析测试一,

Person p=new Person();

可以分为三个步骤,声名Person对象引用p;创建Person对象;返回对象地址并赋值给引用变量p。最后给对象pname赋值为chenyan.

如下图所示:

 

当我们调用方法ser.changeName(p);时,在内存中表示为:

 

由于实参p和形参p指向的是同一个对象,所以在方法publicvoid changeName(Person p){

       p.name="alan";

   }

中,形参p改将name的值改为alan时,实际修改是的实参和形参共同指向的对象的name值,如图:

 

所以实参p.name的值输出的值为alan

现在我们再来分析一下测试二,

当我们调用方法二时ser.changeNameTwo(p);在内存中对象存在的形式和方法一是一样,同样为:

 

直到方法二中的p=new Person();改变了实参和形参指向同一个对象的关系,如图:

 

最后变为:

 

再给形参指向的对象赋值:p.name="alan";

如图:

 

此时由于实参和形参指向的是各自的对象,所以形参pname更改为alan,实参pname并没有改变,扔用chenyan。由上图可知,引用和对象是分开存储在不同的内存中的,引用存在栈内存中,对象存在堆内存中。

三、内存中的堆(stack)与栈(heap)

Java把内存分成两种,一种叫做栈内存,一种叫做堆内存。

在函数中定义的些基本类型的变量和对象的引用变量都是在函数的栈内存中分配。当在一段代码块中定义一个变量时,java就在栈中为这个变量分配内存空间,当超过变量的作用域后,java会自动释放掉为该变量分配的内存空间,该内存空间可以立刻被另作他用。堆内存用于存放由new创建的对象和数组

 

java中内存分配策略及堆和栈的比较

 

1内存分配策略

 

按照编译原理的观点,程序运行时的内存分配有三种策略,分别是静态的,栈式的,和堆式的。

静态存储分配是指在编译时就能确定每个数据目标在运行时刻的存储空间需求,因而在编译时就可以给他们分配固定的内存空间。这种分配策略要求程序代码中不允许有可变数据结构(比如可变数组)的存在,也不允许有嵌套或者递归的结构出现,因为它们都会导致编译程序无法计算准确的存储空间需求。

栈式存储分配也可称为动态存储分配,是由一个类似于堆栈的运行栈来实现的。和静态存储分配相反,在栈式存储方案中,程序对数据区的需求在编译时是完全未知的,只有到运行的时候才能够知道,但是规定在运行中进入一个程序模块时,必须知道该程序模块所需的数据区大小才能够为其分配内存。和我们在数据结构所熟知的栈一样,栈式存储分配按照先进后出的原则进行分配。

静态存储分配要求在编译时能知道所有变量的存储要求,栈式存储分配要求在过程的入口处必须知道所有的存储要求,而堆式存储分配则专门负责在编译时或运行时模块入口处都无法确定存储要求的数据结构的内存分配,比如可变长度串和对象实例。堆由大片的可利用块或空闲块组成,堆中的内存可以按照任意顺序分配和释放。

 

2 堆和栈的比较

 

从堆和栈的功能和作用来通俗的比较,堆主要用来存放对象的,栈主要是用来执行程序的。而这种不同又主要是由于堆和栈的特点决定的:

在编程中,例如C/C++中,所有的方法调用都是通过栈来进行的,所有的局部变量,形式参数都是从栈中分配内存空间的。实际上也不是什么分配,只是从栈顶向上用就行,就好像工厂中的传送带(conveyor belt)一样,Stack Pointer会自动指引你到放东西的位置,你所要做的只是把东西放下来就行。退出函数的时候,修改栈指针就可以把栈中的内容销毁。这样的模式速度最快,当然要用来运行程序了。需要注意的是,在分配的时候,比如为一个即将要调用的程序模块分配数据区时,应事先知道这个数据区的大小,也就说是虽然分配是在程序运行时进行的,但是分配的大小多少是确定的,不变的,而这个"大小多少"是在编译时确定的,不是在运行时。

堆是应用程序在运行的时候请求操作系统分配给自己内存,由于从操作系统管理的内存分配,所以在分配和销毁时都要占用时间,因此用堆的效率非常低。但是堆的优点在于,编译器不必知道要从堆里分配多少存储空间,也不必知道存储的数据要在堆里停留多长的时间,因此,用堆保存数据时会得到更大的灵活性。事实上,面向对象的多态性,堆内存分配是必不可少的,因为多态变量所需的存储空间只有在运行时创建了对象之后才能确定。在C++中,要求创建一个对象时,只需用 new命令编制相关的代码即可。执行这些代码时,会在堆里自动进行数据的保存。当然,为达到这种灵活性,必然会付出一定的代价:在堆里分配存储空间时会花掉更长的时间!这也正是导致效率低的原因。

 

另外,栈有一个很重要的特殊性,就是存在栈中的数据可以共享。假设我们同时定义:

 int a = 3

      int b=3

编译器先处理int a = 3;首先它会在栈中创建一个变量为a的引用,然后查找有没有字面值为3的地址,没找到,就开辟一个存放3这个字面值的地址,然后将a指向3的地址。接着处理int b = 3;在创建完b这个引用变量后,由于在栈中已经有3这个字面值,便将b直接指向3的地址。这样,就出现了ab同时均指向3的情况。

   【上文提到了"引用+数值+内存地址"这三个名词,其中变量名就是引用,给变量赋的值就是数值,而所提到的内存是抽象的内容,让引用指向的不是数值,而是存取数值的那块内存地址】定义完ab的值后,再令a = 4;那么,b不会等于4,还是等于3。在编译器内部,遇到时,它就会重新搜索栈中是否有4的字面值,如果没有,重新开辟地址存放4的值;如果已经有了,则直接将a指向这个地址。因此a值的改变不会影响到b的值。【定义变量,给变量赋值,然后在编译的过程中就可以将其保存在内存中了】。

分享到:
评论

相关推荐

    C语言嵌入式Linux编程第4期:堆栈管理

    1)程序运行过程中堆栈的内存分布2)栈初始化、大小、栈在函数调用和参数传递过程中的作用3)栈与作用域、栈对形参和实参的管理4)黑客栈溢出攻击原理及实践5)堆内存的维护、嵌入式裸机环境下、ucos、linux环境下...

    C#运行时相互关系浅析

    线程栈:在一个进程中可能包含多个线程,一个线程在创建的时候,会分配到一个大小1MB大小的栈,栈用于存储方法的实参、形参以及方法内部的局部变量,栈是从高位内存地址向地位地址构建的,由于栈有先进后出的特点,...

    C++ Primer第四版【中文高清扫描版】.pdf

    7.8.2 函数匹配与实参转换 231 7.8.3 重载确定的三个步骤 232 7.8.4 实参类型转换 234 7.9 指向函数的指针 237 小结 239 术语 240 第8章 标准IO库 243 8.1 面向对象的标准库 244 8.2 条件状态 247 8.3 输出缓冲区的...

    Java程序设计基础:方法的值传递.pptx

    方法调用时,需要提供实参,它们必须与方法签名中所对应的形参次序相同,这称作参数顺序匹配。 参数顺序匹配 public static void nPrintln(String message , int n){ for(int i = 0 ; i; i ++) System.out.println...

    c/c++ 学习总结 初学者必备

    而其他类型如int作为参数时,由于函数参数值实质上是实参的一份拷贝,被调函数内部对形参的改变并不影响实参的值。 22、数据结构和算法: A:查找: (1)二分法查找; B:写出下列算法的时间复杂度和实现排序: (1)...

    C语言讲义.doc

    9.2 函数的形参与实参 45 9.3 函数的返回类型与返回值 46 9.4 MAIN函数与EXIT函数与函数的RETURN语句 46 9.5 多个源代码文件程序的编译 47 9.5.1 头文件的使用 47 9.5.2 #include与#define的意义 47 9.5.3 #ifndef与...

    关于C++中值传递和引用传递的总结

    因为函数内部参数在内存栈中进行分配,所以当函数返回时,会至少调用一次析构函数来回收资源。  拷贝构造函数在以下情况下会被调用:  1. 当用类的一个对象去初始化类的另一个对象时。  2. 如果函数的形参是类...

    宋劲彬的嵌入式C语言一站式编程

    1. 内存与地址 2. CPU 3. 设备 4. MMU 5. Memory Hierarchy 18. x86汇编程序基础 1. 最简单的汇编程序 2. x86的寄存器 3. 第二个汇编程序 4. 寻址方式 5. ELF文件 5.1. 目标文件 5.2. 可执行文件 19. 汇编与C之间的...

    125集专攻JAVA基础 JAVA零基础入门学习视频教程 动力节点JAVA视频教程.txt

    北京动力节点-Java编程零基础教程-114-Java基本语法-方法详解-方法的形参与实参.avi 北京动力节点-Java编程零基础教程-115-Java基本语法-方法详解-方法的参数与返回值的应用场景.avi 北京动力节点-Java编程零基础...

    Visual C++ 2010入门经典(第5版)--源代码及课后练习答案

    8.1.3 析构函数与动态内存分配 366 8.2 实现复制构造函数 369 8.3 在变量之间共享内存 370 8.3.1 定义联合 371 8.3.2 匿名联合 372 8.3.3 类和结构中的联合 372 8.4 运算符重载 373 8.4.1 实现重载的运算符 ...

    新手学习C++入门资料

    主体:(一)与C语言的区别> 一、C++概述 (一) 发展历史 1980年,Bjarne Stroustrup博士开始着手创建一种模拟语言,能够具有面向对象的程序设计特色。在当时,面向对象编程还是一个比较新的理念,Stroustrup博士并...

    java面试题

    Struts1只是在第一次请求的时候创建一个action实例,以后每次相同的请求都直接从内存中去读取,它是单例模式,安全性较差。 Struts2是如何实现MVC模式的? 答:在Struts2里面是将每次页面的请求进行处理,然后将请求...

    Java开发技术大全(500个源代码).

    invokeByObject.java 对象实参传递示例程序 invokeByValue.java 传值调用示例程序 invokeMethod.java 同一个类中调用方法示例 invokeOther.java 类的外部调用方法示例 invokeStaticMethod.java 调用静态方法...

    Linux c编程一站式学习

    3.3. 形参和实参................................................32 3.4. 局部变量与全局变量........................................34 第 4 章 分支语句...............................................40 ...

Global site tag (gtag.js) - Google Analytics