C#语言参考--(4)类型

4. 类型
C#语言的类型被分为三类:数值类型、引用类型和指针类型。
type:
value-type
reference-type
pointer-type
指针类型只能用在不安全代码,并且将在§18.2中进行讨论。
数值类型与引用类型所不同的是,数值类型变量直接含有它们的数据,然而引用类型的变量存储对它们的数据的引用,就是后面要介绍的对象。对于引用类型,可能会出现两个变量引用相同对象的情况,这样对于一个变量的的操作就有可能影响到由其它变量引用的对象。对于数值类型,每个变量都有它们自己对数据的拷贝,这样就不可能出现一个对变量的操作影响到另外一个的情况。
C#的类型系统是统一的,这样任何类型的数据都可以被看做对象。C#中的任何类型都直接或间接地从objiect类类型派生,而object是所有类型的最基本类类。引用类型的数值被看做通过对象,这些对象通过把数值看做类型对象来简化。数值类型的数 值通过包装和解包操作来被当做对象 (§4.3)。
4.1 数值类型
数值类型既是一个结构类型也是枚举类型。C#提供了一系列预定义结构类型,称为简单类型。简单类型通过保留字指定,并且进一步分成数字类型,整数类型和浮点数类型。
value-type:
struct-type
enum-type
struct-type:
type-name
simple-type
simple-type:
numeric-type
bool
numeric-type:
integral-type
floating-point-type
decimal
integral-type:
sbyte
byte
short
ushort
int
uint
long
ulong
char
floating-point-type:
float
double
enum-type:
type-name
所有数值类型都隐式地从类object继承。不允许任何类型从数值类型派生,因而数值类型是被默认封闭的。
一个数值类型的变量通常包含一个那种类型的数值。不像引用类型,数值类型的数值不能为null或是引用一个进一步派生类型的变量。
对某个数值类型的变量赋值就会创建一个对所赋数值的拷贝,它复制了引用而不是引用所指定的对象。
4.1.1 默认构造函数
所有类型都隐含地声明了一个公共的无参数的构造函数,称为默认构造函数。默认构造函数返回一个初始值为零的实例,为数值类型的默认数值:
· 对于所有单独类型,默认数值是由一个零位格式产生的数值:
· 对于sbyte、byte、short、ushort、int、uint、long和ulong,默认的数值为0。
· 对于char,默认的数值为’/x0000’。
· 对于float,默认的数值是0.0f。
· 对于double,默认的数值为0.0d。
· 对于decimal,默认的数值为0.0m。
· 对于bool,默认的数值为false。
· 对于一个枚举类型E,默认数值是0。
· 对于结构类型,默认数值是把所有数值类型域设为它们的默认类型并且把所有引用类型域设为null的数值。
像其它任何构造函数一样,一个数值类型的默认的构造函数用new操作符调用。在下面的例子中,变量i和j都初始化为0。
class A
{
 void F() {
  int i = 0;
  int j = new int();
 }
}
因为每个数值类型隐含的都有公共无参数构造函数,所以让一个结构类型包含一个外部声明的无参数构造函数是不可能的。一个结构类型可以允许声明一个参数化的构造函数。例如
struct Point
{
 int x, y;
 public Point(int x, int y) {
  this.x = x;
  this.y = y;
 }
}
如果已经给出上面的声明,那么语句
Point p1 = new Point();
Point p2 = new Point(0, 0);
都会创建一个Point,其中x和y被初始化为0。
4.1.2 结构类型
一个结构类型是一个数值类型,它可以声明构造函数、常数、域、方法、属性、索引、操作符和嵌套类型。结构类型在§11中描述。
4.1.3 简单类型
C#提供了一系列的预定义结构类型,称为简单类型。这些简单类型通过关键词确定,但是这些关键词可以为在System名称空间中的预定义结构类型关键词起简单的别名,就像下面表中所示。

关键字 有别名的类型
sbyte System.SByte
byte System.Byte
short System.Int16
ushort System.Uint16
int System.Int32
uint System.Uint32
long System.Int64
ulong System.Uint64
char System.Char
float System.Single
double System.Double
bool System.Boolean
decimal System.Decimal

一个简单类型和它有别名的结构类型是不可分辨的。换句话说,当写下保留字byte时和写System.Byte确实没有什么区别,并且用System.Int32也与用保留字int相同。
因为一个简单类型代表了一个结构类型,所以每个简单类型都有成员。例如,int有在System.Int32中声明的成员和从System.Object中继承的成员,并且下面的语句是允许的:
int i = int.Maxvalue;   // System.Int32.Maxvalue constant
string s = i.ToString();  // System.Int32.ToString() instance method
string t = 123.ToString();  // System.Int32.ToString() instance method
注意,整数文字上是int类型数据,并且同时也是System.Int32结构类型的数据。
简单类型与其它结构类型,其它结构类型允许包含附加操作符:
· 大多数简单类型允许通过使用文字来 创建 (§错误!未找到引用源。)。例如,123是int类型量,而’a’是字符类型量。C#使得不用对其它结构类型文字进行预定义,而其它结构类型数据基本上是通过那些结构类型的构造函数来创建。
· 当一个表达式的运算符都是简单类型常数时,编译器在编译时就可以对这个表达式进行赋值。这样一个表达式称为常数表达式(§错误!未找到引用源。)。包括其它结构类型定义的操作符的表达式通常意味着运行时赋值。
· 通过const声明,就有可能声明一个简单类型(§10.3)的常数。不可能有其它结构类型的常数,但是static readonly域提供了相似的作用。
· 包括简单类型的转换可以参加由其它结构类型定义的转换操作符的赋值,但是用户定义的转换操作符不能参与另外一个用户定义操作符的赋值(§错误!未找到引用源。)。
4.1.4 整数类型
C#支持九种整数类型: sbyte、byte、short、ushort、int、uint、long、ulong和 char。这些整数类型有下面的大小和数值范围:
· sbyte类型表示有符号的8位整数,数值范围为-128到127。
· byte 类型表示无符号8位整数,数值范围为0到255。
· short 类型表示有符号16位整数,数值范围为-32768到32767。
· ushort类型表示无符号16位整数,数值范围为0到65535。
· int类型表示有符号32位整数,数值范围为–2147483648到2147483647。
· uint类型表示无符号32位整数,数值范围为0到4294967295。
· long类型表示有符号64位整数,数值范围为–9223372036854775808到9223372036854775807。
· ulong类型表示无符号64位整数,数值范围为0到18446744073709551615。
· char类型表示无符号16位整数,数值范围为0到65535。char类型的可能数值集符合Unicode字符集。
整数类型一元和二元操作符总是按有符号32位精度、无符号32位精度、有符号64位精度或无符号64位精度进行操作。
· 对于一元+和~操作符,操作数被转换为类型T,这里T是int、uint、long和ulong中第一个可以完全代表操作数的所有可能值的类型。操作使用类型T的精度来实现,而结果的精度也是T。
· 对于一元操作符-,操作数被转换为类型T,这里T是int和long中第一个可以完全代表操作数的所有可能值的类型。操作使用类型T的精度来实现,而结果的精度也是T。一元操作符-不能应用于ulong类型操作数。
· 对于二元操作符+、–、*、/、%、&、^、|、==、!=、>、<、>=和<= 操作符,操作数被转换为类型T,这里T是int、uint、long和ulong中第一个可以完全代表操作数的所有可能值的类型。操作使用类型T的精度来实现,而结果的精度也是T(或相关操作符bool)。
· 对于二元操作符<<和>> 操作符,操作数被转换为类型T,这里T是int、uint、long和ulong中第一个可以完全代表操作数的所有可能值的类型。操作使用类型T的精度来实现,而结果的精度也是T
char类型被分类为一种整数类型,但是它在两点上不同于其它整数类型:
· 没有从其它类型到字符类型的隐含的转换。甚至,即使sbyte、byte和ushort类型的数据完全可以用char类型代表,但是从sbyte、 byte和ushort类型到char的隐含转换也不存在。
· char类型的常数必须写成字符文字。字符常量可以只是写成与一个斜杠结合的整数文字。例如, (char)10与’/x000A’相同。
checked和unchecked操作符和语句用来控制检查整数类型算术操作和转换(§7.5.13)的溢出。在一段checked上下文中,一个溢出产生一个编译时错误或者引起扔出一个OverflowException。在一段unchecked的上下文里,溢出被忽略并且不需要送到目标类型的任何高端位被丢弃。
4.1.5 浮点类型
C#支持两个浮点类型: float和double。float和double类型用32位单精度和64位双精度IEEE754格式来表示,它提供了一系列数值:
· 正零和负零。在大多数情况下,正零和负零与简单的零值相同,但是它们的使用中间有一些区别。
· 正无穷大和负无穷大。无穷大是由一个非零成员除以零的操作产生的。例如,1.0 / 0.0产生正无穷大,而–1.0 / 0.0产生负无穷大。
· 非数字数据,通常缩写为NaN。NaN是无效的浮点数操作产生的,例如零除以零。
· 形如s × m × 2e 的非零数据有限集,这里s是1或者-1,而m和e由具体浮点数类型决定:对于float,0 < m < 224 和−149 ≤ e ≤ 104,对于double,0 < m < 253 和−1075 ≤ e ≤ 970。
float类型可以代表的数值范围大约从1.5 × 10−45 到3.4 × 1038,有7位数字位精度。
double类型可以代表的数值范围大约从5.0 × 10−324 到1.7 × 10308 ,有15到16位数字位精度。
如果二元运算符的一个操作数是浮点类型,那么其它操作数必须是整数类型或者是浮点数类型,并且操作按下面求值:
· 如果一个操作数是整数类型,那么那个操作数会被转换为与其它操作数一样的浮点数类型。
· 如果操作数是double类型,其它 操作数就要转换为double,操作就要按照double类型的范围和精度来进行,而且计算的结果也是double类型(对于相关操作,或者是bool)。
· 否则,操作至少使用float的范围和精度,而且计算的结果也是float类型(对于相关操作,或者是bool)。
包括赋值操作符的浮点操作符,从不产生异常。在异常情况下,浮点数操作会产生下面介绍的零、无穷大或NaN作为替代:
· 如果浮点数操作的结果对于目标形式来说太小,操作的结果就会转换为正零或负零。
· 如果浮点数操作的结果对于目标形式来说太大,操作的结果就会转换为正无穷大或负无穷大。
· 如果浮点数的操作是无效的,操作的结果就会转换为NaN。
· 如果一个或所有浮点操作的操作数都是NaN,那么操作的结果就变为NaN。
浮点数操作可以用比操作结果的类型更高的精度来执行。例如,一些硬件结构支持一个比double类型更大范围和更高精度的“扩展的”或“long double”浮点数类型,并且会隐含地使用这个更高的精度来实现浮点数操作。只有在性能要额外付出时,这样的硬件结构才会被用来实现精度小一些的浮点数操作,而不需要执行同时丧失性能和精度,C#允许所有的浮点数操作使用更高的精度类型。与给出更高精度的结果不同,这样几乎没有任何可测量的影响。在形如x * y / z 的表达式中,这里的乘法产生一个超出double类型范围的结果,但是后面的除法带来一个回到double范围的暂时结果,实际上在大一些的范围形式计算这个表达式会产生有限的结果而不是无穷大。
4.1.6 十进制类型
十进制类型是一个128位数据类型,适合金融和货币计算。十进制类型可以代表的数值范围是从1.0 × 10−28到大约7.9 × 1028,有28到29个有效数字位。
十进制类型数值的有限集合形式为s × m × 10e ,这里s是1或者-1,0 ≤ m < 296而−28 ≤ e ≤ 0。十进制类型不支持有符号零、无穷大和NaN。
一个十进制数由96位整数和十位幂表示。对于一个绝对数值小于1.0m的十进制数,数据就是第28个十进制位,但是没有更多。对于绝对值大于或等于1.0m的十进制数,数据可能是28或29数字位。与float和double类型相比,如0.1的十进制小数成员可以就用十进制表示。在用float和double表示时,这样的成员经常为无穷小数,使得这些表示有更大的舍入误差。
如果一个二元操作符的操作数是十进制类型,其它操作数也必须是整数类型或十进制类型。如果要使用一个整数类型操作数,在操作被执行前它会被转换为十进制数。
十进制类型的数值的操作就是28或29数字位,但是不会多于28十进制位。结果为最接近的可表示的数值,当结果与两个可表示数值都距离都相等时,选择在最小数据位上为奇数的数值。
如果十进制算术操作产生了一个在舍入后对于十进制形式太小的数据,操作的结果就变为零。如果一个十进制算术操作产生了一个对于十进制形式太大的数据,就会抛出一个OverflowException错误。
十进制类型比浮点类型有更高的精度但是有更小的范围。这样,从浮点数类型转换到十进制类型也许会产生溢出的异常,并且从十进制类型转换到浮点数类型也许会有精度损失。出于这些原因,不存在浮点数类型和十进制类型间的隐式转换,并且也没有显式的情况,在同一个表达式中把浮点数和十进制操作数混合在一起是不可能的。
4.1.7 布尔类型
bool类型表示布尔逻辑量,bool类型的可能值为true和false。
在bool和其它类型间不存在标准的转换。特别是,bool类型与整数类型截然不同,bool数据不能用于使用整数类型的地方,反之亦然。
在C和C++语言中,零整数值或空指针可以被转换为布尔数值false,而非零整数数值或非空指针可以转换为布尔数值true。在C#中,这样的转换由显式地把整数数值和零比较或显式地把对象和null比较来实现。
4.1.8 枚举类型
枚举类型是一种有名称常数的独特类型。每个枚举类型都有前级类型,可以是byte、short、int或long。枚举类型通过枚举声明来定义(§14.1)。
4.2 引用类型
引用类型是一个类类型、一个接口类型、一个数组类型或是一个代表类型。
reference-type:
class-type
interface-type
array-type
delegate-type
class-type:
type-name
object
string
interface-type:
type-name
array-type:
non-array-type   rank-specifiers
non-array-type:
type
rank-specifiers:
rank-specifier
rank-specifiers   rank-specifier
rank-specifier:
[   dim-separatorsopt   ]
dim-separators:
,
dim-separators   ,
delegate-type:
type-name
一个引用数值是对于一个那种类型实例的引用,后面称为对象。特殊数值null是所有引用类型都适用的,并且表示缺乏实例。
4.2.1 类类型
类类型定义了一个包括数据成员(常数、域和事件)、函数成员(方法、属性、索引、操作符、构造函数和析构函数)和嵌套类型。类类型支持继承,因为这种机制派生的类可以对基类进行扩展和特殊化。使用对象创建表达式(§7.5.10.1)来创建类类型的实例。
类类型将在§10讨论。
4.2.2 对象类型
object(对象)类型是所有其它类型的最基本类。C#中的任何一个类型都是直接或间接地从object类类型派生的。
object关键字是预定义System.Object类的简化的别名。使用object跟使用System.Object是相同的,反之亦然。
4.2.3 字符串类型
字符串类型是直接从object派生的包装好的类类型。字符串类的实例表示统一的字符编码标准字符串。
字符串类型的数据写成一串文字 (§错误!未找到引用源。)。
string关键字是预定义System.String类的简化的别名。使用string跟使用System.String是相同的,反之亦然。
4.2.4 接口类型
一个接口定义了一个协定。一个实现了接口的类或结构必须遵守它的协定。一个接口也许会从多个基本接口继承,而一个类或结构可以实现多个接口。
接口类型在§13中祥述。
4.2.5 数组类型
数组是一种数据结构,它包含了通过计算索引访问的变量成员。包含于数组中的变量,也称为数组的元素,都有相同的类型,而这个类型被称为数组的类型。
数组类型在§错误!未找到引用源。中祥述。
4.2.6 代表类型
代表是一种指向一个静态方法或一个对象的对象实例和对象方法的数据结构。
在C或C++中与代表相同的是函数指针,但是功能指针只能指向静态函数,而代表可以指向静态和实例方法。在后面,代表不仅存储对于方法的入口点的引用,同时也存储对调用方法的对象实例的引用。
代表类型在§15中祥述。
4.3 包装和解包
包装(boxing)和解包(unboxing)是C#类型系统中重要的概念。它通过允许任何数值类型的数据被转换为任何形式类型的对象提供了数值类型和引用类型间的紧密联系。包装和解包使得对在其中任何类型都可以最终被看作对象的类型系统的统一的观察变为可能。
4.3.1 包装转换
包装转换允许任何数值类型可以隐式地转换为object类型或任何由数值类型实现的接口类型。包装一个数值类型的数据包括对对象实例的定位和把数值类型数据拷贝到那个实例中。
包装数值类型的数据的实际过程,可以通过想像一个对那种类型的包装类的实际例子来解释。对于数值类型T,包装类要按下面定义:
class T_Box
{
 T value;
 T_Box(T t) {
  value = t;
 }
}
对于类型T的数值v的包装现在由执行表达式T_Box(v)来代替,并且返回类型为object的结果实例。这样,语句
int i = 123;
object box = i;
从概念上符合
int i = 123;
object box = new int_Box(i);
如上面的T_Box和int_Box的包装类型实际不存在,而被包装数据的动态类型实际上并不是一个类类型。作为替代,类型T的一个被包装的数据有动态类型T,而使用is操作符的动态类型检查可以很方便地引用T。例如,
int i = 123;
object box = i;
if (box is int) {
 Console.Write("Box contains an int");
}
将在控制台输出字符串 “Box contains an int”。
包装转换隐式地把被包装的数据进行了备份。这与从引用类型到object类型的转换不同,在那里数据一直引用相同的实例,并被认为几乎不从类型object派生。例如,给出声明
struct Point
{
 public int x, y;
 public Point(int x, int y) {
  this.x = x;
  this.y = y;
 }
}
下面的语句
Point p = new Point(10, 10);
object box = p;
p.x = 20;
Console.Write(((Point)box).x);
因为在发生把p赋值给box的隐含包装操作时,p被拷贝,所以将在控制台上输出数值10。如果Point被声明为一个类,因为p和box将引用相同的实例,就会输出20。
4.3.2 解包转换
解包转换允许任何object类型或从任何由数值类型实现的接口类型,可以显式地转换为任何数值类型。一个解包操作由几部分动作组成,首先检查object实例是一个所给数值类型的被包装数据,然后把数值从实例中拷贝出来。
参考前面章节描述的假象的包装类型,从对象box到数值类型T的解包转换包括执行表达式((T_Box)box).value。这样,语句
object box = 123;
int i = (int)box;
从概念上符合
object box = new int_Box(123);
int i = ((int_Box)box).value;
对于为了在运行时提供数值类型的解包转换,源变量数据必须是一个指向一个早先对那个数值类型数据打包创建的对象。如果源变量是null或引用一个不相关的对象,就会抛出一个InvalidCastException错误。 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值