1. 数据类型概述
在 C# 中,数据类型决定了变量可以存储的数据种类和大小,以及可以对该数据执行的操作。C# 中的数据类型分为两大类:值类型和引用类型。
2. 值类型
值类型直接存储数据值,当将一个值类型变量赋值给另一个值类型变量时,会复制其值。值类型包括简单类型、枚举类型和结构类型。
2.1 简单类型
| 类型 | 描述 | 大小 | 范围 | 默认值 |
|---|---|---|---|---|
| sbyte | 有符号字节 | 1 字节 | -128 到 127 | 0 |
| byte | 无符号字节 | 1 字节 | 0 到 255 | 0 |
| short | 有符号短整型 | 2 字节 | -32,768 到 32,767 | 0 |
| ushort | 无符号短整型 | 2 字节 | 0 到 65,535 | 0 |
| int | 有符号整型 | 4 字节 | -2,147,483,648 到 2,147,483,647 | 0 |
| uint | 无符号整型 | 4 字节 | 0 到 4,294,967,295 | 0 |
| long | 有符号长整型 | 8 字节 | -9,223,372,036,854,775,808 到 9,223,372,036,854,775,807 | 0 |
| ulong | 无符号长整型 | 8 字节 | 0 到 18,446,744,073,709,551,615 | 0 |
| float | 单精度浮点数 | 4 字节 | ±3.40282347E+38 | 0.0f |
| double | 双精度浮点数 | 8 字节 | ±1.7976931348623157E+308 | 0.0d |
| decimal | 十进制浮点数 | 16 字节 | ±1.0 × 10^-28 到 ±7.9 × 10^28 | 0.0m |
| char | 字符 | 2 字节 | 0 到 65,535 | \0 |
| bool | 布尔值 | 1 字节 | true 或 false | false |
2.2 枚举类型
枚举类型是一种值类型,用于定义一组命名的常量。
// 定义枚举类型
enum Weekday
{
Monday,
Tuesday,
Wednesday,
Thursday,
Friday,
Saturday,
Sunday
}
// 使用枚举类型
Weekday today = Weekday.Monday;
Console.WriteLine(today); // 输出: Monday
Console.WriteLine((int)today); // 输出: 0(默认从 0 开始)
2.3 结构类型
结构类型是一种用户定义的值类型,可以包含字段、属性、方法等。
// 定义结构类型
struct Point
{
public int X;
public int Y;
public Point(int x, int y)
{
X = x;
Y = y;
}
public void Display()
{
Console.WriteLine($"({X}, {Y})");
}
}
// 使用结构类型
Point p1 = new Point(10, 20);
Point p2 = p1; // 复制值
p2.X = 30;
p1.Display(); // 输出: (10, 20)
p2.Display(); // 输出: (30, 20)
3. 引用类型
引用类型存储对数据的引用(内存地址),当将一个引用类型变量赋值给另一个引用类型变量时,它们指向同一个内存地址。引用类型包括类、接口、数组、委托和字符串。
3.1 类
类是引用类型的基础,用于定义对象的属性和行为。
// 定义类
class Person
{
public string Name { get; set; }
public int Age { get; set; }
public Person(string name, int age)
{
Name = name;
Age = age;
}
public void Display()
{
Console.WriteLine($"Name: {Name}, Age: {Age}");
}
}
// 使用类
Person p1 = new Person("张三", 25);
Person p2 = p1; // 引用同一个对象
p2.Name = "李四";
p1.Display(); // 输出: Name: 李四, Age: 25
p2.Display(); // 输出: Name: 李四, Age: 25
3.2 接口
接口定义了一组方法签名,类可以实现这些方法。
// 定义接口
interface IAnimal
{
void Eat();
void Sleep();
}
// 实现接口
class Dog : IAnimal
{
public void Eat()
{
Console.WriteLine("狗在吃东西");
}
public void Sleep()
{
Console.WriteLine("狗在睡觉");
}
}
// 使用接口
IAnimal animal = new Dog();
animal.Eat(); // 输出: 狗在吃东西
animal.Sleep(); // 输出: 狗在睡觉
3.3 数组
数组是一种引用类型,用于存储相同类型的多个元素。
// 创建数组
int[] numbers = new int[5] { 1, 2, 3, 4, 5 };
int[] numbers2 = numbers; // 引用同一个数组
numbers2[0] = 100;
Console.WriteLine(numbers[0]); // 输出: 100
3.4 委托
委托是一种引用类型,用于存储对方法的引用。
// 定义委托
delegate int Calculator(int a, int b);
// 定义方法
static int Add(int a, int b)
{
return a + b;
}
static int Subtract(int a, int b)
{
return a - b;
}
// 使用委托
Calculator calc = Add;
Console.WriteLine(calc(5, 3)); // 输出: 8
calc = Subtract;
Console.WriteLine(calc(5, 3)); // 输出: 2
3.5 字符串
字符串是一种特殊的引用类型,它是不可变的。
string s1 = "Hello";
string s2 = s1; // 引用同一个字符串
// 字符串是不可变的,修改时会创建新的字符串
s2 = s2 + " World";
Console.WriteLine(s1); // 输出: Hello
Console.WriteLine(s2); // 输出: Hello World
4. 值类型与引用类型的区别
4.1 存储方式
- 值类型:存储在栈上,直接存储数据值
- 引用类型:存储在堆上,栈上存储的是对堆中数据的引用
4.2 赋值行为
- 值类型:赋值时复制整个值
- 引用类型:赋值时复制引用,两个变量指向同一个对象
4.3 默认值
- 值类型:有默认值(如 int 默认值为 0,bool 默认值为 false)
- 引用类型:默认值为 null(表示不指向任何对象)
4.4 内存管理
- 值类型:超出作用域后自动释放内存
- 引用类型:由垃圾回收器负责回收内存
5. 类型转换
在 C# 中,类型转换分为隐式转换和显式转换。
5.1 隐式转换
隐式转换是自动进行的,不需要显式指定。通常发生在从小范围类型转换到大范围类型时。
int i = 100;
double d = i; // 隐式转换,从 int 到 double
Console.WriteLine(d); // 输出: 100
5.2 显式转换
显式转换需要使用强制转换运算符,通常发生在从大范围类型转换到小范围类型时,可能会导致数据丢失。
double d = 100.99;
int i = (int)d; // 显式转换,从 double 到 int
Console.WriteLine(i); // 输出: 100(小数部分被截断)
5.3 使用 Convert 类
Convert 类提供了各种类型转换方法。
string s = "123";
int i = Convert.ToInt32(s);
double d = Convert.ToDouble(s);
bool b = Convert.ToBoolean(1); // 非零值转换为 true
Console.WriteLine(i); // 输出: 123
Console.WriteLine(d); // 输出: 123
Console.WriteLine(b); // 输出: True
5.4 使用 Parse 和 TryParse 方法
Parse 方法用于将字符串转换为其他类型,TryParse 方法在转换失败时不会抛出异常。
string s = "123";
int i = int.Parse(s);
string s2 = "abc";
int result;
bool success = int.TryParse(s2, out result);
if (success)
{
Console.WriteLine(result);
}
else
{
Console.WriteLine("转换失败");
}
6. 可空类型
可空类型允许值类型接受 null 值,通过在类型后面添加 ? 来表示。
int? nullableInt = null;
double? nullableDouble = 10.5;
bool? nullableBool = true;
// 使用可空类型
if (nullableInt.HasValue)
{
Console.WriteLine(nullableInt.Value);
}
else
{
Console.WriteLine("nullableInt 为 null");
}
// 空合并运算符 ??
int result = nullableInt ?? 0; // 如果 nullableInt 为 null,则使用 0
Console.WriteLine(result); // 输出: 0
7. 类型推断
使用 var 关键字可以让编译器自动推断变量的类型。
var i = 10; // 推断为 int
var d = 3.14; // 推断为 double
var s = "Hello"; // 推断为 string
var list = new List<int>(); // 推断为 List<int>
Console.WriteLine(i.GetType()); // 输出: System.Int32
Console.WriteLine(d.GetType()); // 输出: System.Double
Console.WriteLine(s.GetType()); // 输出: System.String
8. 常见错误与注意事项
错误 1:空引用异常
解决方案:在使用引用类型之前,检查是否为 null。
// 错误示例
string s = null;
int length = s.Length; // 抛出 NullReferenceException
// 正确示例
if (s != null)
{
int length = s.Length;
}
// 使用空条件运算符
int? length = s?.Length; // 如果 s 为 null,length 为 null
错误 2:类型转换失败
解决方案:使用 TryParse 方法或异常处理。
// 错误示例
string s = "abc";
int i = int.Parse(s); // 抛出 FormatException
// 正确示例
string s = "abc";
int result;
if (int.TryParse(s, out result))
{
Console.WriteLine(result);
}
else
{
Console.WriteLine("转换失败");
}
// 或使用异常处理
try
{
int i = int.Parse(s);
Console.WriteLine(i);
}
catch (FormatException ex)
{
Console.WriteLine("转换失败: " + ex.Message);
}
错误 3:值类型和引用类型的混淆
解决方案:了解值类型和引用类型的区别,特别是在赋值和方法参数传递时。
// 值类型示例
struct Point
{
public int X { get; set; }
public int Y { get; set; }
}
void ModifyPoint(Point p)
{
p.X = 100; // 修改的是副本
}
Point point = new Point { X = 10, Y = 20 };
ModifyPoint(point);
Console.WriteLine(point.X); // 输出: 10(未改变)
// 引用类型示例
class Person
{
public string Name { get; set; }
}
void ModifyPerson(Person p)
{
p.Name = "李四"; // 修改的是原对象
}
Person person = new Person { Name = "张三" };
ModifyPerson(person);
Console.WriteLine(person.Name); // 输出: 李四(已改变)
9. 示例程序
下面是一个完整的示例程序,演示了 C# 数据类型的使用:
using System;
using System.Collections.Generic;
namespace DataTypeDemo
{
// 枚举类型
enum Gender
{
Male,
Female
}
// 结构类型
struct Point
{
public int X { get; set; }
public int Y { get; set; }
public Point(int x, int y)
{
X = x;
Y = y;
}
public override string ToString()
{
return $"({X}, {Y})";
}
}
// 类
class Student
{
public string Name { get; set; }
public int Age { get; set; }
public Gender Gender { get; set; }
public Student(string name, int age, Gender gender)
{
Name = name;
Age = age;
Gender = gender;
}
public void Display()
{
Console.WriteLine($"Name: {Name}, Age: {Age}, Gender: {Gender}");
}
}
class Program
{
static void Main(string[] args)
{
// 值类型示例
Console.WriteLine("值类型示例:");
int i = 10;
double d = 3.14;
bool b = true;
char c = 'A';
Point point = new Point(100, 200);
Gender gender = Gender.Male;
Console.WriteLine($"int: {i}");
Console.WriteLine($"double: {d}");
Console.WriteLine($"bool: {b}");
Console.WriteLine($"char: {c}");
Console.WriteLine($"Point: {point}");
Console.WriteLine($"Gender: {gender}");
// 引用类型示例
Console.WriteLine("\n引用类型示例:");
string s = "Hello, C#";
Student student = new Student("张三", 18, Gender.Male);
List<int> numbers = new List<int> { 1, 2, 3, 4, 5 };
Console.WriteLine($"string: {s}");
student.Display();
Console.Write("List: ");
foreach (int num in numbers)
{
Console.Write(num + " ");
}
Console.WriteLine();
// 类型转换示例
Console.WriteLine("\n类型转换示例:");
int intValue = 100;
double doubleValue = intValue; // 隐式转换
Console.WriteLine($"int 到 double: {doubleValue}");
double doubleValue2 = 123.45;
int intValue2 = (int)doubleValue2; // 显式转换
Console.WriteLine($"double 到 int: {intValue2}");
string stringValue = "456";
int parsedValue = int.Parse(stringValue);
Console.WriteLine($"string 到 int: {parsedValue}");
// 可空类型示例
Console.WriteLine("\n可空类型示例:");
int? nullableInt = null;
Console.WriteLine($"nullableInt: {nullableInt}");
nullableInt = 789;
Console.WriteLine($"nullableInt 赋值后: {nullableInt}");
int result = nullableInt ?? 0;
Console.WriteLine($"使用 ?? 运算符: {result}");
// 类型推断示例
Console.WriteLine("\n类型推断示例:");
var inferredInt = 123;
var inferredDouble = 456.78;
var inferredString = "类型推断";
var inferredList = new List<string> { "a", "b", "c" };
Console.WriteLine($"inferredInt 类型: {inferredInt.GetType().Name}");
Console.WriteLine($"inferredDouble 类型: {inferredDouble.GetType().Name}");
Console.WriteLine($"inferredString 类型: {inferredString.GetType().Name}");
Console.WriteLine($"inferredList 类型: {inferredList.GetType().Name}");
Console.WriteLine("\n按任意键退出...");
Console.ReadKey();
}
}
}
10. 总结
通过本教程,您应该已经掌握了 C# 中各种数据类型的特性和使用方法。数据类型是 C# 编程的基础,正确理解和使用数据类型对于编写高效、可靠的代码至关重要。
在使用数据类型时,需要注意以下几点:
- 了解值类型和引用类型的区别,特别是在赋值和方法参数传递时的行为
- 根据需要选择合适的数据类型,平衡内存使用和性能
- 使用可空类型处理可能为 null 的值
- 正确处理类型转换,避免转换失败和数据丢失
- 使用类型推断简化代码,但要确保代码的可读性
掌握好数据类型的使用,将帮助您编写更加清晰、高效的 C# 代码。