tft每日頭條

 > 生活

 > 如何理解堆和棧

如何理解堆和棧

生活 更新时间:2024-11-25 16:51:42

前言

作為一名程序員,許多同學會把堆和棧這兩個字,變成一個詞放在一起,其實這是錯誤的。

堆(heap)

堆是一個内存空間,這個内存可以由程序員分配和釋放,當然部分語言自帶 GC( Garbage Collection 垃圾回收),部分堆内存可以由 GC 回收。這裡千萬要注意,這裡說的「堆」數據結構 裡面說的「堆」不是同一個概念,大家千萬不要混淆。

堆是程序在運行的時候請求操作系統分配給自己内存。由于從操作系統管理的内存分配,所以在分配和銷毀時都要占用時間,因此用堆的效率相對棧來說略低。但是堆的優點在于,編譯器不必知道要從堆裡分配多少内存空間,也不必知道存儲的數據要在堆裡停留多長的時間,因此用堆保存數據時會得到更大的靈活性。所以為達到這種靈活性,在堆裡分配存儲空間時會花相對更長的時間,這也是效率低于棧的原因。

棧(stack)

棧是由編譯器自動分配和釋放的,存放函數的參數值,局部變量的值等。也請注意,這裡說的「棧」不是 數據結構 中的「棧」,大家千萬不要混淆。這裡請注意,棧是由由系統自動分配。

棧的優勢是存取速度比堆要快,僅次于寄存器,棧數據可以共享。但缺點是,存在棧中的數據大小與生存期必須是确定的,缺乏靈活性。

說的再通俗點

使用棧就像我們去飯館裡吃飯,隻管點菜(發出申請)、付錢、和吃(使用),吃飽了就走,不必理會切菜、洗菜等準備工作和洗碗、刷鍋等掃尾工作,他的好處是快捷,但是自由度小。

使用堆就像是自己動手做喜歡吃的菜肴,比較麻煩,但是比較符合自己的口味,而且自由度大。

實際應用

看完上面一段文字,大多數同學自然就懂了,而不懂的,還是一臉懵。這些東西我知道了有什麼實際意義?比如棧既然是系統分配和釋放,我幹預不了,我知道它幹什麼?那這裡就以編程語言 C#(C Sharp)來舉例說明。也可以看相應的 LeetBook

如何理解堆和棧(堆和棧它們的内涵有多深)1

堆、棧的實際意義

堆、棧,算是相對底層一些的内容,大部分的語言其實這部分内容都是類似的。如果你不會 C# 語言也能看懂~。

我們都知道,C# 的數據類型有兩大類型 —— 值類型引用類型。大緻結構如下圖:

如何理解堆和棧(堆和棧它們的内涵有多深)2

值類型我們看到包含内置值類型、用戶定義的值類型和枚舉。枚舉就不用說了,内置值類型指的是 int、float、bool、double 等等。

用戶定義的值類型指的就是 struct。引用類型大概我們學 C 語言的時候學到的指針類型(C# 做了封裝,所以我們看不到 int * 這類的代碼,當然可以使用 unsafe 關鍵字,這裡就不多贅述)、接口類型、用戶自定義的類(class),數組等。

如何理解堆和棧(堆和棧它們的内涵有多深)3

類型的含義

這裡為什麼要解釋這兩個大的類型?首先是因為值類型是放在棧上的,值類型變量聲明後,不管是否已經賦值,編譯器為其分配内存。然而引用類型當聲明一個變量時,隻在棧中分配一小片内存用于容納一個地址,而此時并沒有為其分配堆上的内存空間。當使用 new 創建一個類的實例時,這時會分配堆上的空間,并把堆上空間的地址保存到棧上分配的小片空間中。

通俗一點,堆上存放的是我們聲明出來的實例對象,當我們需要訪問這個實例對象時,我們找到它的方法是先找到變量對應在棧上的内存,然後通過棧上存放的數據,我們才能找到其真正的實例在堆的任意位置。

代碼

class A{ public A(int x){ X = x; } public int X { get; set; } } static void Main(string[] args){ int m = 1; int n = m; m = 2; Console.WriteLine($"m = {m}, n = {n}"); A a = new A(1); var b = a; a.X = 2; Console.WriteLine($"a.X = {a.X}, b.X = {b.X}"); }

我們會發現輸出結果是

m = 2, n = 1 a.X = 2, b.X = 2

我們發現 m 和 n 的值是分别獨立的,而 a 和 b 修改其中一個會修改兩個值。這是因為 m 和 n 都是分配在棧上的,而 a 和 b 雖然也是在棧上,但是其隻是存儲的堆上的一個地址索引,我們不管通過 a 還是 b 索引到的堆上的内存都是同一份。

我們再來看值類型和引用類型,在棧上的數據訪問快,在堆上的數據訪問相對慢,因此,當我們開發過程中,通過需求,比如底層的一些不變數據,完全可以有 struct 來實現,因為其不會為 null,符合值類型的要求,而且我們經常訪問會更快一些。

我們看上圖發現,值類型和引用類型都是繼承自 Object ,也就是說:

Object a = 1; int b = (int)a;

這兩個都是合法語句。但是這裡面隐藏了一個非常常見的一個現象,那就是裝箱和拆箱。

  • 什麼是拆裝箱?

裝箱實際上是将值類型轉換為引用類型,而拆箱是将引用類型轉換為值類型。再透徹點,實際上裝箱拆箱就是棧内存和堆内存的來回拷貝和賦值,這不僅僅浪費性能,而且還會造成沒必要的 GC。因此,能避免 “裝箱”、“拆箱” 操作的就盡量避免。除上面所說的這些數據類型,還有一個寫代碼必不可少的,那就是函數。

函數是如何調用的?實際上函數調用的參數是通過棧空間來傳遞,在調用過程中會占用線程的棧資源。

當我們使用遞歸算法時,每次雖然調用的是同一個函數,但是會在棧中占用一個空間,隻有走到最後的結束點後函數才能依次退出,而未到達最後的結束點之前,占用的棧空間一直沒有釋放。因此如果遞歸調用次數過多,就可能導緻占用的棧資源超過線程的最大值,從而導緻棧溢出,這也是為什麼一定要盡量少用遞歸的原因。

寫在最後

上面說了很多,側面說明了堆、棧的應用的優缺點,平時應用中需要更加了解我們寫的代碼都“幹了什麼”,這樣才有可能寫出更高效、更可靠的代碼。對于一名程序員來說,不管未來編程語言發展的多麼容易上手,一定不要忘了學習計算機的基礎知識,哪怕是先學會了編程再返回來補習這些知識。祝大家工作順利、評論留言少 BUG !點贊轉發不脫發!

BY /

本文作者:力扣

聲明:本文歸“力扣”版權所有,如需轉載請聯系。

,

更多精彩资讯请关注tft每日頭條,我们将持续为您更新最新资讯!

查看全部

相关生活资讯推荐

热门生活资讯推荐

网友关注

Copyright 2023-2024 - www.tftnews.com All Rights Reserved