1. 程式人生 > >資料在記憶體中的儲存方式

資料在記憶體中的儲存方式

一、資料概述

C語言為例,裡面所有的基本資料型別,都是以符合人類世界和自然世界的邏輯而出現的。比如說int,bool,float等等。這些資料型別出現的目的,是更於讓人容易理解,可以說,這些資料型別是架通人類思維 與 計算機的橋樑。

我們知道。依照馮諾依曼體系,計算機中並沒有這些int  float等等,而全部都是0和1表示的二進位制資料,並且計算器只能理解這些0和1的資料。所以說,所有的資料在計算機裡面都是以0和1儲存和運算的,這是馮諾依曼體系的基礎。因此,符合我們人類思維的資料都要通過一定的轉換才能被正確的儲存到計算機中。

二、進位制

要想理解資料的儲存,首先要明白最基本的二進位制問題,因為,這是計算機中資料最基本的形式,首先看下面的問題:

1、什麼是二進位制?進位制的概念?

2、計算機中為什麼要用二進位制?

3、二進位制和符合人類思維的十進位制之間的關係?

4、為什麼又會出現八進位制、十六進位制?

5、所有進位制之間的轉換?

(1)、進位制的概念

進位制也就是進位制,是人們規定的一種進位方法。 對於任何一種進位制---X進位制,就表示某一位置上的數運算時是逢X進一位。 十進位制是逢十進一,十六進位制是逢十六進一,二進位制就是逢二進一

在採用進位計數的數字系統中,如果只用r個基本符號表示數值,則稱為r進位制(Radix-r Number System),r稱為該數制的基數(Radix)。不同的數制的共同特點如下:

(1)、每一種數制都有篤定的符號集。例如,十進位制數制的基本符號有十個:0,1,2。。。,9。二進位制數制的基本符號有兩個:0和1.

(2)、每一種數制都使用位置表示法。即處於不同位置的數符所代表的值不同,與它所在位的權值有關。

例如:十進位制1234.55可表示為

1234.55=1×10^3+2×10^2+3×10^1+4×10^0+5×10^(-1)+5×10^(-2)

可以看出,各種進位計數制中權的值恰好是基礎的某次冪。因此,對任何一種進位計數製表示的數都可以寫成按權展開的多項式。

(2)、計算機中為什麼要用二進位制

電腦使用二進位制是由它的實現機理決定的。我們可以這麼理解:電腦的基層部件是由積體電路組成的,這些積體電路可以看成是一個個閘電路組成,(當然事實上沒有這麼簡單的)。

當計算機工作的時候,電路通電工作,於是每個輸出端就有了電壓。電壓的高低通過模數轉換即轉換成了二進位制:高電平是由1

表示,低電平由0表示。也就是說將類比電路轉換成為數位電路。這裡的高電平與低電平可以人為確定,一般地,2.5伏以下即為低電平,3.2伏以上為高電平

電子計算機能以極高速度進行資訊處理和加工,包括資料處理和加工,而且有極大的資訊儲存能力。資料在計算機中以器件的物理狀態表示,採用二進位制數字系統,計算機處理所有的字元或符號也要用二進位制編碼來表示。用二進位制的優點是容易表示,運算規則簡單,節省裝置。人們知道,具有兩種穩定狀態的元件(如電晶體的導通和截止,繼電器的接通和斷開,電脈衝電平的高低等)容易找到,而要找到具有10種穩定狀態的元件來對應十進位制的10個數就困難了

1)技術實現簡單,計算機是由邏輯電路組成,邏輯電路通常只有兩個狀態,開關的接通與斷開,這兩種狀態正好可以用“1”“0”表示。   (2)簡化運算規則:兩個二進位制數和、積運算組合各有三種,運算規則簡單,有利於簡化計算機內部結構,提高運算速度。   (3)適合邏輯運算:邏輯代數是邏輯運算的理論依據,二進位制只有兩個數碼,正好與邏輯代數中的相吻合。   (4)易於進行轉換,二進位制與十進位制數易於互相轉換。   (5)用二進位制表示資料具有抗干擾能力強,可靠性高等優點。因為每位資料只有高低兩個狀態,當受到一定程度的干擾時,仍能可靠地分辨出它是高還是低。


(3)、八進位制和十六進位制出現是為什麼

人類一般思維方式是以十進位制來表示的,而計算機則是二進位制,但是對於程式設計人員來說,都是需要直接與計算器打交道的,如果給我們一大串的二進位制數。比如說一個4個位元組的int型的資料:0000 1010 1111 0101 1000 1111 11111 1111,我想任何程式設計師看到這樣一大串的0、1都會很蛋疼。所以必須要有一種更加簡潔靈活的方式來呈現這對資料了。

你也許會說,直接用十進位制吧,如果是那樣,就不能準確表達計算機思維方式了(二進位制),所以,出現了八進位制、十六進位制,其實十六進位制應用的更加廣泛,就比如說上面的int型的資料,直接轉換為八進位制的話,32./3 餘2 也就是說  ,我們還要在前面加0,但是轉換為十六進位制就不同了。32/4=8,直接寫成十六進位制的8個數值拼接的字串,簡單明瞭。

所以說用十六進位制表達二進位制字串無疑是最佳的方式,這就是八進位制和十六進位制出現的原因。

(4)、進位制間的相互轉換問題

常用的進位制有二進位制、十進位制、八進位制和十六進位制

①、八進位制、十六進位制、二進位制-------------->十進位制

都是按權展開的多項式相加得到十進位制的結果。

比如

二進位制1010.1到十進位制:1×2^3  +  0×2^2  +  1×2^1  +  0×2^0  +  1×2^(-1)=10.5

八進位制13.1到十進位制:1×8^1  +  3×8^0  +  1×8^(-1)=11.125

十六進位制13.1到十進位制:1×16^1  +  3×16^0  +  1×16^(-1)=19.0625

②、十進位制-------------->八進位制、十六進位制、二進位制

都是按照整數部分除以基數(r)取餘,小數部分乘以基數(r)取整

十進位制10.25 到二進位制:整數部分除2,一步步取餘。小數部分乘2,一步步取整


八進位制到十進位制,十六進位制到十進位制都是和上面的一樣,只不過不在是除2乘2,而是8或者16了,這是根據自己的基數來決定的。

③、二進位制<------------->八進位制、十六進位制

二進位制轉換成八進位制的方法是:從小數點起,把二進位制數每三位分成一組,小數點前面的不夠三位的前面加0,小數點後面的不夠三位的後面加0,然後寫出每一組的對應的十進位制數,順序排列起來就得到所要求的八進位制數了。

依照同樣的思想,將一個八進位制數的每一位,按照十進位制轉換二進位制的方法,變成用三位二進位制表示的序列,然後按照順序排列,就轉換為二進位制數了。

二進位制數10101111.10111轉換為八進位制的數:(010   101   111.101  110)=  2   5   7.5  6=257.56

八進位制數257.56轉換為二進位制的數:2   5  7.5  6  =(010   101   111.101   110)=10101111.101

二進位制轉換到十六進位制差不多:從小數點起,把二進位制數每四位分成一組,小數點前面的不夠四位的前面加0,小數點後面的不夠四位的後面加0,然後寫出每一組的對應的十進位制數,然後將大於9的數寫成如下的形式,10---->A,11-->B,12---->C,13----->D,14----->E,15---->F,在順序排列起來就得到所要求的十六進位制了。

同樣,將一個十六進位制數的每一位,按照十進位制轉換二進位制的方法,變成用四位二進位制表示的序列,然後按照順序排列,就轉換為二進位制數了。

二進位制數  10101111.10111轉換為十六進位制的數:(1010   1111.1011 1000)=A  F.B  8=AF.B8

十六進位制AF.B8轉換為二進位制:   A    F.B  8=(1010   1111.1011  1000)=10101111.10111

三、資料的分類

學過程式設計知識的同學肯定知道,特別是面向物件的,資料型別一般分類基本資料型別  和 複合資料型別。其實從本質上將,複合資料型別也是由基本資料型別構成的。所以,這裡先只討論基本資料型別的儲存情況。

以C語言為例,基本資料型別包括,無符號整形,帶符號整形,實型,char型,有朋友說還有bool,其實在C語言中bool型別也還是整形資料,只不過是用巨集宣告的而已,不明白的可以看這篇文章:http://blog.csdn.net/lonelyroamer/article/details/7671242


1、先看無符號整形

無符號整形在資料中的儲存無疑是最方便的,因為沒有符號位,只表示正數,所以在儲存計算方面都很簡單。無符號整形在就是以純粹的二進位制串儲存在計算機中的。

比如說看下面的例子:

從輸出的十六進位制數中可以看出,它就是以直接的二進位制
數表示的。

2、在看帶符號整形

對於帶符號數,機器數的最高位是表示正、負號的符號位,其餘位則表示數值。

先不談其他的問題,只談二進位制表達資料的問題(我也不知道怎麼說),看下面的例子:

假設機器字長為8的話:

一個十進位制的帶符號整形 1,表達為二進位制就是 (0000 0001)

一個十進位制的帶符號整形 -1,表達為二進位制就是 (1000 0001)

那麼,兩者相加 ,用十進位制運算 1+(-1)=0

在看二進位制運算  (0000 0010)+(1000 0001)=(1000 0010)    這個數轉換為十進位制結果等於-2。

可以發現出問題了,如上所表示的方式,就是今天所要講的原碼

------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

①、原碼

數值X的原碼記為[x]原,如果機器字長為n(即採用n個二進位制位表示資料)。則最高位是符號位。0表示正號,1表示負號,其餘的n-1位表示數值的絕對值。數值零的原碼錶示有兩種形式:[+0]原=0000 0000   ,-[0]原=1000 0000.

例子:若機器字長n等於8,則

[+1]原=0000 00001           [-1]原=1000 00001  

[+127]原=0111 1111          [-127]原=1111 1111

[+45]原=0010 1101           [-45]原=1010 1101    

可見,原碼,在計算數值上出問題了,當然,你也可以實驗下,原碼在計算正數和正數的時候,它是一點問題都沒有的,但是出現負數的時候就出現問題了。所以才會有我下面將的問題:反碼

------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

②、反碼

數值X的反碼記作[x]反,如果機器字長為n,則最高位是符號位,0表示正號,1表示負號,正數的反碼與原碼相同,負數的反碼則是其絕對值按位求反。數值0的反碼錶示有兩種形式:[+0]反=0000 0000   ,-[0]反=1111 1111.

例子:若機器字長n等於8,則

[+1]反=0000 00001           [-1]反=1111 1110 

[+127]反=0111 1111          [-127]反=1000 0000

[+45]反=0010 1101           [-45]反=1101 0010  

在看反碼計算的問題:

1+(-1)=0               |            (0000 0001)+(1111 1110)=(1111 1111)=(1000 0000)原=【-0】  可以看到,雖然是-0,但是問題還不是很大

1+(-2)=-1              |            (0000 0001)+(1111 1101)=(1111 1110)=(1000 0001)原=【-1】  可以看到,沒有問題

-1+(2)=1              |            (1111 1110)+(0000 0010)=(0000 0000)=(0000 0000)原=【0】  可以看到,問題發生了,因為溢位,導致結果變為0了。

所以,看以看到,用反碼錶示,問題依然沒有解決,所以,出現了下面的補碼

③、補碼

數值X的補碼記作[x]補,如果機器字長為n,則最高位是符號位,0表示正號,1表示負號,正數的補碼與原碼反碼都相同,負數的補碼則等於其反碼的末尾加1。數值0的補碼錶示有唯一的編碼:[+0]補=0000 0000   ,-[0]補=0000 0000.

例子:若機器字長n等於8,則

[+1]補=0000 00001           [-1]補=1111 1111 

[+127]補=0111 1111          [-127]補=1000 0001

[+45]補=0010 1101           [-45]補=1101 0011   

在看補碼計算的問題:

1+(-1)=0               |            (0000 0001)+(1111 1111)=(0000 0000)=(0000 0000)原=【0】  可以看到。沒有問題

1+(-2)=-1              |            (0000 0001)+(1111 1110)=(1111 1111)=(1000 0001)原=【-1】  可以看到,沒有問題

-1+(2)=1              |            (1111 1111)+(0000 0010)=(0000 0001) =(0000 0001)原=【1】  可以看到,沒有問題

通過上面的計算,我們發現,用補碼的方式,就不存在在原碼和反碼中存在的計算問題了。其實,這也是計算機表達帶符號整數用補碼的原因。如果,你覺得我舉得例子太少,缺少代表行,你可以自己試試。不過,放心補碼一定是不會存在原碼和反碼的問題的。

討論下原碼反碼補碼的原理,沒興趣的同學可以跳過 。不過我覺得從本質上了解補碼的機制還是很有好處的。

1、為什麼原碼不行?

( 1 ) 10-  ( 1 )10 =  ( 1 )10 + ( -1 )10 =  ( 0 )10

(00000001) + (10000001) = (10000010) = ( -2 ) 顯然不正確.
   通過上面原碼計算式可以看出,當正數加上負數時,結果本應是正值,得到的卻是負值(當然也有可能得到的是正數,因為被減數與減數相加數值超過0111 1111,即127,就會進位,從而進位使符號位加1變為0了,這時結果就是正的了)。而且數值部分還是被減數與減數的和。

並且,當負數加上負數時(這裡就拿兩個數值部分加起來不超過0111 1111的來說),我們可以明顯看出符號位相加變為0,進位1被溢位。結果就是正數了。

因此原碼的錯誤顯而易見,是不能用在計算機中的。

2、補碼的原理

既然原碼並不能表示負數的運算問題,那麼當然要另想他法了。這個方法就是補碼,關於補碼是如何提出的,我並不知道,但不得不說,這是一個最簡潔的方法,當然,也可以用別的更復雜的方法,那就不是我們想要的了。

我自己研究補碼的時候,也在網上找了些資料,都是到處copy,反正我是看的迷糊了,本人數學功底不怎麼樣,看不懂那些大神寫的,只好,自己理解了下。

要談補碼,先看看補數的問題。什麼是補數,舉個簡單的例子,100=25+75。100用數學來說就是模M,那麼就可以這樣概括。在M=100的情況下,25是75的補數。這就是補數。

25是75的補數,這是在常規世界中,在計算機上就不是這樣了,因為,在計算機中,資料存在這溢位的問題。

假設機器字長是8的話,那麼能表達的最大無符號數就是1111 11111,在加1的話,就變成1  0000  0000 ,此時因為溢位,所以1去掉,就變成0了,這個很簡單,相信學計算機的人都會明白。

也就是說,在計算機中,補數的概念稍微不同於數學之中,25+75=100,考略計算機中的溢位問題,那麼25+75就等於0了。也就是說,25和75不是互為補數了。

我覺得用鬧鐘來比喻這個問題在形象不過了,因為鬧鐘也存在著溢位的問題,當時間到達11:59 ,在加1分鐘的話就變成0:0了,這和計算機的溢位是同一個道理。

那麼,有一個時鐘,現在是0點,我想調到5點,有兩種方法,一個是正著撥5,到5點。第二種方法是倒著撥7,也可以到5點。正著撥5記作+5,倒著撥7,記作-7,而鬧鐘的M是12,也就是說,在考略溢位的情況下,M=12,5是-7的補數。用個數學等式可以這樣表達0+5=0+-7,即0+5=0-7

這就是計算機中的數值計算和數學中的計算不同的地方。

明白了計算機中補數的道理,那麼就明白補碼的問題了。還是用例子說明:

在計算機中計算十進位制 1+(-2)。

1的原碼是:0000 0001

-2的原碼是:1000 0010

-2的補碼是:1111 1110   這個二進位制換做無符號的整數大小就是254,而8位二進位制數的M=2^8=256。(很多文章中把M寫成2^7,這根本就是不對的,根本沒有解決符號位的問題)

你發現什麼了沒,當換成補碼後,-2和254就是補數的關係。

也就是1+(-2)  等價於了 1+254了。

這樣做,好處在什麼地方,你自己都可以看得到:

①、利用補數和溢位的原理,減法變成了加法

②、符號位不在是約束計算的問題,不會存在原碼中的問題了,因為變成補碼後,雖然最高位依然是1,但是這個1就不在是最為符號位了,而是作為一個普通的二進位制位,參與運算了。

所以,這就是補碼的原理所在,通過補數和溢位,解決了減法和負數問題。不知道各位理解了沒有,額,反正我是通過這種方法安慰自己的,不知道是不是有失偏頗。

十進位制數求補碼,補碼求十進位制數                                                         

十進位制求補碼:

如果是正數,直接求它的原碼,符號位為0

如果是負數,比較好的方法是先求十六進位制,在由十六進位制求二進位制,符號位為1,在除了符號位都取反,在加1,即可得到補碼。

補碼就十進位制 :

根據符號位判斷,如果符號位是0,表示是正數,就是原碼,直接轉換就十進位制即可。

如果符號為是1,表示是負數。那麼,連符號位在內都取反,在加1,將該二進位制轉換為十進位制,該十進位制數即使該負數的絕對值,加個負號-,就得到該負數。

3、在看小數儲存的問題


四、位運算子

語言位運算子:與、或、異或、取反、左移和右移

位運算是指按二進位制進行的運算。在系統軟體中,常常需要處理二進位制位的問題。C語言提供了6個位操作運算子。這些運算子只能用於整型運算元,即只能用於帶符號或無符號的char,short,int與long型別。

C語言提供的位運算子列表
運算子 含義 描述
& 按位與 如果兩個相應的二進位制位都為1,則該位的結果值為1,否則為0
| 按位或 兩個相應的二進位制位中只要有一個為1,該位的結果值為1
^ 按位異或 若參加運算的兩個二進位制位值相同則為0,否則為1
~ 取反 ~是一元運算子,用來對一個二進位制數按位取反,即將0變1,將1變0
<< 左移 用來將一個數的各二進位制位全部左移N位,右補0
>> 右移 將一個數的各二進位制位右移N位,移到右端的低位被捨棄,對於無符號數,高位補0


1、“按位與”運算子(&)
    按位與是指:參加運算的兩個資料,按二進位制位進行“與”運算。如果兩個相應的二進位制位都為1,則該位的結果值為1;否則為0。這裡的1可以理解為邏輯中的true,0可以理解為邏輯中的false。按位與其實與邏輯上“與”的運算規則一致。邏輯上的“與”,要求運算數全真,結果才為真。若,A=true,B=true,則A∩B=true 例如:3&5 3的二進位制編碼是11(2)。(為了區分十進位制和其他進位制,本文規定,凡是非十進位制的資料均在資料後面加上括號,括號中註明其進位制,二進位制則標記為2)記憶體儲存資料的基本單位是位元組(Byte),一個位元組由8個位(bit)所組成。位是用以描述電腦資料量的最小單位。二進位制系統中,每個0或1就是一個位。將11(2)補足成一個位元組,則是00000011(2)。5的二進位制編碼是101(2),將其補足成一個位元組,則是00000101(2)
按位與運算:
00000011(2)
&00000101(2)
00000001(2)
由此可知3&5=1
c語言程式碼:
#include <stdio.h>
main()
{
int a=3;
int b = 5;
printf("%d",a&b);
}
按位與的用途:
(1)清零
若想對一個儲存單元清零,即使其全部二進位制位為0,只要找一個二進位制數,其中各個位符合一下條件:

原來的數中為1的位,新數中相應位為0。然後使二者進行&運算,即可達到清零目的。
例:原數為43,即00101011(2),另找一個數,設它為148,即10010100(2),將兩者按位與運算:
00101011(2)
&10010100(2)
00000000(2)
c語言原始碼:
#include <stdio.h>
main()
{
int a=43;
int b = 148;
printf("%d",a&b);
}
(2)取一個數中某些指定位
若有一個整數a(2byte),想要取其中的低位元組,只需要將a與8個1按位與即可。
a 00101100 10101100
b 00000000 11111111
c 00000000 10101100
(3)保留指定位:
與一個數進行“按位與”運算,此數在該位取1.
例如:有一數84,即01010100(2),想把其中從左邊算起的第3,4,5,7,8位保留下來,運算如下:
01010100(2)
&00111011(2)
00010000(2)
即:a=84,b=59
    c=a&b=16
c語言原始碼:
#include <stdio.h>
main()
{
int a=84;
int b = 59;
printf("%d",a&b);
}


2、“按位或”運算子(|)
兩個相應的二進位制位中只要有一個為1,該位的結果值為1。借用邏輯學中或運算的話來說就是,一真為真


例如:60(8)|17(8),將八進位制60與八進位制17進行按位或運算。
00110000
|00001111
00111111 
c語言原始碼:
#include <stdio.h>
main()
{
int a=060;
int b = 017;
printf("%d",a|b);
}
應用:按位或運算常用來對一個數據的某些位定值為1。例如:如果想使一個數a的低4位改為1,則只需要將a與17(8)進行按位或運算即可。


3、交換兩個值,不用臨時變數
例如:a=3,即11(2);b=4,即100(2)。
想將a和b的值互換,可以用以下賦值語句實現:
    a=a∧b;
    b=b∧a;
    a=a∧b;
a=011(2)
    (∧)b=100(2)
a=111(2)(a∧b的結果,a已變成7)
    (∧)b=100(2)
b=011(2)(b∧a的結果,b已變成3)
    (∧)a=111(2)


a=100(2)(a∧b的結果,a已變成4)
等效於以下兩步:
    ① 執行前兩個賦值語句:“a=a∧b;”和“b=b∧a;”相當於b=b∧(a∧b)。
    ② 再執行第三個賦值語句: a=a∧b。由於a的值等於(a∧b),b的值等於(b∧a∧b),

因此,相當於a=a∧b∧b∧a∧b,即a的值等於a∧a∧b∧b∧b,等於b。
很神奇吧!
c語言原始碼:
#include <stdio.h>
main()
{
int a=3;
int b = 4;
a=a^b;
b=b^a;
a=a^b;
printf("a=%d b=%d",a,b);
}


4、“取反”運算子(~)
他是一元運算子,用於求整數的二進位制反碼,即分別將運算元各二進位制位上的1變為0,0變為1。
例如:~77(8)
原始碼:
#include <stdio.h>
main()
{
int a=077;
printf("%d",~a);
}


5、左移運算子(<<)

左移運算子是用來將一個數的各二進位制位左移若干位,移動的位數由右運算元指定(右運算元必須是非負

值),其右邊空出的位用0填補,高位左移溢位則捨棄該高位。
例如:將a的二進位制數左移2位,右邊空出的位補0,左邊溢位的位捨棄。若a=15,即00001111(2),左移2

位得00111100(2)。
原始碼:
#include <stdio.h>
main()
{
int a=15;
printf("%d",a<<2);
}
左移1位相當於該數乘以2,左移2位相當於該數乘以2*2=4,15<<2=60,即乘了4。但此結論只適用於該

數左移時被溢位捨棄的高位中不包含1的情況。
    假設以一個位元組(8位)存一個整數,若a為無符號整型變數,則a=64時,左移一位時溢位的是0

,而左移2位時,溢位的高位中包含1。


6、右移運算子(>>)
右移運算子是用來將一個數的各二進位制位右移若干位,移動的位數由右運算元指定(右運算元必須是非負

值),移到右端的低位被捨棄,對於無符號數,高位補0。對於有符號數,某些機器將對左邊空出的部分

用符號位填補(即“算術移位”),而另一些機器則對左邊空出的部分用0填補(即“邏輯移位”)。注

意:對無符號數,右移時左邊高位移入0;對於有符號的值,如果原來符號位為0(該數為正),則左邊也是移

入0。如果符號位原來為1(即負數),則左邊移入0還是1,要取決於所用的計算機系統。有的系統移入0,有的

系統移入1。移入0的稱為“邏輯移位”,即簡單移位;移入1的稱為“算術移位”。 
例: a的值是八進位制數113755: 
   a:1001011111101101 (用二進位制形式表示)
   a>>1: 0100101111110110 (邏輯右移時)
   a>>1: 1100101111110110 (算術右移時)
   在有些系統中,a>>1得八進位制數045766,而在另一些系統上可能得到的是145766。Turbo C和其他一些C

編譯採用的是算術右移,即對有符號數右移時,如果符號位原來為1,左面移入高位的是1。
原始碼:
#include <stdio.h>
main()
{
int a=0113755;
printf("%d",a>>1);
}


7、位運算賦值運算子

位運算子與賦值運算子可以組成複合賦值運算子。
   例如: &=, |=, >>=, <<=, ∧=
   例: a & = b相當於 a = a & b
         a << =2相當於a = a << 2