1、數據概述程序員
以C語言爲例,裏面全部的基本數據類型,都是以符合人類世界和天然世界的邏輯而出現的。好比說int,bool,float等等。這些數據類型出現的目的,是更於讓人容易理解,能夠說,這些數據類型是架通人類思惟 與 計算機的橋樑。編程
咱們知道。依照馮諾依曼體系,計算機中並無這些int float等等,而所有都是0和1表示的二進制數據,而且計算器只能理解這些0和1的數據。因此說,全部的數據在計算機裏面都是以0和1存儲和運算的,這是馮諾依曼體系的基礎。所以,符合咱們人類思惟的數據都要經過必定的轉換才能被正確的存儲到計算機中。ide
2、進制編碼
要想理解數據的存儲,首先要明白最基本的二進制問題,由於,這是計算機中數據最基本的形式,首先看下面的問題:.net
一、什麼是二進制?進制的概念?對象
二、計算機中爲何要用二進制?blog
三、二進制和符合人類思惟的十進制之間的關係?內存
四、爲何又會出現八進制、十六進制?字符串
五、全部進制之間的轉換?get
(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
3、數據的分類
學過編程知識的同窗確定知道,特別是面向對象的,數據類型通常分類基本數據類型 和 複合數據類型。其實從本質上將,複合數據類型也是由基本數據類型構成的。因此,這裏先只討論基本數據類型的存儲狀況。
以C語言爲例,基本數據類型包括,無符號×××,帶符號×××,實型,char型,有朋友說還有bool,其實在C語言中bool類型也仍是×××數據,只不過是用宏聲明的而已,不明白的能夠看這篇文章:http://blog.csdn.net/lonelyroamer/article/details/7671242
一、先看無符號×××
無符號×××在數據中的存儲無疑是最方便的,由於沒有符號位,只表示正數,因此在存儲計算方面都很簡單。無符號×××在就是以純粹的二進制串存儲在計算機中的。
好比說看下面的例子:
從輸出的十六進制數中能夠看出,它就是以直接的二進制
數表示的。
二、在看帶符號×××
對於帶符號數,機器數的最高位是表示正、負號的符號位,其他位則表示數值。
先不談其餘的問題,只談二進制表達數據的問題(我也不知道怎麼說),看下面的例子:
假設機器字長爲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 ) 10- ( 1 )10 = ( 1 )10 + ( -1 )10 = ( 0 )10
(00000001)原 + (10000001)原 = (10000010)原 = ( -2 ) 顯然不正確.
經過上面原碼計算式能夠看出,當正數加上負數時,結果本應是正值,獲得的倒是負值(固然也有可能獲得的是正數,由於被減數與減數相加數值超過0111 1111,即127,就會進位,從而進位使符號位加1變爲0了,這時結果就是正的了)。並且數值部分仍是被減數與減數的和。
而且,當負數加上負數時(這裏就拿兩個數值部分加起來不超過0111 1111的來講),咱們能夠明顯看出符號位相加變爲0,進位1被溢出。結果就是正數了。
所以原碼的錯誤顯而易見,是不能用在計算機中的。
二、補碼的原理
既然原碼並不能表示負數的運算問題,那麼固然要另想他法了。這個方法就是補碼,關於補碼是如何提出的,我並不知道,但不得不說,這是一個最簡潔的方法,固然,也能夠用別的更復雜的方法,那就不是咱們想要的了。
我本身研究補碼的時候,也在網上找了些資料,都是處處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,將該二進制轉換爲十進制,該十進制數即便該負數的絕對值,加個負號-,就獲得該負數。
三、在看小數存儲的問題
4、位運算符
語言位運算符:與、或、異或、取反、左移和右移
位運算是指按二進制進行的運算。在系統軟件中,經常須要處理二進制位的問題。C語言提供了6個位操做運算符。這些運算符只能用於整型操做數,即只能用於帶符號或無符號的char,short,int與long類型。
C語言提供的位運算符列表:
運算符 含義 描述
& 按位與 若是兩個相應的二進制位都爲1,則該位的結果值爲1,不然爲0
| 按位或 兩個相應的二進制位中只要有一個爲1,該位的結果值爲1
^ 按位異或 若參加運算的兩個二進制位值相同則爲0,不然爲1
~ 取反 ~是一元運算符,用來對一個二進制數按位取反,即將0變1,將1變0
<< 左移 用來將一個數的各二進制位所有左移N位,右補0
右移 將一個數的各二進制位右移N位,移到右端的低位被捨棄,對於無符號數,高位補0
一、「按位與」運算符(&)
按位與是指:參加運算的兩個數據,按二進制位進行「與」運算。若是兩個相應的二進制位都爲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);
}
二、「按位或」運算符(|)
兩個相應的二進制位中只要有一個爲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);
}
四、「取反」運算符(~)
他是一元運算符,用於求整數的二進制反碼,即分別將操做數各二進制位上的1變爲0,0變爲1。
例如:~77(8)
源代碼:
#include <stdio.h>
main()
{
int a=077;
printf("%d",~a);
}
五、左移運算符(<<)
左移運算符是用來將一個數的各二進制位左移若干位,移動的位數由右操做數指定(右操做數必須是非負
值),其右邊空出的位用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。
六、右移運算符(>>)
右移運算符是用來將一個數的各二進制位右移若干位,移動的位數由右操做數指定(右操做數必須是非負
值),移到右端的低位被捨棄,對於無符號數,高位補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);
}
七、位運算賦值運算符
位運算符與賦值運算符能夠組成複合賦值運算符。例如: &=, |=, >>=, <<=, ∧=例: a & = b至關於 a = a & ba << =2至關於a = a << 2