4.1 数据类型
4.1.1 C#的数据类型
C#是强类型语言,因此每个变量和对象都必须具有声明类型。C#开发语言的数据类型包括值类型和引用类型。
4.1.2 C#值类型
C#开发语言的值类型有15种,包括bool、byte、char、decimal、double、enum、float、int、long、sbyte、short、struct、uint、ulong和ushort。
4.1.2.1 C#值类型介绍——bool类型
1.bool类型的定义
bool关键字是System.Boolean的别名。它用于声明变量来存储布尔值true和false,可将布尔值赋给bool变量,也可以将计算为bool类型的表达式赋给bool变量。
功能说明:bool类型的定义。
using System;
public class MyClass
{
static void Main()
{
bool J = true;
char D = '0';
Console.WriteLine(J);
J = false;
Console.WriteLine(J);
bool abc = (D > 64 && D < 123);
Console.WriteLine(abc);
}
运行结果如图4-1所示。
图4-1 bool类型的定义
2.bool类型的转换
在C++中,bool类型的值可转换为int类型的值,false等效于零值,而true等效于非零值。但是在C#语言中,不存在bool类型与其他类型之间的相互转换。例如,下列if语句在C#中是非法的,而在C++中则是合法的:
int x = 123;
if (x) // 注意:在C#中此语句是错误的
{
printf("x is 非零值.");
}
若要测试int类型的变量,必须将该变量与一个值(例如零)进行显式比较,如下所示:
int x = 123;
if (x != 0) // C#的判断方式
{
Console.Write("The value of x is nonzero.");
}
功能说明:从键盘输入一个字符,然后程序检查输入的字符是否是一个字母。如果输入的字符是字母,则程序检查是大写还是小写。这些检查是使用IsLetter和IsLower(两者均返回bool类型)来执行的。
using System;
public class BoolTest1
{
static void Main()
{
Console.Write("请输入一个字母: ");
char c = (char)Console.Read();
if (Char.IsLower(c))
{
Console.WriteLine("这个字符是小写字母.");
}
else
{
Console.WriteLine("这个字符是大写字母.");
}
}
运行结果如图4-2所示。
图4-2 bool类型的转换
4.1.2.2 值类型介绍——byte类型
1.byte类型的定义
byte关键字代表一种整型,该类型的存储值如表4-1所示。
表4-1 byte类型
类
型
|
范
围
|
大
小
|
类
型
|
byte
|
0
-
255
|
无符号的
8
位整数
|
System.Byte
|
2.byte类型的标识
如下例所示声明并初始化byte类型的变量:
byte myByte = 15;
3.byte类型的转换
存在从byte到short、ushort、int、uint、long、ulong、float、double或decimal的预定义隐式转换。
注意
不能将更大存储范围的非文本数值类型隐式转换为byte。
如:byte z = x + y;
以上的赋值语句将产生一个编译错误,原因是赋值运算符右侧的算术表达式在默认情况下的计算结果为int类型。
若要解决此问题,请使用强制转换:
byte z = (byte)(x + y);
但是,在目标变量具有相同或更大的存储时,使用下列语句是可能的:
int x = 25, y = 20;
int m = x + y;
long n = x + y;
4.1.2.3 值类型介绍——char类型
1.char类型的定义
char关键字用于声明表4-2所示范围内的Unicode字符。Unicode字符是16位字符,用于表示世界上多数已知的书面语言。
表4-2 char类型
类
型
|
范
围
|
大
小
|
类
型
|
char
|
U+0000
到
U+ffff
|
16
位
Unicode
|
System.Char
|
2.char类型的标识
char类型的常数可以写成字符、十六进制换码序列或Unicode表示形式。你也可以显式转换整数字符代码。以下所有语句均声明了一个char变量并用字符Y将其初始化:
char char1 = 'Y'; // 字符
char char2 = '\x0050'; // 16进制
char char3 = (char)89; // 准会整数字符代码
char char4 = '\u0035'; // Unicode
3.char类型的转换
char可以隐式转换为ushort、int、uint、long、ulong、float、double或decimal。但是,不存在从其他类型到char类型的隐式转换。
4.1.2.4 值类型介绍——decimal类型
1.decimal类型的定义
decimal关键字表示128位数据类型。同浮点型相比,decimal类型具有更高的精度和更小的范围,这使它适合于财务和货币计算。该类型的存储值如表4-3所示。
表4-3 decimal类型
类
型
|
范
围
|
大
小
|
类
型
|
decimal
|
±
1.0 * 10e−28
至±
7.9 *10e28
|
28
到
29
位有效
|
System.Decimal
|
2.decimal类型的标识
如果希望实数被视为decimal类型,请使用后缀m或M,例如:
decimal myMoney = 300.3m;
如果没有后缀m,数字将被视为double类型,从而导致编译器错误。
3.decimal类型的转换
整型被隐式转换为decimal,其计算结果为decimal。因此,可以用整数初始化十进制变量而不使用后缀,如下所示:
decimal myMoney = 300;
在浮点型和decimal类型之间不存在隐式转换;因此,必须使用强制转换在这两种类型之间进行转换,如下所示:
decimal myMoney = 99.8m;
double x = (double)myMoney;
myMoney = (decimal)x;
4.decimal类型的十进制输出
可以通过使用String.Format方法或System.Console.Write方法(它调用String.Format())来格式化结果。指定货币格式时需要使用标准货币格式字符串“C”或“c”。
在此例中,同一个表达式中混合使用了decimal和int,计算结果为decimal类型。
如果试图使用下面这样的语句添加double和decimal变量:
double x = 8;
Console.WriteLine(d + x); // Error
将导致下列错误:
Operator '+' cannot be applied to operands of type 'double' and 'decimal'
功能说明:decimal类型的十进制输出。
using System;
public class Decimal_Test
{
static void Main()
{
decimal d = 5.6m;
int y = 9;
Console.WriteLine(d + y);
}
输出结果如图4-3所示。
图4-3 decimal类型的十进制输出
功能说明:在此例中,输出用货币格式字符串格式化。注意:其中x被舍入,因为其小数点位置超出了$0.99。而表示最大精确位数的变量y严格按照正确的格式显示。
using System;
public class Format_decimal_Test
{
static void Main()
{
decimal x = 0.999m;
decimal y = 1234567890m;
Console.WriteLine("My amount = {0:C}", x);
Console.WriteLine("Your amount = {0:C}", y);
}
输出结果如图4-4所示。
图4-4 输出用货币格式字符串格式化
4.1.2.5 值类型介绍——double类型
1.double类型的定义
double关键字表示存储64位浮点值的简单类型,该类型的存储值如表4-4所示。
表4-4 double类型
类
型
|
范
围
|
大
小
|
类
型
|
double
|
±
5.0
×
10
-
324
到±
1.7
×
10308
|
15
到
16
位
|
System.Double
|
2.double类型的标识
默认情况下,赋值运算符右侧的实数被视为double。但是,如果希望整数被视为double,请使用后缀d或D,例如:
double x = 8D;
3.double类型的转换
可在一个表达式中兼用数值整型和浮点型。在此情况下,整型将转换为浮点型。根据以下规则计算表达式:
— 如果其中一个浮点类型为double,则表达式的计算结果为double类型,在关系表达式或布尔表达式中为bool类型。
— 如果表达式中不存在double类型,则表达式的计算结果为float类型,在关系表达式或布尔表达式中为bool类型。
浮点表达式可以包含下列值集:
— 正零和负零。
— 正无穷和负无穷。
— 非数字值(NaN)。
— 有限的非零值集。
功能说明:在下面的示例中,一个int、一个short、一个float和一个double相加,计算结果为double类型。
using System;
class double_Mixed_Test
{
static void Main()
{
int i1 = 52;
float f1 = 9.5f;
short s1 = 9;
double d1 = 4.2E+3;
Console.WriteLine("数据总和等于: {0}", i1 + f1 + s1 + d1);
}
运行结果如图4-5所示。
图4-5 double类型的转换
4.1.2.6 值类型介绍——enum类型
1.enum类型的定义
enum关键字是一种由一组称为枚举数列表的命名常数组成的独特类型。每种枚举类型都有基础类型,该类型可以是除char以外的任何整型。
注意
枚举元素的默认基础类型为int。
2.enum类型的标识
默认情况下,第一个枚举数的值为0,后面每个枚举数的值依次递增1。例如:
enum Days {Sat, Sun, Mon, Tue, Wed, Thu, Fri};
此枚举中,强制元素序列从1而不是0开始。例如:
enum Days {Sat=1, Sun, Mon, Tue, Wed, Thu, Fri};
注意
枚举数的名称中不能包含空白。
3.enum类型的转换
基础类型指定为每个枚举数分配的存储大小。但是,从enum类型到整型的转换需要用显式类型转换来完成。例如,下面的语句通过使用强制转换从enum转换为int,将枚举数Sun赋给int类型的变量:
int x = (int)Days.Sun;
4.enum类型进行可靠编程
如果其他开发人员将使用你的enum代码,你需要提供相关说明,告诉开发人员将新元素添加到任何enum类型时,它们的代码对应的响应是什么。
功能说明:在此例中,声明了一个枚举names。两个枚举数被显式转换为整数并赋给整型变量。
using System;
public class Enum_Test
{
enum names { mary = 8,tom, jack, jenefer };
static void Main()
{
int x = (int)names.mary; int y = (int)names.jenefer;
Console.WriteLine("mary = {0}", x);
Console.WriteLine("jenefer = {0}", y);
}
运行结果如图4-6所示。
图4-6 enum类型进行可靠编程
4.1.2.7 值类型介绍——float类型
1.float类型的定义
float关键字表示存储32位浮点值的简单类型,该类型的存储值如表4-5所示。
表4-5 float类型
类
型
|
范
围
|
大
小
|
类
型
|
float
|
±
1.5
×
10
-
45 to
±
3.4
×
1038
|
7
位
|
System.Single
|
2.float类型的标识
默认情况下,赋值运算符右侧的实数被视为double。因此,应使用后缀f或F初始化浮点型变量,如下所示:
float x = 3.5F;
注意
如果在以上声明中不使用后缀,则会因为你试图将一个double值存储到float变量中而发生编译错误。
3.float类型的转换
可在一个表达式中兼用数值整型和浮点型。在此情况下,整型将转换为浮点型。根据以下规则计算表达式:
— 如果其中一个浮点型为double,则表达式的计算结果为double类型,在关系表达式或布尔表达式中为bool类型。
— 如果表达式中不存在double类型,则表达式的计算结果为float类型,在关系表达式或布尔表达式中为bool类型。
浮点表达式可以包含下列值集:
— 正零和负零
— 正无穷和负无穷
— 非数字值(NaN)
— 有限的非零值集
功能说明:在下面的示例中,包含int、short和float类型的数学表达式得到一个float结果。请注意,表达式中没有double。
using System;
class float_mixed_Test
{
static void Main()
{
int x = 2;
float y = 6.5f;
short z = 11;
Console.WriteLine("最后的结果是: {0}", x * y / z);
}
输出结果如图4-7所示。
图4-7 float类型转换
4.1.2.8 值类型介绍——int类型
1.int类型的定义
int关键字表示一种整型,该类型的存储值如表4-6所示。
表4-6 int类型
类
型
|
范
围
|
大
小
|
类
型
|
int
|
-
2,147,483,648
到
2,147,483,647
|
有符号
32
位整数
|
System.Int32
|
2.int类型的标识
可以声明并初始化int类型的变量,例如:
int i = 123;
注意
如果整数没有后缀,则其类型为以下类型中可表示其值的第一个类型:int、uint、long、ulong。在此例中为int类型。
3.int类型的转换
存在从int到long、float、double或decimal的预定义隐式转换。例如:
float f = 133;
存在从sbyte、byte、short、ushort或char到int的预定义隐式转换。例如,如果不进行强制转换,下面的赋值语句将产生编译错误。
long aLong = 42;
int i1 = aLong; // 这个语句是错误的,需要做强制转换
int i2 = (int)aLong; //经过强制转换后, 表达式是正确的
注意
不存在从浮点型到int类型的隐式转换。例如,除非使用显式强制转换,否则以下语句将生成一个编译器错误:
int x = 5.0; // 这个表达式是错误的,不存在浮点型到int的隐形转换
int y = (int)5.0; //强制转换后,表达式是正确的
4.1.2.9 值类型介绍——long类型
1.long类型的定义
long关键字表示一种整型,该类型的存储值如表4-7所示。
表4-7 long类型
类
型
|
范
围
|
大
小
|
类
型
|
long
|
-
9,223,372,036,854,775,808
到
9,223,372,036,854,775,807
|
有符号
64
位整数
|
System.Int64
|
2.long类型的标识
可以声明并初始化long类型的变量,例如:
long long1 = 4294967296;
注意
如果整数没有后缀,则其类型为以下类型中可表示其值的第一个类型:int、uint、long、ulong。在上例中,它是long类型,因为它超出了uint的范围。
还可以像下面这样,在long类型中使用后缀L:
long long2 = 4294967296L;
当使用后缀L时,将根据整数的大小确定它的类型为long还是ulong。在此例中,它是long,因为它小于ulong的范围的下限。
3.long类型的转换
存在从long到float、double或decimal的预定义隐式转换。其他情况下必须使用显式转换。例如,不使用显式类型转换时,下列语句将产生编译错误:
int x = 6L; //不可以从long到int的转换
int x = (int)6L; //long类型到int类型必须经过强制转换
存在从sbyte、byte、short、ushort、int、uint或char到long的预定义隐式转换。
注意
不存在从浮点型到long类型的隐式转换。
例如,除非使用显式强制转换,否则以下语句将生成一个编译器错误:
long x =4.0; // 不可以从浮点型到long的转换
long y = (long)4.0; //必须强制转换才可以
4.1.2.10 值类型介绍——sbyte类型
1.sbyte类型的定义
sbyte关键字代表一种整型,该类型的存储值如表4-8所示。
表4-8 sbyte类型
类
型
|
范
围
|
大
小
|
类
型
|
sbyte
|
-
128
~
127
|
有符号的
8
位整数
|
System.Sbyte
|
2.sbyte类型的标识
可如下例所示声明并初始化sbyte类型的变量:
sbyte sbyte1 = 127;
注意
在以上声明中,整数127从int隐式转换为sbyte。如果整数超出了sbyte的范围,将产生编译错误。
3.sbyte类型的转换
存在从sbyte到short、int、long、float、double或decimal的预定义隐式转换。不能将存储范围更大的非文本数值类型隐式转换为sbyte类型。
例如:
sbyte x = 10, y = 20;
注意
不存在从浮点型到sbyte类型的隐式转换。
例如,除非使用显式强制转换,否则以下语句将生成一个编译器错误:
sbyte x = 3.0; // 转换错误
sbyte y = (sbyte)3.0; // 强制转换后,正确
4.1.2.11 值类型介绍——short类型
1.short类型的定义
short关键字代表一种整型数据类型,该类型的存储值如表4-9所示。
表4-9 short类型
类
型
|
范
围
|
大
小
|
类
型
|
short
|
-
32 768~32 767
|
有符号的
16
位整数
|
System.Int16
|
2.short类型的标识
可如下例所示声明并初始化short类型的变量:
short x = 32767;
注意
在以上声明中,整数32 767从int隐式转换为short。如果整数的长度超过了short存储位置的大小,则将产生编译错误。
3.short类型的转换
存在从short到int、long、float、double或decimal的预定义隐式转换。不能将存储更大的非文本数值类型隐式转换为short。
例如:
short x = 5, y = 12;
short z = x + y; // 转换错误
short z = ( short )(x + y); // 强制转换正确
short x = 5.0; // 转换错误
short y = (short)5.0; // 强制转换正确
注意
不存在从浮点型到short的隐式转换。例如,除非使用显式强制转换,否则以下语句将生成一个编译器错误。
4.1.2.12 值类型介绍——struct类型
struct类型是一种值类型,通常用来封装小型相关变量组,例如,商品的特征。下面的示例显示了一个简单的结构声明。
public struct Book
{
public decimal price; //价格
public string title; //题目
public string author; //作者
}
结构还可以包含构造函数、常量、字段、方法、属性、索引器、运算符、事件和嵌套类型,但如果同时需要上述几种成员,则应当考虑改为使用类作为类型。
结构可以实现接口,但它们无法继承另一个结构。因此,结构成员无法声明为protected。
4.1.2.13 值类型介绍——uint类型
1.uint类型的定义
uint关键字表示一种整型,该类型的存储值如表4-10所示。
表4-10 uint类型
类
型
|
范
围
|
大
小
|
类
型
|
uint
|
0
到
4 294 967 295
|
无符号
32
位整数
|
System.UInt32
|
2.uint类型的标识
可以声明并初始化uint类型的变量,例如:
uint myUint = 4294967290;
注意
如果整数没有后缀,则其类型为以下类型中可表示其值的第一个类型:int、uint、long、ulong。在此例中,它是uint。
uint uInt1 = 133;
还可以像下面这样使用后缀u或U:
uint uInt2 = 133U;
3.uint类型的转换
存在从uint到long、ulong、float、double或decimal的预定义隐式转换。
例如:float myFloat = 4294967290; //转换是正确的
注意
存在从byte、ushort或char到uint的预定义隐式转换。否则必须使用显式转换。例如,如果不进行强制转换,下面的赋值语句将产生编译错误。
uint x = 5.0; //转换错误,不支持浮点到uint的转换
uint y = (uint)5.0; //强制转换是正确的
4.1.2.14 值类型介绍——ulong类型
1.ulong类型的定义
ulong关键字表示一种整型,该类型的存储值如表4-11所示。
表4-11 ulong类型
类
型
|
范
围
|
大
小
|
类
型
|
ulong
|
0~18 446 744 073 709 551 615
|
无符号
64
位整数
|
System.UInt64
|
2.ulong类型的标识
可以声明并初始化ulong类型的变量,例如:
ulong uLong = 9223372036854775808;
注意
如果整数没有后缀,则其类型为以下类型中可表示其值的第一个类型:int、uint、long、ulong。在上面的示例中,它是ulong类型。
还可根据以下规则使用后缀指定文字类型:
— 如果使用L或l,那么根据整数的大小,可以判断出其类型为long还是ulong。
— 如果使用U或u,那么根据整数的大小,可以判断出其类型为uint还是ulong。
— 如果使用UL、ul、Ul、uL、LU、lu、Lu或lU,则整数的类型为ulong。
例如,以下三个语句的输出将为系统类型Uint64,此类型对应于别名ulong:
Console.WriteLine(9223372036854775808L.GetType());
Console.WriteLine(123UL.GetType());
Console.WriteLine((123UL + 456).GetType());
3.ulong类型的转换
存在从ulong到float、double或decimal的预定义隐式转换。
不存在从ulong到任何整型的隐式转换。例如,不使用显式类型转换时,下列语句将产生编译错误:
long long1 = 6UL; // 转换错误,不存在从 ulong 到任何整型的隐式转换
存在从byte、ushort、uint或char到ulong的预定义隐式转换。
同样,不存在从浮点型到ulong类型的隐式转换。例如,除非使用显式强制转换,否则以下语句将生成一个编译器错误:
ulong x = 5.0; // 转换错误,不存在从浮点型到 ulong 类型的隐式转换
ulong y = (ulong)5.0; // 强制转换正确
4.1.2.15 值类型介绍——ushort类型
1.ushort类型的定义
ushort关键字表示一种整数数据类型,该类型的存储值如表4-12所示。
表4-12 ushort类型
类
型
|
范
围
|
大
小
|
类
型
|
ushort
|
0~65 535
|
无符号
16
位整数
|
System.UInt16
|
2.ushort类型的标识
可以声明并初始化ushort类型的变量,例如:
ushort myShort = 65535;//超出范围
注意
在以上声明中,整数65 535从int隐式转换为ushort。如果整数超出了ushort的范围,将产生编译错误。
3.ushort类型的转换
存在从ushort到int、uint、long、ulong、float、double或decimal的预定义隐式转换。
存在从byte或char到ushort的预定义隐式转换。其他情况下必须使用显式转换。例如,请看以下两个ushort变量x和y:
ushort x = 4, y = 13;
以下赋值语句将产生一个编译错误,原因是赋值运算符右侧的算术表达式在默认情况下的计算结果为int类型。
ushort z = x + y;
若要解决此问题,请使用强制转换:
ushort z = (ushort)(x + y); //使用强制转换
但是,在目标变量具有相同或更大的存储时,使用下列语句是可能的:
int m = x + y;
long n = x + y;
4.1.3 引用类型包括的内容
引用类型的变量又称为对象,可存储对实际数据的引用。
C#的引用类型包括class(类)、delegate(委托)、interface(接口)、object、string组成,本章先介绍一下object引用类型,其他引用类型会在其他章节重点介绍。
C#引用类型——object类型
1.object类型的定义
在C#的统一类型系统中,所有类型包括预定义类型、用户定义类型、引用类型和值类型等都是直接或间接从Object继承的。可以将任何类型的值赋给object类型的变量。
装箱和取消装箱使值类型能够被视为对象。对值类型来说,装箱是将该值类型打包到Object引用类型的一个实例中,这使得值类型可以存储于垃圾回收堆中。而取消装箱则是将从对象中提取值类型。
功能说明:示例演示了object类型的变量如何接收任何数据类型的值,以及object类型的变量如何在.NET Framework中使用Object的方法。
using System;
class obj_Sample_Class
{
public int i = 10;
}
class MainClass
{
static void Main()
{
object a;
a = 100;
Console.WriteLine(a);
Console.WriteLine(a.GetType());
Console.WriteLine(a.ToString());
a = new obj_Sample_Class();
obj_Sample_Class classRef;
classRef = (obj_Sample_Class)a;
Console.WriteLine(classRef.i);
}
运行结果如图4-8所示。
图4-8 object类型的定义
2.“装箱”的概念
在C#的统一类型系统中,所有类型(预定义类型、用户定义类型、引用类型和值类型)都是直接或间接从Object继承的。将值类型的变量转换为对象的过程称为“装箱”。
3.“取消装箱”的概念
将对象类型的变量转换为值类型的过程称为“取消装箱”。
4.C#做“装箱”转换工作
装箱用于在垃圾回收堆中存储值类型。装箱是值类型到object类型或到此值类型所实现的任何接口类型的隐式转换。对值类型装箱会在堆中分配一个对象实例,并将该值复制到新的对象中。
请看以下值类型变量的声明:
int i = 123;
以下语句对变量i隐式应用装箱操作:
object o = i; // 装箱操作
此语句的结果是在堆栈上创建对象引用o,而在堆上则引用int类型的值。该值是赋给变量i的值类型值的一个副本。
功能说明:此示例通过装箱将整数变量i转换为对象o。该示例表明原始值类型和装箱的对象使用不同的内存位置,因此能够存储不同的值。
using System;
class boxing_Test
{
static void Main()
{
int i = 100;
object o = i; // 装箱
i = 200;
System.Console.WriteLine("直接赋值的数据= {0}", i);
System.Console.WriteLine("装箱后的数据= {0}", o);
}
运行结果如图4-9所示。
图4-9 C#做“装箱”转换工作
5.C#做“装箱取消”转换工作
取消装箱是从object类型到值类型或从接口类型到实现该接口的值类型的显式转换。取消装箱操作包括:
— 检查对象实例,确保它是给定值类型的一个装箱值。
— 将该值从实例复制到值类型变量中。
以下语句同时说明了装箱和取消装箱操作:
int i = 123; // a value type
object o = i; // boxing
int j = (int) o; // unboxing
注意
要在运行时成功取消装箱值类型,被取消装箱的项必须是对一个对象的引用,该对象是先前通过装箱该值类型的实例创建的。尝试对null或对不兼容值类型的引用进行取消装箱操作,将导致InvalidCastException。
功能说明:下面的示例演示无效的取消装箱及引发的InvalidCastException。使用try和catch,在发生错误时显示错误信息。
using System;
class Unboxing_Test
{
static void Main()
{
int i = 100;
object o = i; // 装箱
try
{
int j = (short) o; // 取消装箱
System.Console.WriteLine("取消装箱成功.");
}
catch (System.InvalidCastException e)
{
System.Console.WriteLine("{0} 取消装箱异常.", e.Message);
}
}
运行结果如图4-10所示。
图4-10 C#做“装箱取消”转换工作
如果将下列语句:
int j = (int) o;
功能说明:被取消装箱的项必须是对一个对象的引用。
using System;
class Unboxing_Test
{
static void Main()
{
int i = 100;
object o = i; // 装箱
try
{
int j = (int)o; // 取消装箱
System.Console.WriteLine("取消装箱成功.");
}
catch (System.InvalidCastException e)
{
System.Console.WriteLine("{0} 取消装箱异常.", e.Message);
}
}
运行结果如图4-11所示。
图4-11 被取消装箱的项是对一个对象的引用