C# 数据类型详解

本教程将详细介绍 C# 中的各种数据类型,包括值类型和引用类型的特性与使用方法。

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# 代码。