C# 數據類型
在 C# 中,變量分為以下幾種類型:
- 值類型(Value types)
- 引用類型(Reference types)
- 指針類型(Pointer types)
值類型(Value types)
值類型變量可以直接分配給一個值。它們是從類 System.ValueType 中派生的。
值類型直接包含數據。比如 int、char、float,它們分別存儲數字、字符、浮點數。當您聲明一個 int 類型時,系統分配內存來存儲值。
下表列出了 C# 2010 中可用的值類型:

如需得到一個類型或一個變量在特定平台上的准確尺寸,可以使用 sizeof 方法。表達式 sizeof(type) 產生以字節為單位存儲對象或類型的存儲尺寸。下面舉例獲取任何機器上 int 類型的存儲尺寸:
using System; namespace DataTypeApplication { class Program { static void Main(string[] args) { Console.WriteLine("Size of int: {0}", sizeof(int)); Console.ReadLine(); } } }
當上面的代碼被編譯和執行時,它會產生下列結果:
Size of int: 4
引用類型(Reference types)
引用類型不包含存儲在變量中的實際數據,但它們包含對變量的引用。
換句話說,它們指的是一個內存位置。使用多個變量時,引用類型可以指向一個內存位置。如果內存位置的數據是由一個變量改變的,其他變量會自動反映這種值的變化。內置的 引用類型有:object、dynamic 和 string。
對象(Object)類型
對象(Object)類型 是 C# 通用類型系統(Common Type System - CTS)中所有數據類型的終極基類。Object 是 System.Object 類的別名。所以對象(Object)類型可以被分配任何其他類型(值類型、引用類型、預定義類型或用戶自定義類型)的值。但是,在分配值之前,需要先進行類型轉換。
當一個值類型轉換為對象類型時,則被稱為 裝箱;另一方面,當一個對象類型轉換為值類型時,則被稱為 拆箱。
object obj;
obj = 100; // 這是裝箱
動態(Dynamic)類型
您可以存儲任何類型的值在動態數據類型變量中。這些變量的類型檢查是在運行時發生的。
聲明動態類型的語法:
dynamic <variable_name> = value;
例如:
dynamic d = 20;
動態類型與對象類型相似,但是對象類型變量的類型檢查是在編譯時發生的,而動態類型變量的類型檢查是在運行時發生的。
字符串(String)類型
字符串(String)類型 允許您給變量分配任何字符串值。字符串(String)類型是 System.String 類的別名。它是從對象(Object)類型派生的。字符串(String)類型的值可以通過兩種形式進行分配:引號和 @引號。
例如:
String str = "csharp.com";
一個 @引號字符串:
@"csharp.com";
C# string 字符串的前面可以加 @(稱作"逐字字符串")將轉義字符(\)當作普通字符對待,比如:
string str = @"C:\Windows";
等價於:
string str = "C:\\Windows";
@ 字符串中可以任意換行,換行符及縮進空格都計算在字符串長度之內。
string str = @"<script type=""text/javascript"">
<!--
-->
</script>";
用戶自定義引用類型有:class、interface 或 delegate。
指針類型(Pointer types)
指針類型變量存儲另一種類型的內存地址。C# 中的指針與 C 或 C++ 中的指針有相同的功能。
聲明指針類型的語法:
type* identifier;
例如:
char* cptr;
int* iptr;
補充1:
關於裝箱和拆箱
就像倉庫,倉庫里有貨架,貨架上有編號:A1,A2,A3...................., 這些編號就可以看做是引用類型,現在來了一批貨,有 “土豆,黃瓜,西紅柿”,這些就是值類型,如果你想讓 A1=土豆,那么就要把土豆搬到 A1 里面去,這就叫裝箱,裝箱需要耗費人力和工時(也就是耗費CPU和內存),同理拆箱就要把對應編號的貨物搬出來,也是需要耗費人力和工時。
裝箱:值類型轉換為對象類型, 實例:
int val = 8;
object obj = val;//整型數據轉換為了對象類型(裝箱)
拆箱:之前由值類型轉換而來的對象類型再轉回值類型, 實例:
int val = 8;
object obj = val;//先裝箱
int nval = (int)obj;//再拆箱
只有裝過箱的數據才能拆箱
補充2:
obj 和int之間關系
using System; namespace RectangleApplication { class ExecuteRectangle { static void Main(string[] args) { int a=9; object obj; obj = a; obj =10; Console.WriteLine("2: {0}", a); // 輸出:2: 9 Console.WriteLine("1: {0}", obj); // 輸出:1: 10 Console.ReadLine(); } } }
設置值 int a=9; obj=a; 當obj改變不會對 int a 進行改變,object 只是復制了 int a 的值出來然后對其操作而已。不會影響到 int 原來的值。
補充3:
C# 中 String 跟 string 的區別
string 是 C# 中的類,String 是 .net Framework 的類(在 C# IDE 中不會顯示藍色) C# string 映射為 .net Framework 的String 如果用 string, 編譯器會把它編譯成 String,所以如果直接用 String 就可以讓編譯器少做一點點工作。
如果使用 C#,建議使用 string,比較符合規范 string 始終代表 System.String(1.x) 或 ::System.String(2.0) ,String 只有在前面有 using System;的時候並且當前命名空間中沒有名為 String 的類型(class、struct、delegate、enum)的時候才代表 System.String string 是關鍵字,String 不是,也就是說 string 不能作為類、結構、枚舉、字段、變量、方法、屬性的名稱,而 String 可以。
String 是 CLR 的類型名稱(也算是關鍵字),而 string 是 C# 中的關鍵字。string 在編譯時候 C# 編譯器會默認將其轉換為 String,在這里會多增加幾行轉換的代碼。很多時候都是建議使用 CLR 的類型而不要使用 C# 的類型(這是專家的建議)。比如說還有:使用 int 的時候最好使用 Int32 等。很多時候都是一個習慣問題,規范問題。還有一個不同就是在 VS 中表現的顏色不一樣:String 是綠色,string 是藍色。
補充4:
關於值類型、引用類型以及“棧”跟“堆”的關系
值類型,聲明一個值類型的時候,是在“棧”中開辟一個內存空間來存放對應的值,當值類型的值發生改變的時候,則直接修改該內存空間所保存的值。例:
int n1 = 5; int n2 = n1; Console.WriteLine(n1 + " "+ n2); // 5 5 n2 = 7; Console.WriteLine(n1 + " " + n2) // 5 7
這里首先在“棧”中開辟一個內存空間用來保存 n1 的值 5,接着再在“棧”中開辟一個新的內存空間用來保存 n2 的值 5,所以顯示出來的結果是 5 5。然后將 n2 在“棧”中對應的內存空間保存的值修改成 7,故顯示出來的結果是 5 7。
引用類型,聲明一個引用類型的時候,首先是在“堆”中開辟一個內存空間來存放對應的值,然后在“棧”中開辟一個內存空間用於保存在“堆”中開辟的內存空間的地址。當系統調用引用類型的時候,首先去“棧”中獲取到地址,然后根據地址在“堆”中找到對應的內存空間來獲取到對應值。像數組這樣的引用類型
string[] a1 = new string[]{ "a" , "b" , "c" }; string[] a2 = a1; for(int i = 0; i < a2.Length; i++) { Console.Write(a2[i] + " "); //a b c } a1[2] = "d"; Console.WriteLine(); //換行 for(int i = 0; i < a2.Length; i++) { Console.Write(a2[i] + " "); //a b d } Console.WriteLine();
這里首先是在“堆”中開辟一個內存空間(假設:0X55)用來保存數組a1的值,然后在“棧”中開辟一個內存空間(a1)用於保存地址 0X55。當將 a1 賦給 a2 時,是將地址賦給 a2,即在“棧”中開辟一個內存空間(a2)用於保存地址 0X55,所以輸出 a2 的值是 a b c。當將 a1[2]修改成”d”的時候,修改的是“堆”中 0X55 內存空間保存的值,因為 a2 的地址和 a1 的地址一樣,所以輸出結果是 a b d。
而 string 是一個特殊的引用類型,先看下面代碼:
string a = "123"; string b = a; Console.WriteLine(a+" "+b); //123 123 string b = "456"; Console.WriteLine(a+" "+b); //123 456
和數組類似的,這里首先在“堆”中開辟一個內存空間(假設:0X88)用來保存 a 的值 123,然后在“棧”中開辟一個內存空間(a)用於保存地址 0X88。
和數組不同的是,當將 a 賦給 b 的時候,首先是在“堆”中開辟一個新的內存空間(假設:0X101)用於保存值 123,然后在“棧”中開辟一個內存空間(b)用於保存地址 0X101,所以輸出的結果是 123 123。
當修改 b 值時,並不是修改“堆”中 0X101 內存空間的值,而是在“堆”中重新開辟一個新的內存空間(假設:0X210)用於保存 b 修改后的值,然后將 b 在“棧”中對應的內存空間的所保存的地址修改成 0X210,所以輸出的結果是 123 456。而“堆”中的 0X101 內存空間將在下次的垃圾回收中被回收利用。
C# 類型轉換
類型轉換從根本上說是類型鑄造,或者說是把數據從一種類型轉換為另一種類型。在 C# 中,類型鑄造有兩種形式:
- 隱式類型轉換 - 這些轉換是 C# 默認的以安全方式進行的轉換, 不會導致數據丟失。例如,從小的整數類型轉換為大的整數類型,從派生類轉換為基類。
- 顯式類型轉換 - 顯式類型轉換,即強制類型轉換。顯式轉換需要強制轉換運算符,而且強制轉換會造成數據丟失。
下面的實例顯示了一個顯式的類型轉換:
實例
namespace TypeConversionApplication { class ExplicitConversion { static void Main(string[] args) { double d = 5673.74; int i; // 強制轉換 double 為 int i = (int)d; Console.WriteLine(i); Console.ReadKey(); } } }
當上面的代碼被編譯和執行時,它會產生下列結果:
5673
C# 類型轉換方法
C# 提供了下列內置的類型轉換方法:
| 序號 |
方法 & 描述 |
| 1 |
ToBoolean |
| 2 |
ToByte |
| 3 |
ToChar |
| 4 |
ToDateTime |
| 5 |
ToDecimal |
| 6 |
ToDouble |
| 7 |
ToInt16 |
| 8 |
ToInt32 |
| 9 |
ToInt64 |
| 10 |
ToSbyte |
| 11 |
ToSingle |
| 12 |
ToString |
| 13 |
ToType |
| 14 |
ToUInt16 |
| 15 |
ToUInt32 |
| 16 |
ToUInt64 |
下面的實例把不同值的類型轉換為字符串類型:
實例
namespace TypeConversionApplication { class StringConversion { static void Main(string[] args) { int i = 75; float f = 53.005f; double d = 2345.7652; bool b = true; Console.WriteLine(i.ToString()); Console.WriteLine(f.ToString()); Console.WriteLine(d.ToString()); Console.WriteLine(b.ToString()); Console.ReadKey(); } } }
當上面的代碼被編譯和執行時,它會產生下列結果:
75
53.005
2345.7652
True
補充1:
隱式轉換和顯式轉換
隱式轉換:C# 默認的以安全方式進行的轉換。本質是從小存儲容量數據類型自動轉換為大存儲容量數據類型,從派生類轉換為基類。
實例:
namespace TypeConvertion { class Class1 { } class Class2 : Class1 //類Class2是類Class1的子類 { } class Program { static void Main(string[] args) { int inum = 100; long lnum = inum; // 進行了隱式轉換,將 int 型(數據范圍小)數據轉換為了 long 型(數據范圍大)的數據 Class1 c1 = new Class2(); // 這里也是隱式轉換,將一個新建的 Class2 實例轉換為了其基類 Class1 類型的實例 C1 } } }
顯式轉換:通過用戶使用預定義的函數顯式完成的,顯式轉換需要強制轉換運算符。
轉換類型的范圍大小和從屬關系和隱式轉換相反。顯式轉換可能會導致數據出錯,或者轉換失敗,甚至無法編譯成功。
實例:
double dnum = 100.1; int ifromd = (int)dnum; //double類型顯式轉換轉為int類型 Class1 c11 = new Class1(); Class2 c22 = c11 as Class2; //使用as進行顯式轉換 Console.WriteLine(c22 is Class1); Console.WriteLine(c22 is Class2);
運行結果:
FALSE
FALSE
is操作符:檢查對象是否與給定類型兼容。
as運算符:用於在兼容的引用類型之間執行轉換。
as操作符類似於強制轉換,但又有區別,當對象為null時,不會拋異常而是會返回null。
補充2:
類型之間的轉換 - Convert 和 Parse
string locstr = 123.ToString();
//如果要將"locstr"轉成整型數
//方法一: 用 Convert
int i = Convert.ToInt16(locstr);
//方法二: 用 Parse
int ii = int.Parse(locstr); int.TryParse(string s,out int i)
該方式也是將數字內容的字符串轉換為int類型,但是該方式比int.Parse(string s) 好一些,它不會出現異常,最后一個參數result是輸出值,如果轉換成功則輸出相應的值,轉換失敗則輸出0。
class test { static void Main(string[] args) { string s1="abcd"; string s2="1234"; int a,b; bool bo1=int.TryParse(s1,out a); Console.WriteLine(s1+" "+bo1+" "+a); bool bo2=int.TryParse(s2,out b); Console.WriteLine(s2+" "+bo2+" "+b); } }
結果輸出:
abcd False 0
1234 True 1234
補充3:
C# 中對 double 類型的數據取整,可以使用 convert.toint32() 方法,也可使用 int 強制轉換為整數,使用 int 時並不存在四舍五入的情況,而是直接將后面的小數位數丟掉。比如:
class Program { static void Main(string[] args) { double a = 1.35; double b = 1.65; int a1 = Convert.ToInt32(a); int a2 = (int)(a); int b1 = Convert.ToInt32(b); int b2 = (int)(b); Console.WriteLine("{0}使用convert方法轉化的結果為:{1}",a,a1); Console.WriteLine("{0}使用int強制轉換的結果為:{1}",a,a2); Console.WriteLine("{0}使用convert方法轉化的結果為:{1}", b, b1); Console.WriteLine("{0}使用int強制轉換的結果為:{1}", b, b2); Console.ReadKey(); } }
程序運行結果如下:
1.35使用convert方法轉化的結果為:1
1.35使用int強制轉換的結果為:1
1.65使用convert方法轉化的結果為:2
1.65使用int強制轉換的結果為:1
補充4:
Convert.ToInt32() 與 int.Parse() 的區別
沒搞清楚 Convert.ToInt32 和 int.Parse() 的細細微區別時千萬別亂用,否則可能會產生無法預料的結果,舉例來說:假如從 url 中取一個參數 page 的值,我們知道這個值是一個 int,所以即可以用 Convert.ToInt32(Request.QueryString["page"]),也可以用 int.Parse(Request.QueryString["page"]),但是如果 page 這個參數在 url 中不存在,那么前者將返回 0,0 可能是一個有效的值,所以你不知道 url 中原來根本就沒有這個參數而繼續進行下一下的處理,這就可能產生意想不到的效果,而用后一種辦法的話沒有 page 這個參數會拋出異常,我們可以捕獲異常然后再做相應的處理,比如提示用戶缺少參數,而不是把參數值當做 0 來處理。
(1) 這兩個方法的最大不同是它們對 null 值的處理方法: Convert.ToInt32(null) 會返回 0 而不會產生任何異常,但 int.Parse(null) 則會產生異常。
(2) 對數據進行四舍五入時候的區別
- a. Convert.ToInt32(double value) 如果 value 為兩個整數中間的數字,則返回二者中的偶數;即 3.5 轉換為 4,4.5 轉換為 4,而 5.5 轉換為 6。不過 4.6 可以轉換為 5,4.4 轉換為 4 。
- b. int.Parse("4.5") 直接報錯:"輸入字符串的格式不正確"。
(3) 對被轉換類型的區別 int.Parse 是轉換 String 為 int, Convert.ToInt32 是轉換繼承自 Object 的對象為 int 的(可以有很多其它類型的數據)。你得到一個 object 對象, 你想把它轉換為 int, 用 int.Parse 就不可以, 要用 Convert.ToInt32。
