.NET C# 教程初級篇 1-1 基本資料型別及其存盤方式
本文目錄
- 1. 全文目錄
- 2. 基礎資料型別介紹
- 3. C#中兩種資料存盤方式
- 3.1 堆疊
- 3.2 托管堆
- 4. 常見的幾種資料型別
- 5. 資料的存盤方式
- 5.1 進制
- 5.2 正負數存盤形式及四種碼
- 5.3 定點數與浮點數存盤方式
- 5.4 資料的存盤方式(選看)
- 5.5 值與參考型別的存盤方式
- 6. C#中定義變數的方式及資料轉換的方法
- 7. 陣列
- 8. 運算子及規則多載
- 8.1 基礎的運算子
- 8.2 運算子的多載
- 9. 結構體(選看)
- 10. 練習題
- 10.1 理論分析題
- 10.2 計算題
- 10.3 編程題
- 11. Reference
- 12. About Me
全文目錄
(博客園).NET Core Guide
(Github).NET Core Guide
本節內容是對于C#基礎型別的存盤方式以及C#基礎型別的理論介紹
基礎資料型別介紹
例如以下這句話:“張三是一名程式員,今年15歲重50.3kg,他的代號是‘A’,他家的經緯度是(N30,E134),”,這句話就是一個字串,使用雙引號括起來,而15則表示是一個 整數型別,50.3就是小數型別,不過我們在C# 中通常稱為 浮點型別,最后一個經緯度,我們通常定位地點的時候都是成對出現,所以我們認為這二者是一個密不可分的結構,這種型別我們稱為 結構體型別(struct),
以上我所說的資料型別都是一個所含有資訊量一定的數值,我們稱為值型別;而張三這個人,他所含有的資料大小是不固定的,比如我又了解到了張三是一個富二代,那么他就會增加一個屬性是富二代,我們需要更多的空間去存盤他,張三這個變數我們通常就稱為參考型別,而張三這個名字,我們就稱為參考,如果你對C或者C++熟悉的話,張三這個名字就是指向張三這個人(物件)的一個指標,
C# 中兩種資料存盤方式
在C# 中,資料在記憶體中的存盤方式主要分為在堆中存盤和堆疊中存盤,我們之前提到的值型別就是存盤在堆疊中,參考型別的資料是存盤在堆中,而資料是在堆疊中,
值型別:存盤在堆疊(Stack,一段連續的記憶體塊)中,存盤遵循先進后出,有嚴格的順序讀取訪問速度快,可通過地址推算訪問同一個堆疊的其余變數,
參考型別:參考(本質上和C++中的指標一致)存盤在堆疊中,內含的資料存盤在堆中(一大塊記憶體地址,內部變數存盤不一定連續存盤),
事實上,值型別和參考型別有一個很明顯的區別就是值型別應當都是有值的,而參考型別是可以為空值的,在C#中,記憶體管理相比于C/C++是更加安全的,在C/C++中我們可以自由的申請和釋放記憶體空間,C#采用堆疊和托管堆進行記憶體管理,也就是絕大部分的記憶體管理都交給了CLR,通常來說堆疊負責保存我們的代碼執行(或呼叫)路徑(也就是直接指向的資料的記憶體地址),而堆則負責保存物件(或者說資料,接下來將談到很多關于堆的問題)的路徑,

考慮到實際難度,在這里我們不做太多深入的研究,具體的分析內容讀者可以查看本教程的番外補充篇進行學習,
堆疊
堆疊一般用于存盤資料參考(指標)或是一些值型別,它的空間并不大,通常只有幾M大小,它的讀取速度是快于存盤在堆中的資料的,
托管堆
在C#中微軟使用了托管堆進行記憶體的管理,參考型別的實體是記憶體釋放都交給了GC(垃圾回收器)進行自動的處理,這樣保證了記憶體的安全性,下圖是垃圾回收的機制:

常見的幾種資料型別
- 字符型別:char字符型別,代表無符號的16位整數,對應的可能值是ASCⅡ碼,你可以上網搜索ASCⅡ碼的內容
- 整數型別:常用的一般有:byte,short,int,long,各代表8位、16位、32位、64位整型,占用記憶體分別為(位數/8)位元組,范圍則是 +-(位數)個1組成的二進制的十進制數/2,例如byte的范圍則是11111111轉十進制后除以2取反,即-127~128,范圍絕對值之和為256,
- 浮點型別:float, double, decimal:浮點型別,分別代表32位、64位、128位浮點型別,通常默認型別是double,如果需要指定float型別,需要1.3f,decimal型別則指定1.3m,浮點型存在的問題是精度的損失,并不一定安全,
- 布爾型別:bool型別是一個二進制中的0和1,各代表了false和true,只存在兩個值,
- 字串型別:string本質是一種語法糖,作為字符型別的陣列參考(指標)存在,也是String類的簡寫
- 委托型別:delegate用于系結函式,為參考型別的一種,將函式引數化為變數,本質上就是C++中的函式指標,
- 陣列:繼承自Array類,屬于任意型別的一種集合,但不同于集合,大小必須被初始化,在記憶體中是一段連續的記憶體空間,但是不是值型別,
資料的存盤方式
對于大部分學習者而言,資料的存盤方式是一個相對陌生的概念,但是為了全面理解和學習,還是有必要進行一個簡單的學習的,這里不會講述過難的組成原理知識,只是讓讀者明白一些有關計算機科學的原理和常識,
進制
首先我們學習一下在計算機常用的一些進制,這里以二進制、八進制和十六進制進行展開,在進行講解之前,提出一個問題,為什么我們的計算機都是以二進制為基礎進行算數的運算呢?
其實答案很簡單,因為計算機是采用數字電路進行邏輯運算最終實作我們的功能的,而對于一條電路而言,它的電位只有高低兩種電平,或者理解為只分為有電流和無電流通過,因此使用0和1作為標識是非常實用的,同時采用二進制也有利于我們電路邏輯的設計,
二進制的運算非常的簡單,從低到高位分別賦予權重\(2^{n-1}\),n為位數,而一串二進制的十進制表示的計算公式為
\[\sum_{i=0}^{-m}K_i*2^i \]其中\(K_i\)稱為位權,取值是0或1,更一般的,一個r進制數的的位權取值是一個大于0小于r-1的數,r進制數轉換為10進制的計算公式如下:
\[\sum_{i=0}^{-m}K_i*r^i \]在C#中,表示一個二進制通常用Ob開頭,8進制則是以0開頭,16進制以0x開頭,例如
int a = 0b101011;//二進制
int b = 035167;//八進制
int a = 0xD2F3;//十六進制
講完了二進制數,接下來我們講講八進制和十六進制,既然二進制如此美妙好用,為什么各位計算機學家還是要在計算機大量的使用八進制和十六進制呢?一個很明顯的例子就是變數在記憶體中往往都是以8或16進制進行存盤,不知道你有沒有看過時常彈出來的錯誤視窗中會提示記憶體0xfffff錯誤,這里就是使用了我們的十六進制,原因是因為一段過長的二進制值是可讀性非常差的,而選擇八進制和十六進制正是縮短了過長的二進制,因為八進制逢8進1,也就是2的3次方,十六進制則是2的4次方,十六進制超過9以后的數以字母A~F表示,例如101011011011這串二進制代碼,如果換算成八進制則是05333,轉換成為十六進制則是0xACB,很明顯大大縮小了我們的閱讀難度,同時因為其是2的整數次方,轉換也十分的簡單迅速,

二進制轉八進制的訣竅是,從低到高位,每三位一組(\(2^3\)),最后不足三位的前面添0,以每一組二進制的值為位權,最終就是我們的八進制數,十六進制也一樣,只不過改成以4個為一組(\(2^4\)),如果將16或8進制轉換成為2進制,則將十六或八進制中從每一位按4或3位展開即可,例如
1011011011轉八進制的程序,先添0補足長度為3的倍數,001011011011,分組001|011|011|011,則表示為1333,十六進制和N進制轉2進制希望讀者自己嘗試解決,
如果帶小數點,則依次類推,只不過我們指數冪就換成負數即可,這里不再展開贅述,
在C#中也提供了相關的函式方便我們迅速進行進制間的轉換
// value為需轉換的R進制數,以字串表示,fromBase為需轉換的進制
Convert.ToInt32(string value, int fromBase):
// value為需轉換的十進制數,toBase為需轉換的進制
Convert.ToString(int value, int toBase);
值得補充的一點是,資料在記憶體中的存盤大小本身是由資料的 位(bit) 決定的,我們常說的一位元組在現在的計算機中指有8個位元空間大小,一個位元位可以存盤一位二進制代碼,而我們常見的int型別默認是Int32,也就是32位整形,因此你知道為什么int是4個位元組了吧?
正負數存盤形式及四種碼
在計算機中,資料往往并不是直接以數值本身的二進制碼(機器數)進行存盤和計算的,我們往往需要對數值的二進制碼進行一些變換,同時你是否想過,正數我們可以直接寫出它的二進制碼,那么碰到負數我們又應該如何做呢?也許聰明的你已經想要脫口而出:既然因為電位只有兩種狀態我們用0和1進行表示,正負也只有兩種表示方法!因此我們在二進制碼的頭部增加一位符號位進行有符號數的正負標識,這里我們用1表示負號,0表示正號,這里似乎又解決了我們一個很頭大的問題:為什么int、long這種有符號數表示的范圍是要比它所占的位數少一位,因為最高位用于標識它的符號了,
這里我們引入下一個概念 “原碼”:原碼是最簡單、直觀的機器數表示方法了,也就是用機器數的最高位標識它的符號,其余為資料位是數的絕對值,例如-8這個十進制數用二進制原碼表示就是1100,值得一提的是,0在原碼表示法中有兩種表示,+0和-0,
反碼 :反碼的概念非常的簡單,通常反碼在計算機中只起到原碼到補碼轉換的過渡程序,在這直接拋出計算方法而不做贅述,對于正數,反碼就是其本身,對于負數,反碼則是將原碼中除符號位外每一位數字進行邏輯取反,因此它的性質和原碼其實是一致的, 例如+8的二進制為0,100,反碼就是0,100,對于-8的二進制1,100,反碼則為1,011
接下來介紹的是計算機中真正的資料存盤方式,補碼:首先,補碼正如其名,和原碼是一對互補的數字,它的和原碼之間的關系是:對于正數,補碼就是其本身,對于負數,原碼的反碼+1=補碼,
我們引入一個生活中的小例子,我們在看鐘表的時候,如果以0(12)作為基準,如果現在指標指向3,我們正常會以順時針從0(12)開始數到3,得知現在是3點,如果是指向9,我們則會從0(12)開始逆時針開始數,或者說,你看到15點會不自覺的知道指標指向3,因為15-12=3,這里其實就已經用到了補碼的概念,事實上,在計算機的結構中,加法是可以直接進行運算的,但是并沒有針對減法設計數字電路,因為減法的數字電路并不容易設計,同時也出于節約成本的考慮,如果只設計加法電路的情況,如何去得到我們的減法?這里先需要知道一個運算求余——%,例如7%3=1,即除法后的余數,我們就以7-3為例子,試著將一個減法運算成加法,
答案非常的顯而易見,7-3不就是7+(-3)嗎?你可以假設一個鐘表,它的最大值是12,現在指向7,我們定義順時針為正,逆時針為負,現在鐘表指向了7,我們逆時針往回轉3個小時,指標指向了4,那么問題來了,我們是不是也可以順時針轉9格也得到4呢?按著我們的定義7+9=16并不等于4,但我們的鐘表最大也只有12呀,因此我們需要將溢位位丟棄,也就是取余操作(7+9) mod 12=4,這樣我們就成功的將一個減法運算設計成了加法運算了,

因此回到我們補碼的概念,那么7-3實際上是7和-3進行相加,加法是可以直接運算的,而從補碼和反碼的定義我們知道負數的反碼是數值位進行取反而符號位不變,因此負數的\([反碼+原碼+1]_原=最大值+1\)也就是\([補碼+原碼]_原=最大值+1\),這也就體現了補碼的名稱了,因此對于減法\(x-y(x>0,y>0)\),可以化為\((x+[y]_補)\%(max+1)\),其實證明并不難,如下
\[\forall x>0,\forall y>0;\\ y_補=max-y_原+1\\ x+y_補 = x-y_原+max+1\\ 因此很顯然x-y = x+y_補=(x-y_原+max+1)\%(max+1)得證 \\\]更一般的,若資料表示的最大原碼為M-1,對于定點型別數(整數、定點小數),有
\[[A+B]_補 = (A_補+B_補)mod M \\ \\ [A-B]_補 = (A_補+[-B]_補)mod M \]講到這里,其實也就解釋通了為什么在計算機中,資料都是以補碼的形式進行存盤和運算了,因為可以講任意的加減法(乘除法實際上也就是回圈型的加減)都按著加法進行運算,有利于節省成本和降低設計難度,
移碼是我們四碼里面的最后一種碼,它通常用于表示浮點數的階碼,具體的運用在下文會詳細的進行介紹,這里不再展開,移碼的定義非常簡單,就是在真值X上加上偏置量,通常是以2的n次方為偏置量,就相當于X在數軸之上偏移了若干個單位,移碼的求解方法非常簡單,將補碼的符號位取反就是移碼,例如真值1,進行移位\(2^4\)得到了17,轉換成為補碼形式就是10001,
定點數與浮點數存盤方式
定點數和浮點數統稱實型,點指代小數點,定點數無需解釋,我們只要事先規定好整數位和小數位的數量即可表示,對于浮點數,
*資料的存盤方式(選看)
資料的存盤方式主要分為大端存盤和小端存盤、邊界對齊存盤(詳情請看結構體的內容)兩種,對于現代的計算機,資料的存盤通常以位元組編址,也就是一個地址編號對應的記憶體單元存盤1個位元組,那么對于一個大的資料,我們可能會存盤在連續的多個記憶體單元之中,
大端小端沒有誰優誰劣,各自優勢便是對方劣勢,我們不太需要關注哪一種存盤方式,只需要大體了解一下即可,
- 小端存盤就是低位位元組排放在記憶體的低地址端,高位位元組排放在記憶體的高地址端,
- 大端存盤就是高位位元組排放在記憶體的低地址端,低位位元組排放在記憶體的高地址端,
例如數字0x12345678進行存盤時,存盤記憶體結構如下圖,

小端模存盤中強制轉換資料不需要調整位元組內容,1、2、4位元組的存盤方式一樣,而在大端存盤中符號位的判定固定為第一個位元組,容易判斷正負,
為什么要學這個奇怪的知識呢?因為在跨語言或平臺的通信之中,不了解這個知識總是會有一些奇奇怪怪的錯誤出現,例如Java網路通信中,資料流是按大端位元組序,和網路位元組序一致的方法進行傳輸,而C#在Windows平臺上是小端位元組序進行資料存盤,那么如果一個Java程式往一個C#程式發送網路資料包的時候,由于資料存盤順序的不同就會導致資料讀取結果的不同,
大家可以閱讀這兩篇博文進行一個理解:
- 大端和小端存盤模式詳解
- C# 大端與小端(因為大小端引起的奇怪問題)
值與參考型別的存盤方式
在前文中我們其實已經講過許多有關值型別和參考型別的存盤,大體上我們值型別、指令、指標等是直接存盤在堆疊中,而參考型別、委托等指標指向的型別則存盤在托管堆中,具體請看文章開頭處對資料型別的簡介,
C#中定義變數的方式及資料轉換的方法
在C#中定義變數的方式和其他的主流語言沒有太大的區別,以下是幾種定義方式:
int number = 5;//定義一個32位整數型別
bool b = true;//定義
//注意看以下兩條,string定義的字串必須為雙引號,而char使用單引號并且只允許輸入一個字符
string str = "test";
char a = 'a';
//記得后綴
float f = 1.3f;
decimal d = 1.5m;
資料型別的轉換分為隱式轉換和顯式轉換,看下面幾個例子:
string a = "15";
int b = int.Parse(a);//顯式轉換
b = (int)a;//強制轉換
b = Convert.ToInt32(a);//顯式轉換,較常用
double d = 1.5;
b = d;//隱式轉換
如果我們定義的資料大小超過了資料型別本身的大小,那么位于高位的資料會被首先舍棄,
這里還有一種相對特殊的型別——無符號型別,通過前文的介紹,我們大體已經知道了有符號數字的定義以及存盤方式,而對于無符號數,補碼原碼反碼都是其本身,也就是將首位的符號位替換成了資料位,當有符號數向無符號數進行轉換時,我們需要計算出有符號數的補碼,然后直接按公式進行計算,例如:
int a = -3;//補碼為100
uint b = a;//b=8
陣列
陣列指一個型別(任意)的集合,例如你定義一個變數為a=5,很輕松,假設你需要100個呢?因此我們使用陣列來存盤,
陣列的定義以及使用如下:
//偽代碼,T為型別,n為大小
T [] t = new T[n];
//定義一個整型陣列
int [] a = new int [5];
//你也可以選擇初始化的方式定義
int [] b = new int [] {1,2,3,4,5};
//或
int [] c = new int [5]{1,2,3,4,5};
//陣列的訪問,從0開始索引
Console.WriteLine(b[0]);
有時候我們也許會想用一個表格進行資料的存盤,例如我們存盤一個矩陣就需要二維的空間,這里給出二維陣列的定義:
//偽代碼,T為型別,m,n為大小
T [,] t = new T[m,n];
本質上二維陣列的概念就是陣列的陣列,一個組成元素為一維陣列的陣列就是我們的二維陣列,一般而言,我們需要指定二維陣列的行列寬,當然我們也可以不指定行數直接初始化,但我們必須指定列數,因為記憶體是按行進行分配,
運算子及規則多載
基礎的運算子
- +-*/:對應數學中的加減乘除,
- %: 求余運算,a%b指a除以b的余數,
- & | ~ ^ :分別為按位與、按位或、按位取反、按位異或
- <<、>>:左右移位運算子,例如0010 --> 0100
- ?:三元判斷運算子
^是異或,result=1110,就是說異是不同回傳1,相同是0,或就是只要有1就回傳1,
&是與, result=0001,也就是相同回傳1,不同為0
|是或, result=1111,除了兩個都為0,否則回傳1
~稱為按位取反,我們表示符號是用四個0表示,運算規則就是正數的反碼,補碼都是其本身的原始碼,
負數的反碼是符號位不變,本身的0變1,1變0,補碼就是反碼+1,
最后進行補碼取反時連同符號位一起變得到的反碼就是結果
流程如下:0000 0111 --> 0000 1000 --> 0000 1001 --> 1111 0110 = -8
>>稱為右移,右移一位流程如下 0000 1001 --> 0000 0100 = 4
<< 稱為左移,左移一位流程如下 0000 1001 --> 0000 10010 = 18
移位運算需要注意的一點是,由于我們計算機保存資料的方式是采取補碼存盤,因此,當我們對一個負數進行移位時,在添加的并不是0而是1,
運算子的多載
我們在大部分時候,語言自身提供的運算子運算規則已經足夠我們使用,但往往我們會涉及到一些奇怪的場景,例如我需要知道某兩個節日的日期相距多少天而我并不想借助DateTime類的方法,我想用date1-date2進行計算,那么我們就需要使用運算子多載去改寫減號的規則,
事實上我們仔細思考不難得出結論,一切的運算子本質上都是一種函式的對應關系,那么我們使用operator關鍵字進行某類中運算子的多載,例如:
// T是修改型別的回傳值
public static T operator +(D d1,D d2)
{
return something;
}
通過運算子多載,我們可以更有效的書寫高質量的代碼,同時可讀性也可以大大提升,
具體的操作我會在我在BiliBili上發布的 .Net Core 教程上進行詳細的講述,
*結構體(選看)
結構體是一種比較特殊的資料型別,它很像我們后面講述到的類,但是他并不是一個類,他本質還是值型別,結構體的使用是很重要的,如果結構體使用得當,可以有效的提升程式的效率,
結構體你可以理解為將將若干個型別拼接在一起,但是存在一個很重要的內容——記憶體對齊,例如下面兩個結構體:
struct S
{
int a;
long b;
int c;
}
struct SS
{
int a;
int b;
long c;
}
乍一看你會覺得這兩個結構體完全一致,絲毫沒有任何的差別,但事實上,在大多數編程語言里面,對于結構體這種大小并不是定值的值型別,都存在一個最小分配單元用于結構體內單個變數的大小分配,在記憶體中,他們兩個的存盤方式有很大的不同,
對于上面兩個結構體,他們在記憶體中的單元分配是:
- S:a(4 byte + 4 free) --> b(8 byte) --> c(4 byte + 4 free),共計24位元組
- SS:a(4 byte)b(4 byte) --> c(8 byte),共計16位元組
在C#中,如果你不指定最小分配單元,那么編譯器將會把結構體中占用記憶體最大的作為最小分配單元,不過尤其需要注意一件事,就是參考型別在結構體中,鑒于我們現在尚未講解面向物件的類,我們用string作為成員寫一個結構體,如下面這個例子:
struct S
{
char a;
long b;
string c;
}
//函式中創建
S s = new S();
s.a = 'a';
s.b = 15;
s.c = "I Love .NET Core And Microsoft"
很顯然s.c的大小超過了結構體中其余兩個,但是記憶體分配的時候就是以最大的c作為標準嗎?
顯然不是,我們要知道struct是在堆疊中分配記憶體,string的內容是在堆中的,所以在結構體中存盤的string只是一個參考,并不會包含其他的東西,只占用4個位元組,并且特別的,參考型別在記憶體中的位置位于大于四位元組的欄位前,小于四位元組欄位后,
上面記憶體分配應當是這樣:
a(8) --> c(8) --> b(8),
如果需要深入了解這一方面內容,建議去閱讀《CLR Via C#》這本書,以及學習SOS除錯相關內容,
10. 練習題
理論分析題
- 計算出int和long的數值范圍
- 為什么在大部分提供科學計算或編程語言會存在精度問題?例如浮點數2.5在任何一種采用二進制計算的編程語言中也不是一個精確值?或者說如果我們展開浮點數的所有精確位,最后的幾位小數并不是0?(較難)
- 為什么參考型別即使不存盤內容也需要記憶體空間?
- 試說明參考型別和值型別的優缺點
- 陣列為什么需要初始化大小?如果是多維陣列,不指定列寬可以嗎?
計算題
- 求123.6875的二進制、八進制、十六進制運算式,
- 求\((11011.101)_2\)二進制小數轉換為十進制,
- a=5,b=8,試手算a&b,a|b,a^b,a<<1, b>>1
- 若a=12,試手算~a
- 若a為8位二進制,試著寫出將a的高四位取反,第四位不變的運算運算式
- int a = 15,試求a+int.MaxValue的值
編程題
- 請學習指標內容以及C#unsafe除錯,試著不使用索引進行陣列的讀取,
- 將字串”15”轉成整數?
- 使用運算子多載,計算向量的加減和點乘(內積)
Reference
《C# in Depth》—— Jon Skeet
《計算機組成原理》——唐朔飛
C#托管堆和垃圾回收(GC)
C# Heap(ing) Vs Stack(ing) In .NET
大端和小端存盤模式詳解
C# 大端與小端(因為大小端引起的奇怪問題)
About Me
作 者:WarrenRyan
出 處:https://www.cnblogs.com/WarrenRyan/
本文對應視頻:BiliBili(待重錄)
關于作者:熱愛數學、熱愛機器學習,喜歡彈鋼琴的不知名小菜雞,
著作權宣告:本文著作權歸作者所有,歡迎轉載,但未經作者同意必須保留此段宣告,且在文章頁面明顯位置給出原文鏈接,若需商用,則必須聯系作者獲得授權,
特此宣告:所有評論和私信都會在第一時間回復,也歡迎園子的大大們指正錯誤,共同進步,或者直接私信我
聲援博主:如果您覺得文章對您有幫助,可以點擊文章右下角【推薦】一下,您的鼓勵是作者堅持原創和持續寫作的最大動力!
博主一些其他平臺:
微信公眾號:寤言不寐
BiBili——小陳的學習記錄
Github——StevenEco
BiBili——記錄學習的小陳(計算機考研紀實)
掘金——小陳的學習記錄
知乎——小陳的學習記錄
聯系方式
電子郵件:[email protected]
社交媒體聯系二維碼:
轉載請註明出處,本文鏈接:https://www.uj5u.com/net/412696.html
標籤:C#
上一篇:C# 指標簡單使用
