.NET C# 教程初級(jí)篇 1-1 基本數(shù)據(jù)類(lèi)型及其存儲(chǔ)方式
全文目錄
本節(jié)內(nèi)容是對(duì)于C#基礎(chǔ)類(lèi)型的存儲(chǔ)方式以及C#基礎(chǔ)類(lèi)型的理論介紹
基礎(chǔ)數(shù)據(jù)類(lèi)型介紹
例如以下這句話:“張三是一名程序員嚷硫,今年15歲重50.3kg稠肘,他的代號(hào)是‘A’夺欲,他家的經(jīng)緯度是(N30,E134)宁仔《凸”鳞仙,這句話就是一個(gè)字符串隧枫,使用雙引號(hào)括起來(lái)第晰。而15則表示是一個(gè) 整數(shù)類(lèi)型,50.3就是小數(shù)類(lèi)型裸卫,不過(guò)我們?cè)?em>C# 中通常稱為 浮點(diǎn)類(lèi)型仿贬,最后一個(gè)經(jīng)緯度,我們通常定位地點(diǎn)的時(shí)候都是成對(duì)出現(xiàn)墓贿,所以我們認(rèn)為這二者是一個(gè)密不可分的結(jié)構(gòu)茧泪,這種類(lèi)型我們稱為 結(jié)構(gòu)體類(lèi)型(struct)蜓氨。
以上我所說(shuō)的數(shù)據(jù)類(lèi)型都是一個(gè)所含有信息量一定的數(shù)值,我們稱為值類(lèi)型队伟;而張三這個(gè)人穴吹,他所含有的數(shù)據(jù)大小是不固定的,比如我又了解到了張三是一個(gè)富二代嗜侮,那么他就會(huì)增加一個(gè)屬性是富二代港令,我們需要更多的空間去存儲(chǔ)他,張三這個(gè)變量我們通常就稱為引用類(lèi)型锈颗,而張三這個(gè)名字顷霹,我們就稱為引用,如果你對(duì)C或者C++熟悉的話宜猜,張三這個(gè)名字就是指向張三這個(gè)人(對(duì)象)的一個(gè)指針泼返。
C# 中兩種數(shù)據(jù)存儲(chǔ)方式
在C# 中,數(shù)據(jù)在內(nèi)存中的存儲(chǔ)方式主要分為在堆中存儲(chǔ)和棧中存儲(chǔ)姨拥。我們之前提到的值類(lèi)型就是存儲(chǔ)在棧中绅喉,引用類(lèi)型的數(shù)據(jù)是存儲(chǔ)在堆中,而數(shù)據(jù)是在棧中叫乌。
值類(lèi)型:存儲(chǔ)在棧(Stack柴罐,一段連續(xù)的內(nèi)存塊)中,存儲(chǔ)遵循先進(jìn)后出憨奸,有嚴(yán)格的順序讀取訪問(wèn)速度快革屠,可通過(guò)地址推算訪問(wèn)同一個(gè)棧的其余變量。
引用類(lèi)型:引用(本質(zhì)上和C++中的指針一致)存儲(chǔ)在棧中排宰,內(nèi)含的數(shù)據(jù)存儲(chǔ)在堆中(一大塊內(nèi)存地址似芝,內(nèi)部變量存儲(chǔ)不一定連續(xù)存儲(chǔ))。
事實(shí)上板甘,值類(lèi)型和引用類(lèi)型有一個(gè)很明顯的區(qū)別就是值類(lèi)型應(yīng)當(dāng)都是有值的党瓮,而引用類(lèi)型是可以為空值的。在C#中盐类,內(nèi)存管理相比于C/C++是更加安全的寞奸,在C/C++中我們可以自由的申請(qǐng)和釋放內(nèi)存空間,C#采用堆棧和托管堆進(jìn)行內(nèi)存管理在跳。也就是絕大部分的內(nèi)存管理都交給了CLR枪萄。通常來(lái)說(shuō)棧負(fù)責(zé)保存我們的代碼執(zhí)行(或調(diào)用)路徑(也就是直接指向的數(shù)據(jù)的內(nèi)存地址),而堆則負(fù)責(zé)保存對(duì)象(或者說(shuō)數(shù)據(jù),接下來(lái)將談到很多關(guān)于堆的問(wèn)題)的路徑猫妙。
考慮到實(shí)際難度瓷翻,在這里我們不做太多深入的研究,具體的分析內(nèi)容讀者可以查看本教程的番外補(bǔ)充篇進(jìn)行學(xué)習(xí)。
堆棧
堆棧一般用于存儲(chǔ)數(shù)據(jù)引用(指針)或是一些值類(lèi)型逻悠,它的空間并不大元践,通常只有幾M大小,它的讀取速度是快于存儲(chǔ)在堆中的數(shù)據(jù)的童谒。
托管堆
在C#中微軟使用了托管堆進(jìn)行內(nèi)存的管理,引用類(lèi)型的實(shí)例是內(nèi)存釋放都交給了GC(垃圾回收器)進(jìn)行自動(dòng)的處理沪羔。這樣保證了內(nèi)存的安全性饥伊。下圖是垃圾回收的機(jī)制:
常見(jiàn)的幾種數(shù)據(jù)類(lèi)型
- 字符類(lèi)型:char字符類(lèi)型,代表無(wú)符號(hào)的16位整數(shù)蔫饰,對(duì)應(yīng)的可能值是ASCⅡ碼,你可以上網(wǎng)搜索ASCⅡ碼的內(nèi)容
- 整數(shù)類(lèi)型:常用的一般有:byte琅豆,short,int篓吁,long茫因。各代表8位、16位杖剪、32位冻押、64位整型。占用內(nèi)存分別為(位數(shù)/8)字節(jié)盛嘿。范圍則是 +-(位數(shù))個(gè)1組成的二進(jìn)制的十進(jìn)制數(shù)/2洛巢。例如byte的范圍則是11111111轉(zhuǎn)十進(jìn)制后除以2取反,即-127~128次兆。范圍絕對(duì)值之和為256稿茉。
- 浮點(diǎn)類(lèi)型:float, double, decimal:浮點(diǎn)類(lèi)型,分別代表32位芥炭、64位漓库、128位浮點(diǎn)類(lèi)型。通常默認(rèn)類(lèi)型是double园蝠,如果需要指定float類(lèi)型渺蒿,需要1.3f,decimal類(lèi)型則指定1.3m砰琢。浮點(diǎn)型存在的問(wèn)題是精度的損失蘸嘶,并不一定安全。
- 布爾類(lèi)型:bool類(lèi)型是一個(gè)二進(jìn)制中的0和1陪汽,各代表了false和true训唱。只存在兩個(gè)值。
- 字符串類(lèi)型:string本質(zhì)是一種語(yǔ)法糖挚冤,作為字符類(lèi)型的數(shù)組引用(指針)存在况增,也是String類(lèi)的簡(jiǎn)寫(xiě)
- 委托類(lèi)型:delegate用于綁定函數(shù),為引用類(lèi)型的一種训挡,將函數(shù)參數(shù)化為變量澳骤。本質(zhì)上就是C++中的函數(shù)指針歧强。
- 數(shù)組:繼承自Array類(lèi),屬于任意類(lèi)型的一種集合为肮,但不同于集合摊册,大小必須被初始化。在內(nèi)存中是一段連續(xù)的內(nèi)存空間颊艳,但是不是值類(lèi)型茅特。
數(shù)據(jù)的存儲(chǔ)方式
對(duì)于大部分學(xué)習(xí)者而言,數(shù)據(jù)的存儲(chǔ)方式是一個(gè)相對(duì)陌生的概念棋枕,但是為了全面理解和學(xué)習(xí)白修,還是有必要進(jìn)行一個(gè)簡(jiǎn)單的學(xué)習(xí)的。這里不會(huì)講述過(guò)難的組成原理知識(shí)重斑,只是讓讀者明白一些有關(guān)計(jì)算機(jī)科學(xué)的原理和常識(shí)兵睛。
進(jìn)制
首先我們學(xué)習(xí)一下在計(jì)算機(jī)常用的一些進(jìn)制,這里以二進(jìn)制窥浪、八進(jìn)制和十六進(jìn)制進(jìn)行展開(kāi)祖很。在進(jìn)行講解之前,提出一個(gè)問(wèn)題寒矿,為什么我們的計(jì)算機(jī)都是以二進(jìn)制為基礎(chǔ)進(jìn)行算數(shù)的運(yùn)算呢突琳?
其實(shí)答案很簡(jiǎn)單,因?yàn)橛?jì)算機(jī)是采用數(shù)字電路進(jìn)行邏輯運(yùn)算最終實(shí)現(xiàn)我們的功能的符相,而對(duì)于一條電路而言拆融,它的電位只有高低兩種電平,或者理解為只分為有電流和無(wú)電流通過(guò)啊终。因此使用0和1作為標(biāo)識(shí)是非常實(shí)用的镜豹。同時(shí)采用二進(jìn)制也有利于我們電路邏輯的設(shè)計(jì)。
二進(jìn)制的運(yùn)算非常的簡(jiǎn)單蓝牲,從低到高位分別賦予權(quán)重趟脂,n為位數(shù),而一串二進(jìn)制的十進(jìn)制表示的計(jì)算公式為
其中稱為位權(quán)例衍,取值是0或1昔期,更一般的,一個(gè)r進(jìn)制數(shù)的的位權(quán)取值是一個(gè)大于0小于r-1的數(shù)佛玄,r進(jìn)制數(shù)轉(zhuǎn)換為10進(jìn)制的計(jì)算公式如下:
在C#中硼一,表示一個(gè)二進(jìn)制通常用Ob開(kāi)頭,8進(jìn)制則是以0開(kāi)頭梦抢,16進(jìn)制以0x開(kāi)頭般贼,例如
int a = 0b101011;//二進(jìn)制
int b = 035167;//八進(jìn)制
int a = 0xD2F3;//十六進(jìn)制
講完了二進(jìn)制數(shù),接下來(lái)我們講講八進(jìn)制和十六進(jìn)制。既然二進(jìn)制如此美妙好用哼蛆,為什么各位計(jì)算機(jī)學(xué)家還是要在計(jì)算機(jī)大量的使用八進(jìn)制和十六進(jìn)制呢蕊梧?一個(gè)很明顯的例子就是變量在內(nèi)存中往往都是以8或16進(jìn)制進(jìn)行存儲(chǔ),不知道你有沒(méi)有看過(guò)時(shí)常彈出來(lái)的錯(cuò)誤窗口中會(huì)提示內(nèi)存0xfffff錯(cuò)誤腮介,這里就是使用了我們的十六進(jìn)制肥矢。原因是因?yàn)橐欢芜^(guò)長(zhǎng)的二進(jìn)制值是可讀性非常差的,而選擇八進(jìn)制和十六進(jìn)制正是縮短了過(guò)長(zhǎng)的二進(jìn)制叠洗,因?yàn)榘诉M(jìn)制逢8進(jìn)1橄抹,也就是2的3次方,十六進(jìn)制則是2的4次方惕味,十六進(jìn)制超過(guò)9以后的數(shù)以字母A~F表示。例如101011011011這串二進(jìn)制代碼玉锌,如果換算成八進(jìn)制則是05333名挥,轉(zhuǎn)換成為十六進(jìn)制則是0xACB,很明顯大大縮小了我們的閱讀難度主守,同時(shí)因?yàn)槠涫?的整數(shù)次方禀倔,轉(zhuǎn)換也十分的簡(jiǎn)單迅速。
二進(jìn)制轉(zhuǎn)八進(jìn)制的訣竅是参淫,從低到高位救湖,每三位一組(),最后不足三位的前面添0涎才,以每一組二進(jìn)制的值為位權(quán)鞋既,最終就是我們的八進(jìn)制數(shù)。十六進(jìn)制也一樣耍铜,只不過(guò)改成以4個(gè)為一組(
)邑闺。如果將16或8進(jìn)制轉(zhuǎn)換成為2進(jìn)制,則將十六或八進(jìn)制中從每一位按4或3位展開(kāi)即可棕兼。例如
1011011011轉(zhuǎn)八進(jìn)制的過(guò)程陡舅,先添0補(bǔ)足長(zhǎng)度為3的倍數(shù),001011011011伴挚,分組001|011|011|011靶衍,則表示為1333,十六進(jìn)制和N進(jìn)制轉(zhuǎn)2進(jìn)制希望讀者自己嘗試解決茎芋。
如果帶小數(shù)點(diǎn)颅眶,則依次類(lèi)推,只不過(guò)我們指數(shù)冪就換成負(fù)數(shù)即可败徊,這里不再展開(kāi)贅述帚呼。
在C#中也提供了相關(guān)的函數(shù)方便我們迅速進(jìn)行進(jìn)制間的轉(zhuǎn)換
// value為需轉(zhuǎn)換的R進(jìn)制數(shù),以字符串表示,fromBase為需轉(zhuǎn)換的進(jìn)制
Convert.ToInt32(string value, int fromBase):
// value為需轉(zhuǎn)換的十進(jìn)制數(shù)煤杀,toBase為需轉(zhuǎn)換的進(jìn)制
Convert.ToString(int value, int toBase);
值得補(bǔ)充的一點(diǎn)是眷蜈,數(shù)據(jù)在內(nèi)存中的存儲(chǔ)大小本身是由數(shù)據(jù)的 位(bit) 決定的,我們常說(shuō)的一字節(jié)在現(xiàn)在的計(jì)算機(jī)中指有8個(gè)比特空間大小沈自,一個(gè)比特位可以存儲(chǔ)一位二進(jìn)制代碼酌儒,而我們常見(jiàn)的int類(lèi)型默認(rèn)是Int32,也就是32位整形枯途,因此你知道為什么int是4個(gè)字節(jié)了吧忌怎?
正負(fù)數(shù)存儲(chǔ)形式及四種碼
在計(jì)算機(jī)中,數(shù)據(jù)往往并不是直接以數(shù)值本身的二進(jìn)制碼(機(jī)器數(shù))進(jìn)行存儲(chǔ)和計(jì)算的酪夷,我們往往需要對(duì)數(shù)值的二進(jìn)制碼進(jìn)行一些變換榴啸。同時(shí)你是否想過(guò),正數(shù)我們可以直接寫(xiě)出它的二進(jìn)制碼晚岭,那么碰到負(fù)數(shù)我們又應(yīng)該如何做呢鸥印?也許聰明的你已經(jīng)想要脫口而出:既然因?yàn)殡娢恢挥袃煞N狀態(tài)我們用0和1進(jìn)行表示,正負(fù)也只有兩種表示方法坦报!因此我們?cè)诙M(jìn)制碼的頭部增加一位符號(hào)位進(jìn)行有符號(hào)數(shù)的正負(fù)標(biāo)識(shí)库说,這里我們用1表示負(fù)號(hào),0表示正號(hào)片择。這里似乎又解決了我們一個(gè)很頭大的問(wèn)題:為什么int潜的、long這種有符號(hào)數(shù)表示的范圍是要比它所占的位數(shù)少一位,因?yàn)樽罡呶挥糜跇?biāo)識(shí)它的符號(hào)了字管。
這里我們引入下一個(gè)概念 “原碼”:原碼是最簡(jiǎn)單啰挪、直觀的機(jī)器數(shù)表示方法了,也就是用機(jī)器數(shù)的最高位標(biāo)識(shí)它的符號(hào)纤掸,其余為數(shù)據(jù)位是數(shù)的絕對(duì)值脐供。例如-8這個(gè)十進(jìn)制數(shù)用二進(jìn)制原碼表示就是1100。值得一提的是借跪,0在原碼表示法中有兩種表示政己,+0和-0。
反碼 :反碼的概念非常的簡(jiǎn)單掏愁,通常反碼在計(jì)算機(jī)中只起到原碼到補(bǔ)碼轉(zhuǎn)換的過(guò)渡過(guò)程歇由。在這直接拋出計(jì)算方法而不做贅述。對(duì)于正數(shù)果港,反碼就是其本身沦泌,對(duì)于負(fù)數(shù),反碼則是將原碼中除符號(hào)位外每一位數(shù)字進(jìn)行邏輯取反辛掠,因此它的性質(zhì)和原碼其實(shí)是一致的谢谦。 例如+8的二進(jìn)制為0,100释牺,反碼就是0,100,對(duì)于-8的二進(jìn)制1,100回挽,反碼則為1,011
接下來(lái)介紹的是計(jì)算機(jī)中真正的數(shù)據(jù)存儲(chǔ)方式没咙,補(bǔ)碼:首先,補(bǔ)碼正如其名千劈,和原碼是一對(duì)互補(bǔ)的數(shù)字祭刚。它的和原碼之間的關(guān)系是:對(duì)于正數(shù),補(bǔ)碼就是其本身墙牌,對(duì)于負(fù)數(shù)涡驮,原碼的反碼+1=補(bǔ)碼。
我們引入一個(gè)生活中的小例子喜滨,我們?cè)诳寸姳淼臅r(shí)候捉捅,如果以0(12)作為基準(zhǔn),如果現(xiàn)在指針指向3虽风,我們正常會(huì)以順時(shí)針從0(12)開(kāi)始數(shù)到3锯梁,得知現(xiàn)在是3點(diǎn),如果是指向9焰情,我們則會(huì)從0(12)開(kāi)始逆時(shí)針開(kāi)始數(shù)“粒或者說(shuō)内舟,你看到15點(diǎn)會(huì)不自覺(jué)的知道指針指向3,因?yàn)?5-12=3初橘,這里其實(shí)就已經(jīng)用到了補(bǔ)碼的概念验游。事實(shí)上,在計(jì)算機(jī)的結(jié)構(gòu)中保檐,加法是可以直接進(jìn)行運(yùn)算的耕蝉,但是并沒(méi)有針對(duì)減法設(shè)計(jì)數(shù)字電路,因?yàn)闇p法的數(shù)字電路并不容易設(shè)計(jì)夜只,同時(shí)也出于節(jié)約成本的考慮垒在,如果只設(shè)計(jì)加法電路的情況,如何去得到我們的減法扔亥?這里先需要知道一個(gè)運(yùn)算求余——%场躯,例如7%3=1,即除法后的余數(shù)旅挤。我們就以7-3為例子踢关,試著將一個(gè)減法運(yùn)算成加法。
答案非常的顯而易見(jiàn)粘茄,7-3不就是7+(-3)嗎签舞?你可以假設(shè)一個(gè)鐘表秕脓,它的最大值是12,現(xiàn)在指向7儒搭,我們定義順時(shí)針為正吠架,逆時(shí)針為負(fù)。現(xiàn)在鐘表指向了7师妙,我們逆時(shí)針往回轉(zhuǎn)3個(gè)小時(shí)诵肛,指針指向了4。那么問(wèn)題來(lái)了默穴,我們是不是也可以順時(shí)針轉(zhuǎn)9格也得到4呢怔檩?按著我們的定義7+9=16并不等于4,但我們的鐘表最大也只有12呀蓄诽,因此我們需要將溢出位丟棄薛训,也就是取余操作(7+9) mod 12=4。這樣我們就成功的將一個(gè)減法運(yùn)算設(shè)計(jì)成了加法運(yùn)算了仑氛。
因此回到我們補(bǔ)碼的概念乙埃,那么7-3實(shí)際上是7和-3進(jìn)行相加,加法是可以直接運(yùn)算的锯岖,而從補(bǔ)碼和反碼的定義我們知道負(fù)數(shù)的反碼是數(shù)值位進(jìn)行取反而符號(hào)位不變介袜,因此負(fù)數(shù)的也就是
,這也就體現(xiàn)了補(bǔ)碼的名稱了出吹。因此對(duì)于減法
遇伞,可以化為
,其實(shí)證明并不難捶牢,如下
更一般的鸠珠,若數(shù)據(jù)表示的最大原碼為M-1,對(duì)于定點(diǎn)類(lèi)型數(shù)(整數(shù)秋麸、定點(diǎn)小數(shù))渐排,有
講到這里,其實(shí)也就解釋通了為什么在計(jì)算機(jī)中灸蟆,數(shù)據(jù)都是以補(bǔ)碼的形式進(jìn)行存儲(chǔ)和運(yùn)算了驯耻,因?yàn)榭梢灾v任意的加減法(乘除法實(shí)際上也就是循環(huán)型的加減)都按著加法進(jìn)行運(yùn)算,有利于節(jié)省成本和降低設(shè)計(jì)難度炒考。
移碼是我們四碼里面的最后一種碼吓歇,它通常用于表示浮點(diǎn)數(shù)的階碼,具體的運(yùn)用在下文會(huì)詳細(xì)的進(jìn)行介紹票腰,這里不再展開(kāi)城看。移碼的定義非常簡(jiǎn)單,就是在真值X上加上偏置量杏慰,通常是以2的n次方為偏置量测柠,就相當(dāng)于X在數(shù)軸之上偏移了若干個(gè)單位炼鞠。移碼的求解方法非常簡(jiǎn)單,將補(bǔ)碼的符號(hào)位取反就是移碼轰胁。例如真值1谒主,進(jìn)行移位得到了17,轉(zhuǎn)換成為補(bǔ)碼形式就是10001。
定點(diǎn)數(shù)與浮點(diǎn)數(shù)存儲(chǔ)方式
定點(diǎn)數(shù)和浮點(diǎn)數(shù)統(tǒng)稱實(shí)型赃阀,點(diǎn)指代小數(shù)點(diǎn)霎肯,定點(diǎn)數(shù)無(wú)需解釋?zhuān)覀冎灰孪纫?guī)定好整數(shù)位和小數(shù)位的數(shù)量即可表示。對(duì)于浮點(diǎn)數(shù)榛斯,
*數(shù)據(jù)的存儲(chǔ)方式(選看)
數(shù)據(jù)的存儲(chǔ)方式主要分為大端存儲(chǔ)和小端存儲(chǔ)观游、邊界對(duì)齊存儲(chǔ)(詳情請(qǐng)看結(jié)構(gòu)體的內(nèi)容)兩種。對(duì)于現(xiàn)代的計(jì)算機(jī)驮俗,數(shù)據(jù)的存儲(chǔ)通常以字節(jié)編址懂缕,也就是一個(gè)地址編號(hào)對(duì)應(yīng)的內(nèi)存單元存儲(chǔ)1個(gè)字節(jié)。那么對(duì)于一個(gè)大的數(shù)據(jù)王凑,我們可能會(huì)存儲(chǔ)在連續(xù)的多個(gè)內(nèi)存單元之中搪柑。
大端小端沒(méi)有誰(shuí)優(yōu)誰(shuí)劣,各自優(yōu)勢(shì)便是對(duì)方劣勢(shì)索烹,我們不太需要關(guān)注哪一種存儲(chǔ)方式工碾,只需要大體了解一下即可。
- 小端存儲(chǔ)就是低位字節(jié)排放在內(nèi)存的低地址端百姓,高位字節(jié)排放在內(nèi)存的高地址端倚喂。
- 大端存儲(chǔ)就是高位字節(jié)排放在內(nèi)存的低地址端,低位字節(jié)排放在內(nèi)存的高地址端瓣戚。
例如數(shù)字0x12345678進(jìn)行存儲(chǔ)時(shí),存儲(chǔ)內(nèi)存結(jié)構(gòu)如下圖焦读。
小端模存儲(chǔ)中強(qiáng)制轉(zhuǎn)換數(shù)據(jù)不需要調(diào)整字節(jié)內(nèi)容子库,1、2矗晃、4字節(jié)的存儲(chǔ)方式一樣仑嗅。而在大端存儲(chǔ)中符號(hào)位的判定固定為第一個(gè)字節(jié),容易判斷正負(fù)张症。
為什么要學(xué)這個(gè)奇怪的知識(shí)呢仓技?因?yàn)樵诳缯Z(yǔ)言或平臺(tái)的通信之中,不了解這個(gè)知識(shí)總是會(huì)有一些奇奇怪怪的錯(cuò)誤出現(xiàn)俗他,例如Java網(wǎng)絡(luò)通信中脖捻,數(shù)據(jù)流是按大端字節(jié)序,和網(wǎng)絡(luò)字節(jié)序一致的方法進(jìn)行傳輸兆衅,而C#在Windows平臺(tái)上是小端字節(jié)序進(jìn)行數(shù)據(jù)存儲(chǔ)地沮。那么如果一個(gè)Java程序往一個(gè)C#程序發(fā)送網(wǎng)絡(luò)數(shù)據(jù)包的時(shí)候嗜浮,由于數(shù)據(jù)存儲(chǔ)順序的不同就會(huì)導(dǎo)致數(shù)據(jù)讀取結(jié)果的不同。
大家可以閱讀這兩篇博文進(jìn)行一個(gè)理解:
值與引用類(lèi)型的存儲(chǔ)方式
在前文中我們其實(shí)已經(jīng)講過(guò)許多有關(guān)值類(lèi)型和引用類(lèi)型的存儲(chǔ)摩疑,大體上我們值類(lèi)型危融、指令、指針等是直接存儲(chǔ)在棧中雷袋,而引用類(lèi)型吉殃、委托等指針指向的類(lèi)型則存儲(chǔ)在托管堆中。具體請(qǐng)看文章開(kāi)頭處對(duì)數(shù)據(jù)類(lèi)型的簡(jiǎn)介楷怒。
C#中定義變量的方式及數(shù)據(jù)轉(zhuǎn)換的方法
在C#中定義變量的方式和其他的主流語(yǔ)言沒(méi)有太大的區(qū)別蛋勺,以下是幾種定義方式:
int number = 5;//定義一個(gè)32位整數(shù)類(lèi)型
bool b = true;//定義
//注意看以下兩條,string定義的字符串必須為雙引號(hào)率寡,而char使用單引號(hào)并且只允許輸入一個(gè)字符
string str = "test";
char a = 'a';
//記得后綴
float f = 1.3f;
decimal d = 1.5m;
數(shù)據(jù)類(lèi)型的轉(zhuǎn)換分為隱式轉(zhuǎn)換和顯式轉(zhuǎn)換迫卢,看下面幾個(gè)例子:
string a = "15";
int b = int.Parse(a);//顯式轉(zhuǎn)換
b = (int)a;//強(qiáng)制轉(zhuǎn)換
b = Convert.ToInt32(a);//顯式轉(zhuǎn)換,較常用
double d = 1.5;
b = d;//隱式轉(zhuǎn)換
如果我們定義的數(shù)據(jù)大小超過(guò)了數(shù)據(jù)類(lèi)型本身的大小冶共,那么位于高位的數(shù)據(jù)會(huì)被首先舍棄乾蛤。
這里還有一種相對(duì)特殊的類(lèi)型——無(wú)符號(hào)類(lèi)型,通過(guò)前文的介紹捅僵,我們大體已經(jīng)知道了有符號(hào)數(shù)字的定義以及存儲(chǔ)方式家卖,而對(duì)于無(wú)符號(hào)數(shù),補(bǔ)碼原碼反碼都是其本身庙楚,也就是將首位的符號(hào)位替換成了數(shù)據(jù)位上荡。當(dāng)有符號(hào)數(shù)向無(wú)符號(hào)數(shù)進(jìn)行轉(zhuǎn)換時(shí),我們需要計(jì)算出有符號(hào)數(shù)的補(bǔ)碼馒闷,然后直接按公式進(jìn)行計(jì)算酪捡。例如:
int a = -3;//補(bǔ)碼為100
uint b = a;//b=8
數(shù)組
數(shù)組指一個(gè)類(lèi)型(任意)的集合,例如你定義一個(gè)變量為a=5纳账,很輕松逛薇,假設(shè)你需要100個(gè)呢?因此我們使用數(shù)組來(lái)存儲(chǔ)疏虫。
數(shù)組的定義以及使用如下:
//偽代碼永罚,T為類(lèi)型,n為大小
T [] t = new T[n]卧秘;
//定義一個(gè)整型數(shù)組
int [] a = new int [5];
//你也可以選擇初始化的方式定義
int [] b = new int [] {1,2,3,4,5};
//或
int [] c = new int [5]{1,2,3,4,5};
//數(shù)組的訪問(wèn)呢袱,從0開(kāi)始索引
Console.WriteLine(b[0]);
有時(shí)候我們也許會(huì)想用一個(gè)表格進(jìn)行數(shù)據(jù)的存儲(chǔ),例如我們存儲(chǔ)一個(gè)矩陣就需要二維的空間翅敌,這里給出二維數(shù)組的定義:
//偽代碼羞福,T為類(lèi)型,m,n為大小
T [,] t = new T[m,n]蚯涮;
本質(zhì)上二維數(shù)組的概念就是數(shù)組的數(shù)組坯临,一個(gè)組成元素為一維數(shù)組的數(shù)組就是我們的二維數(shù)組焊唬。一般而言,我們需要指定二維數(shù)組的行列寬看靠,當(dāng)然我們也可以不指定行數(shù)直接初始化赶促,但我們必須指定列數(shù),因?yàn)閮?nèi)存是按行進(jìn)行分配挟炬。
運(yùn)算符及規(guī)則重載
基礎(chǔ)的運(yùn)算符
- +-*/:對(duì)應(yīng)數(shù)學(xué)中的加減乘除鸥滨。
- %: 求余運(yùn)算,a%b指a除以b的余數(shù)谤祖。
- & | ~ ^ :分別為按位與婿滓、按位或、按位取反粥喜、按位異或
- <<凸主、>>:左右移位運(yùn)算符,例如0010 --> 0100
- ?:三元判斷運(yùn)算符
^是異或,result=1110,就是說(shuō)異是不同返回1额湘,相同是0卿吐,或就是只要有1就返回1。
&是與, result=0001,也就是相同返回1锋华,不同為0
|是或, result=1111,除了兩個(gè)都為0嗡官,否則返回1
~稱為按位取反,我們表示符號(hào)是用四個(gè)0表示毯焕,運(yùn)算規(guī)則就是正數(shù)的反碼衍腥,補(bǔ)碼都是其本身的源碼,
負(fù)數(shù)的反碼是符號(hào)位不變纳猫,本身的0變1,1變0婆咸,補(bǔ)碼就是反碼+1,
最后進(jìn)行補(bǔ)碼取反時(shí)連同符號(hào)位一起變得到的反碼就是結(jié)果
流程如下:0000 0111 --> 0000 1000 --> 0000 1001 --> 1111 0110 = -8
>>稱為右移,右移一位流程如下 0000 1001 --> 0000 0100 = 4
<< 稱為左移,左移一位流程如下 0000 1001 --> 0000 10010 = 18
移位運(yùn)算需要注意的一點(diǎn)是芜辕,由于我們計(jì)算機(jī)保存數(shù)據(jù)的方式是采取補(bǔ)碼存儲(chǔ)尚骄,因此,當(dāng)我們對(duì)一個(gè)負(fù)數(shù)進(jìn)行移位時(shí)物遇,在添加的并不是0而是1。
運(yùn)算符的重載
我們?cè)诖蟛糠謺r(shí)候憾儒,語(yǔ)言自身提供的運(yùn)算符運(yùn)算規(guī)則已經(jīng)足夠我們使用询兴,但往往我們會(huì)涉及到一些奇怪的場(chǎng)景,例如我需要知道某兩個(gè)節(jié)日的日期相距多少天而我并不想借助DateTime類(lèi)的方法起趾,我想用date1-date2進(jìn)行計(jì)算诗舰,那么我們就需要使用運(yùn)算符重載去改寫(xiě)減號(hào)的規(guī)則。
事實(shí)上我們仔細(xì)思考不難得出結(jié)論训裆,一切的運(yùn)算符本質(zhì)上都是一種函數(shù)的對(duì)應(yīng)關(guān)系眶根,那么我們使用operator關(guān)鍵字進(jìn)行某類(lèi)中運(yùn)算符的重載蜀铲,例如:
// T是修改類(lèi)型的返回值
public static T operator +(D d1,D d2)
{
return something;
}
通過(guò)運(yùn)算符重載,我們可以更有效的書(shū)寫(xiě)高質(zhì)量的代碼属百,同時(shí)可讀性也可以大大提升记劝。
具體的操作我會(huì)在我在BiliBili上發(fā)布的 .Net Core 教程上進(jìn)行詳細(xì)的講述。
*結(jié)構(gòu)體(選看)
結(jié)構(gòu)體是一種比較特殊的數(shù)據(jù)類(lèi)型族扰,它很像我們后面講述到的類(lèi)厌丑,但是他并不是一個(gè)類(lèi),他本質(zhì)還是值類(lèi)型渔呵,結(jié)構(gòu)體的使用是很重要的怒竿,如果結(jié)構(gòu)體使用得當(dāng),可以有效的提升程序的效率扩氢。
結(jié)構(gòu)體你可以理解為將將若干個(gè)類(lèi)型拼接在一起耕驰,但是存在一個(gè)很重要的內(nèi)容——內(nèi)存對(duì)齊。例如下面兩個(gè)結(jié)構(gòu)體:
struct S
{
int a;
long b;
int c;
}
struct SS
{
int a;
int b;
long c;
}
乍一看你會(huì)覺(jué)得這兩個(gè)結(jié)構(gòu)體完全一致录豺,絲毫沒(méi)有任何的差別朦肘。但事實(shí)上,在大多數(shù)編程語(yǔ)言里面巩检,對(duì)于結(jié)構(gòu)體這種大小并不是定值的值類(lèi)型厚骗,都存在一個(gè)最小分配單元用于結(jié)構(gòu)體內(nèi)單個(gè)變量的大小分配。在內(nèi)存中兢哭,他們兩個(gè)的存儲(chǔ)方式有很大的不同领舰。
對(duì)于上面兩個(gè)結(jié)構(gòu)體,他們?cè)趦?nèi)存中的單元分配是:
- S:a(4 byte + 4 free) --> b(8 byte) --> c(4 byte + 4 free)迟螺,共計(jì)24字節(jié)
- SS:a(4 byte)b(4 byte) --> c(8 byte)冲秽,共計(jì)16字節(jié)
在C#中,如果你不指定最小分配單元矩父,那么編譯器將會(huì)把結(jié)構(gòu)體中占用內(nèi)存最大的作為最小分配單元锉桑。不過(guò)尤其需要注意一件事,就是引用類(lèi)型在結(jié)構(gòu)體中窍株。鑒于我們現(xiàn)在尚未講解面向?qū)ο蟮念?lèi)民轴,我們用string作為成員寫(xiě)一個(gè)結(jié)構(gòu)體。如下面這個(gè)例子:
struct S
{
char a;
long b;
string c;
}
//函數(shù)中創(chuàng)建
S s = new S();
s.a = 'a';
s.b = 15;
s.c = "I Love .NET Core And Microsoft"
很顯然s.c的大小超過(guò)了結(jié)構(gòu)體中其余兩個(gè)球订,但是內(nèi)存分配的時(shí)候就是以最大的c作為標(biāo)準(zhǔn)嗎后裸?
顯然不是,我們要知道struct是在棧中分配內(nèi)存冒滩,string的內(nèi)容是在堆中的微驶,所以在結(jié)構(gòu)體中存儲(chǔ)的string只是一個(gè)引用,并不會(huì)包含其他的東西,只占用4個(gè)字節(jié)因苹。并且特別的苟耻,引用類(lèi)型在內(nèi)存中的位置位于大于四字節(jié)的字段前,小于四字節(jié)字段后扶檐。
上面內(nèi)存分配應(yīng)當(dāng)是這樣:
a(8) --> c(8) --> b(8)凶杖。
如果需要深入了解這一方面內(nèi)容,建議去閱讀《CLR Via C#》這本書(shū)蘸秘,以及學(xué)習(xí)SOS調(diào)試相關(guān)內(nèi)容官卡。
練習(xí)題
理論分析題
- 計(jì)算出int和long的數(shù)值范圍
- 為什么在大部分提供科學(xué)計(jì)算或編程語(yǔ)言會(huì)存在精度問(wèn)題?例如浮點(diǎn)數(shù)2.5在任何一種采用二進(jìn)制計(jì)算的編程語(yǔ)言中也不是一個(gè)精確值醋虏?或者說(shuō)如果我們展開(kāi)浮點(diǎn)數(shù)的所有精確位寻咒,最后的幾位小數(shù)并不是0?(較難)
- 為什么引用類(lèi)型即使不存儲(chǔ)內(nèi)容也需要內(nèi)存空間颈嚼?
- 試說(shuō)明引用類(lèi)型和值類(lèi)型的優(yōu)缺點(diǎn)
- 數(shù)組為什么需要初始化大忻亍?如果是多維數(shù)組阻课,不指定列寬可以嗎叫挟?
計(jì)算題
- 求123.6875的二進(jìn)制、八進(jìn)制限煞、十六進(jìn)制表達(dá)式抹恳。
- 求
二進(jìn)制小數(shù)轉(zhuǎn)換為十進(jìn)制。
- a=5署驻,b=8奋献,試手算a&b,a|b旺上,a^b瓶蚂,a<<1, b>>1
- 若a=12宣吱,試手算~a
- 若a為8位二進(jìn)制窃这,試著寫(xiě)出將a的高四位取反,第四位不變的運(yùn)算表達(dá)式
- int a = 15征候,試求a+int.MaxValue的值
編程題
- 請(qǐng)學(xué)習(xí)指針內(nèi)容以及C#unsafe調(diào)試杭攻,試著不使用索引進(jìn)行數(shù)組的讀取。
- 將字符串”15”轉(zhuǎn)成整數(shù)疤坝?
- 使用運(yùn)算符重載兆解,計(jì)算向量的加減和點(diǎn)乘(內(nèi)積)
Reference
《C# in Depth》—— Jon Skeet
《計(jì)算機(jī)組成原理》——唐朔飛