數據在計算機中的存儲形式和運算( 原碼,反碼,補碼)&=, |=, >>=,

一、數據概述

以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



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章