1. 程式人生 > >棧、堆、隊列

棧、堆、隊列

生命周期 程序代碼 字符串 avi width 空間 instr 執行 cli

1 數據結構–棧和隊列


1. 棧

1.1 棧的定義
棧是一種特殊的線性表。其特殊性在於限定插入和刪除數據元素的操作只能在線性表的一端進行。如下圖所示:
技術分享圖片
1.2 棧的特點:後進先出的特點(Last in first out);

1.3 棧的聲明:
stack< int > stk;

1.4 棧的操作

    s.empty()               如果棧為空返回true,否則返回false  
    s.size()                返回棧中元素的個數  
    s.pop()                 刪除棧頂元素但不返回其值  
    s.top()                 返回棧頂的元素,但不刪除該元素  
    s.push()                在棧頂壓入新元素  

1.5 棧的補充知識
棧是線性表,因此線性表的存儲結構對棧也是適用的。通常棧有順序棧和鏈棧兩種存儲結構。
1)順序棧 :有上溢和下溢的概念。順序棧可以比作是一個盒子裏裝了很多書,第一本拿出的書就是你最後放的書。當書本放不下了,這時就是上溢。上溢就是棧頂指針指出棧的外面。下溢本身就表示空棧。
2)鏈棧沒有上溢,並且鏈棧沒有頭結點。由於棧的特點,後進先出,所以所有操作都是在頭結點完成的。
技術分享圖片

2 隊列

2.1 隊列定義
隊列(Queue)是一種運算受限的線性表,它的與棧不同的地方在於,是兩頭都受到限制,插入只能在表的一端進行(只進不出),此處稱之為隊頭(Front);而刪除只能在表的另一端進行(只出不進),這一端稱之為隊尾(rear)。

2.2 隊列的特點:先進先出(排隊原則)

2.3 隊列的聲明
queue < int> q;

2.4 隊列的操作

    q.empty()               如果隊列為空返回true,否則返回false  
    q.size()                返回隊列中元素的個數  
    q.pop()                 刪除隊列首元素但不返回其值  
    q.front()               返回隊首元素的值,但不刪除該元素  
    q.push()                在隊尾壓入新元素  
    q.back()                返回隊列尾元素的值,但不刪除該元素  

2.5 隊列的補充知識
隊列也存在順序存儲和鏈式存儲兩種存儲結構,前者稱順序隊列,後者為鏈隊。
1)順序隊列: 在一段連續的存儲空間,由一個隊列頭指針和一個尾指針表示這個隊列,當頭指針和尾指針指向同一個位置是,隊列為空。當出隊操作時,頭指針向空間尾部增加一個位置;入隊時,尾指針向前增加一個位置。
順序隊列示意圖如下:
技術分享圖片

記住:元素進隊,尾指針後移,元素出隊,頭指針後移。


2 程序的內存分配

2.1 序言
一個由C/C++編譯的程序占用的內存分為以下幾個部分
1)棧區(stack)– 由編譯器自動分配釋放,存放函數的參數值,局部變量的值等。其操作方式類似於數據結構的棧。
2)堆區(heap)–堆區的數據一般由程序員分配釋放。
3)全局區(靜態區)(static)–全局變量和靜態變量的存儲是放在一塊區域的。初始化的全局變量和靜態變量放在一塊區域,未初始化的全局變量和靜態變量放在相鄰的區域。–程序結束後由系統自動釋放。
4)文字常量區–常量字符串等常量放在這裏。程序結束釋放。
5)程序代碼區–存放函數體的二進制代碼。

具體分析如下:
技術分享圖片
由上圖可以看出,此可執行程序在存儲時分為代碼區(text)、數據區(data)和未初始化數據區(bss)。
(1)代碼區(text segment),存放CPU執行的機器指令(machine instructions)。通常,代碼區是可共享的(即另外的執行程序可以調用它),因為對於頻繁被執行的程序,只需要在內存中有一份代碼即可。

(2)全局初始化數據區/靜態數據區(initialized data segment/data segment)。該區包含了在程序中明確被初始化的全局變量、靜態變量(包括全局靜態變量和局部靜態變量)和常量數據(如字符串常量)。
例如,一個不在任何函數內的聲明(全局數據),使得變量maxcount根據其初始值被存儲到初始化數據區中:
技術分享圖片
聲明一個靜態數據,如果是在任何函數體外聲明,則表示其為一個全局靜態變量,如果在函數體內(局部),則表示其為一個局部靜態變量。另外,如果在函數名前加上static,則表示此函數只能在當前文件中被調用。
技術分享圖片

(3)未初始化數據區。亦稱BSS區(uninitialized data segment),存入的是全局未初始化變量。BSS這個叫法是根據一個早期的匯編運算符而來,這個匯編運算符標誌著一個塊的開始。BSS區的數據在程序開始執行之前被內核初始化為0或者空指針(NULL)。例如一個不在任何函數內的聲明:
技術分享圖片

下面展示C程序的內存布局:
下圖所示為可執行代碼存儲時結構和運行時結構的對照圖。一個正在運行著的C編譯程序占用的內存分為代碼區、初始化數據區、未初始化數據區、堆區和棧區5個部分。
技術分享圖片

(1)代碼區(text segment)。代碼區指令根據程序設計流程依次執行,對於順序指令,則只會執行一次(每個進程),如果反復,則需要使用跳轉指令,如果進行遞歸,則需要借助棧來實現。

(2)*全局初始化數據區/靜態數據區(Data Segment)。只初始化一次*。

(3)未初始化數據區(BSS)。在運行時改變其值。

(4)棧區(stack)。由編譯器自動分配釋放,存放函數的參數值、局部變量的值等。每當一個函數被調用,該函數返回地址和一些關於調用的信息,比如某些寄存器的內容,被存儲到棧區。

(5)堆區(heap)。用於動態內存分配。堆在內存中位於bss區和棧區之間。一般由程序員分配和釋放,若程序員不釋放,程序結束時有可能由OS回收。

記憶方式:
堆區和棧區是最基礎的兩種內存分配結構,堆區是程序員手動申請釋放,如malloc;棧區是系統自動釋放;
還有三種內存結構比較特殊。分別是代碼區和未初始化全局數據區(BSS)和全局初始化的數據區(還包括靜態數據區)

分成這麽多個區域,主要基於以下考慮:
一個進程在運行過程中,代碼是根據流程依次執行的,只需要訪問一次,當然跳轉和遞歸有可能使代碼執行多次,而數據一般都需要訪問多次,因此單獨開辟空間以方便訪問和節約空間。
臨時數據及需要再次使用的代碼在運行時放入棧區中,生命周期短。
全局數據和靜態數據有可能在整個程序執行過程中都需要訪問,因此單獨存儲管理。
堆區由用戶自由分配,以便管理。
2.2 舉例描述

int a = 0; //a在全局已初始化數據區
char *p1; //p1在BSS區(未初始化全局變量)
main()
{
int b; //b在棧區
char s[] = “abc”; //s為數組變量,存儲在棧區,
//”abc”為字符串常量,存儲在已初始化數據區
char *p1,p2; //p1、p2在棧區
char *p3 = “123456”; //123456\0在已初始化數據區,p3在棧區
static int c =0; //C為全局(靜態)數據,存在於已初始化數據區
//另外,靜態數據會自動初始化
p1 = (char *)malloc(10);//分配得來的10個字節的區域在堆區
p2 = (char *)malloc(20);//分配得來的20個字節的區域在堆區
free(p1);
free(p2);
}

2.3 棧和堆申請方式的區別
棧:由系統自動分配
ep: int b ; //系統自動在棧中為b開辟空間
堆:程序員自己申請空間,指明大小
ep: p1 =(char * )malloc (10); //c中申請空間
p2=new char[10]; //C++中申請空間
註意:p1,p2本身在棧中,只是申請的空間在堆中。

2.4 申請後系統的響應
棧:棧的剩余空間大於申請空間,系統提供內存,反之提示棧溢出。
堆:程序申請,尋找第一個空間大於申請空間的堆結點,刪除空閑結點並將該結點分配給程序。代碼中的delete語句才能正確釋放本內存空間。

2.5 申請大小的限制
棧:棧頂的地址和棧的最大空間是系統預先規定好的,如果申請空間超過棧的剩余空間,將提示overflow。因此,棧空間較小。
堆:堆是向高地址擴展的數據結構,是不連續的內存區域。

2.6 申請效率的比較:
棧:由系統自動分配,速度較快。
堆:由new 分配的內存,速度慢,易產生碎片,但是方便。

2.7 堆和棧中的存儲內容
棧:第一個進棧的是主函數中下一條指令,然後是函數各參數,然後是局部變量。註意靜態變量不入棧。函數結束後,局部變量先出棧,然後是參數,最後是地址。
堆:由程序員安排。

2.8 小結
打比喻總結堆和棧在內存存儲方面的區別:
棧就像區飯店吃飯,只點菜(申請),吃菜(使用)和付錢。其余的工作都是由系統完成,速度快。
堆就是自己動手做飯,麻煩但自由。

[附]:

堆棧向上增長和向下增長

問題解決:

技術分享圖片

堆棧增長演示:

技術分享圖片

上圖顯示了堆棧 向上增長和向下增長的區別。

如果堆棧是向下增長,也就是從高地址向低地址增長,那麽在任務剛開始創建後,堆棧是空的。如圖中例子,棧頂在為TaskStk[0][511],棧底為在TaskStk[0][0]。相反,如果堆棧是向上增長的,棧頂在為TaskStk[0][0],棧底為在TaskStk[0][511]。

那麽,如果我們向堆棧中壓入數據,例如推入0x0012ff78後,堆棧變化如下圖:

技術分享圖片

如圖,壓棧後,若堆棧向下增長,在原來棧頂位置插入數據0x0012ff78,然後棧頂位置向低地址方向移4個字節,指向TaskStk[0][510]。若堆棧向上增長,在原來棧頂位置壓入0x0012ff78,棧頂變為TaskStk[0][1]。

註意:

堆棧中向上增長----低地址向高地址方向增長。

向下增長----高地址向低地址方向增長。

堆棧中數據插入、刪除遵循 後進先出的規則,因此插入數據向上增長和向下增長需要註意插入的方向

棧、堆、隊列