Visual C# 语言概念--数据类型(C# 与 Java)
本主题讨论 Java 和 C# 在数据的表示、分配和垃圾回收等方面的一些主要相同点和不同点。
复合数据类型
在 Java 和 C# 中,类作为有字段、方法和事件的复合数据类型这一概念是相似的。(有关类继承的概念在名为继承与派生类(C# 与 Java)的主题中单独讨论。)C# 引入结构的概念,结构是一种堆栈分配的复合数据类型,它不支持继承。在其他许多方面,结构与类非常相似。结构提供一种将相关字段和方法组合在一起的轻量方法,以便在紧凑循环和其他性能关键的方案中使用。
C# 使您能够创建一个在对类的实例进行垃圾回收前调用的析构函数方法。在 Java 中,可以使用 finalize 方法来包含代码,用于在将对象作为垃圾回收前清理资源。在 C# 中,此功能由类析构函数执行。析构函数就像是没有参数和前面不带颚化符 (~) 的构造函数。
内置数据类型
C# 提供 Java 中可用的所有数据类型,并增加了对无符号数字和新的 128 位高精度浮点类型的支持。
核心类库为 Java 中的每个基元数据类型提供了一个包装类,此包装类将基元数据类型表示为 Java 对象。例如,Int32 类包装 int 数据类型,Double 类包装 double 数据类型。
另一方面,C# 中的所有基元数据类型都是 System 命名空间中的对象。对于每个数据类型,提供了一个简称(或别名)。例如,int 是 System.Int32 的简称,而 double 是 System.Double 的简写。
下表提供了 C# 数据类型列表及其别名。如表所示,前八个数据类型对应于 Java 中可用的基元类型。但请注意,Java 的 boolean 在 C# 中称为 bool。
简称 | .NET 类 | 类型 | 宽度 | 范围(位) |
---|---|---|---|---|
byte | 无符号整数 | 8 | 0 到 255 | |
sbyte | 有符号整数 | 8 | -128 到 127 | |
int | Int32 | 有符号整数 | 32 | -2,147,483,648 到 2,147,483,647 |
uint | 无符号整数 | 32 | 0 到 4294967295 | |
short | 有符号整数 | 16 | -32,768 到 32,767 | |
ushort | 无符号整数 | 16 | 0 到 65535 | |
long | 有符号整数 | 64 | -922337203685477508 到 922337203685477507 | |
ulong | 无符号整数 | 64 | 0 到 18446744073709551615 | |
float | 单精度浮点型 | 32 | -3.402823e38 至 3.402823e38 | |
double | Double | 双精度浮点型 | 64 | -1.79769313486232e308 至 1.79769313486232e308 |
char | 单 Unicode 字符 | 16 | 文本中使用的 Unicode 符号 | |
bool | 逻辑布尔值类型 | 8 | True 或 False | |
object | 所有其他类型的基类型 |
|
| |
string | 字符序列 |
|
| |
decimal | 精确小数类型或整型,可以表示带有 29 个有效位的十进制数 | 128 | ±1.0 × 10e−28 至 ±7.9 × 10e28 |
因为 C# 将所有基元数据类型当作对象表示,所以可以在基元数据类型上调用对象方法。例如:
static void Main()
{
int i = 10;
object o = i;
System.Console.WriteLine(o.ToString());
}
借助自动装箱和取消装箱完成此操作。有关更多信息,请参见装箱和取消装箱(C# 编程指南)。
常数
Java 和 C# 均能够声明这样一个变量:它的值在编译时指定,在运行时不能更改。Java 使用 final 字段修饰符声明此类变量,而 C# 则使用 const 关键字。除了 const 以外,C# 还提供 readonly 关键字以声明可以在运行时进行一次赋值(在声明语句中或在构造函数中)的变量。初始化以后,readonly 变量的值不能更改。当已单独编译的模块需要共享版本号等数据时,可以使用 readonly 变量。如果模块 A 更新了,并使用一个新的版本号进行了重新编译,则模块 B 可以用这个新的常数值进行初始化,而无需重新编译。
枚举
枚举用于对已命名常数进行分组,与在 C 和 C++ 中的用法相似,但不可用于 Java。下面的示例定义一个简单的 Color 枚举。
public enum Color
{
Green, //defaults to 0
Orange, //defaults to 1
Red, //defaults to 2
Blue //defaults to 3
}
也可以将整数值分配给枚举,如下面的枚举声明所示:
public enum Color2
{
Green = 10,
Orange = 20,
Red = 30,
Blue = 40
}
下面的代码示例调用 Enum 类型的 GetNames 方法来显示枚举的可用常数。然后,将值分配给枚举,并显示此值。
class TestEnums
{
static void Main()
{
System.Console.WriteLine("Possible color choices: ");
//Enum.GetNames returns a string array of named constants for the enum.
foreach(string s in System.Enum.GetNames(typeof(Color)))
{
System.Console.WriteLine(s);
}
Color favorite = Color.Blue;
System.Console.WriteLine("Favorite Color is {0}", favorite);
System.Console.WriteLine("Favorite Color value is {0}", (int) favorite);
}
}
输出
Possible color choices:
Green
Orange
Red
Blue
Favorite Color is Blue
Favorite Color value is 3
字符串
Java 和 C# 中的字符串类型的行为相似,只有细微的差异。两种字符串类型都是不可变的,意味着一旦创建了字符串,字符串的值就无法更改。两个实例中的方法看上去修改了 字符串的实际内容,实际上创建并返回了一个新字符串,而原始字符串保持不变。C# 和 Java 中比较字符串值的过程有所不同。若要在 Java 中比较字符串值,则开发人员需要在字符串类型上调用 equals 方法,原因是默认情况下 == 运算符会比较引用类型。在 C# 中,开发人员可以直接使用 == 或 != 运算符来比较字符串值。尽管在 C# 中字符串是引用类型,但在默认情况下 == 和 != 运算符将比较字符串值而不是引用。
和在 Java 中一样,C# 开发人员不应使用字符串类型来串连字符串,以避免在每次串连字符串时创建新字符串类所产生的开销。相反,开发人员可以使用 StringBuilder 类,它与 Java 的 StringBuffer 类在功能上等效。
字符串文本
C# 能够避免在字符串常数中使用转义序列,如用于制表符的 "\t" 或用于反斜杠字符的 "\"。若要达到此目的,只需在分配字符串值之前使用 @ 符号声明原义字符串。下面的示例演示如何使用转义字符以及如何分配字符串文本:
static void Main()
{
//Using escaped characters:
string path1 = "\\\\FileShare\\Directory\\file.txt";
System.Console.WriteLine(path1);
//Using String Literals:
string path2 = @"\\FileShare\Directory\file.txt";
System.Console.WriteLine(path2);
}
转换和强制转换
Java 和 C# 对数据类型的自动转换和强制转换遵循相似的规则。
和 Java 一样,C# 支持隐式和显式类型转换。如果是扩大转换,则为隐式转换。例如,下面从 int 到 long 的转换为隐式转换,与在 Java 中相同:
int int1 = 5;
long long1 = int1; //implicit conversion
下面是 .NET Framework 数据类型之间的隐式转换列表:
源类型 | 目标类型 |
---|---|
Byte | short、ushort、int、uint、long、ulong、float、double 或 decimal |
Sbyte | short、int、long、float、double 或 decimal |
Int | long、float、double 或 decimal |
Uint | long、ulong、float、double 或 decimal |
Short | int、long、float、double 或 decimal |
Ushort | int、uint、long、ulong、float、double 或 decimal |
Long | float、double 或 decimal |
Ulong | float、double 或 decimal |
Float | double |
Char | ushort、int、uint、long、ulong、float、double 或 decimal |
使用与 Java 相同的语法强制转换要显式转换的表达式:
long long2 = 5483;
int int2 = (int)long2; //explicit conversion
下表列出了显式转换。
源类型 | 目标类型 |
---|---|
Byte | sbyte 或 char |
Sbyte | byte、ushort、uint、ulong 或 char |
Int | sbyte、byte、short、ushort、uint、ulong 或 char |
Uint | sbyte、byte、short、ushort、int 或 char |
Short | sbyte、byte、ushort、uint、ulong 或 char |
Ushort | sbyte、byte、short 或 char |
Long | sbyte、byte、short、ushort、int、uint、ulong 或 char |
Ulong | sbyte、byte、short、ushort、int、uint、long 或 char |
Float | sbyte、byte、short、ushort、int、uint、long、ulong、char 或 decimal |
Double | sbyte、byte、short、ushort、int、uint、long、ulong、char、float 或 decimal |
Char | sbyte、byte 或 short |
Decimal | sbyte、byte、short、ushort、int、uint、long、ulong、char、float 或 double |
值与引用类型
C# 支持两种变量类型:
-
值类型
这些是内置基元数据类型(如 char、int 和 float)以及用 struct 声明的用户定义类型。
-
引用类型
从基元类型构造的类和其他复杂的数据类型。这种类型的变量不包含类型的实例而仅包含对实例的引用。
如果创建两个值类型变量 i 和 j(如下所示),则 i 和 j 完全相互独立:
int i = 10;
int j = 20;
为它们指定了独立的内存位置:
如果更改这两个变量中其中一个的值,另一个变量当然不受影响。例如,如果具有如下形式的表达式,则这两个变量之间仍然没有关联:
int k = i;
也就是说,如果更改 i 的值,则 k 将保留赋值时 i 具有的值。
i = 30;
System.Console.WriteLine(i.ToString()); // 30
System.Console.WriteLine(k.ToString()); // 10
但是,引用类型的行为则不同。例如,可以声明如下所示的两个变量:
Employee ee1 = new Employee();
Employee ee2 = ee1;
现在,因为类在 C# 中为引用类型,所以 ee1 被视为对 Employee 的引用。在前面的两行中,第一行在内存中创建 Employee 的一个实例,并设置 ee1 以引用该实例。因此,将 ee2 设置为等于 ee1 时,它包含了对内存中的类的重复引用。如果现在更改 ee2 上的属性,则 ee1 上的属性将反映这些更改,因为两者都指向内存中的相同对象,如下所示:
装箱和取消装箱
将值类型转换为引用类型的过程称为装箱。反之,将引用类型转换为值类型的过程则称为取消装箱。下面的代码说明了这一点:
int i = 123; // a value type
object o = i; // boxing
int j = (int)o; // unboxing
Java 要求手动执行这种转换。可以通过构造这种对象或装箱,将基元数据类型转换为包装类的对象。同样地,可以通过在这种对象上调用合适的方法或取消装箱,从包装类对象中提取基元数据类型的值。有关装箱和取消装箱的更多信息,请参见装箱转换(C# 编程指南)和取消装箱转换(C# 编程指南)。
请参见
参考
数据类型(C# 编程指南)概念
C# 编程指南其他资源
Visual C#C# 编程语言(针对 Java 开发人员)