我们已经准备好了,你呢?

我们与您携手共赢,为您的企业形象保驾护航!

在计算机领域,堆栈是一个不容忽视的概念,堆栈是一种数据结构。堆栈都是一种数据项按序排列的数据结构,只能在一端对数据项进行插入和删除。在单片机应用中,堆栈是个特殊的存储区,主要功能是暂时存放数据和地址,通常用来保护断点和现场。

在计算机领域,堆栈是一个不容忽视的概念,堆栈是一种数据结构。堆栈都是一种数据项按序排列的数据结构,只能在一端(称为栈顶(top))对数据项进行插入和删除。在单片机应用中,堆栈是个特殊的存储区,主要功能是暂时存放数据和地址,通常用来保护断点和现场。

简介

堆栈是一个特定的存储区或寄存器,它的一端是固定的,另一端是浮动的。对这个存储区存入的数据,是一种特殊的数据结构。所有的数据存入或取出,只能在浮动的一端(称栈顶)进行,严格按照“先进后出”的原则存取,位于其中间的元素,必须在其栈上部(后进栈者)诸元素逐个移出后才能取出。在内存储器(随机存储器)中开辟一个区域作为堆栈,叫软件堆栈;用寄存器构成的堆栈,叫硬件堆栈。

单片机应用中,堆栈是个特殊存储区,堆栈属于 RAM 空间的一部分,堆栈用于函数调用、中断切换时保存和恢复现场数据。堆栈中的物体具有一个特性:第一个放入堆栈中的物体总是被最后拿出来, 这个特性通常称为先进后出 (FILO—First-In/Last-Out)。 堆栈中定义了一些操作, 两个最重要的是 PUSH 和 POP。 PUSH(入栈)操作:堆栈指针(SP)加 1,然后在堆栈的顶部加入一 个元素。POP(出栈)操作相反,出栈则先将 SP 所指示的内部 ram 单元中内容送入直接地址寻址的单元中(目的位置),然后再将堆栈指针(SP)减 1。这两种操作实现了数据项的插入和删除。

对比分析

堆栈是计算机科学领域重要的数据结构,它被用于多种数值计算领域。表达式求值是编译程序中较为常见的操作,在算术表达式求值的过程中,需要使用堆栈来保存表达式的中间值和运算符,堆栈使得表达式的中间运算过程的结果访问具有了一定的自动管理能力。大部分编译型程序设计语言具有程序递归特性,递归能够增强语言的表达能力和降低程序设计难度。递归程序的递归深度通常是不确定的,需要将子程序执行的返回地址保存到堆栈这种先进后出式的结构中,以保证子程序的返回地址的正确使用顺序。函数式程序设计语言中,不同子函数的参数的种类和个数是不相同的,编译器也是使用堆栈来存储子程序的参数。

堆栈空间分配

栈(操作系统):由操作系统自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。

堆(操作系统): 一般由程序员分配释放, 若程序员不释放,程序结束时可能由 OS(操作系统)回收,分配方式倒是类似于链表。

区别介绍

Java

1. 栈(stack)与堆(heap)都是 Java 用来在 Ram 中存放数据的地方。与 C++不同,Java 自动管理栈和堆,程序员不能直接地设置栈或堆。

2. 栈的优势是,存取速度比堆要快,仅次于直接位于 CPU 中的寄存器。但缺点是,存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。另外,栈数据在多个线程或者多个栈之间是不可以共享的,但是在栈内部多个值相等的变量是可以指向一个地址的,详见第 3 点。堆的优势是可以动态地分配内存大小,生存期也不必事先告诉编译器,Java 的垃圾收集器会自动收走这些不再使用的数据。但缺点是,由于要在运行时动态分配内存,存取速度较慢。

3.Java 中的数据类型有两种。

一种是基本类型(primitivetypes), 共有 8 种,即 int,short, long, byte, float, double, boolean, char(注意,并没有 string 的基本类型)。这种类型的定义是通过诸如 int a= 3; long b = 255L;的形式来定义的,称为自动变量。值得注意的是,自动变量存的是字面值,不是类的实例,即不是类的引用,这里并没有类的存在。如 int a= 3; 这里的 a 是一个指向 int 类型的引用,指向 3 这个字面值。这些字面值的数据,由于大小可知,生存期可知(这些字面值固定定义在某个程序块里面,程序块退出后,字段值就消失了),出于追求速度的原因,就存在于栈中。

另外,栈有一个很重要的特殊性,就是存在栈中的数据可以共享。假设我们同时定义:
int a=3;
int b=3;
编译器先处理 int a= 3;首先它会在栈中创建一个变量为 a 的内存空间,然后查找有没有字面值为 3 的地址,没找到,就开辟一个存放 3 这个字面值的地址,然后将 a 指向 3 的地址。接着处理 int b= 3;在创建完 b 的引用变量后,由于在栈中已经有 3 这个字面值,便将 b 直接指向 3 的地址。这样,就出现了 a 与 b 同时均指向 3 的情况。

特别注意的是,这种字面值的引用与类对象的引用不同。假定两个类对象的引用同时指向一个对象,如果一个对象引用变量修改了这个对象的内部状态,那么另一个对象引用变量也即刻反映出这个变化。相反,通过字面值的引用来修改其值,不会导致另一个指向此字面值的引用的值也跟着改变的情况。如上例,我们定义完 a 与 b 的值后,再令 a=4;那么,b 不会等于 4,还是等于 3。在编译器内部,遇到 a=4;时,它就会重新搜索栈中是否有 4 的字面值,如果没有,重新开辟地址存放 4 的值;如果已经有了,则直接将 a 指向这个地址。因此 a 值的改变不会影响到 b 的值。

另一种是包装类数据,【如 Integer,String, Double 等将相应的基本数据类型包装起来的类。这些类数据全部存在于【堆】中】,Java 用 new()语句来显示地告诉编译器,在运行时才根据需要动态创建,因此比较灵活,但缺点是要占用更多的时间。 4.String 是一个特殊的包装类数据。即可以用 String str = new String("abc");的形式来创建,也可以用 String str = "abc";的形式来创建(作为对比,在 JDK 5.0 之前,你从未见过 Integer i = 3;的表达式,因为类与字面值是不能通用的,除了 String。而在 JDK5.0 中,这种表达式是可以的!因为编译器在后台进行 Integer i = new Integer(3)的转换)。前者是规范的类的创建过程,即在 Java 中,一切都是对象,而对象是类的实例,全部通过 new()的形式来创建。Java 中的有些类,如 DateFormat 类,可以通过该类的 getInstance()方法来返回一个新创建的类,似乎违反了此原则。其实不然。该类运用了单例模式来返回类的实例,只不过这个实例是在该类内部通过 new()来创建的,而 getInstance()向外部隐藏了此细节。那为什么在 String str = "abc";中,并没有通过 new()来创建实例,是不是违反了上述原则?其实没有。

4. 关于 String str = "abc"的内部工作。Java 内部将此语句转化为以下几个步骤:【String str = "abc",String str 不要连着】

(1)先定义一个名为 str 的对 String 类的对象引用变量:String str;

(2)【在【栈】中查找有没有存放值为"abc"的地址,如果没有,则开辟一个存放字面值为"abc"的地址,接着创建一个新的 String 类的对象 o,并将 o 的字符串值指向这个地址,而且在栈中这个地址旁边记下这个引用的对象 o。如果已经有了值为"abc"的地址,则查找对象 o,并返回 o 的地址。】【上文说数据时存放在堆中,此文说数据存放在栈中】[因为此处不是通过 new()创建的啊]

(3)将 str 指向对象 o 的地址。

值得注意的是,一般 String 类中字符串值都是直接存值的。但像 String str = "abc";这种场合下,其字符串值却是保存了一个指向存在栈中数据的引用!

为了更好地说明这个问题,我们可以通过以下的几个代码进行验证。  复制内容到剪贴板代码:
String str1="abc";
String str2="abc";
System.out.println(str1==str2);//true
注意,我们这里并不用 str1.equals(str2);的方式,因为这将比较两个字符串的值是否相等。==号,根据 JDK 的说明,只有在两个引用都指向了同一个对象时才返回真值。而我们在这里要看的是,str1 与 str2 是否都指向了同一个对象。  结果说明,JVM 创建了两个引用 str1 和 str2,但只创建了一个对象,而且两个引用都指向了这个对象。

我们再来更进一步,将以上代码改成:  复制内容到剪贴板代码:
String str1="abc";
String str2="abc";
str1="bcd";
System.out.println(str1+","+str2);//bcd,abc
System.out.println(str1==str2);//false
这就是说,赋值的变化导致了类对象引用的变化,str1 指向了另外一个新对象!而 str2 仍旧指向原来的对象。上例中,当我们将 str1 的值改为"bcd"时,JVM 发现在栈中没有存放该值的地址,便开辟了这个地址,并创建了一个新的对象,其字符串的值指向这个地址。

事实上,String 类被设计成为不可改变(immutable)的类。如果你要改变其值,可以,但 JVM 在运行时根据新值悄悄创建了一个新对象,然后将这个对象的地址返回给原来类的引用。这个创建过程虽说是完全自动进行的,但它毕竟占用了更多的时间。在对时间要求比较敏感的环境中,会带有一定的不良影响。

再修改原来代码:  复制内容到剪贴板代码:
String str1="abc";
String str2="abc";
str1="bcd";
String str3=str1;
System.out.println(str3);//bcd
String str4="bcd";
System.out.println(str1==str4);//true
我们再接着看以下的代码。  复制内容到剪贴板代码:  String str1 = new String("abc");  String str2 = "abc";  System.out.println(str1==str2); //false

String str1 = "abc";  String str2 = new String("abc");  System.out.println(str1==str2); //false  创建了两个引用。创建了两个对象。两个引用分别指向不同的两个对象。

以上两段代码说明,只要是用 new()来新建对象的,都会在堆中创建,而且其字符串是单独存值的,即使与栈中的数据相同,也不会与栈中的数据共享。

5. 数据类型包装类的值不可修改。不仅仅是 String 类的值不可修改,所有的数据类型包装类都不能更改其内部的值。

6. 结论与建议:

(1)我们在使用诸如 String str = "abc";的格式定义类时,总是想当然地认为,我们创建了 String 类的对象 str。担心陷阱!对象可能并没有被创建。可以肯定的是,指向 String 类的引用被创建了。至于这个引用到底是否指向了一个新的对象,必须根据上下文来考虑,除非你通过 new()方法来显要地创建一个新的对象。因此,更为准确的说法是,我们创建了一个指向 String 类的对象的引用变量 str,这个对象引用变量指向了某个值为"abc"的 String 类。清醒地认识到这一点对排除程序中难以发现的 bug 是很有帮助的。

(2)使用 String str = "abc";的方式,可以在一定程度上提高程序的运行速度,因为 JVM 会自动根据栈中数据的实际情况来决定是否有必要创建新对象。而对于 Stringstr = new String("abc");的代码,则一概在堆中创建新对象,而不管其字符串值是否相等,是否有必要创建新对象,从而加重了程序的负担。这个思想应该是享元模式的思想,但 JDK 的内部在这里实现是否应用了这个模式,不得而知。

(3)当比较包装类里面的数值是否相等时,用 equals()方法;当测试两个包装类的引用是否指向同一个对象时,用==。

(4)由于 String 类的 immutable 性质,当 String 变量需要经常变换其值时,应该考虑使用 StringBuffer 类,以提高程序效率

理论知识

申请方式

stack:

由系统自动分配。 例如,声明在函数中一个局部变量 int b; 系统自动在栈中为 b 开辟空间。

heap:

需要程序员自己申请,并指明大小,在 c 中 malloc 函数

如 p1 = (char *)malloc(10);

在 C++中用 new 运算符

如 p2 = new char[10];//(char *)malloc(10);

但是注意 p1、p2 本身是在栈中的。

免责声明:本站内容(文字信息+图片素材)来源于互联网公开数据整理或转载,仅用于学习参考,如有侵权问题,请及时联系本站删除,我们将在5个工作日内处理。联系邮箱:chuangshanghai#qq.com(把#换成@)

我们已经准备好了,你呢?

我们与您携手共赢,为您的企业形象保驾护航!

在线客服
联系方式

热线电话

132-7207-3477

上班时间

周一到周五

二维码
线