C Sharp/数据类型
C#是静态类型语言。即每个变量和常量都有固定的类型。类型分为值类型与引用类型两大类。值类型变量存放在栈上;引用类型在堆中分配空间。在.Net内建类型外,用户也可以定制类型。
值类型
[编辑]- 函数参数传值。
- 值类型没有/不需要调用构造函数。总是自动初始化。
- 值类型的字段初始化为0或null.
- 值类型不能赋值为null值,但可用Nullable类型
- 值类型可以装箱。
- 存储在栈中。声明一个值类型变量,则立即分配内存。如果变量出了作用域,则自动销毁。
- 值类型包括简单类型、枚举类型和结构类型。
struct
[编辑]用户自定义的值类型:
struct Person
{
public string Name;
public int Height;
public string Occupation;
}
public class StructWikiBookSample2
{
public static void Main()
{
Person john = new Person { Name = "John", Height = 182, Occupation = "Programmer" };
}
}
struct是值类型的容器。可以有构造函数、方法、实现接口。但不支持继承。总是有一个缺省构造函数。作为函数参数时传值。
从性能角度,建议struct不超过16个字节。
枚举
[编辑]enums是代表整数的命名的值:
enum Season
{
Winter = 0,
Spring = 1,
Summer = 2,
Autumn = 3,
Fall = Autumn // Autumn is called Fall in American English.
}
enum变量默认初始化为0:
Season season;
season = Season.Spring;
enum类型是整数值,允许加减运算。但乘除运算需要显式cast。枚举类型和整型的相互转换,也需要显式cast。
season = (Season)2; // cast 2 to an enum-value of type Season.
season = season + 1; // Adds 1 to the value.
season = season + season2; // Adding the values of two enum variables.
int value = (int)season; // Casting enum-value to integer value.
season++; // Season.Spring (1) becomes Season.Summer (2).
season--; // Season.Summer (2) becomes Season.Spring (1).
枚举类型可以按位或操作:
Color myColors = Color.Green | Color.Yellow | Color.Blue;
装箱和拆箱
[编辑]装箱(Boxing)就是从值类型到引用类型的转换。[1]装箱在C#是隐式的。拆箱(unboxing)是从引用类型到值类型的转换。拆箱需要显式的类型cast。
class Test
{
static void Mian()
{
int i = 3;
object a = i;//装箱
int j = (int)a;//拆箱
}
}
可空类型
[编辑]可空类型(nullable type) T?表示该类型还可以取空值。例如:
int? a=null;
任何可空值类型T?是泛型结构System.Nullable<T> 的实例。System.Nullable<T>有两个只读属性:Value与HasValue。
C# 2.0引入了可空类型。允许值类型为null(用于适配数据库)。
int? n = 2;
n = null;
Console.WriteLine(n.HasValue);
实际上这是用Nullable<T> struct实现的:
Nullable<int> n = 2;
n = null;
Console.WriteLine(n.HasValue);
从可空值类型转化为基础值类型,可用空值结合运算符??或者System.Nullable<T>.GetValueOrDefault()。例如:
int? c = null; int d = c ?? -1; Console.WriteLine($"d is {d}"); // output: d is -1
引用类型
[编辑]引用类型的变量指向堆中的一块空间。为受管的引用。构造函数调用时,在堆上分配创建一个对象,并把它赋给变量。CLR执行下述4步:
- CLR计算堆中需要的内存;
- CLR在堆中插入数据;
- CLR标记内存空间中的占用结尾标记;
- CLR返回新创建空间的引用
当引用类型的变量出了作用域,则引用失效(broken)。如果一个对象没有引用,则被标记为垃圾。垃圾回收器随后会收集、摧毁它。
引用变量如果为null,则它不引用任何对象。
引用类型包括:
- 接口
- 类
- 装箱类型
- 委托
- 自定义类
- 数组
- 指针
说明:尽管string是类,但如果用到了相等运算符==或者!=时则表示比较string对象的值。
定制类型
[编辑]- 定制的值类型使用struct或enum关键字。
- 定制的引用类型使用class关键字。
.NET框架中对应的值类型
[编辑]// C#
public void UsingCSharpTypeAlias()
{
int i = 42;
}
public void EquivalentCodeWithoutAlias()
{
System.Int32 i = 42;
}
' Visual Basic .NET
Public Sub UsingVisualBasicTypeAlias()
Dim i As Integer = 42
End Sub
Public Sub EquivalentCodeWithoutAlias()
Dim i As System.Int32 = 42
End Sub
值类型的长度是固定的。引用类型都是继承自object
类,其长度随平台而定。
.NET框架的类型有成员方法,如:
int i = 97;
string s = i.ToString(); // The value of s is now the string "97".
System.Int32
类型实现了Parse()
方法:
string s = "97";
int i = int.Parse(s); // The value of i is now the integer 97.
值类型可以装箱,然后可以拆箱:
object boxedInteger = 97;
int unboxedInteger = (int) boxedInteger;
装箱与拆箱不是类型安全的。编译器不会产生报错,弹运行时可能爆异常:
object getInteger = "97";
int anInteger = (int) getInteger; // No compile-time error. The program will crash, however.
C#内建类型与对应的.NET框架类型列表:
整型
[编辑]C# 别名 | .NET 类型 | 比特长度 | 值域 |
---|---|---|---|
sbyte |
System.SByte | 8 | -128 to 127 |
byte |
System.Byte | 8 | 0 to 255 |
short |
System.Int16 | 16 | -32,768 to 32,767 |
ushort |
System.UInt16 | 16 | 0 to 65,535 |
char |
System.Char | 16 | A unicode character of code 0 to 65,535 |
int |
System.Int32 | 32 | -2,147,483,648 to 2,147,483,647 |
uint |
System.UInt32 | 32 | 0 to 4,294,967,295 |
long |
System.Int64 | 64 | -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 |
ulong |
System.UInt64 | 64 | 0 to 18,446,744,073,709,551,615 |
整型字面量后缀(不区分大小写)u、l、ul,分别表示:uint、long、ulong。
字符字面量用单引号围起来。内容可以是转义序列,可以用\x前缀后跟1至4个十六进制数字,或者\u后跟4个十六进制数字。
整型字面量只允许十进制或者十六进制(0x前缀)。
浮点和定点
[编辑]C# 别名 | .NET类型 | 比特长度 | 精度 | 值域 |
---|---|---|---|---|
float |
System.Single | 32 | 7 digits | 1.5 x 10-45 to 3.4 x 1038 |
double |
System.Double | 64 | 15-16 digits | 5.0 x 10-324 to 1.7 x 10308 |
decimal |
System.Decimal | 128 | 28-29 decimal places | 1.0 x 10-28 to 7.9 x 1028 |
字面量后缀(不区分大小写)m、d、f,分别表示:decimal、double、float。
浮点字面量或者是带小数点的定点表示,如3.14;或者是科学计数法。
整型将被隐式转换为 decimal 类型。浮点型和 decimal 类型之间不存在隐式转换;因此,必须使用强制转换以在这两个类型之间转换。可以在同一表达式中混合使用 decimal 和数值整型。但是,不进行强制转换就混合使用 decimal 和浮点型将导致编译错误。通过使用 String.Format 方法或Console.Write 方法(其调用String.Format())来设置结果的格式。 货币格式是使用标准货币格式字符串“C”或“c”指定的
在CLR中,Decimal类型不是基元类型。这就意味着CLR没有知道如何处理Decimal的IL指令。
其他预定义类型
[编辑]C# 类型 | .NET 类型 | 比特长度 | 值域 |
---|---|---|---|
bool |
System.Boolean | 32 | true or false, which aren't related to any integer in C#. |
object |
System.Object | 32/64 | Platform dependent (a pointer to an object). |
string |
System.String | 16*length | A unicode string with no special upper bound. |
字符串类型
[编辑]string是System.String的别名。System.String不是一个struct因此不是基本类型。
string表示一个不可变的unicode字符序列。string可包含多个\x0字符。
System.StringBuilder类可用做“可变的”字符串。
var sb = new StringBuilder();
sb.Append('H');
sb.Append("el");
sb.AppendLine("lo!");
// Declare without initializing.
string message1;
// Initialize to null.
string message2 = null;
// Initialize as an empty string.
// Use the Empty constant instead of the literal "".
string message3 = System.String.Empty;
// Initialize with a regular string literal.
string oldPath = "c:\\Program Files\\Microsoft Visual Studio 8.0";
// Initialize with a verbatim string literal.
string newPath = @"c:\Program Files\Microsoft Visual Studio 9.0";
// Use System.String if you prefer.
System.String greeting = "Hello World!";
// In local variables (i.e. within a method body)
// you can use implicit typing.
var temp = "I'm still a strongly-typed System.String!";
// Use a const string to prevent 'message4' from
// being used to store another string value.
const string message4 = "You can't get rid of me!";
// Use the String constructor only when creating
// a string from a char*, char[], or sbyte*. See
// System.String documentation for details.
char[] letters = { 'A', 'B', 'C' };
string alphabet = new string(letters);//不要使用new运算符创建string对象,除非用字符数组初始化
string.IsNullOrEmpty(message4);//为避免NullReferenceException
//String.Format利用占位符来构造格式字符串。如:
var pw = (firstName: "Phillis", lastName: "Wheatley", born: 1753, published: 1773);
Console.WriteLine("{0} {1} was an African American poet born in {2}.", pw.firstName, pw.lastName, pw.born);
字符串对象是immutable
字符串字面量:
- 用引号的字符串字面量(Quoted string literals)
- 逐字的字符串字面量(Verbatim string literals):用@符号作为前缀。更适合多行、包含反斜线或嵌入的双引号(用两个双引号表示)等情形。
- 原始字符串字面量(Raw string literals):从C#11.0开始,以连续3个双引号开始和结束的字符串。对于多行情形,要求开始和结束的3个连续双引号必须各自独占一行,该行前缀之后、后缀之前的字符都被忽略。
转义字符序列需要注意:\u、\U、\x分别开始4位、8位、0至4位的16进制数字序列,对应Unicode的码位。
C#6开始支持格式化(插值)字符串以$号为前缀,插值表达式写在大括号中。是String.Format方法的简化形式。语法为:
{<interpolatedExpression>[,<alignment>][:<formatString>]}
从C#10.0开始,可以用插值字符串初始化constant 字符串。从C#11.0开始,原始字符串中也可用插值字符串,并在原始字符串的前缀之前的$号的个数来表示插值表达式用多少层大括号包围。逐字字符串字面量如果用$@ 或 @$作为前缀也可以使用插值表达式。
空字符串对象是String.Empty,即""对应的0长度字符串对象。null字符串不是指引到System.String的一个对象,调用其方法可引发NullReferenceException. 但可以用null字符串与其他字符串连接或比较操作。
方法String.Join可以把数组的多个元素连接为一个字符串并用指定的分隔符隔开。
StringBuilder类用于创建一个字符缓冲区,在此上可以逐个字符的修改、追加。适用于连续追加上百次操作的情形。
接口
[编辑]使用interface关键字声明接口。它类似于类声明。默认情况下,接口语句是public的。例如:
public interface ITransactions {
// interface members
void showTransaction();
double getAmount();
}
类
[编辑]装箱类型
[编辑]委托
[编辑]委托和事件的变量,可以看作是一个或多个面向对象的函数指针绑定到一个变量上。
delegate void MouseEventHandler(object sender, MouseEventArgs e);
public class Button : System.Windows.Controls.Control
{
private event MouseEventHandler _onClick;
/* Imaginary trigger function */
void Click()
{
_onClick(this, new MouseEventArgs(data));
}
}
类中声明的事件,只能在作为事件的拥有者的该类中才能调用。
public class MainWindow : System.Windows.Controls.Window
{
private Button _button1;
public MainWindow()
{
_button1 = new Button();
_button1.Text = "Click me!";
/* Subscribe to the event */
_button1.ClickEvent += Button1_OnClick;
/* Alternate syntax that is considered old:
_button1.MouseClick += new MouseEventHandler(Button1_OnClick); */
}
protected void Button1_OnClick(object sender, MouseEventArgs e)
{
MessageBox.Show("Clicked!");
}
}
也可以做定制事件实现:
private EventHandler _clickHandles = (s, e) => { };
public event EventHandler Click
{
add
{
// Some code to run when handler is added...
...
_clickHandles += value;
}
remove
{
// Some code to run when handler is removed...
...
_clickHandles -= value;
}
}
自定义类
[编辑]数组
[编辑]数组的元素具有相同的类型。数组类型的基类型是System.Array。
数组每个维度的长度可以不声明:
string[] a_str;
给数组变量赋值时,要指明每个维度的长度:
a_str = new string[5];
声明与初始化可以写在一起:
string[] a_str = new string[5];
数组是传引用。下例中数组的两个元素的内容交换了:
static void swap (int[] a_iArray, int iI, int iJ)
{
int iTemp = a_iArray[iI];
a_iArray[iI] = a_iArray[iJ];
a_iArray[iJ] = iTemp;
}
C#的数组对应于C语言的动态数组。在运行时确定数组长度:
int[] numbers = new int[2];
numbers[0] = 2;
numbers[1] = 5;
int x = numbers[0];
数组初始化器
[编辑]// Long syntax
int[] numbers = new int[5]{ 20, 1, 42, 15, 34 };
// Short syntax
int[] numbers2 = { 20, 1, 42, 15, 34 };
// Inferred syntax
var numbers3 = new[] { 20, 1, 42, 15, 34 };
多维数组
[编辑]int[,] numbers = new int[3, 3];
numbers[1,2] = 2;
int[,] numbers2 = new int[3, 3] { {2, 3, 2}, {1, 2, 6}, {2, 4, 5} };
指针
[编辑]C#允许在“unsafe”上下文中像C语言那样使用指针,不做运行时安全检查。只有如下类型可以使用指针:一些基本类型, enums, string, pointer, 只包含了允许类型的arrays和struct。[2]
static void Main(string[] args)
{
unsafe
{
int a = 2;
int* b = &a;
Console.WriteLine("Address of a: {0}. Value: {1}", (int)&a, a);
Console.WriteLine("Address of b: {0}. Value: {1}. Value of *b: {2}", (int)&b, (int)b, *b);
// Will output something like:
// Address of a: 71953600. Value: 2
// Address of b: 71953596. Value: 71953600. Value of *b: 2
}
}
Struct只能是纯结构,不能有受管的引用类型,如string或其他class:
public struct MyStruct
{
public char Character;
public int Integer;
}
public struct MyContainerStruct
{
public byte Byte;
public MyStruct MyStruct;
}
使用:
MyContainerStruct x;
MyContainerStruct* ptr = &x;
byte value = ptr->Byte;
在同一个声明中声明多个指针时,星号 * 仅与基础类型一起写入;而不是用作每个指针名称的前缀。 例如:
int* p1, p2, p3; // 正确
int *p1, *p2, *p3; // 错误
c#中指向堆内存的指针必须在unsaved和fixed的上下文中使用。获取数组空间的指针,需要使用fixed关键字:
int[] a = { 1, 2, 3 };
unsafe
{
fixed (int* p = a) {
//do something...
};
}
Dynamic
[编辑]C Sharp 4.0和w:.NET Framework 4.0引入了dynamic关键字,用于运行时动态确定类型。dynamic被编译后是一个Object类型,编译器编译时不会对dynamic进行类型检查。
dynamic x = new Foo();
x.DoSomething(); // Will compile and resolved at runtime. An exception will be thrown if invalid.
匿名类型
[编辑]C# 3.0引入匿名类型。是静态编译时的语法糖。用于表示复杂的数据类型,特别是匿名函数或LINQ查询。
var carl = new { Name = "Carl", Age = 35 }; // Name of the type is only known by the compiler.
var mary = new { Name = "Mary", Age = 22 }; // Same type as the expression above
参考文献
[编辑]- ↑ Archer, Part 2, Chapter 4:The Type System
- ↑ Pointer types (C# Programming Guide), http://msdn.microsoft.com/en-us/library/y31yhkeb.aspx