前言:
此刻朋友们对“数据结构算法题答题格式”大体比较讲究,咱们都想要知道一些“数据结构算法题答题格式”的相关内容。那么小编同时在网摘上汇集了一些有关“数据结构算法题答题格式””的相关资讯,希望各位老铁们能喜欢,你们一起来了解一下吧!引用类型
引用类型是第二类主要的类型组。简单回顾一下,引用类型的变量并不直接包含数据,因为它只是存储了指向数据的引用,而数据实际上位于其他位置。在这个组中,你可以找到四个内置类型,分别是object、string、delegate和dynamic。此外,你还可以声明类、记录和接口。还存在可空的引用类型。所有这些类型将在本节中进行描述。让我们开始吧!
对象
Object类(对象别名)在System命名空间中声明,并在使用C#开发应用程序时扮演着重要角色。为什么?因为C#统一类型系统中的所有其他类型都直接或间接地继承自Object类。这意味着内置的值类型、内置的引用类型以及用户定义的值类型和用户定义的引用类型,都是从Object类派生出来的。
想象一个对象
如果你想更容易地理解对象类型,可以将其想象为“某物”。因为一切都是“某物”,所以一切都是对象。各种值类型和引用类型的代表都是对象。哦不——对象无处不在!;-)
让我们来看看一组适用于所有对象的方法:
• ToString 返回对象的字符串表示形式
• GetType 返回实例的类型
• Equals 检查对象是否等于给定的对象
• GetHashCode 使用哈希函数并返回其结果
由于 Object 类型是所有值类型的基类,这意味着可以将任何值类型的变量(例如,int 或 float)转换为对象类型,也可以将对象类型的变量转换为特定的值类型。这样的操作分别被称为装箱(第一个)和拆箱(另一个),如下所示
int age = 28;object ageBoxing = age;int ageUnboxing = (int)ageBoxing;
尽管前面的代码看起来很简单,但在解包时你应该小心。如果你尝试将ageBoxing强制转换为bool而不是int,代码仍然可以编译,没有任何错误。然而,它会在运行时因System.InvalidCastException错误而失败。附加的消息会告诉你,将System.Int32类型的对象转换为System.Boolean类型是不可能的。
字符串
通常有必要存储一些文本值。你可以使用System命名空间中的内置引用类型String来实现这一点,也可以使用string关键字。String类型是一系列Unicode字符。它可以有零个字符(空字符串)或一个或多个字符,或者字符串变量可以设置为null。
想象一个字符串
如果你想更好地可视化一个字符串,请看这个句子。这是一个字符串!你的名字也是一个字符串。暂时合上这本书,看看窗外。你街道的名字也是一个字符串。不仅如此——甚至车牌号也是一个字符串。这是你在开发应用程序时最常用到的类型之一,所以请仔细阅读这一章和这本书,因为这本书中的所有文本都是字符串!
你可以对字符串对象执行各种操作,比如连接。你还可以使用[]操作符访问特定字符,如下所示://
string firstName = "Marcin", lastName = "Jamro";int year = 1988;string note = firstName + " " + lastName.ToUpper() + " was born in " + year;string initials = firstName[0] + "." + lastName[0] + ".";
首先,声明了firstName变量,并将Marcin的值赋给它。同样,Jamro被设置为lastName变量的值。在第三行中,我们使用+操作符连接了五个元素,即firstName的当前值、一个空格、将lastName的当前值转换为大写(通过调用ToUpper方法)、"was born in"字符串(带有额外的空格),以及year的当前值。在最后一行中,使用[]操作符获取firstName和lastName变量的第一个字符,并与两个点号连接形成缩写——即M.J.——这些缩写被存储为initials变量的值。
Format方法也可以用来构造这个字符串,如下所示:
string note = string.Format("{0} {1} was born in {2}", firstName, lastName.ToUpper(), year);
在这个例子中,我们指定了一个包含三个格式项的复合格式字符串,分别是firstName(由{0}表示)、大写的lastName({1})和年份({2})。格式化对象被指定为方法的以下参数。
值得一提的是插值字符串,它使用插值表达式来构造字符串。要使用这种方法创建字符串,应该在"前加上$字符,如下例所示:
string note = $"{firstName} {lastName.ToUpper()} was born in {year}";
插值字符串配备了更多功能,例如指定带有对齐方式的插值表达式(例如,最小10个字符的正数,10值用于右对齐,以及最小10个字符的负数,-10值用于左对齐)或格式字符串(例如:F2用于小数点后有两位数字的浮点数,或:HH:mm用于以小时和分钟呈现时间)。
以下是一些示例代码:
string[] names = ["Marcin", "Adam", "Martyna"];DateTime[] dates = [new(1988, 11, 9), new(1995, 4, 25), new(2003, 7, 24)];float[] temperatures = [36.6f, 39.1f, 35.9f];Console.WriteLine($"{"Name",-8} {"Birth date",10} { "Temp. [C]",11} -> Result");for (int i = 0; i < names.Length; i++){ string line = $"{names[i],-8} {dates[i],10:dd.MM.yyyy} { temperatures[i],11:F1} -> { temperatures[i] switch { > 40.0f => "Very high", > 37.0f => "High", > 36.0f => "Normal", > 35.0f => "Low", _ => "Very low" } } "; Console.WriteLine(line);}
这个例子展示了一个简单的表格,其中包含三个人的体温,分别是Marcin、Adam和Martyna,以及他们的出生日期。使用了对齐方式(例如,-8)和格式字符串(例如,dd.MM.yyyy)。此外,应用了带有模式匹配和关系模式的switch表达式来呈现关于体温是否非常高(> 40.0f)、高(> 37.0f)、正常(> 36.0f)、低(> 35.0f)或非常低的额外信息。后者是最终情况,也被称为丢弃模式。这由`_`表示,并匹配所有其他值。
当你执行这段代码时,你将看到以下结果:
正如你所见,C#语言配备了各种可能性,即使是与字符串类型相关的。更重要的是,你可以结合不同的特性,比如字符串插值与switch语句和模式匹配,来创建易于理解和维护的代码。
然而,你应该记住字符串并不是一个典型的引用类型,它的行为与其他引用类型有所不同。当你使用==运算符比较两个字符串变量时,可以看到这种差异。在这里,如果两个字符串实例包含相同的字符序列,那么它们就是相同的,这与值类型的行为类似。
类
如前所述,C# 是一种面向对象的语言,支持声明类以及包括构造函数、终结器、常量、字段、属性、索引器、事件、方法和运算符在内的各种成员,还包括委托。此外,类支持继承和实现接口。静态、抽象、密封和虚拟成员也是可用的。更进一步,类的各个成员可以具有不同的访问级别,通过以下访问修饰符之一来指定:public(公共)、protected(受保护)、internal(内部)、private(私有)和file(文件)。这些访问修饰符与类一起提及,但你应该记住它们也可以用于其他一些类型。
想象一个类
如果你想可视化一个类,可以想象一辆交通工具。每辆交通工具都有些属性,比如品牌、型号、颜色、长度、宽度、高度和重量。交通工具可以执行一些动作,比如行驶一定的距离。你还可以定义更具体的交通工具变体,比如汽车、飞机和船只。每种都有与基础交通工具相同的属性,同时还有些额外的属性,比如汽车有车牌号和燃料类型(例如汽油、柴油或电动),飞机有门的开启动作。你还可以创建这样的类的实例——例如,你可以创建三辆不同型号和车牌号的汽车实例,以及创建一架飞机的实例。然后,你可以对这些实例执行动作。
下面是一个示例类的展示:
public class Person{ private string _location = string.Empty; public string Name { get; set; } public required int Age { get; set; } public Person() => Name = "---"; public Person(string name) { Name = name; } public void Relocate(string location) { if (!string.IsNullOrEmpty(location)) { _location = location; } } public float GetDistance(string location) => DistanceHelpers.GetDistance(_location, location);}
Person类包含一个私有字段_location,其默认值设置为空字符串(string.Empty),以及两个公共的自动实现属性(Name和Age)。在编写本书中展示的代码示例时,你会经常使用这些属性,所以让我们稍微停一下来解释它们。
每个属性都是类的一个成员,它提供了一种使用访问器读写属性值的机制:
• get 用于返回属性值
• set 用于为属性分配新值
• init 用于在对象构造时设置值,并防止后续修改
通过组合这些访问器,属性可以处于以下几种情况之一:
• 只读,有 get 访问器但没有 set 访问器
• 只写,有 set 访问器但没有 get 访问器
• 读写,同时具有 get 和 set 访问器
另一个有趣的功能是属性的必需变体,它由在访问修饰符之后紧跟的 required 关键字指定,如 Age 属性所示。它要求客户端代码初始化属性,如果属性应在使用类实例开始时初始化,那么标记属性为必需是一个好主意。还值得注意的是,属性具有访问级别作为访问修饰符之一,包括 public 和 private。
让我们更仔细地看看示例类:
• 它包含一个默认构造函数,该构造函数使用表达式体定义将Name属性的值设置为---。
• 它包含一个接受一个参数的构造函数,并设置Name属性的值。
• 它包含Relocate方法,该方法更新私有字段的值。
• 它包含GetDistance方法,该方法调用DistanceHelpers类中的GetDistance静态方法,并返回两个城市之间的距离,以公里为单位提供。辅助类的实现没有在前面的代码中显示。注意,如果你对如何创建计算城市间距离的实际机制感兴趣,你可以查看第8章,探索图,其中将提到图的这种应用。
你可以使用new运算符创建类的实例。然后,你可以对创建的对象执行各种操作,例如调用一个方法,如下所示:
Person person = new("Martyna") { Age = 20 };person.Relocate("Rzeszow");float distance = person.GetDistance("Warsaw");
随着C#语言仍在开发和改进中,以下版本中引入了一些新的令人惊叹的功能,这些功能也与类有关。例如,在最新版本中,你可以使用一些概念,这些概念使得大幅度减少代码量成为可能。以下代码展示了其中的一些:
public class Person(string name){ private string _location = string.Empty; public string Name { get; set; } = name; public required int Age { get; set; } public void Relocate(string? location) => _location = location ?? _location; public float GetDistance(string location) => DistanceHelpers.GetDistance(_location, location);}
前面的代码执行的功能几乎与之前的变体相同,但它甚至更短。如果你也喜欢这样的改进,请继续阅读——你将在本书的剩余章节中看到C#语言的各种可能性。
让我们继续进入下一节,这一节专门讲解记录。
记录(Record)
在C#语言的最新版本中,引入了另一种很棒的引用类型:记录。它们为你提供了封装数据和使用基于值的等价性的内置功能。
这是什么意思?如果两个记录的记录类型定义相同,并且两个记录中每个字段的值都相同,则这两个记录是相等的。这与类不同,在类中,只有当两个实例引用相同的数据时,两个相同的类实例才是相等的。可以使用record或record class关键字定义记录。
值类型记录也存在
值得注意的是,还存在record struct构造。这种构造代表了一个具有类似功能的值类型。然而,在本书中,我将只关注引用类型版本。当然,你也可以自己尝试另一个版本。
记录的一个优点是,由于编译器会自动为记录声明中提供的所有参数(称为位置参数)生成公共的init-only属性(称为位置属性),因此需要编写的代码量较少。还会创建一个与记录声明中的位置参数匹配的主构造函数,以及一个Deconstruct方法,该方法带有一组out参数,每个参数代表一个位置参数。这意味着这种数据类型是以数据为中心的,并且设计为不可变的,提供了简洁明了的语法。
想象一个记录
如果你想可视化一个记录,站起来,照照镜子,专注于你漂亮的T恤。它具有一些属性,比如尺寸(例如,S、M或L)、颜色(例如,白色或红色)和品牌。所有这些属性都是不可变的,所以你不能像不能改变你最喜欢的T恤尺寸一样改变它们,因为它已经生产出来了。所以,就像你漂亮的T恤是数据为中心的,它的属性是不可变的,它是记录的一个很好的代表。在镜子中对自己微笑,然后回到这本书的第一章继续阅读!
让我们来看看下面这个记录声明的例子:
public record Dog(string Name, string Breed, int Height, float Weight, int Age);
就这样!现在,你有了一个包含五个不可变属性(名称、品种、身高、体重和年龄)的记录,以及一个构造函数,它包含五个与记录声明上的位置参数相关的参数。你可以像下面这行代码所示的那样使用它来创建一个新的实例:
Dog rex = new("Rex", "Schnauzer", 40, 11, 5);
这很简单明了,不是吗?此外,您还可以使用编译器生成的ToString方法来使用内置的格式化功能,这在调试时是一个很好的特性,因为您可以轻松看到所有属性的值。要了解其工作原理,请添加以下代码行:
Dog { Name = Rex, Breed = Schnauzer, Height = 40, Weight = 11, Age = 5 }
正如你所见,记录的名称显示了出来,以及以下属性的名称和值。请记住,属性是通过get和init访问器定义的,因此它们的值可以被读取,但在初始化后不能被改变。所以,以下这行代码将会导致编译错误:
rex.Name = "Puppy";
如果你想要改变这种行为,你可以通过在记录声明中使用一个没有位置参数的记录来实现,但是通过使用标准语法定义特定的属性,如下面所示:
public record Dog{ public required string Name { get; set; } public required string Breed { get; set; } public required int Height { get; set; } public required float Weight { get; set; } public required int Age { get; set; }}
另一个有用的特性是清晰的非破坏性变异语法,它允许你创建一个实例的副本,并在属性值上进行一些修改。你可以使用with表达式来实现这一点,如下所示:
Dog beauty = rex with { Name = "Beauty", Height = 35 };
这里,基于Rex创建了一个代表美的实例。所有属性的值都取自Rex,除了在with关键字后面指定的Name和Height属性。你还需要记住,你可以调整位置属性和使用标准语法创建的属性,这些属性具有init或set访问器。在这种情况下,创建了一个浅拷贝,所以对于值类型,使用了一个副本,而对于引用类型,只复制了引用,因此源属性和目标属性将引用同一个引用类型的实例。
让我们来看看下面这行代码:
(string name, _, _, _, int age) = beauty;
你知道这里发生了什么吗?如果不知道,让我们回顾一下如何解构一个值元组。你也可以解构一个记录类型。在前一行中,你只获取了Name和Age位置属性的值,使用下划线字符表示的丢弃符号忽略其他属性,即_。正如你所见,记录类型配备了许多有用的功能,但还有更多,比如支持继承。
标签: #数据结构算法题答题格式