C# string,回调函数,数组,接口,结构,枚举

日之朝矣

本文基于.NET Framwork 4.8版本书写,也就是C# 7.3。内容可能与最新版的C#已经有很大的不同,尤其是接口

string

不同于intshortbyte等值类型是个结构体,string这个引用类型是一个类。

1
2
3
4
string s1 = "hello";

// 也可以实例化对象
string s2 = new string(new char[] { 'h','e','l','l','o'});

几种字符串

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class Program
{
static void Main()
{
// 常用的字符串书写形式,使用反斜杠(\)进行转义,\u0041是A
string s1 = "\" 123\u0041 \"";

// 逐字文本,可以多行写字符串,以及会忽略除了 双引号("") 以外的转义
string s2 =@"第一行:
第二行:He said, ""This is the last \u0063hance\x0021""
第三行:";

// 内插字符串,使用$放在前面,{}内容可以使用变量或语句(语句需要用括号)
string s3 = $"{(s1 == s2 ? "好诶":"坏了")}aaa";

// 内插逐字字符串
string s4 = $@"第一行:{ s1 == s2 }
第二行:He said, ""This is the last \u0063hance\x0021""
第三行:";
Console.WriteLine(s1);
Console.WriteLine();
Console.WriteLine(s2);
Console.WriteLine();
Console.WriteLine(s3);
Console.WriteLine();
Console.WriteLine(s4);
Console.ReadKey();
}
}

输出结果:

1
2
3
4
5
6
7
8
9
10
11
" 123A "

第一行:
第二行:He said, "This is the last \u0063hance\x0021"
第三行:

坏了aaa

第一行:False
第二行:He said, "This is the last \u0063hance\x0021"
第三行:

还有一种字符串文本,叫原始字符串文本,是在 C# 11版本引入的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public static void Main()
{
string xml = """
<element attr="content">
<body>
</body>
</element>
""";

int i = 1;
var json = $$"""
{
"summary": "text",
"length" : {{i}},
};
""";
Console.WriteLine(xml);
Console.WriteLine();
Console.WriteLine(json);
}

与逐字文本相比,它在书写时的格式更美观,无需因为多行问题导致在代码编辑器内的缩进看起来很奇怪。并且如果三引号所在行是空白行,则会自动删除当前行。

也可以与内插字符串联合使用

原始字符串文本中不需要再考虑转义问题,仅仅在使用内插字符串时需要{{}}来写入内插内容。

如果原始字符串字面量需要用到连续三个"的情况,那么在写原始字符串文本时可以写大于三个"。如:

1
var moreQuotes = """" As you can see,"""Raw string literals""" can start and end with more than three double-quotes when needed."""";

运算符在字符串中的使用

尽管 string 为引用类型,但是定义相等运算符 ==!= 是为了比较 string 对象(而不是引用)的值。 基于值的相等性使得对字符串相等性的测试更为直观。 例如:

1
2
3
4
5
6
string a = "hello";
string b = "h";
// Append to contents of 'b'
b += "ello";
Console.WriteLine(a == b);
Console.WriteLine(object.ReferenceEquals(a, b));

前面的示例显示“True”,然后显示“False”,因为字符串的内容是相等的,但 ab 并不指代同一字符串实例。

+ 运算符连接字符串:

1
string a = "good " + "morning";

前面的代码会创建一个包含“good morning”的字符串对象。

字符串是不可变的,即:字符串对象在创建后,其内容不可更改。 例如,编写此代码时,编译器实际上会创建一个新的字符串对象来保存新的字符序列,且该新对象将赋给 b。 已为 b 分配的内存(当它包含字符串“h”时)可用于垃圾回收。

1
2
string b = "h";
b += "ello";

[] 运算符可用于只读访问字符串的个别字符。 有效索引于 0 开始,且必须小于字符串的长度:

1
2
string str = "test";
char x = str[2]; // x = 's';

同样,[] 运算符也可用于循环访问字符串中的每个字符:

1
2
3
4
5
6
7
string str = "test";

for (int i = 0; i < str.Length; i++)
{
Console.Write(str[i] + " ");
}
// Output: t e s t

提示

在 C# 中,string 类型内部由字符数组 (char[]) 组成,但封装了更多的功能和方法。

与其他引用类型的区别

博主本人看法:string 是一个使用起来像 值类型 的 引用类型。

虽说string也是引用类型,但在日常使用中,与其他引用类型的差别还是很明显的。

不可变性

字符串是不可变的。一旦创建了一个字符串对象,它的值就不能被改变。任何对字符串进行修改的操作(如拼接、替换等)都会创建一个新的字符串对象,而不会改变原有对象。

1
2
3
string s1 = "Hello";
string s2 = s1 + " World";
// s1 仍然是 "Hello",而 s2 是 "Hello World"

由于不可变性这个特性,string 作为方法的参数传递时,虽然和其他引用类型一样传递的是地址,但方法内一旦对这个 string 进行修改,它的地址就会被改变,变成一个新的对象,从而不会影响到原来的 string。

字面量缓存

C# 编译器会对字符串字面量进行缓存。这意味着相同的字符串字面量在内存中只存储一次,这样可以节省内存并提高性能。

1
2
3
4
string s1 = "Hello";
string s2 = "Hello";
// 判断引用是否一致
bool areEqual = object.ReferenceEquals(s1, s2); // True

内存管理

由于不可变性和缓存机制,字符串的内存管理和垃圾回收有其特殊之处。频繁的字符串操作可能会导致大量的临时字符串对象,影响性能和内存使用。

使用 StringBuilder 可以优化频繁的字符串操作。

1
2
3
4
5
StringBuilder sb = new StringBuilder();
sb.Append("Hello");
sb.Append(" ");
sb.Append("World");
string result = sb.ToString(); // "Hello World"

string类中常用方法

表格中只列出博主常用的方法,由于一些方法有过多重载,因此参数不写入表格中,该表格目的仅仅是为了列出常用的方法方便大家有目标去学习。每个方法都有对应文档的超链接,点击即可跳转至对应的官方文档。

方法名描述
Compare() 比较两个指定的 String 对象,并返回一个整数,大于 0 表示第一个字符串位于第二个字符串之前,等于 0 表示位置相同,小于 0 表示此实例位于第二个字符串之后。
CompareTo() 将此实例与指定对象或 String 进行比较,并返回一个整数,大于 0 表示此实例位于 value 之前,等于 0 表示位置相同,小于 0 表示此实例位于 value 之后
Concat() 连接 String 的一个或多个实例,或 String 的一个或多个实例的值的 Object 表示形式。
Copy() 创建一个与指定的 String 具有相同值的 String 的新实例。
CopyTo() 将指定数目的字符从此实例中的指定位置复制到 Unicode 字符数组中的指定位置。
EndsWith() 确定此字符串实例的结尾是否与指定的字符串匹配。
Equals() 确定两个 String 对象是否具有相同的值。
Format() 将对象的值转换为基于指定格式的字符串,并将其插入到另一个字符串。
如果不熟悉 String.Format 该方法,请参阅 String.Format 方法入门 以获取快速概述。
IndexOf() 报告指定 Unicode 字符或字符串在此实例中的第一个匹配项的从零开始的索引。 如果未在此实例中找到该字符或字符串,则此方法返回 -1。
IsNullOrEmpty() 指示指定的字符串是 null 还是空字符串 (“”)。
IsNullOrWhiteSpace() 指示指定的字符串是 null、空还是仅由空白字符组成。
Join() 连接指定数组的元素或集合的成员,在每个元素或成员之间使用指定的分隔符。
LastIndexOf() 报告指定 Unicode 字符或字符串在此实例中的最后一个匹配项的从零开始的索引的位置。 如果未在此实例中找到该字符或字符串,则此方法返回 -1。
PadLeft() 返回一个指定长度的新字符串,其中在当前字符串的开头填充空格或指定的 Unicode 字符。
PadRight() 返回一个指定长度的新字符串,其中在当前字符串的结尾填充空格或指定的 Unicode 字符。
Remove() 返回一个新字符串,它相当于从当前字符串删除了指定数量的字符。
Replace() 返回一个新字符串,其中已将当前字符串中的指定 Unicode 字符或 String 的所有匹配项替换为其他指定的 Unicode 字符或 String。
Split() 返回的字符串数组包含此实例中的子字符串(由指定字符串或 Unicode 字符数组的元素分隔)。
StartsWith() 确定此字符串实例的开头是否与指定的字符串匹配。
Substring() 从此实例检索子字符串。
ToCharArray() 将此实例中的字符复制到 Unicode 字符数组。
ToLower() 返回此字符串转换为小写形式的副本。
ToUpper() 返回此字符串转换为大写形式的副本。
Trim() 返回一个新字符串,它相当于从当前字符串中删除了一组指定字符的所有前导匹配项和尾随匹配项,无参默认删除前后空白字符。
TrimEnd() 从当前字符串删除数组中指定的一组字符的所有尾随匹配项。
TrimStart() 从当前字符串删除数组中指定的一组字符的所有前导匹配项。

其他方法:System.String 类官方文档

重要

(该 Important 来自官方文档,但博主本人并未成功复现下面描述的情况。)

从 .NET Core 3.0 开始,String.Copy(String) 方法已过时。 但是,我们不建议在任何 .NET 实现中使用它。 特别是,由于 .NET Core 3.0 中字符串插入的变化,在某些情况下, Copy 该方法不会创建新字符串,而只是返回对现有驻留池字符串的引用。

回调函数

回调函数之所以叫“回调函数”,是因为它表示一种编程模式,在这种模式中,函数A将函数B作为参数传递给函数C。当函数C完成它的任务后,会“回调”函数B。

也就是说,回调函数就是在任务完成后被调用的函数,反映了它的“回过头来调用”的特点。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class Program
{
// 回调函数,处理任务完成后的逻辑
public static void MyCallback(int result)
{
Console.WriteLine($"任务完成,结果是:{result}");
}

// 执行任务的函数,接受一个回调函数作为参数
public static void PerformTask(Action<int> callback)
{
Console.WriteLine("正在执行任务...");
// 模拟一些工作
System.Threading.Thread.Sleep(2000);
int result = 42; // 假设任务返回一个结果
// 调用回调函数,并传递结果
callback(result);
}

public static void Main()
{
// 调用PerformTask并传入MyCallback作为回调函数
PerformTask(MyCallback);
}
}

匿名函数

匿名函数(Anonymous Function)是一种没有名称的函数,通常用于需要将函数作为参数传递的场景。

定义匿名函数:

1
delegate (parameters) { body }

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Program
{
public static void Main()
{
// 使用匿名方法定义一个委托
Func<int, int, int> add = delegate (int x, int y)
{
return x + y;
};

Console.WriteLine(add(3, 4)); // 输出7
}
}

虽然还没有说过委托,但我们只看匿名函数部分

1
Func<int, int, int> add = delegate (int x, int y){return x + y;};

普通的函数需要在参数列表前写上名字与返回值,而匿名函数不需要函数签名,只需要参数列表与方法体就可以了

Lambda表达式

Lambda表达式实际上是一种更简洁的,用于编写匿名函数的写法。

使用匿名方法或Lambda表达式来定义回调函数,可以简化代码。回调函数部分的示例可以使用Lambda表达式来改写:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
using System;

public class Program
{
public static void PerformTask(Action<int> callback)
{
Console.WriteLine("正在执行任务...");
System.Threading.Thread.Sleep(2000);
int result = 42;
callback(result);
}

public static void Main()
{
PerformTask(result =>
{
Console.WriteLine($"任务完成,结果是:{result}");
});
}
}

基本语法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
(parameters) => { statement }

(没有参数、一个参数或多个参数) => { 语句 }

// 不同的参数的个数,语句的行数都有对应的简便写法
// 参数有多个时,需要用括号括起来
(x, y) => x + y

// 参数只有一个时,不需要括号就行
x => x*x

// 没有参数时,写个括号
() => Console.WriteLine("Ciallo~(∠·ω< )⌒★")

// 只有一行语句时,不需要代码块,不需要写 return 便会自动返回
x => x*x

// 有多行语句时,需要代码块括起来,如果需要返回值,需要显式使用 return
(x, y) =>
{
int sum = x + y;
return sum;
}

至于具体如何在代码里使用,这里推荐一下上一篇文章:如何写出符合要求的Lambda表达式

数组

数组是引用类型,数组是一种数据结构,用于存储相同类型的数据元素的集合。在 C# 中,数组可以是单维、多维或交错(锯齿状)的。

对于单维数组(例如 int[]),数组元素在内存中是连续存储的。这意味着数组的第一个元素在内存中的位置紧跟着数组的第二个元素,以此类推。

定义方式

1
2
3
4
5
6
7
8
9
10
数据类型1[] 数组名;
数据类型1[] 数组名 = new 数据类型1[数组长度];
数据类型1[] 数组名 = new 数据类型1[]{对应类型的数据1,对应类型的数据2,对应类型的数据3 ... 对应类型的数据N}
数据类型1[] 数组名 = {对应类型的数据1,对应类型的数据2,对应类型的数据3 ... 对应类型的数据N}

// 如:来存储int类型
int[] a = {123,124,125};
// 等效于
int[] a = new int[]{123,124,125};
// 因此一般初始化并赋值时,可以直接使用上面的方式

访问方式:通常我们使用索引(也称下标)来访问数组的元素,索引从0开始,0表示第一个元素。

1
2
int[] a = {123,124,125};
// 第一个元素 a[0] = 123; 最后一个元素a[2] = 125;

经常用到的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
int[] a = {123,124,125};

// 获取数组长度
// 数组.Length 获取的是该数组的元素总数,但如果要获取每个维度的长度, 请使用 数组.GetLength();
int length = a.Length;

// 数组排序
Array.Sort(a);

// 查找数组元素的位置
int index = Array.IndexOf(a, 123); // 查找元素 123 的索引

// 输出数组的每个元素
// foreach
foreach (int number in a)
{
Console.WriteLine(number +" ");
}

// for i
foreach (int i = 0; i < a.Length, i++)
{
Console.WriteLine(number[i] +" ");
}

// 简单输出数组的每个元素的方式:
Console.WriteLine(string.Join(",",a)); // 123,124,125

多维数组

声明和初始化

1
int[,] matrix = new int[3, 3]; // 声明一个3x3的二维数组

声明并赋值

1
2
3
4
5
int[,] matrix = { 
{ 1, 2, 3 },
{ 4, 5, 6 },
{ 7, 8, 9 }
}; // 声明并初始化二维数组

访问与修改数组元素

1
2
matrix[0, 0] = 10; // 修改第一行第一列元素的值
int element = matrix[0, 0]; // 访问第一行第一列元素的值

获取不同维度的长度

1
2
3
4
5
6
7
8
9
10
11
12
13
// 使用 GetLength() 来获取不同维度的长度
int[,] matrix = {
{ 1, 2, 3 },
{ 4, 5, 6 },
{ 7, 8, 9 },
{ 10, 11, 12 }
};

Console.WriteLine(matrix.GetLength(0)); // 第一维度的长度
Console.WriteLine(matrix.GetLength(1)); // 第二维度的长度

// 数组.Length 获取的是该数组的元素总数
Console.WriteLine(matrix.Length);

对于多维数组(例如 int[,]),元素在内存中也是连续存储的。C# 使用行优先(row-major)顺序来存储多维数组,这意味着数组按行顺序存储,即所有第一行元素按顺序存储,然后是第二行,依此类推。

交错数组

声明和初始化

1
2
3
4
int[][] jaggedArray = new int[3][]; // 声明一个包含3个一维数组的交错数组
jaggedArray[0] = new int[2]; // 初始化第一个一维数组,长度为2
jaggedArray[1] = new int[3]; // 初始化第二个一维数组,长度为3
jaggedArray[2] = new int[4]; // 初始化第三个一维数组,长度为4

声明并赋值

1
2
3
4
5
6
int[][] jaggedArray = new int[][] 
{
new int[] { 1, 2 },
new int[] { 3, 4, 5 },
new int[] { 6, 7, 8, 9 }
}; // 声明并初始化交错数组

访问方式与多维数组一致。

对于交错数组(例如 int[][]),每个子数组都是单独分配的,因此子数组的内存地址不一定是连续的。但每个子数组本身是连续存储的。

数组相关API

方法名描述
Clear(Array, Int32, Int32) 清除数组的内容。设置为每个元素类型的默认值
Clone() 创建 Array 的浅表副本。浅复制
Copy(Array, Array, Int32) 将一个 Array 的一部分元素复制到另一个 Array 中,并根据需要执行类型转换和装箱。
CopyTo(Array, Int32) 从指定的目标数组索引处开始,将当前一维数组的所有元素复制到指定的一维数组中。
Exists(T[], Predicate) 确定指定数组包含的元素是否与指定的条件匹配。
Fill(T[], T) 将类型为 T 的给定 value 分配给指定的 array 的每个元素。
Find(T[], Predicate) 搜索与指定条件相匹配的元素,并返回整个 Array 中的第一个匹配元素。
FindAll(T[], Predicate) 检索与指定的条件匹配的所有元素。
FindIndex(T[], Predicate) 在 Array 或其某个部分中搜索与定义的条件相匹配的元素,并返回第一个匹配项的从零开始的索引。
FindLast(T[], Predicate) 搜索与指定条件相匹配的元素,并返回整个 Array 中的最后一个匹配元素。
FindLastIndex(T[], Predicate) 在 Array 或其某个部分中搜索与定义的条件相匹配的元素,并返回最后一个匹配项的索引。
ForEach(T[], Action) 对指定数组的每个元素执行指定操作。
GetLength(Int32) 获取该数组实例指定维度的元素数量。
IndexOf(Array, Object) 在一个一维数组或该数组的一系列元素中搜索指定对象,并返回其首个匹配项的索引。
LastIndexOf(Array, Object) 返回一维 Array 或 Array部分的值的最后一个匹配项的索引。
Resize(T[], Int32) 将一维数组的元素数更改为指定的新大小。
Reverse(Array) 反转一维 Array 或部分 Array 中元素的顺序。
Sort(Array) 对一维数组中的元素进行排序。
TrueForAll(T[], Predicate) 确定数组中的每个元素是否与指定条件匹配。

用到回调函数的方法

这部分主要写出,需要用到回调函数的几个方法的示例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
int[] arr = new int[] { 107,208,158,70,97,218,174,173,38,210 };

// Array.Exists 判断数组中是否含有满足指定条件的元素
bool exists = Array.Exists(arr, item => item > 200);
Console.WriteLine($"arr中{(exists ? "存在": "不存在")}大于200的数字");

// Array.Find()、Array.FindAll()、FindLast()、Array.FindIndex()、FindLastIndex() 查找方法
Console.WriteLine($"arr中第一个大于50且小于100的数:{Array.Find(arr, item => item > 50 && item < 100)}");

Console.WriteLine($"arr中所有大于50且小于100的数:{string.Join(",",Array.FindAll(arr, item => item > 50 && item < 100))}");

Console.WriteLine($"arr中最后一个大于50且小于100的数:{Array.FindLast(arr, item => item > 50 && item < 100)}");

Console.WriteLine($"arr中第一个大于50且小于100的数的索引为:{Array.FindIndex(arr, item => item > 50 && item < 100)}");

Console.WriteLine($"arr中最后一个大于50且小于100的数的索引为:{Array.FindLastIndex(arr, item => item > 50 && item < 100)}\n");

// Array.ForEach() 让数组每个元素都执行某段代码
Array.ForEach(arr, item => Console.Write(item + ","));
Console.WriteLine("\n");

// Array.Sort() 排序
Array.Sort(arr,(i,j) => i-j); // 正序排序,第一个参数-第二个参数
Console.WriteLine($"arr正序: {string.Join(",",arr)}");
Array.Sort(arr, (i,j) => j-i); // 逆序排序,第二个参数-第一个参数
Console.WriteLine($"arr倒序: {string.Join(",",arr)}\n");

// Array.TrueForAll() 数组中的条件是否全部满足某个条件
bool b = Array.TrueForAll(arr,item => item % 2 == 0);
Console.WriteLine($"arr里面的元素{(b ? "能" : "不能")}都被2整除");

// arr: 107,208,158,70,97,218,174,173,38,210
//
// arr中存在大于200的数字
// arr中第一个大于50且小于100的数:70
// arr中所有大于50且小于100的数:70,97
// arr中最后一个大于50且小于100的数:97
// arr中第一个大于50且小于100的数的索引为:3
// arr中最后一个大于50且小于100的数的索引为:4
//
// 107,208,158,70,97,218,174,173,38,210,
//
// arr正序: 38,70,97,107,158,173,174,208,210,218
// arr倒序: 218,210,208,174,173,158,107,97,70,38
//
// arr里面的元素不能都被2整除

接口

接口(interface)是一种定义一组方法和属性的契约,但不包含任何实现细节。接口可以被类或结构体实现,一个类或结构体可以实现多个接口。

  • 接口可以包含实例方法、属性、事件、索引器或这四种成员类型的任意组合。
  • 接口不能包含常量、字段、运算符、实例构造函数、析构函数或类型。
  • 不能包含静态成员。
  • 接口成员是自动公开的,且不能包含任何访问修饰符。
  • 类或结构实现接口时,该类或结构将为该接口定义的所有成员提供实现。
  • 接口本身不提供类或结构能够以继承基类功能的方式继承的任何功能。 但是,如果基类实现接口,派生类将继承该实现。 派生的类被认为是隐式地实现接口。

接口与类的关系,相当于合同与签订人的关系

接口与类合同与签订人
接口:定义一组方法和属性,但不不包含任何实现细节合同:表明双方约定的行为与责任
类:实现需要实现的所有内容,签名一致,其他随意。签订人:在遵照约定的前提下,随便做什么都可以。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
interface ITest
{
// 方法
void MyMethod();

// 属性
int MyProperty { get; set; }

// 事件
event EventHandler MyEvent;

// 索引器
object this[int index] { get;set; }
}

// MyTest类实现ITest接口
class MyTest : ITest
{
public void MyMethod()
{
throw new NotImplementedException();
}

public int MyProperty { get; set; }
public event EventHandler MyEvent;

public object this[int index]
{
get => throw new NotImplementedException();
set => throw new NotImplementedException();
}
}

显式实现与隐式实现

隐式实现是指类直接实现接口中的成员,并且这些成员在类的外部可以直接访问。隐式实现是最常见的接口实现方式。

例子同上

由于一个类或结构体一次可以实现多个接口,在这种情况下难免会遇到需要实现的成员名出现重复,但并不会提示你需要实现的成员出现重复,如果按照隐式实现的方式,两个接口在使用同一个实现的成员时可能会出现问题。

而显式实现则需要移除权限修饰符,并使用接口名.成员名的方式来书写成员名称部分。

显式实现会导致只能通过接口类型来调用这些被显示实现的成员,而不能通过类的实例直接访问,只能先转换为接口类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
namespace 测试2
{
internal class Program
{
static void Main(string[] args)
{
ITest test1 = new MyTest();
ITest2 test2 = new MyTest();
test1.MyMethod();
test2.MyMethod();
// new MyTest().MyMethod(); 报错找不到MyMethod();
}
}


interface ITest
{
void MyMethod();
}
interface ITest2
{
void MyMethod();
}

class MyTest : ITest,ITest2
{

void ITest.MyMethod()
{
Console.WriteLine("Hello World!");
}

void ITest2.MyMethod()
{
Console.WriteLine("Ciallo~(∠·ω< )⌒★");
}
}
}

结构

结构体(struct)是一种值类型(value type),用于组织和存储相关数据。

结构可以包含字段、属性、方法、事件、运算符、索引器和嵌套类型。

  • 值类型:结构是值类型,在栈上分配内存。结构的赋值操作是值的复制。
  • 无参数构造函数:结构不能包含无参数构造函数。所有字段都必须在构造函数中初始化。
  • 继承:结构不能从其他类或结构继承,也不能被继承(即结构是密封的)。结构可以实现接口。
  • 默认构造函数:结构有一个隐式的无参数构造函数,它会将所有字段初始化为默认值(例如,数值类型为0,布尔类型为false)。
1
2
3
struct 结构体名字{
...
}

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
struct MyStruct
{
// 字段
private int m;

// 属性
public int X { get; set; }

public int Y { get; set; }

// 方法
public static void SayHello()
{
}

// 构造函数
public MyStruct(int x, int y)
{
Y = y;
X = x;
MyEvent = null;
m = 0;
}

// 事件
event EventHandler MyEvent;

// 重载运算符
public static MyStruct operator +(MyStruct p1, MyStruct p2)
{
return new MyStruct(p1.X + p2.X, p1.Y + p2.Y);
}

// 索引器
public object this[int index]
{
get => index;
set => index = (int)value;
}

// 嵌套类型
struct MyStruct2{}
class MyClass{}
}

结构适用于以下情况:

  1. 表示轻量级对象:结构适合表示小型数据对象,如几何点、矩形、颜色等。
  2. 不可变性:结构通常设计为不可变的,这样可以避免对象状态的意外修改。
  3. 性能考虑:结构在栈上分配内存,相较于类在堆上分配和垃圾回收,结构的内存分配和回收成本更低。

枚举

枚举主要用于定义一组命名的常量。这些常量通常表示某种概念的不同取值,例如一周的天数、方向、状态等。使用枚举可以提高代码的可读性和可维护性。

1
2
3
4
5
6
7
8
9
10
11
12
13
enum 枚举名字
{
枚举列表
};

// 如
enum Season
{
Spring, // 0
Summer, // 1
Autumn, // 2
Winter // 3
}

枚举项的值为int类型,如果不给枚举项输入初始值,则默认枚举项从0 开始,其他的项依次加 +1

如果真要使枚举的值为long类型或者short类型,则如下

1
2
3
enum MyEnum : short{

}

枚举不能被实例化,只能通过枚举名称获取其中的一个枚举项

1
Season season = Season.Spring;

通过使用 [Flags] 特性,可以对枚举类型的值进行按位运算,从而允许一个变量同时包含多个枚举值。

1
2
3
4
5
6
7
8
9
10
11
[Flags]
enum MyEnum{
North = 1,
West = 2,
South = 4,
East = 8
}

public static void Main(){
MyEnum m = MyEnum.North | MyEnum.East;
}

注意事项

  1. 枚举值的定义:为了使位运算有效,枚举值通常定义为 2 的幂(即 1, 2, 4, 8, 16 等)。
  2. 组合使用:可以使用按位 OR (|) 操作符组合多个枚举值,并使用按位 AND (&) 操作符检查是否包含某个枚举值。
  3. 无重复值:每个枚举值应该是唯一的位掩码,这样可以避免重复和冲突。

枚举常用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 常用方法
// 将字符串转换为想要转换的枚举类型
// Enum.Parse(); Enum.TryParse();
// 下面的例子,即可以输入对应的枚举值,也可以输入枚举项的名字
Season season = (Season)Enum.Parse(typeof(Season),Console.ReadLine()); // input:0 output: Spring

// 获取枚举值对应的的枚举项的名字;Enum.GetName();
string name = Enum.GetName(typeof(Season),1); // Summer

// Enum.GetNames(); 获取指定枚举的所有枚举名字
string[] names = Enum.GetNames(typeof(Season));
Console.WriteLine(string.Join(",",names)); // Spring,Summer,Autumn,Winter

// Enum.GetValues(); 获取指定枚举的所有枚举值
Array values = Enum.GetValues(typeof(Season));
Console.WriteLine(string.Join(",",values));

附录

行优先

行优先(row-major order)是多维数组在内存中的一种存储方式。在这种存储方式中,数组按行顺序存储,即所有的第一行元素按顺序存储,然后是第二行,依此类推。

示例

考虑一个 2x3 的二维数组:

1
2
3
4
int[,] matrix = {
{ 1, 2, 3 },
{ 4, 5, 6 }
};

在行优先存储中,这个数组在内存中的布局如下:

1
1 2 3 4 5 6

每一行的元素是连续存储的,然后是下一行的元素。具体来说,matrix[0, 0] 存储在内存中的第一个位置,matrix[0, 1] 存储在第二个位置,matrix[0, 2] 存储在第三个位置,接下来是 matrix[1, 0] 存储在第四个位置,以此类推。

参考内容

参考内容:

Microsoft Learn .NET API

Microsoft Learn C#编程指南 早期版本

菜鸟教程 - C# 教程

ChatGPT

  • 标题: C# string,回调函数,数组,接口,结构,枚举
  • 作者: 日之朝矣
  • 创建于 : 2024-07-30 11:58:55
  • 更新于 : 2024-08-18 09:25:27
  • 链接: https://blog.rzzy.fun/2024/07/30/csharp-string-callbackFunction-array-interface-structure-enumeration/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论