C#基础数据类型

0 阅读1分钟

C# 里到底有哪些数据类型?它们之间有什么区别?什么时候该用 int​,什么时候该用 long?这篇就来聊聊 C# 的基础数据类型体系,帮你理清分类、存储方式和使用场景。

  1. 值类型 vs 引用类型:核心区别是什么,如何选择
  2. 常用数据类型一览:整数、浮点、布尔、字符、字符串、对象
  3. 类型转换与注意事项:隐式转换、显式转换、装箱拆箱的陷阱

一、C# 数据类型体系

1.1 类型分类概述

C# 的类型系统分为三大类:

类型分类存储位置示例特点
值类型栈(Stack)int​, double​, bool​, struct​, enum直接包含数据,复制时拷贝整个值
引用类型堆(Heap)string​, class​, array​, delegate​, object存储对象的引用,复制时拷贝引用(地址)
指针类型非托管内存int*仅用于 unsafe 上下文,一般开发不常用

划重点: 值类型变量直接保存数据,引用类型变量保存对象的地址(引用)。赋值时值类型是值拷贝,引用类型是引用拷贝(两个变量指向同一对象)。

1.2 值类型详解

值类型包括:简单类型枚举类型结构体类型可空值类型

常用简单值类型

类型关键字字节范围
整型sbyte1-128 ~ 127
无符号byte10 ~ 255
短整型short2-32,768 ~ 32,767
无符号短整型ushort20 ~ 65,535
整型(默认)int4-2.1e9 ~ 2.1e9
无符号整型uint40 ~ 4.2e9
长整型long8-9.2e18 ~ 9.2e18
无符号长整型ulong80 ~ 1.8e19
浮点型float4±1.5e-45 ~ ±3.4e38(7 位精度)
双精度double8±5.0e-324 ~ ±1.7e308(15-16 位精度)
十进制decimal16±1.0e-28 ~ ±7.9e28(28-29 位精度,货币计算)
布尔bool1true​ / false
字符char2单个 Unicode 字符(U+0000 ~ U+FFFF)

常见坑: float​ 精度只有 7 位,大量累加或金融计算时请用 decimal,否则会出现意想不到的精度丢失。

结构体(struct)

结构体是自定义值类型,适用于表示轻量级数据(如坐标、RGB颜色)。

public struct Point
{
    public int X;
    public int Y;

    public Point(int x, int y) => (X, Y) = (x, y);
}

代码解析:

  1. struct 关键字:声明一个值类型,实例化时分配在栈上(或作为其他类型的字段内联存储)。
  2. 构造函数:struct 中允许定义带参构造函数,但不能定义无参构造函数(C# 10 之前)。
  3. 字段:可以直接公开字段(但不推荐,通常用属性)。值类型的字段会直接存储值。

1.3 引用类型详解

引用类型包括:字符串数组接口委托对象

类型关键字说明
对象object所有类型的基类,可引用任何类型
字符串string不可变的 Unicode 字符串序列
动态dynamic运行时类型检查,跳过编译时类型检查
class引用类型,支持继承、多态

字符串细节

string s1 = "Hello";
string s2 = s1;
s2 = "World";
Console.WriteLine(s1); // 输出 "Hello"(不变)

代码解析:

  1. string 不可变性:每次修改字符串(如 s2 = "World")都会在堆上创建新对象,原对象不变。
  2. 引用拷贝s2 = s1​ 只是拷贝引用,两个变量指向同一对象。但后续 s2​ 指向新对象后,s1 不受影响。
  3. 性能提示:大量字符串拼接应使用 StringBuilder,避免频繁创建新对象。

二、类型转换

2.1 隐式转换

发生条件:从小范围大范围转换,且数值不会有损失。

int i = 10;
long l = i;          // int → long,安全,隐式
float f = i;         // int → float,安全,隐式
double d = f;        // float → double,安全,隐式

2.2 显式转换(强制类型转换)

发生条件:可能丢失精度或范围超限,需要显式告诉编译器。

double d = 123.456;
int i = (int)d;      // 显式转换,小数部分被截断
Console.WriteLine(i); // 输出 123(丢失精度)

long l = 3000000000;
int i2 = (int)l;     // 超出 int 范围,会溢出(checked 模式下抛异常)

常见坑: 使用显式转换时建议用 Convert​ 类或 TryParse​,或者放在 checked 块中捕获溢出。

2.3 装箱与拆箱

装箱(boxing):值类型 → object 或接口类型。拆箱(unboxing):逆过程。

int num = 42;
object obj = num;     // 装箱:在堆上分配对象,拷贝值
int numBack = (int)obj; // 拆箱:从堆对象拷贝回栈

代码解析:

  1. 装箱:会在堆上分配一个 object 对象,将值类型数据拷贝进去,性能开销较大(分配+拷贝)。
  2. 拆箱:检查对象类型是否匹配,然后拷贝回值类型,同样有性能开销。
  3. 避免策略:使用泛型(List<T>​)代替 ArrayList,减少装箱拆箱。

划重点: 在性能敏感代码中,装箱拆箱是隐形杀手。多用泛型、避免把值类型频繁赋值给 object


三、选择数据类型的指南

场景推荐类型原因
普通整数计数int平衡大小和性能,默认整数类型
索引/遍历数组int​ 或 long数组长度通常不超 int 范围
数据库 ID(可能超大)long分布式系统 ID 可能超过 21 亿
浮点运算(图形/物理)float精度要求不高,节省带宽
科学计算/精度要求高double默认浮点类型
货币/财务计算decimal精确到小数点后 28 位
短文本stringC# 标准字符串

| 不可变值对象 | struct​ | 小体积、免 GC 分配 |
| 可能需要 null | 可空值类型(int?​)或 string? | 表示“无值”语义更清晰 |

最后

数据类型看似基础,但很多运行时 Bug 都源于对类型转换、装箱拆箱、引用拷贝的理解不够透彻。写代码时多问自己一句:这个变量是值类型还是引用类型?赋值后另一个变量会受影响吗?养成习惯,能避开一堆坑。

对于新手,遇到奇怪的“对象被修改了”问题,十有八九是引用类型赋值导致共享了同一个对象。记得用 Clone()或深拷贝来隔离。