熱線電話:0755-23712116
郵箱(xiang):contact@legoupos.cn
地址:深圳市(shi)寶安區沙井街(jie)道后亭茅(mao)洲山工業(ye)園(yuan)工業(ye)大廈(sha)全(quan)至科技(ji)創(chuang)新園(yuan)科創(chuang)大廈(sha)2層2A
我(wo)們都知道(dao),計算機是處理數據(ju)的設備,而數據(ju)的主(zhu)要(yao)存(cun)儲位置就是磁盤和(he)內(nei)存(cun),并且對(dui)于程序(xu)員來講,CPU 和(he)內(nei)存(cun)是我(wo)們必須了解的兩個(ge)物理結(jie)構,它是你通(tong)向高階(jie)程序(xu)員很重要(yao)的橋梁(liang),那么本篇文章(zhang)我(wo)們就來介(jie)紹(shao)一下基本的內(nei)存(cun)知識。
內(nei)存(cun)(Memory)是(shi)(shi)計算(suan)機中(zhong)最重要(yao)的(de)(de)部件之一,它是(shi)(shi)程(cheng)序與CPU進行(xing)(xing)(xing)溝(gou)通的(de)(de)橋梁。計算(suan)機中(zhong)所(suo)有程(cheng)序的(de)(de)運(yun)行(xing)(xing)(xing)都是(shi)(shi)在內(nei)存(cun)中(zhong)進行(xing)(xing)(xing)的(de)(de),因(yin)此(ci)內(nei)存(cun)對計算(suan)機的(de)(de)影(ying)響非常(chang)大,內(nei)存(cun)又(you)被稱為主存(cun),其作用是(shi)(shi)存(cun)放 CPU 中(zhong)的(de)(de)運(yun)算(suan)數(shu)據(ju),以及與硬盤等外部存(cun)儲設備交換的(de)(de)數(shu)據(ju)。只要(yao)計算(suan)機在運(yun)行(xing)(xing)(xing)中(zhong),CPU 就會把需要(yao)運(yun)算(suan)的(de)(de)數(shu)據(ju)調到主存(cun)中(zhong)進行(xing)(xing)(xing)運(yun)算(suan),當運(yun)算(suan)完(wan)成(cheng)后CPU再將結果傳(chuan)送(song)出來,主存(cun)的(de)(de)運(yun)行(xing)(xing)(xing)也(ye)決定了(le)計算(suan)機的(de)(de)穩定運(yun)行(xing)(xing)(xing)。
在(zai)了解一個事物之(zhi)前,你首先得(de)先需要見過它,你才會有印象,才會有想要了解的興趣,所(suo)以(yi)我(wo)們首先需要先看一下什(shen)么(me)是(shi)內存以(yi)及它的物理結構是(shi)怎(zen)樣(yang)的。
內(nei)存(cun)(cun)的(de)內(nei)部(bu)是(shi)由各種IC電路組成的(de),它的(de)種類(lei)很龐(pang)大(da),但是(shi)其主要(yao)分為三種存(cun)(cun)儲器
內(nei)(nei)存 IC 是(shi)一(yi)個完整的(de)結構(gou),它內(nei)(nei)部也有(you)電源、地址信號、數據信號、控制(zhi)信號和用于尋址的(de) IC 引(yin)腳(jiao)來(lai)進(jin)行(xing)數據的(de)讀寫(xie)。下面是(shi)一(yi)個虛擬(ni)的(de) IC 引(yin)腳(jiao)示意圖
圖中 VCC 和 GND 表示電源,A0 - A9 是地址信號的引腳,D0 - D7 表示的是數據信號、RD 和 WR 都是控制信號,我用不同的顏色進行了區分,將電源連接到 VCC 和 GND 后,就可以對其他引腳傳遞 0 和 1 的信號,大多數情況下,+5V 表示1,0V 表示 0。
我們都知道內(nei)存(cun)是(shi)用(yong)來存(cun)儲(chu)數(shu)據,那么這個(ge)內(nei)存(cun) IC 中能存(cun)儲(chu)多少數(shu)據呢?D0 - D7 表示(shi)的(de)是(shi)數(shu)據信號,也就是(shi)說,一次可(ke)以(yi)(yi)輸(shu)(shu)入輸(shu)(shu)出(chu) 8 bit = 1 byte 的(de)數(shu)據。A0 - A9 是(shi)地址(zhi)信號共十個(ge),表示(shi)可(ke)以(yi)(yi)指(zhi)定 00000 00000 - 11111 11111 共 2 的(de) 10次方 = 1024個(ge)地址(zhi)。每個(ge)地址(zhi)都會存(cun)放 1 byte 的(de)數(shu)據,因此我們可(ke)以(yi)(yi)得出(chu)內(nei)存(cun) IC 的(de)容量(liang)就是(shi) 1 KB。
如(ru)果我(wo)們使用的(de)是 512 MB 的(de)內(nei)存,這(zhe)就相(xiang)當于是 512000(512 * 1000) 個(ge)內(nei)存 IC。當然,一臺計算機不太可能有(you)這(zhe)么多個(ge)內(nei)存 IC ,然而,通常(chang)情況下,一個(ge)內(nei)存 IC 會(hui)有(you)更(geng)多的(de)引腳,也(ye)就能存儲(chu)更(geng)多數據。
讓我(wo)(wo)們把關注點放在內(nei)存 IC 對(dui)數據(ju)(ju)的讀寫(xie)過程上來吧!我(wo)(wo)們來看(kan)一個對(dui)內(nei)存IC 進行數據(ju)(ju)寫(xie)入和(he)讀取的模型
來詳細描述一下這(zhe)個過程,假設我們(men)要向內存 IC 中寫入(ru) 1byte 的數據的話(hua),它的過程是這(zhe)樣的:
為了便于記憶,我(wo)們(men)把內(nei)存(cun)模型(xing)映射成為我(wo)們(men)現(xian)實世(shi)(shi)界的(de)模型(xing),在現(xian)實世(shi)(shi)界中(zhong),內(nei)存(cun)的(de)模型(xing)很想我(wo)們(men)生(sheng)活的(de)樓房(fang)。在這個樓房(fang)中(zhong),1層可以存(cun)儲一個字節(jie)的(de)數據,樓層號就(jiu)是地(di)址,下面是內(nei)存(cun)和樓層整(zheng)合的(de)模型(xing)圖
我們知(zhi)道,程序中(zhong)的數(shu)據不僅只有數(shu)值(zhi),還有數(shu)據類型的概念,從內存(cun)上來看,就是占用(yong)內存(cun)大(da)小(占用(yong)樓(lou)層數(shu))的意思(si)。即使物理上強(qiang)制以 1 個字(zi)節為單位(wei)來逐一讀寫數(shu)據的內存(cun),在程序中(zhong),通過指定其數(shu)據類型,也能實(shi)現以特定字(zi)節數(shu)為單位(wei)來進行(xing)讀寫。
下面是一個(ge)以特定字節數為例來讀寫指令(ling)字節的程(cheng)序的示例
// 定(ding)義(yi)變量
char a;
short b;
long c;
// 變量賦值(zhi)
a = 123;
b = 123;
c = 123;
我(wo)們(men)分別聲明了(le)三(san)個變量(liang) a,b,c ,并給(gei)每個變量(liang)賦上(shang)了(le)相同的(de) 123,這(zhe)三(san)個變量(liang)表示內(nei)存的(de)特定區域。通過變量(liang),即使不(bu)指定物理地址(zhi),也可以直接完成讀寫操作,操作系統會自動為變量(liang)分配內(nei)存地址(zhi)。
這三個(ge)(ge)(ge)變量分(fen)別表示(shi) 1 個(ge)(ge)(ge)字節長(chang)度(du)的(de)(de) char,2 個(ge)(ge)(ge)字節長(chang)度(du)的(de)(de) short,表示(shi)4 個(ge)(ge)(ge)字節的(de)(de) long。因此(ci),雖然數據(ju)都表示(shi)的(de)(de)是(shi) 123,但(dan)是(shi)其存(cun)儲時(shi)所(suo)占的(de)(de)內(nei)存(cun)大小是(shi)不一樣(yang)的(de)(de)。如(ru)下所(suo)示(shi)
這里的(de) 123 都沒有(you)超(chao)過每個(ge)類(lei)型的(de)最大長度,所以 short 和 long 類(lei)型為所占(zhan)用的(de)其他內(nei)存空(kong)間分配的(de)數值(zhi)是0,這里我們采用的(de)是低字節序列的(de)方式存儲
低(di)字(zi)節序列:將數(shu)據低(di)位(wei)存儲在(zai)內存低(di)位(wei)地(di)址(zhi)。
高(gao)字節(jie)序(xu)列:將數(shu)據的高(gao)位存(cun)儲在內存(cun)地位的方式稱(cheng)為高(gao)字節(jie)序(xu)列。
指針是(shi) C 語言非常重(zhong)要的(de)特征,指針也是(shi)一種變量,只不(bu)過(guo)(guo)它所表示的(de)不(bu)是(shi)數據(ju)的(de)值(zhi),而是(shi)內存(cun)(cun)的(de)地(di)址。通過(guo)(guo)使(shi)用指針,可以對任意內存(cun)(cun)地(di)址的(de)數據(ju)進行(xing)讀寫(xie)。
在(zai)(zai)了(le)解指針(zhen)(zhen)讀寫的(de)過程前,我(wo)們(men)先需要了(le)解如(ru)何定(ding)義一個指針(zhen)(zhen),和普通的(de)變(bian)(bian)量不同,在(zai)(zai)定(ding)義指針(zhen)(zhen)時,我(wo)們(men)通常會(hui)在(zai)(zai)變(bian)(bian)量名前加一個 * 號(hao)。例如(ru)我(wo)們(men)可以用指針(zhen)(zhen)定(ding)義如(ru)下的(de)變(bian)(bian)量
char *d; // char類(lei)型的指針(zhen) d 定(ding)義(yi)
short *e; // short類型的指(zhi)針 e 定義
long *f; // long類型(xing)的指針 f 定(ding)義(yi)
我(wo)們以32位(wei)計算(suan)機為(wei)(wei)例(li),32位(wei)計算(suan)機的(de)內存地址(zhi)是 4 字節,在這種情況(kuang)下,指針的(de)長(chang)度也是 32 位(wei)。然而,變量 d e f 卻代表(biao)了不(bu)同的(de)字節長(chang)度,這是為(wei)(wei)什(shen)么呢(ni)?
實際上,這些(xie)數(shu)(shu)據(ju)(ju)表(biao)示的是從(cong)內(nei)存(cun)中一(yi)次(ci)讀取的字節數(shu)(shu),比如 d e f 的值都為 100,那(nei)么使用(yong) char 類(lei)型(xing)時就(jiu)(jiu)能夠從(cong)內(nei)存(cun)中讀寫 1 byte 的數(shu)(shu)據(ju)(ju),使用(yong) short 類(lei)型(xing)就(jiu)(jiu)能夠從(cong)內(nei)存(cun)讀寫 2 字節的數(shu)(shu)據(ju)(ju), 使用(yong) long 就(jiu)(jiu)能夠讀寫 4 字節的數(shu)(shu)據(ju)(ju),下面是一(yi)個(ge)完整(zheng)的類(lei)型(xing)字節表(biao)
我(wo)們可以(yi)用圖來描述(shu)一下這個讀(du)寫(xie)過(guo)程
數(shu)(shu)組是指多個相同的(de)(de)數(shu)(shu)據類型在內存中連續排列的(de)(de)一(yi)種形式。作(zuo)為數(shu)(shu)組元(yuan)素的(de)(de)各個數(shu)(shu)據會通過下(xia)標編(bian)號來區分,這個編(bian)號也叫做(zuo)索引,如此一(yi)來,就可以對指定索引的(de)(de)元(yuan)素進行(xing)讀寫操作(zuo)。
首先先來認(ren)識一下(xia)數(shu)(shu)組,我(wo)們(men)還是用(yong) char、short、long 三(san)種元素來定義數(shu)(shu)組,數(shu)(shu)組的元素用(yong)[value] 擴起來,里面的值代(dai)表的是數(shu)(shu)組的長度,就(jiu)像下(xia)面的定義
char g[100];
short h[100];
long i[100];
數組定(ding)義的數據類型(xing),也表示一(yi)次能夠讀(du)寫的內(nei)存(cun)大小,char 、short 、long 分別以 1 、2 、4 個字節為例進行內(nei)存(cun)的讀(du)寫。
數(shu)組(zu)是(shi)內存的(de)實現(xian),數(shu)組(zu)和(he)內存的(de)物(wu)理結構完全(quan)一致,尤(you)其(qi)是(shi)在讀寫1個字節(jie)的(de)時候,當字節(jie)數(shu)超過(guo) 1 時,只能通過(guo)逐個字節(jie)來讀取,下面(mian)是(shi)內存的(de)讀寫過(guo)程
數組(zu)是(shi)我(wo)們學習的(de)(de)(de)第(di)一(yi)個數據結構,我(wo)們都(dou)知道數組(zu)的(de)(de)(de)檢索效率(lv)是(shi)比較(jiao)快的(de)(de)(de),至于(yu)數組(zu)的(de)(de)(de)檢索效率(lv)為什么這(zhe)么快并(bing)不(bu)是(shi)我(wo)們這(zhe)篇文章(zhang)討論(lun)的(de)(de)(de)重(zhong)點(dian)。
我們(men)(men)上面(mian)(mian)提(ti)到數(shu)(shu)(shu)組是內存的一(yi)種實現,使用數(shu)(shu)(shu)組能(neng)夠使編程更加高效,下面(mian)(mian)我們(men)(men)就來(lai)認識一(yi)下其(qi)他數(shu)(shu)(shu)據結構,通過這(zhe)些(xie)數(shu)(shu)(shu)據結構也可以操作內存的讀寫。
棧(stack)是一種很重要的(de)(de)(de)(de)(de)數據結(jie)構(gou),棧采(cai)用(yong) LIFO(Last In First Out)即后入(ru)先出的(de)(de)(de)(de)(de)方式對(dui)內存進(jin)行操(cao)作。它就像(xiang)一個大的(de)(de)(de)(de)(de)收納(na)箱(xiang)(xiang)(xiang),你可以往里面(mian)放相同類型的(de)(de)(de)(de)(de)東西,比如書,最(zui)(zui)先放進(jin)收納(na)箱(xiang)(xiang)(xiang)的(de)(de)(de)(de)(de)書在最(zui)(zui)下面(mian),最(zui)(zui)后放進(jin)收納(na)箱(xiang)(xiang)(xiang)的(de)(de)(de)(de)(de)書在最(zui)(zui)上面(mian),如果(guo)你想拿(na)書的(de)(de)(de)(de)(de)話(hua), 必須從最(zui)(zui)上面(mian)開始取,否則是無法取出最(zui)(zui)下面(mian)的(de)(de)(de)(de)(de)書籍的(de)(de)(de)(de)(de)。
棧(zhan)的(de)數據結構(gou)就是這(zhe)樣(yang),你(ni)把(ba)書(shu)籍壓入收納箱(xiang)的(de)操(cao)作叫做壓入(push),你(ni)把(ba)書(shu)籍從收納箱(xiang)取(qu)出的(de)操(cao)作叫做彈出(pop),它(ta)的(de)模型圖大(da)概是這(zhe)樣(yang)
入棧相當(dang)于是(shi)增加操作,出棧相當(dang)于是(shi)刪除操作,只不(bu)過(guo)叫法(fa)不(bu)一樣。棧和內存不(bu)同(tong),它不(bu)需(xu)要(yao)指定元素(su)的(de)地址(zhi)。它的(de)大概使用如下
// 壓入數據
Push(123);
Push(456);
Push(789);
// 彈出(chu)數據
j = Pop();
k = Pop();
l = Pop();
在棧(zhan)(zhan)中,LIFO 方式表示棧(zhan)(zhan)的數(shu)組中所保存的最后面的數(shu)據(Last In)會被最先讀取出來(First On)。
隊列和(he)棧很相(xiang)似但又(you)不(bu)(bu)同,相(xiang)同之處在(zai)于隊列也不(bu)(bu)需要指定元素的地址,不(bu)(bu)同之處在(zai)于隊列是(shi)一(yi)種(zhong) 先(xian)入先(xian)出(First In First Out) 的數據(ju)結構。隊列在(zai)我們生活中的使用很像是(shi)我們去景區排(pai)隊買票(piao)一(yi)樣,第一(yi)個(ge)排(pai)隊的人最先(xian)買到票(piao),以此類推,俗(su)話(hua)說(shuo): 先(xian)到先(xian)得。它的使用如(ru)下
// 往隊(dui)列(lie)中(zhong)寫(xie)入數據(ju)
EnQueue(123);
EnQueue(456);
EnQueue(789);
// 從隊(dui)列(lie)中(zhong)讀出數據(ju)
m = DeQueue();
n = DeQueue();
o = DeQueue();
向隊(dui)列中(zhong)寫(xie)入數據稱為 EnQueue()入列,從隊(dui)列中(zhong)讀出數據稱為DeQueue()。
與棧相對(dui),FIFO 的方式(shi)表示隊列(lie)中最先(xian)所保存的數(shu)據會(hui)優先(xian)被讀(du)取出來。
隊(dui)(dui)列的(de)實現(xian)一般有兩種:順序(xu)隊(dui)(dui)列 和 循環(huan)隊(dui)(dui)列,我們(men)上面(mian)的(de)事例使(shi)用的(de)是順序(xu)隊(dui)(dui)列,那么(me)下面(mian)我們(men)看(kan)一下循環(huan)隊(dui)(dui)列的(de)實現(xian)方式
環(huan)形緩沖區(qu)
循環(huan)隊列一般是以環(huan)狀緩(huan)沖(chong)區(qu)(ring buffer)的(de)方式實現(xian)的(de),它是一種用(yong)于表示(shi)一個固定(ding)尺寸、頭(tou)尾相連的(de)緩(huan)沖(chong)區(qu)的(de)數據結構,適(shi)合緩(huan)存(cun)數據流。假如我們要用(yong) 6 個元素的(de)數組(zu)來實現(xian)一個環(huan)形緩(huan)沖(chong)區(qu),這(zhe)時可以從(cong)起始(shi)位(wei)置開(kai)始(shi)有(you)序的(de)存(cun)儲數據,然后(hou)(hou)再按照存(cun)儲時的(de)順序把數據讀出。在(zai)數組(zu)的(de)末尾寫入(ru)數據后(hou)(hou),后(hou)(hou)一個數據就(jiu)會從(cong)緩(huan)沖(chong)區(qu)的(de)頭(tou)開(kai)始(shi)寫。這(zhe)樣,數組(zu)的(de)末尾和開(kai)頭(tou)就(jiu)連接了(le)起來。
下(xia)面我們來介(jie)紹一下(xia)鏈(lian)表(biao)和 二(er)叉樹,它們都是可以(yi)不用(yong)考慮索引的(de)順序就可以(yi)對元素進(jin)行讀寫的(de)方式。通過使用(yong)鏈(lian)表(biao),可以(yi)高效(xiao)(xiao)的(de)對數據元素進(jin)行添加 和 刪除操作。而(er)通過使用(yong)二(er)叉樹,則(ze)可以(yi)更高效(xiao)(xiao)的(de)對數據進(jin)行檢(jian)索。
在實現(xian)數(shu)組的(de)基礎上(shang),除了數(shu)據的(de)值之外,通過為其附(fu)帶(dai)上(shang)下一個(ge)元(yuan)素(su)的(de)索(suo)引,即可實現(xian)鏈表(biao)。數(shu)據的(de)值和下一個(ge)元(yuan)素(su)的(de)地址(zhi)(索(suo)引)就構成了一個(ge)鏈表(biao)元(yuan)素(su),如下所示
對鏈表的(de)添加(jia)和(he)刪(shan)除(chu)都是非常高效(xiao)的(de),我們(men)來敘述一(yi)下(xia)這個添加(jia)和(he)刪(shan)除(chu)的(de)過程,假如(ru)我們(men)要(yao)刪(shan)除(chu)地址為 p[2] 的(de)元素,鏈表該如(ru)何變化呢?
我們可(ke)以看到,刪除地址為 p[2] 的(de)(de)元素(su)后(hou),直接將鏈表剔(ti)除,并把(ba) p[2] 前一(yi)個位置的(de)(de)元素(su) p[1] 的(de)(de)指針域指向 p[2] 下一(yi)個鏈表元素(su)的(de)(de)數據(ju)區即可(ke)。
那么對于(yu)新添(tian)加(jia)進來的(de)(de)(de)(de)鏈表,需要(yao)確定(ding)插入位置,比如要(yao)在 p[2] 和 p[3] 之間插入地(di)址為 p[6] 的(de)(de)(de)(de)元素,需要(yao)將 p[6] 的(de)(de)(de)(de)前(qian)一個位置 p[2] 的(de)(de)(de)(de)指(zhi)針域改(gai)為 p[6] 的(de)(de)(de)(de)地(di)址,然后將 p[6] 的(de)(de)(de)(de)指(zhi)針域改(gai)為 p[3] 的(de)(de)(de)(de)地(di)址即(ji)可。
鏈(lian)表(biao)的添加不涉及到數據的移動,所以鏈(lian)表(biao)的添加和刪除很快(kuai),而數組(zu)的添加設計到數據的移動,所以比較慢,通常情況下,使用數組(zu)來檢索數據,使用鏈(lian)表(biao)來進行添加和刪除操作。
二叉樹(shu)(shu)也是一種檢索效率非常高的(de)(de)(de)數(shu)據結構,二叉樹(shu)(shu)是指在鏈(lian)表的(de)(de)(de)基礎上(shang)往數(shu)組追加元素(su)時(shi),考慮到數(shu)組的(de)(de)(de)大(da)小(xiao)關系,將其分(fen)成左右(you)兩(liang)個方向的(de)(de)(de)表現形式。假如我們把(ba) 50 這(zhe)個值(zhi)保存到了數(shu)組中,那么,如果接下來要進行值(zhi)寫入的(de)(de)(de)話,就需要和(he)50比較,確定誰(shui)大(da)誰(shui)小(xiao),比50數(shu)值(zhi)大(da)的(de)(de)(de)放(fang)(fang)右(you)邊,小(xiao)的(de)(de)(de)放(fang)(fang)左邊,下圖(tu)是二叉樹(shu)(shu)的(de)(de)(de)比較示例
二叉(cha)樹(shu)(shu)是由鏈表(biao)發展而來,因此二叉(cha)樹(shu)(shu)在追加和刪除(chu)元素方面也是同(tong)樣有(you)效的(de)。
這一(yi)切(qie)的演變都是以內存為(wei)基(ji)礎的。