5.1 编程语言的基元类型
2011-12-23 10:23 iRead 阅读(313) 评论(0) 编辑 收藏 举报某些数据类型如此常用,以至于许多编译器允许代码以简化的语法来操作它们。例如,可以使用以下语法来分配一个整数:
System.Int32 a = new System.Int32();
但你肯定不愿意使用这种语法来声明并初始化一个整数,它实在是太繁琐了。幸运的是,包括C#在内的许多编译器都允许换用如下所示的语法:
int a = 0;
这种语法不仅增强了代码的可读性,而且生成的IL代码与使用System.Int32时生成的IL代码是完全一致的。编译器直接支持的数据类型成为基元类型(primitive type)。基元类型直接映射到Framework类库(FCL)中存在的类型。比如在C#中,int直接映射到System.Int32类型。因此,以下4行代码都能正确编译,并生成完全相同的IL:
int a = 0; //最方便的语法
System.Int32 a = 0; //方便的语法
int a = new int(); //不方便的语法
System32.Int32 a = new System.Int32(); //最不方便的语法
表5-1列出的FCL类型在C#中都有对应的基元类型。对于相容于公共语言规范(CLS)的类型,其他语言将提供类似的基元类型。然而,并不要求语言为非CLS相容的类型提供任何支持。
表5-1 C#基元类型与对应的FCL类型
C#基元类型 | FCL类型 | CLS相容 | 说明 |
sbyte | System.Sbyte | 否 | 有符号8位值 |
byte | System.Byte | 是 | 无符号8位值 |
short | System.Int16 | 是 | 有符号16位值 |
ushort | System.UInt16 | 否 | 无符号16位值 |
int | System.Int32 | 是 | 有符号32位值 |
uint | System.UInt32 | 否 | 无符号32位值 |
long | System.Int64 | 是 | 有符号64位值 |
ulong | System.UInt64 | 否 | 无符号64位值 |
char | System.Char | 是 | 16位Unicode字符(char不像在非托管C++中那样代表一个8位值) |
float | System.Single | 是 | IEEE32位浮点值 |
double | System.Double | 是 | IEEE64位浮点值 |
bool | System.Boolean | 是 | 一个true/false值 |
decimal | System.Decimal | 是 | 一个128位高精度浮点值,常用于不容许舍入误差的金融计算。在128位中,有1位代表值的符号(正负号),有96位代表值本身,并有8位代表一个比例因子。比例因子用作96位整数的除数并指定整数的哪一部分为小数。比例因子隐式地定位数字10的幂,范围从0到28.其余位没有使用 |
string | System.String | 是 | 一个字符数组 |
object | System.Object | 是 | 所有类型的基类型 |
dynamic | System.Object | 是 | 对于CLR,dynamic和object完全一致。然而,C#编译器允许使用一个简单的语法,让dynamic变量参与动态调用。欲知详情,请参见本章最后的5.5节”dynamic基元类型” |
从另一个角度思考,可以想象C#编译器自动假定在所有源代码文件中添加了以下using指令(参考第4章):
using sbyte = System.Sbyte;
using byte = System.Byte;
using short = System.Int16;
using ushort = System.UInt16;
using int = System.Int32;
using uint = System.UInt32;
…
C#语言规范称:”从风格上说,最好使用关键字,而不是使用完整的系统类型名称。”但我并不同意语言规范的说法:我情愿使用FCL类型名称,并完全避免使用基元类型名称。事实上,我希望编译器根本不要提供基元类型名称,强制开发人员使用FCL类型名称。下面是我的理由:
- 许多开发人员都困惑到底应该使用string还是String。由于C#的string(一个关键字)直接映射到System.String(一个FCL类型),所以两者没有区别,都可以使用。类似地,还有一些开发人员说应用程序在32位操作系统上运行时,int代表32位整数;在64位操作系统上运行时,int代表64位整数。这个说法是完全错误的。在C#中,int始终映射到System.Int32,所以不管在什么操作系统上运行,代表的都是32位整数。如果程序员习惯了在代码中使用Int32,像这样的误解也就不会产生了。
- 在C#中,long映射到System.Int64,但在其他编程语言中,long可能映射到Int16或Int32。例如,C++/CLI就将long视为一个Int32。习惯于用一种语言写程序的人在看用另一种语言写的源代码时,很容易错误地理解代码的意图。事实上,大多数语言甚至不将long看作一个关键字,根本不会编译使用了它的代码。
- FCL的许多方法都将类型名称作为方法名的一部分。例如,BinaryReader类型提供的方法包括ReadBoolean, ReadInt32, ReadSingle等;而System.Convert类型提供的方法包括ToBoolean,ToInt32,ToSingle等。以下代码虽然语法没有问题,但包含float的那一行显得颇不自然,无法一下子判断该行的正确性:
BinaryReader br = new BinaryReader(…);
Float val = br.ReadSingle(); //正确,但感觉不自然
Single val = br.ReadSingle(); //正确,而且让人一目了然
- 平时只用C#的许多程序员逐渐忘了还可以用其他语言写面向CLR的代码。因此,”C#主义”逐渐入侵类库代码。例如,Microsoft的FCL几乎是完全用C#写的,FCL团队的开发人员现在向库中引入像Array的GetLongLength这样的方法。该方法返回一个Int64值。这种值在C#中是long,但在其他语言中不是(C++/CLI)。另一个例子是System.Linq.Enumerable的LongCount方法。
考虑到所有这些原因,本书将坚持使用FCL类型名称。
在许多编程语言中,以下代码都能正确编译并运行:
Int32 i = 5; //一个32位值
Int64 l = i; //隐式转型为一个64位值
然而,根据第4章对类型转换的讨论,你或许会认为上述代码无法编译通过。毕竟,System.Int32和System.Int64是不同的类型,相互不存在派生关系。但实际上,你会欣喜地发现C#编译器正确编译了上述代码,运行起来也没有任何问题。这是为什么呢?原因是C#编译器非常熟悉基元类型,并会在编译代码时应用它自己的特殊规则。换言之,编译器能识别常见的编程模式,并生成必要的IL,使写好的代码能够像预期的那样工作。具体地说,C#编译器支持与类型转换、文本常量以及操作符有关的模式。在后面的几个例子中,我们将对它们进行演示。
首先,编译器能执行基元类型之间的隐式或显示转型,例如:
Int32 i=5; //从Int32隐式转型为Int32
Int64 l = i; //从Int32隐式转型为Int64
Single s = i; //从Int32隐式转型为Single
Byte b =(Byte)i; //从Int32显示转型为Byte
Int16 v=(Int16) s; //从Single显示转型为Int16
只有在转换“安全”的时候,C#才允许隐式转型。所谓“安全”,是指不会发生数据丢失的情况,比如从Int32转换为Int64。然而,如果一次转换有可能是不安全的,C#就要求进行显式转型。对于数值类型,“不安全”意味着在转换之后,有可能丢失精度或者数量级。例如,从Int32转换为Byte要求显示转型,因为大的Int32数字可能丢失精度;从Single转换为Int16也要求显式转型,因为Single能表示比Int16更大数量级的数字。
注意,不同的编译器可能生成不同的代码来处理这些转型操作。例如,将值为6.8的一个Single转型为一个Int32时,有的编译器可能生成代码,对其进行截断处理(向下取整),最终将6放到Int32中;其他编译器则可能将结果向上取整为7。顺便说一句,C#总是对结果进行截断处理,而不进行向上取整。要了解C#对基元类型进行转型时的具体规则,请参见C#语言规范的”Conversions”一节。
除了转型,基本类型还能写成文本常量(literal)。文本常量可被看成是类型本身的一个实例,所以可以像下面为一个实例(123和456)调用实例方法:
Console.WriteLine(123.ToString() + 456.ToString()); //”123456”
此外,如果一个表达式由文本常量构成,编译器能在编译时完成表达式的求值,从而增强应用程序的性能:
Boolean found = false; //生成的代码将found设为0
Int32 x = 100+20+3; //生成的代码将x设为123
String s = “a “+”bc”; //生成的代码将s设为”a bc”
最后,编译器知道如何以及以什么顺序解析代码中出现的操作符(比如+,-,*,/,%,&,^,|,==,!=,>,<,>=,<=,<<,>>,~,!,++,--等):
Int32 y = 100; //赋值操作符
Int32 y = x+23; //加和赋值操作符
Boolean lessThanFifty = (y<50); //小于和赋值操作符