第2章 数据类型
C#中有着大量的数据类型,而且可以合并不同的数据类型来创建新的类型。然而C#中也有几种非常简单的类型,这些类型被视为所有类型的基础,称为预定义类型(predefined type)或者基元类型(primitive type)。
C#中的基本类型包括8种整数类型、2种浮点类型、1中高精度类型、1种布尔类型以及1中字符串类型。
一、基本数值类型
1. 整数类型
类型 | 大小 | 范围 | BCL名称 | 有符号? |
---|---|---|---|---|
sbyte | 8 位 | -128~127 | System.SByte | 是 |
byte | 8 位 | 0~255 | System.Byte | 否 |
short | 16位 | -32768~32767 | System.Int16 | 是 |
ushort | 16位 | 0~65535 | System,UInt16 | 否 |
int | 32位 | -2147783648~2147483647 | System.Int32 | 是 |
uint | 32位 | 0~4294967295 | System.UInt32 | 否 |
long | 64位 | -9223372036854775808~ 9223372036854775807 | System.Int64 | 是 |
uling | 64位 | 0~18446744073709551615 | System.UInt64 | 否 |
2. 浮点数类型
类型 | 大小 | 范围 | BCL名称 | 有效数字 |
---|---|---|---|---|
float | 32位 | ±1.5×〖10〗^45~±3.4×〖10〗^38 | System.Single | 7 |
double | 64位 | ±1.5×〖10〗^324~±3.4×〖10〗^308 | System.Double | 15~16 |
3. decimal类型
类型 | 大小 | 范围 | BCL名称 | 有效数字 |
---|---|---|---|---|
decimal | 128位 | 1.0×〖10〗^28~大约7.9×〖10〗^28 | System.Decimal | 28~29 |
4. 字面值
表示源代码中的一个固定值,默认情况下,如果输入一个带小数点的字面值,编译器会自动解释为double类型;相反,一个整数的字面值默认为int。
1)对于没有后缀的数值字面量按以下顺序存储:int、Uint、long、ulong
2)对于有u后缀的数值字面量按以下顺序存储:uint、ulong
3)对于有l后缀的数值字面量按以下顺序存储:long、ulong
4)如果数值字面量的后缀是ul或lu,就解析成ulong
注:因为小写字符l与1太过相似,所以建议对于需要使用l后缀的字面值,l写成大写的'L'。
进制转换:十进制与十六进制的转换
Console.WriteLine(0x002A);
Console.WriteLine("{0:X}=={0:x}", 42, 42);
Round-Trip格式化:不丢失值的转换
const Double number = 1.12345678901234567;
Double result;
String text;
text = String.Format("{0}", number);
result = Double.Parse(text);
Console.WriteLine("result = {0},compare:{1}", result, result == number);
text = String.Format("{0:R}", number);
result = Double.Parse(text);
Console.WriteLine("result = {0},compare:{1}", result, result == number);
5. 布尔类型
类型 | 大小 | 范围 | BCL名称 | 说明 |
---|---|---|---|---|
bool | System.Boolean |
6. 字符类型
类型 | 大小 | 范围 | BCL名称 | 说明 |
---|---|---|---|---|
char | 16 位 | 0~65535 | System.Char | |
Unicode标准:是对人类大多数语言中的字符进行表示的一个国际标准,为不同语言文化显示具有本地特色的字符。16位不足以支持所有的语言,所以一些Unicode字符要由一对称为“代理项”的char构成,总共32位。
输入一个字符:'A'
输入转义字符:'\n'
转义字符表:
转义序列 | 字符名称 | Unicode编码 |
\' | 单引号 | 0x0027 |
\" | 双引号 | 0x0022 |
\\ | 反斜杠 | 0x005c |
\0 | Null | 0x0000 |
\a | Alert(System beep) | 0x0007 |
\b | 退格 | 0x0008 |
\f | 换页(Form feed) | 0x000C |
\n | 换行(Line feed 或者 newline) | 0x000A |
\r | 回车 | 0x000D |
\t | 水平制表符 | 0x0009 |
\v | 垂直制表符 | 0x000B |
\uxxxx | 十六进制Unicode字符 | \u0029 |
\x[n][ n][ n]n | 十六进制Unicode字符(前三个占位符是可选的) | \x3A |
\Uxxxxxxxx | 用于创建“代理项”对的Unicode转义序列 | \UD840DC01 |
7. 字符串
Console.WriteLine(@"
Begin
/\
/ \
/ \
/______\
End!
");
注:当程序中同一个字符串字面量出现多次,那么编译器编译时在程序集中只定义字符串一次。之后代码中出现的字符串字面量都是对已定义字符串的引用。字符串还有一个特定,就是定义之后就是恒定不变的,一切对字符串的操作都会生成新的字符串。可以用一个程序来进行验证:
String variable1 = "hello,JJ";
String variable2 = "hello,JJ";
String variable3 = "hello,JJ";
Console.WriteLine("variable1 == variable2: {0}, \nvariable2 == variable3: {1}, \nvariable1 == variable3: {2}", String.ReferenceEquals(variable1, variable2), String.ReferenceEquals(variable2, variable3), String.ReferenceEquals(variable1, variable3));
variable1 = "Hello,JJ";
Console.WriteLine("\nvariable1 == variable2: {0}, \nvariable2 == variable3: {1}, \nvariable1 == variable3: {2}", String.ReferenceEquals(variable1, variable2), String.ReferenceEquals(variable2, variable3), String.ReferenceEquals(variable1, variable3));
字符串方法:http://msdn.microsoft.com/en-us/library/system.string_methods.aspx
如果有大量的字符串需要修改,则应该考虑使用System.Text.StringBuilder
二、null和void
与类型有关的两个额外的关键字是null和void。null是一个字面值,用来表示数据类型未被赋予任何值,void用来表示没有类型,或者没有任何值。
null
null可以作为字符串的字面量的类型来使用,表示将一个变量设置为“无”。null值只能赋给引用类型,不能赋给值类型。
String name1 = ""; // 已分配内存空间,name1为空
String name2 = null; // 未分配内存空间,name2未知
Console.WriteLine("String.ReferenceEquals(name1, name2):{0}", String.ReferenceEquals(name1, name2));
void
表示没有数据类型。
三、类型的分类
所有的类型都可以分为两类:值类型和引用类型。它们的区别在于使用的内存位置不同:值类型存储在栈上,而引用类型存储在堆上。
堆栈是两种数据结构
堆:顺序随意;堆区:由程序员分配释放,若程序员不释放,则由OS进行回收。
栈:后进先出;栈区:有编译器自动分配释放,存放函数的参数值,局部变量等。
值类型存储在栈上,对于值变量之间的赋值操作,会在新变量的位置上创建原始变量的一个副本,所以改变原始变量的值不会使得新变量的值发送变化。同样的在将一个变量值传递给方法时也会生成一个内存副本,在方法内部对该值变量进行的任何修改都不会影响到原始的值,若需要改变所传递参数的值的话就需要使用ref或out关键字。
引用类型并不直接存储值,而是存储一个内存位置的引用,其真正的值在该位置存储的数据。从中可以知道,访问引用类型的数据比值类型更慢一些,因为访问引用类型要进行一次额外的跳转,但是也由于值类型需要创建副本的原因,引用类型在内存的利用率上面显得比值类型要更好。引用类型的大小即是地址本身的大小,对于32位机则是32bit,对于64位机则是64bit。随着数据量的增大,引用类型的内存利用效率就显得更加有效。
四、可空修饰符
null值不能赋给值类型,但是有时候给值类型变量赋空值是必要的,这个时候怎么办呢?C#2.0提供了可空修饰符这一新特性。在值类型后面加上?来表示可空的值类型变量。例如 int? a = null; 定义了一个可空的整型变量a,并将null赋给变量a。int?等价于Nullable<int>,由此还引出了空接合运算符??。用法举例:
五、数据类型之间的转换
显示转型:在转型过程中有可能造成数据丢失或者引发异常的任何转换都需要执行显示转型;例如long类型转成int类型,由前面的表可知,long类型的取值范围大于int类型,在转型过程中就有可能引起数据丢失,此时想要转型成功就必须用显示转型。当然也会有转型不成功的时候
隐式转型:在转型过程中不会丢失数据且不会引发异常的任何转换都属于隐式转型。
int? a = null;
int b = a.HasValue ? a.Value : 1;
// 等价于
int c = a ?? 1;
Console.WriteLine("{0}", b.Equals(c));
显示转型中难免会有转型之后达不到自己需要的情况,这个时候怎么办好呢?那就需要进行判断了,在C#中可以使用checked和unchecked这两个关键字来进行判断。
还有一种类型转换,字符串类型转为值类型,每一个值类型都配置了一个Parse(String)的方法,将字符串转成相应的值类型;还可以使用System.Convert来进行类型转换。每一种类型都有从Object类中继承的ToString()方法转成字符串。还有一个TryParse(String)方法,与Parse(String)方法不同的是在转换失败时并不是引发一个异常而是返回false,以期进行相应的处理。
六、数组
数组是在一个变量中存储同一中类型的多个数据项。从0开始计数,所以数组的最后一项的索引比数组长度小1.
数组的声明:String[] languages;
数组实例化和赋值:String[] languages = {"C","C++","C#","Javascript","Java"};
String[] languages; languages = new String[]{"C","C++","C#","Javascript","Java"};
初始化默认值:default(String) = null; default([值类型]) = 0; default(bool) = false; default(char) = '\0';
二维数组:int[,] cells = new int[3,3];
锯齿数组:int[][] cells ;