ZetCode

C# 数据类型

最后修改于 2023 年 7 月 5 日

在本文中,我们将讨论 C# 中的数据类型。

计算机程序,包括电子表格、文本编辑器、计算器或聊天客户端,都使用数据。使用各种数据类型的工具是现代计算机语言的重要组成部分。数据类型是一组值以及允许对这些值进行的操作。

C# 数据类型

数据类型是一组值,以及允许对这些值进行的操作。

C# 中的两个基本数据类型是值类型和引用类型。原始类型(字符串除外)、枚举、元组和结构是值类型。类、记录、字符串、接口、数组和委托是引用类型。每种类型都有一个默认值。

引用类型是在堆上创建的。引用类型的生命周期由 .NET 框架管理。引用类型的默认值为 null 引用。将引用类型赋值给变量会创建引用的副本,而不是被引用值的副本。

值类型是在堆栈上创建的。生命周期由变量的生命周期决定。将值类型赋值给变量会创建被赋值的值的副本。值类型具有不同的默认值。例如,布尔值的默认值为 false,decimal 的默认值为 0,字符串的默认值为空字符串 ""。

C# 布尔值

bool 数据类型是一种原始数据类型,具有两个值之一:truefalse

我们将为一个新生儿选择一个名字。如果是个男孩,我们选择 John。如果是个女孩,我们选择 Victoria。

Program.cs
var random = new Random();

bool male = Convert.ToBoolean(random.Next(0, 2));

if (male)
{
    Console.WriteLine("We will use name John");
}
else
{
    Console.WriteLine("We will use name Victoria");
}

该程序使用随机数生成器来模拟我们的情况。

var random = new Random();

我们创建一个 Random 对象,用于计算随机数。它是 System 命名空间的一部分。

bool male = Convert.ToBoolean(random.Next(0, 2));

Next 方法返回指定范围内的随机数。包含下限,不包含上限。换句话说,我们收到 0 或 1。稍后,Convert 方法将这些值转换为布尔值,0 转换为 false,1 转换为 true。

if (male)
{
    Console.WriteLine("We will use name John");
} else
{
    Console.WriteLine("We will use name Victoria");
}

如果 male 变量设置为 true,我们将选择名字 John。否则,我们选择名字 Victoria。像 if/else 语句这样的控制结构使用布尔值。

$ dotnet run
We will use name John
$ dotnet run
We will use name John
$ dotnet run
We will use name Victoria

C# 整数

整数是实数的子集。它们不带分数或小数部分书写。整数属于集合 Z = {..., -2, -1, 0, 1, 2, ...}。整数是无限的。

在计算机语言中,整数是原始数据类型。计算机实际上只能处理整数值的一个子集,因为计算机的容量是有限的。整数用于计数离散的实体。我们可以有 3、4、6 个人,但我们不能有 3.33 个人。我们可以有 3.33 公斤。

VB 别名 .NET 类型 大小 范围
sbyte System.SByte 1 字节 -128 到 127
byte System.Byte 1 字节 0 到 255
short System.Int16 2 字节 -32,768 到 32,767
ushort System.UInt16 2 字节 0 到 65,535
int System.Int32 4 字节 -2,147,483,648 到 2,147,483,647
uint System.UInt32 4 字节 0 到 4,294,967,295
long System.Int64 8 字节 -9,223,372,036,854,775,808 到 9,223,372,036,854,775,807
ulong System.UInt64 8 字节 0 到 18,446,744,073,709,551,615

这些整数类型可以根据我们的需要使用。没有人(也许除了一些圣经里的人物)能活到 120、130 岁以上。那么我们可以在程序中使用 byte 类型作为 age 变量。这将节省一些内存。

离散的实体

如果我们处理整数,我们就是在处理离散实体。我们会用整数来计数苹果。

Program.cs
int baskets = 16;
int applesInBasket = 24;

int total = baskets * applesInBasket;

Console.WriteLine($"There are total of {total} apples");

在我们的程序中,我们计算苹果的总数。我们使用乘法运算。

int baskets = 16;
int applesInBasket = 24;

篮子的数量和每个篮子里的苹果数量都是整数值。

int total = baskets * applesInBasket;

将这些值相乘,我们也得到一个整数。

$ dotnet run
There are total of 384 apples

C# 整数表示法

整数可以用 C# 中的三种不同的表示法指定:十进制、十六进制和二进制。没有八进制值的表示法。十进制数像我们所知的那样正常使用。十六进制数以 0x 字符开头,二进制数以 0b 开头。

注意:有些语言也支持八进制表示法;C# 不支持。
Program.cs
int num1 = 31;
int num2 = 0x31;
int num3 = 0b1101;

Console.WriteLine(num1);
Console.WriteLine(num2);
Console.WriteLine(num3);

在该程序中,我们有三个用三种不同表示法表示的整数。

$ dotnet run
31
49
13

默认表示法是十进制。该程序以十进制显示这三个数字。

使用下划线

C# 允许对数字字面量使用下划线字符,以提高值的可读性。

Program.cs
var num1 = 234_321_000;
Console.WriteLine(num1);

var num2 = 0b_0110_000_100;
Console.WriteLine(num2);

该程序使用带有下划线字符的整数文字来提高值的可读性。

算术溢出

算术溢出是一种当计算产生的结果在幅度上大于给定的寄存器或存储位置可以存储或表示的结果时发生的条件。

Program.cs
byte a = 254;

Console.WriteLine(a);
a++;

Console.WriteLine(a);
a++;

Console.WriteLine(a);
a++;

Console.WriteLine(a);

在此示例中,我们尝试分配一个超出数据类型范围的值。这会导致算术溢出。

$ dotnet run
254
255
0
1

发生溢出时,变量将重置为数据类型的下限。(对于字节类型,它为零。)

使用 checked 关键字,我们可以在发生溢出时强制引发异常。

Program.cs
checked
{
    byte a = 254;

    Console.WriteLine(a);
    a++;

    Console.WriteLine(a);
    a++;

    Console.WriteLine(a);
    a++;

    Console.WriteLine(a);
}

在该示例中,语句放置在 checked 块的主体中。

$ dotnet run
254
255
Unhandled Exception: System.OverflowException: Arithmetic operation resulted in an overflow.
    ...

这次会抛出一个 System.OverflowException

C# 浮点数

浮点数表示计算机中的实数。实数测量连续的量,如重量、高度或速度。在 C# 中,我们有三种浮点类型:floatdoubledecimal

C# 别名 .NET 类型 大小 精度 范围
float System.Single 4 字节 7 位数字 +-1.5 x 10-45 到 +-3.4 x 1038
double System.Double 8 字节 15-16 位数字 +-5.0 x 10-324 到 +-1.7 x 10308
decimal System.Decimal 16 字节 28-29 位小数 +-1.0 x 10-28 到 +-7.9 x 1028

上表给出了浮点类型的特征。

默认情况下,实数在 C# 程序中为 double。要使用不同的类型,我们必须使用后缀。F/f 用于 float 数字,M/m 用于 decimal 数字。

Program.cs
float n1 = 1.234f;
double n2 = 1.234;
decimal n3 = 1.234m;

Console.WriteLine(n1);
Console.WriteLine(n2);
Console.WriteLine(n3);

Console.WriteLine(n1.GetType());
Console.WriteLine(n2.GetType());
Console.WriteLine(n3.GetType());

在上面的程序中,我们对浮点数使用了三种不同的字面量表示法。

float n1 = 1.234f;

f 后缀用于 float 数字。

double n2 = 1.234;

如果我们不使用后缀,则它是一个 double 数字。我们可以选择使用 d 后缀。

Console.WriteLine(n1.GetType());

GetType 方法返回数字的类型。

$ dotnet run
1.234
1.234
1.234
System.Single
System.Double
System.Decimal

我们可以使用各种语法来创建浮点值。

Program.cs
float n1 = 1.234f;
float n2 = 1.2e-3f;
float n3 = (float)1 / 3;

Console.WriteLine(n1);
Console.WriteLine(n2);
Console.WriteLine(n3);

我们有三种方法来创建浮点值。第一种是使用小数点的“正常”方式。第二种使用科学计数法。最后一种是数值运算的结果。

float n2 = 1.2e-3f;

这是浮点数的科学计数法。也称为指数计数法,它是书写太大或太小而无法方便地以标准十进制计数法书写的数字的一种方式。

float n3 = (float) 1 / 3;

(float) 结构称为强制转换。除法运算默认返回整数。通过强制转换,我们得到一个浮点数。

$ dotnet run
1.234
0.0012
0.3333333

floatdouble 类型是不精确的。

Program.cs
double n1 = 0.1 + 0.1 + 0.1;
double n2 = 0.3;

Console.WriteLine(n1);
Console.WriteLine(n2);


if (n1 == n2)
{
    Console.WriteLine("Numbers are equal");
}
else
{
    Console.WriteLine("Numbers are not equal");
}

比较浮点值时应谨慎。

$ dotnet run
0.30000000000000004
0.3
Numbers are not equal

一位 100 米赛跑运动员跑了 9.87 秒。他的速度是多少公里/小时?

Program.cs
using System;

float distance = 0.1f;

float time = 9.87f / 3600;

float speed = distance / time;

Console.WriteLine($"The average speed of a sprinter is {speed} km/h");

在此示例中,必须使用浮点值。

float distance = 0.1f;

100 米是 0.1 公里。

float time = 9.87f / 3600;

9.87 秒是 9.87/(60*60) 小时。

float speed = distance / time;

为了获得速度,我们将距离除以时间。

$ dotnet run
The average speed of a sprinter is 36.47416 km/h

C# 枚举

枚举类型(也称为枚举或 enum)是一种数据类型,由一组命名的值组成。已声明为具有枚举类型的变量可以被分配任何枚举器作为值。枚举使代码更具可读性。

Program.cs
Days day = Days.Monday;

if (day == Days.Monday)
{
    Console.WriteLine("It is Monday");
}

Console.WriteLine(day);

foreach (int i in Enum.GetValues(typeof(Days)))
{
    Console.WriteLine(i);
}

enum Days
{
    Monday,
    Tuesday,
    Wednesday,
    Thursday,
    Friday,
    Saturday,
    Sunday
}

在我们的代码示例中,我们为星期几创建一个枚举。

enum Days
{
    Monday,
    Tuesday,
    Wednesday,
    Thursday,
    Friday,
    Saturday,
    Sunday
}

该枚举使用 enum 关键字创建。Monday、Tuesday、... barewords 实际上存储数字 0..6。

Days day = Days.Monday;

我们有一个名为 day 的变量,它的类型为枚举类型 Days。它被初始化为 Monday。

if (day == Days.Monday)
{
    Console.WriteLine("It is Monday");
}

此代码比将 day 变量与某个数字进行比较更具可读性。

Console.WriteLine(day);

此行将 Monday 打印到控制台。

foreach (int i in Enum.GetValues(typeof(Days)))
{
    Console.WriteLine(i);
}

此循环将 0..6 打印到控制台。我们获得了 enum 值的底层类型。对于计算机来说,enum 只是一个数字。 typeof 是一个用于获取类型的 System.Type 对象的运算符。GetValues 方法需要它。此方法返回指定枚举的值的数组。 foreach 关键字遍历数组,逐个元素并将其打印到终端。

我们进一步使用枚举。

Program.cs
Seasons s1 = Seasons.Spring;
Seasons s2 = Seasons.Autumn;

Console.WriteLine(s1);
Console.WriteLine(s2);

public enum Seasons : byte
{
    Spring = 1,
    Summer = 2,
    Autumn = 3,
    Winter = 4
}

季节可以很容易地用作枚举。我们可以为 enum 指定底层类型,并且可以为它们提供精确的值。

public enum Seasons : byte
{
    Spring = 1,
    Summer = 2,
    Autumn = 3,
    Winter = 4
}

使用冒号和数据类型,我们指定 enum 的底层类型。我们还为每个成员指定一个特定的数字。

Console.WriteLine(s1);
Console.WriteLine(s2);

这两行将 enum 值打印到控制台。

$ dotnet run
Spring
Autumn

C# 元组

元组是异构数据值的有序、不可变列表。元组是值类型。元组必须包含至少两个元素。元组用圆括号 定义。

Program.cs
var words = ("sky", "blue", "rock", "fountain");

Console.WriteLine(words);

Console.WriteLine(words.Item1);
Console.WriteLine(words.Item2);

var words2 = (w1: "forest", w2: "deep", w3: "sea");

Console.WriteLine(words2.w1);
Console.WriteLine(words2.w2);
Console.WriteLine(words2.w3);

在此示例中,我们定义了两个元组。

var words = ("sky", "blue", "rock", "fountain");

这是一个未命名的元组定义。

Console.WriteLine(words);

我们将元组的所有元素打印到控制台。

Console.WriteLine(words.Item1);
Console.WriteLine(words.Item2);

我们打印前两个元素。我们使用特殊的 Item1Item2、... 属性访问未命名元组的元素。

var words2 = (w1: "forest", w2: "deep", w3: "sea");

这是一个命名元组的定义。

Console.WriteLine(words2.w1);
Console.WriteLine(words2.w2);
Console.WriteLine(words2.w3);

我们通过它们的名称访问元素。

$ dotnet run
(sky, blue, rock, fountain)
sky
blue
forest
deep
sea

C# 记录

C# 9 引入了记录。记录是不可变的引用类型。记录类型使用基于值的相等性。使用 record 关键字创建记录。

记录的主要目的是作为数据持有者。

Program.cs
var cars = new List<Car>
{
    new Car("Audi", 52642),
    new Car("Mercedes", 57127),
    new Car("Skoda", 9000),
    new Car("Volvo", 29000),
    new Car("Bentley", 350000),
    new Car("Citroen", 21000),
    new Car("Hummer", 41400),
    new Car("Volkswagen", 21600)
};

var res = from car in cars
          where car.Price > 30000 && car.Price < 100000
          select new { car.Name, car.Price };

foreach (var car in res)
{
    Console.WriteLine($"{car.Name} {car.Price}");
}

record Car(string Name, int Price);

在此示例中,我们使用 LINQ 过滤汽车对象列表。我们包含所有价格在 30000 到 100000 之间的汽车。语言集成查询 (LINQ) 位于 System.Linq 命名空间中,该命名空间包含隐式 using。

record Car(string Name, int Price);

汽车是一种 record 类型。

$ dotnet run
Audi 52642
Mercedes 57127
Hummer 41400

C# 字符串和字符

string 是一种数据类型,表示计算机程序中的文本数据。 C# 中的字符串是 Unicode 字符序列。 char 是一个 Unicode 字符。字符串用双引号括起来。

Program.cs
string word = "ZetCode";

char c = word[0];

Console.WriteLine(c);

该程序将 'Z' 字符打印到终端。

string word = "ZetCode";

在这里,我们创建一个字符串变量,并将其赋值为 "ZetCode" 值。

char c = word[0];

string 是 Unicode 字符数组。我们可以使用数组访问表示法从字符串中获取特定字符。方括号内的数字是字符数组的索引。索引从零开始计数。这意味着第一个字符的索引为 0。

$ dotnet run
Z

C# 数组

数组是一种复杂的数据类型,用于处理元素集合。每个元素都可以通过索引访问。数组的所有元素必须具有相同的数据类型。

Program.cs
int[] numbers = new int[5];

numbers[0] = 3;
numbers[1] = 2;
numbers[2] = 1;
numbers[3] = 5;
numbers[4] = 6;

int len = numbers.Length;

for (int i = 0; i < len; i++)
{
    Console.WriteLine(numbers[i]);
}

在此示例中,我们声明一个数组,用数据填充它,然后将数组的内容打印到控制台。

int[] numbers = new int[5];

我们声明一个可以存储最多五个整数的整数数组。因此,我们有一个包含五个元素的数组,索引为 0..4。

numbers[0] = 3;
numbers[1] = 2;
numbers[2] = 1;
numbers[3] = 5;
numbers[4] = 6;

在这里,我们将值分配给创建的数组。我们可以通过数组访问表示法访问数组的元素。它由数组名称后跟方括号组成。在方括号内,我们指定要访问的元素的索引。

int len = numbers.Length;

每个数组都有一个 Length 属性,该属性返回数组中元素的数量。

for (int i=0; i<len; i++)
{
    Console.WriteLine(numbers[i]);
}

我们遍历数组并将数据打印到控制台。

C# DateTime

DateTime 是一种值类型。它表示时间中的一个时刻,通常表示为日期和时间。

Program.cs
DateTime now = DateTime.Now;

System.Console.WriteLine(now);
System.Console.WriteLine(now.ToShortDateString());
System.Console.WriteLine(now.ToShortTimeString());

我们以三种不同的格式显示今天的日期:日期和时间、日期和时间。

DateTime now = DateTime.Now;

获取一个 DateTime 对象,该对象设置为此计算机上的当前日期和时间,以本地时间表示。

System.Console.WriteLine(now);

此行以完整格式打印日期。

System.Console.WriteLine(now.ToShortDateString());
System.Console.WriteLine(now.ToShortTimeString());

ToShortDateString 返回一个短日期字符串格式,ToShortTimeString 返回一个短时间字符串格式。

$ dotnet run
11/1/2022 10:26:06 AM
11/1/2022
10:26 AM

C# 类型转换

我们经常一次处理多种数据类型。将一种数据类型转换为另一种数据类型是编程中的一项常见工作。 类型转换类型强制转换 是指将一种数据类型的实体更改为另一种数据类型。有两种类型的转换:隐式转换和显式转换。隐式类型转换,也称为强制,是编译器自动进行的类型转换。

Program.cs
int val1 = 0;
byte val2 = 15;

val1 = val2;

Console.WriteLine(val1.GetType());
Console.WriteLine(val2.GetType());

Console.WriteLine(12 + 12.5);
Console.WriteLine("12" + 12);

在此示例中,我们有几个隐式转换。

val1 = val2;

在这里,我们使用两种不同的类型:intbyte。我们将 byte 值分配给 int 值。这是一个扩大操作。 int 值有四个字节;字节值只有一个字节。允许进行扩大转换。如果我们想将 int 分配给 byte,这将是缩短转换。

C# 编译器不允许隐式缩短转换。这是因为在隐式缩短转换中,我们可能会无意中丢失精度。我们可以进行缩短转换,但我们必须告知编译器。我们知道我们在做什么。它可以通过显式转换来完成。

Console.WriteLine(12 + 12.5);

我们添加两个值:一个整数值和一个浮点值。结果是一个浮点值。这是一个扩大的隐式转换。

Console.WriteLine("12" + 12);

结果是 1212。一个整数转换为一个字符串,并且两个字符串连接起来。

接下来,我们展示一些 C# 中的显式转换。

Program.cs
double b = 13.5;

float a = (float) b;
float c = (int) a;

Console.WriteLine(a);
Console.WriteLine(b);
Console.WriteLine(c);

我们有三个值。我们使用这些值进行一些显式转换。

float a = (float) b;

我们将 double 值转换为 float 值。显式转换是通过在两个圆括号之间指定预期类型来完成的。在这种情况下,没有精度损失。数字 13.5 可以安全地分配给这两种类型。

float c = (int) a;

我们将 float 值转换为 int 值。在此语句中,我们损失了一些精度:13.5 变为 13。

$ dotnet run
13.5
13.5
13

C# 可空类型

值类型不能被赋值为 null 字面量,而引用类型可以。处理数据库的应用程序会遇到 null 值。因此,C# 语言中引入了特殊的 Nullable 类型。Nullable 类型是 System.Nullable<T> 结构体的实例。

Program.cs
Nullable<bool> male = null;
int? age = null;

Console.WriteLine(male.HasValue);
Console.WriteLine(age.HasValue);

一个简单的例子演示了 Nullable 类型。

Nullable<bool> male = null;
int? age = null;

有两种方式声明 Nullable 类型。一种是使用 Nullable<T> 泛型结构,其中类型在尖括号中指定;另一种是在类型后面使用问号。后者实际上是第一种表示法的简写。

$ dotnet run
False
False

C# Convert & Parse 方法

有两组方法用于转换值。

Program.cs
Console.WriteLine(Convert.ToBoolean(0.3));
Console.WriteLine(Convert.ToBoolean(3));
Console.WriteLine(Convert.ToBoolean(0));
Console.WriteLine(Convert.ToBoolean(-1));

Console.WriteLine(Convert.ToInt32("452"));
Console.WriteLine(Convert.ToInt32(34.5));

Convert 类有很多转换值的方法。我们使用其中的两个。

Console.WriteLine(Convert.ToBoolean(0.3));

我们将 double 值转换为 bool 值。

Console.WriteLine(Convert.ToInt32("452"));

在这里,我们将 string 转换为 int

$ dotnet run
True
True
False
True
452
34
Program.cs
Console.WriteLine(int.Parse("34"));
Console.WriteLine(int.Parse("-34"));
Console.WriteLine(int.Parse("+34"));

将字符串转换为整数是一项非常常见的任务。当我们从数据库或 GUI 组件获取值时,我们经常进行此类转换。

Console.WriteLine(int.Parse("34"));

我们使用 intParse 方法将 string 转换为 int 值。

$ dotnet run
34
-34
34

来源

内置类型

在本文中,我们介绍了 C# 中的数据类型及其转换。

作者

我叫 Jan Bodnar,是一位充满热情的程序员,拥有丰富的编程经验。我从 2007 年开始撰写编程文章。到目前为止,我已经撰写了 1400 多篇文章和 8 本电子书。我拥有超过十年的编程教学经验。

列出所有 C# 教程