C# 里到底有哪些数据类型?它们之间有什么区别?什么时候该用 int,什么时候该用 long?这篇就来聊聊 C# 的基础数据类型体系,帮你理清分类、存储方式和使用场景。
- 值类型 vs 引用类型:核心区别是什么,如何选择
- 常用数据类型一览:整数、浮点、布尔、字符、字符串、对象
- 类型转换与注意事项:隐式转换、显式转换、装箱拆箱的陷阱
一、C# 数据类型体系
1.1 类型分类概述
C# 的类型系统分为三大类:
| 类型分类 | 存储位置 | 示例 | 特点 |
|---|---|---|---|
| 值类型 | 栈(Stack) | int, double, bool, struct, enum | 直接包含数据,复制时拷贝整个值 |
| 引用类型 | 堆(Heap) | string, class, array, delegate, object | 存储对象的引用,复制时拷贝引用(地址) |
| 指针类型 | 非托管内存 | int* | 仅用于 unsafe 上下文,一般开发不常用 |
划重点: 值类型变量直接保存数据,引用类型变量保存对象的地址(引用)。赋值时值类型是值拷贝,引用类型是引用拷贝(两个变量指向同一对象)。
1.2 值类型详解
值类型包括:简单类型、枚举类型、结构体类型、可空值类型。
常用简单值类型
| 类型 | 关键字 | 字节 | 范围 |
|---|---|---|---|
| 整型 | sbyte | 1 | -128 ~ 127 |
| 无符号 | byte | 1 | 0 ~ 255 |
| 短整型 | short | 2 | -32,768 ~ 32,767 |
| 无符号短整型 | ushort | 2 | 0 ~ 65,535 |
| 整型(默认) | int | 4 | -2.1e9 ~ 2.1e9 |
| 无符号整型 | uint | 4 | 0 ~ 4.2e9 |
| 长整型 | long | 8 | -9.2e18 ~ 9.2e18 |
| 无符号长整型 | ulong | 8 | 0 ~ 1.8e19 |
| 浮点型 | float | 4 | ±1.5e-45 ~ ±3.4e38(7 位精度) |
| 双精度 | double | 8 | ±5.0e-324 ~ ±1.7e308(15-16 位精度) |
| 十进制 | decimal | 16 | ±1.0e-28 ~ ±7.9e28(28-29 位精度,货币计算) |
| 布尔 | bool | 1 | true / false |
| 字符 | char | 2 | 单个 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);
}
代码解析:
- struct 关键字:声明一个值类型,实例化时分配在栈上(或作为其他类型的字段内联存储)。
- 构造函数:struct 中允许定义带参构造函数,但不能定义无参构造函数(C# 10 之前)。
- 字段:可以直接公开字段(但不推荐,通常用属性)。值类型的字段会直接存储值。
1.3 引用类型详解
引用类型包括:类、字符串、数组、接口、委托、对象。
| 类型 | 关键字 | 说明 |
|---|---|---|
| 对象 | object | 所有类型的基类,可引用任何类型 |
| 字符串 | string | 不可变的 Unicode 字符串序列 |
| 动态 | dynamic | 运行时类型检查,跳过编译时类型检查 |
| 类 | class | 引用类型,支持继承、多态 |
字符串细节
string s1 = "Hello";
string s2 = s1;
s2 = "World";
Console.WriteLine(s1); // 输出 "Hello"(不变)
代码解析:
- string 不可变性:每次修改字符串(如
s2 = "World")都会在堆上创建新对象,原对象不变。 - 引用拷贝:
s2 = s1 只是拷贝引用,两个变量指向同一对象。但后续s2 指向新对象后,s1不受影响。 - 性能提示:大量字符串拼接应使用
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; // 拆箱:从堆对象拷贝回栈
代码解析:
- 装箱:会在堆上分配一个
object对象,将值类型数据拷贝进去,性能开销较大(分配+拷贝)。 - 拆箱:检查对象类型是否匹配,然后拷贝回值类型,同样有性能开销。
- 避免策略:使用泛型(
List<T>)代替ArrayList,减少装箱拆箱。
划重点: 在性能敏感代码中,装箱拆箱是隐形杀手。多用泛型、避免把值类型频繁赋值给 object。
三、选择数据类型的指南
| 场景 | 推荐类型 | 原因 |
|---|---|---|
| 普通整数计数 | int | 平衡大小和性能,默认整数类型 |
| 索引/遍历数组 | int 或 long | 数组长度通常不超 int 范围 |
| 数据库 ID(可能超大) | long | 分布式系统 ID 可能超过 21 亿 |
| 浮点运算(图形/物理) | float | 精度要求不高,节省带宽 |
| 科学计算/精度要求高 | double | 默认浮点类型 |
| 货币/财务计算 | decimal | 精确到小数点后 28 位 |
| 短文本 | string | C# 标准字符串 |
| 不可变值对象 | struct | 小体积、免 GC 分配 |
| 可能需要 null | 可空值类型(int?)或 string? | 表示“无值”语义更清晰 |
最后
数据类型看似基础,但很多运行时 Bug 都源于对类型转换、装箱拆箱、引用拷贝的理解不够透彻。写代码时多问自己一句:这个变量是值类型还是引用类型?赋值后另一个变量会受影响吗?养成习惯,能避开一堆坑。
对于新手,遇到奇怪的“对象被修改了”问题,十有八九是引用类型赋值导致共享了同一个对象。记得用 Clone() 或深拷贝来隔离。