Skip to main content

基本数据类型

在C#中,数据类型主要可以分为以下几类:值类型(Value Types)、引用类型(Reference Types)和指针类型(Pointer Types)(尽管在安全的代码环境中指针的使用是受限制的)。值类型包括简单类型、结构类型、枚举类型等,而引用类型包括类、接口、数组和委托等。

数据类型

1. 值类型(Value Types)

  • 整数类型

  • sbyte: 有符号8位整数,取值范围 -128 到 127

  • byte: 无符号8位整数,取值范围 0 到 255

  • short: 有符号16位整数,取值范围 -32,768 到 32,767

  • ushort: 无符号16位整数,取值范围 0 到 65,535

  • int: 有符号32位整数,取值范围 -2,147,483,648 到 2,147,483,647

  • uint: 无符号32位整数,取值范围 0 到 4,294,967,295

  • long: 有符号64位整数,取值范围 -9,223,372,036,854,775,808 到 9,223,372,036,854,775,807

  • ulong: 无符号64位整数,取值范围 0 到 18,446,744,073,709,551,615

  • 浮点类型

  • float: 32位单精度浮点数

  • double: 64位双精度浮点数

  • decimal: 128位高精度浮点数,适用于金融和货币计算

  • 其他值类型

  • bool: 布尔值,true 或 false

  • char: 16位Unicode字符

  • struct: 用户定义的结构类型,可以包含多个不同类型的数据成员

  • enum: 枚举类型,用于定义一组命名的整数值

值类型的特点

  1. 直接包含数据:值类型的变量直接存储其数据值,而不是指向数据的引用。
  2. 栈内存分配:值类型变量在栈内存中分配空间,它们的生命周期与包含它们的方法或代码块的执行时间相关。
  3. 数据复制:当值类型变量被赋值给另一个变量时,会创建该值的一个副本。两个变量将具有相同的值,但存储在不同的内存位置。
  4. 不可变性:大多数值类型是不可变的,一旦赋值就不能更改(例如,int、double等)。但是,结构体(struct)和枚举(enum)可以包含可变字段。
  5. 自动内存管理:值类型的内存管理由C#运行时自动处理。当值类型变量超出其作用域时,其内存会自动释放。

2. 引用类型(Reference Types)

  • class: 用户定义的类类型
  • interface: 接口类型,定义一组方法、属性、事件或索引器的合约
  • array: 数组类型,用于存储多个同类型元素的集合
  • delegate: 委托类型,表示引用方法的对象,类似于C++中的函数指针
  • string: 字符串类型,代表一系列Unicode字符的不可变文本

引用类型的特点

  1. 存储引用:引用类型变量存储的是一个引用(或指针),指向在堆内存中存储的实际对象。
  2. 堆内存分配:实际对象在堆内存中分配空间,而引用类型变量在栈内存中分配空间,仅存储对堆上对象的引用。
  3. 数据复制:当引用类型变量被赋值给另一个变量时,复制的是引用本身,而不是对象本身。这意味着两个变量现在指向同一个对象。
  4. 可变性:引用类型通常是可变的,可以通过修改其属性或字段来改变其状态。
  5. 垃圾回收:引用类型的内存管理由C#的垃圾回收器(Garbage Collector)负责。当对象不再被任何引用类型变量引用时,垃圾回收器会释放其占用的内存。

3. 指针类型(Pointer Types)(在不安全上下文中使用)

  • 例如:int*, float*, void* 等,用于直接操作内存地址。在C#中,指针的使用默认是不安全的,并且需要特别小心以避免内存损坏或数据丢失。要在C#中使用指针,你需要在项目设置中允许不安全代码,并在代码中使用unsafe关键字标记包含指针操作的块。

需要注意的是,值类型不能直接继承自引用类型,反之亦然。引用类型继承自引用类型(例如,类可以继承自另一个类或实现一个或多个接口)。

注意,结构体不能继承自类。因为结构体是值类型,而类是引用类型,但是可以实现接口

例外: C++中,结构体是可以被继承的。

基本类型占用的字节数

在C#中,基本类型(也称为值类型)的大小是固定的,并且这些大小是由.NET运行时规定的。以下是一些C#中基本类型及其通常占用的字节数(在32位和64位系统中通常是相同的,但请注意,这可能会因系统和JIT编译器的具体实现而略有不同,但以下给出的是最常见的情况):

  • bool / System.Boolean: 不一定直接对应一个字节,因为它可能会受到内存对齐的影响,但通常可以认为它至少占用1字节的存储空间。然而,在数组中,每个bool通常占用1字节。
  • byte / System.Byte: 1 字节
  • sbyte / System.SByte: 1 字节
  • char / System.Char: 2 字节 (UTF-16 编码)
  • short / System.Int16: 2 字节
  • ushort / System.UInt16: 2 字节
  • int / System.Int32: 4 字节
  • uint / System.UInt32: 4 字节
  • long / System.Int64: 8 字节
  • ulong / System.UInt64: 8 字节
  • float / System.Single: 4 字节 (IEEE 754 单精度浮点数)
  • double / System.Double: 8 字节 (IEEE 754 双精度浮点数)
  • decimal / System.Decimal: 16 字节 (128位数据类型,用于高精度的十进制计算)

请注意,这些大小通常不会因运行程序的操作系统是32位还是64位而改变,它们是固定的。然而,对于引用类型(如类、数组和委托)和某些结构(如果它们包含引用类型的字段或自动实现的属性),它们的大小可能会因系统架构而异,并且可能受到内存对齐和填充的影响。

此外,当在结构中嵌套其他结构或基本类型时,内存对齐可能会导致整个结构占用的空间比其字段单独占用的空间之和要大。这是为了提高内存访问的效率。因此,在实际应用中,通过Marshal.SizeOf方法或unsafe代码块中的sizeof操作符来确定特定类型或结构在内存中的确切大小通常是一个好主意。

数据类型转换

在C#中,数据类型之间可以通过显式转换(Casting)和隐式转换(Implicit Conversion)进行转换。隐式转换通常发生在源类型和目标类型之间存在兼容关系时,比如从较小的整数类型到较大的整数类型,或者从派生类到基类类型的引用转换。显式转换(或称强制转换)则发生在源类型和目标类型之间没有隐式转换的情况下,比如从较大的整数类型到较小的整数类型,或者从基类类型到派生类类型的引用转换。

  • 显式转换(Casting):需要使用强制转换操作符 (T),其中 T 是目标类型的名称。例如,将 double 类型显式转换为 int 类型:int x = (int)someDouble;。这可能会导致数据丢失或截断。
  • 隐式转换(Implicit Conversion):由编译器自动执行,无需使用特殊语法。例如,将 int 类型隐式转换为 double 类型:double y = someInt;。这通常是安全的,因为不会丢失数据。
  • 用户自定义转换:你可以在类或结构中定义显式和隐式的转换操作符来自定义两个类型之间的转换行为。这通常用于创建更加符合领域逻辑的转换。例如,你可以定义一个从摄氏度到华氏度的显式转换操作符。
  • 转换方法:除了使用转换操作符外,你还可以使用 Convert 类中的静态方法来执行不同类型的转换。这些方法提供了更好的错误处理和格式化选项。例如,int i = Convert.ToInt32(someString); 将尝试将字符串转换为整数类型。如果转换失败,它会抛出一个异常。你还可以使用 TryParse 方法来尝试转换并返回一个布尔值来指示转换是否成功,而不是抛出异常。例如,int.TryParse(someString, out int result);。如果转换成功,result 将包含转换后的值;否则,它将被设置为零(对于 int 类型而言)。这种方法在处理用户输入或不确定的数据时非常有用。