使用 .NET Core 3 编程 Linux:第 3 章
学习通过 .NET Core 3 和 Visual Studio Code 编程 Linux:控制台应用程序入门
如果您是新读者,可以在以下位置找到本书的开头:
第 3 章 - 数据类型和基本控制结构
以下是本章将学习的一些内容:
- 编译器错误和警告
Main()
函数签名要求- 数据类型 (
int
,string
,float
) - 数据类型数组
- 如何从终端命令行获取输入
在学习编程的路上使用代码
有些书会教你关于语法、数据类型以及所有这些来教你一门语言,但在这本书中,我将向你展示如何使用代码,然后我们再讨论它为什么有效。在此过程中,你将学习语法、数据类型和其他内容,但你也会看到代码的实际运行。
让我们创建另一个新项目。打开你的终端,进入目标目录并运行以下命令:
$ dotnet new console -o SuperConsole
完成后,打开 Visual Studio Code (VSC) 并打开项目文件夹。
当你打开项目文件夹时,VSC 会注意到你正在打开一个基于 C# 的项目,它会在右下角弹出一个小窗口。
那是我们之前安装的 OmniSharp 插件(或扩展)。它看起来像下面这样:
由于我们希望激活该插件以帮助我们完成这个项目,你可以点击 [Yes] 按钮,它将为这个项目激活。
完成此操作后,返回并点击左上角的文件导航器图标,然后选择 Program.cs 文件。
当然,你再次看到这个项目有那一行代码,它将输出 Hello World
!
继续删除那行代码,然后我们将更多地讨论 main 函数。
重命名 Main 函数
删除代码行后,继续重命名 Main
函数,使其名为 MainX
,就像我在以下代码片段中所做的那样。如果你愿意,可以复制它:
static void MainX(string[] args)
{
}
保存更改并返回终端窗口,尝试运行程序。
$ dotnet run
学习处理错误
当你这样做时,你会得到一个错误。
一步一步地处理每个部分
当你看到所有红色文本时,可能会有点不知所措。这就是为什么最好仔细检查它,看看你能从中理解什么。
错误代码
它说 CSC : error CS5001
。这是 CSC(C Sharp 编译器)——它查看你的代码并将其转换为程序的工具——告诉你发生了它识别为 CS5001 的错误。
提供了具体的错误代码,以便于查找。一开始,错误代码对我们来说意义不大,但如果我们在 DuckDuckGo.com(我选择的搜索引擎)上搜索它,那么你可以获得更多详细信息。
这是微软官方文档:
正如我在上一章中所说,你的程序中必须有一个 Main
方法作为程序的入口点(起始点)。
什么是函数签名?
但是,你的 Main
方法名称可能正确,但签名不正确。
让我们回去将 MainX
改回 Main
。但这次,从函数开头删除 static 关键字。它现在看起来像下面这样:
void Main(string[] args)
{
}
更改完成后,保存文件并返回终端,再次尝试运行它。
$ dotnet run
你将得到相同的错误,尽管错误发生的两个原因略有不同。
编译器不能总是区分并给出错误发生的具体原因,你必须习惯于仔细查看为什么事情可能不起作用。
在这种情况下,Main 函数签名不太正确。
这个术语又出现了:函数签名。但是什么是函数签名呢?
函数签名由四部分组成:
- 函数名
- 函数参数类型
- 函数返回类型
- 函数修饰符
要确定你的 Main
函数是否正确,你必须确保这四件事都与 C# 语言定义的允许签名匹配。
在这种情况下,我们有正确的函数名。它是 Main
,并且必须是首字母大写,其余小写。
我们有正确的参数类型。在这种情况下,唯一的参数是一个 String
类型的数组,它看起来像:
string args[ ]
接下来,我们有正确的返回类型,即 void
。
关键字 void
表示函数实际上不返回任何内容。
最后,函数签名也必须具有相同的修饰符。在这种情况下,缺少函数修饰符 static
。**注意**:方法的 public
部分也是修饰符的一部分。我们稍后会了解更多关于这些修饰符的信息,但现在不用担心。
由于函数签名中缺少关键字 static
,CSC(C# 编译器)无法找到有效的 Main
方法。
重载函数:众多可接受的函数签名
如果你在微软关于 Main
函数的文档中进行更多挖掘,你会发现一个有效 Main
函数签名的列表。Main
函数被称为重载,因为它识别同一函数名的多个签名。随着本书的继续,你将了解更多关于重载函数的信息。
如你所见,所有有效的 Main
方法都具有 public static
修饰符。如果其中任何一个修饰符关键字被移除或更改,方法签名将不正确。
我们不会讨论所有其他签名,但现在你明白,对于有效控制台应用程序的 Main
方法如何创建,有一个明确定义的要求。
修复你的 Main 方法
继续将 static
关键字添加回你的方法,这样我们就不会一直收到错误。
public static void Main(string)
{
}
另外,最好继续构建并运行此程序,以确保所有内容都再次正确设置。
$ dotnet run
当然,该程序现在不输出任何内容,因此运行时不会发生任何实际情况。
现在,让我们开始研究控制台库。
更多关于 string args[]
模板项目提供的 Main
的一个参数是 string args[]
。
这是一个 string
数组。这意味着名为 args
的这个变量可以包含 0 到多个字符串。
Args 代表什么?
名称 args
意为“参数”。一些开发人员将函数接受的参数称为其“参数”。参数或参数是函数将接收并处理或使用的变量列表。
在这种情况下,由于它是 main
方法,这些是用户在启动应用程序时可以从终端传递给程序的值。
一个很好的例子是当我们使用 cd
命令更改目录并提供路径或目录名时。
$ cd ~
这会将我们带到用户主目录。~
是 cd
命令的参数。
另一个例子是 ls
命令。
如果我们要它将文件和目录分别列在一行上,那么我们给它提供 -l
参数。
这是 ls
本身和 ls -l
的示例。
Args[ ] 能做什么?
使用 args
数组,我们可以获取用户在启动程序时在命令行中键入的输入。这实际上很有趣。
让我们开始添加一些代码。我们想获取用户提供的参数数量。我们可以通过检查 args 变量的一个名为 Length
的属性来做到这一点。
OmniSharp 可以提供帮助
由于我们已经在 VSC 中安装并激活了 OmniSharp 扩展,我们将获得 Intellisense 代码完成功能。这意味着当我们开始键入代码时,该扩展实际上会运行一个程序来查看我们正在使用的 .NET 库,并获取一些可能对我们有用的信息。
这是一个例子。
当你键入 Console.WriteLine
时,扩展将查看库中的注释和参数名称等,然后它会在 VSC 中弹出信息。
你可以看到它开始尝试帮助我完成 Console.WriteLine
,但它停在了 Console.WriteLine
方法的一个重载上。看到上下箭头之间显示 02/18 了吗?这表示这是 18 个 Console.WriteLine
方法中的第二个重载。有许多 Console.WriteLine
方法接受许多不同的参数。
此外,请注意,我在键入 args.L
后立即拍摄了快照,OmniSharp 扩展告诉我存在两个可能的选择(Length
和 LongLength
)。我知道我想要 Length
,所以我可以单击列表中的项目或按
这是我的最终代码:
public static void Main(string[] args)
{
Console.WriteLine(args.Length);
}
有了这段代码后,请保存文件并切换回终端窗口并运行它。
$ dotnet run
你可以看到它报告用户没有发送任何参数。这是有道理的,因为我们没有添加任何参数。
再试一次,但这次添加两个参数。每个参数只是用空格分隔。
$ dotnet run first second
你可以看到它现在报告提供了两个参数。
使用字符串插值
我们的输出不太好,因为它只输出数字。让我们学习如何使用字符串插值器,它将允许我们创建一个包含变量值和描述性字符串的良好输出字符串。
让我们更改当前将行写入输出的代码,使其现在看起来像下面这样:
Console.WriteLine($"There are {args.Length} args.");
一个正常的字符串字面量是任何用两个双引号括起来的字符,如下所示:
"This is a string"
"I am also a string"
"Anything between double quotes"
但是,在我们需要在字符串中包含变量值并希望输出该值的情况下,我们在第一个双引号之前立即使用字符串插值器 $
。
这告诉编译器,每当它在双引号之间发现也在花括号之间的项目时,它应该从该项目检索值并将其替换到字符串中,以便该值而不是变量名显示在字符串中。在我们的例子中,字符串插值器将获取 args.Length
的值并将其替换到 string
中。
你的代码现在将如下所示。保存后,继续运行它。
public static void Main(string[] args)
{
Console.WriteLine($"There are {args.Length} args.");
}
该输出对用户来说更美观、信息更丰富,现在你知道如何在漂亮的格式化字符串中间向输出添加值了。我们将继续使用字符串插值,所以请留意它。
如果您现在想阅读更多关于插值的信息,请查看此链接。
让我们使用参数值
嗯,知道发送了多少个参数很好,但如果能以某种方式使用实际参数会更好。
仅在至少有一个参数时才尝试使用 args
最好只在有一个或多个参数时才尝试使用 args。
为此,我们可以使用 if
语句。if
语句允许我们通过仅在某个条件为 true
时才执行某操作来控制程序。
例如,如果 args.Length
大于零,那么让我们做一些事情。
要将其编写为代码,你必须使用 if
语句的正确 C# 语法(语言规则)。
它看起来像下面这样:
if (predicate){
// execute functionality between curly { } braces.
}
谓词是用于确定它是否等于 true
的语句。
在我们的例子中,我们想确定 args.Length > 0
,所以我们像下面这样编写代码:
if (args.Length > 0){
Console.WriteLine($"The first parameter is : {args[0]}");
}
现在整个代码列表如下:
进行更改并保存文件,然后我们再次运行程序,但这次不带参数。
$ dotnet run<span id="cke_bm_138E" style="display: none;"> </span>
由于没有传入参数,并且代码检查了 args.Length > 0
,因此新 if
语句中的代码不会运行。
现在再次尝试,但这次带上 1 个或更多参数。
$ dotnet run one two
你可以看到 if
语句中的代码现在确实运行了。
让我们更多地讨论代码实际做了什么,并讨论这种被称为数组的特殊事物。
字符串数组
首先,C# 会跟踪我们正在使用的变量类型。这意味着我们必须告诉它变量将保存的数据类型。
C# 被认为是一种强类型语言,这意味着它总是希望知道我们创建的每个变量中数据类型。
这是对开发人员的一种保护,这样她就不会意外地将错误的类型存储在变量中,然后尝试对变量执行错误的类型操作并得到奇怪的结果。
我是这个意思。
声明变量类型
当我们在程序中创建变量以供使用时,我们需要做两件事:
- 命名变量
- 定义将存储在变量中的数据类型
要设置一个新的 string
变量以供使用,我们可以简单地执行以下操作:
string firstName;
string lastName;
变量名
现在程序会明白你有两个不同的桶来放置字符串。
名称是为开发人员提供一种访问变量的方式,以便她可以在其中存储值。这可能看起来很明显,但如果你没有一个可以在整个程序中使用的名称,那么你就无法再次引用该变量。
名称是一个内存地址
请记住,程序本身并不真正关心名称。在程序内部,名称实际上是一个内存地址。
在 C# 中,你可以轻松地执行以下操作:
string firstName = "Ted"
然而,C# 将其转换为类似:
0x00FC53 ["Ted"]
那是一个内存地址,字符串存储在那里。显然,知道 firstName
保存着表示一个人名字的值比记住随机的十六进制内存值要容易得多。
以前,程序员会查看内存地址,因为他们没有这些高级语言,无法像我们现在这样使用变量名代替内存地址。这就是 C# 为你做的事情。它是一种高级语言,允许你使用自然语言(英语、西班牙语等)来指代程序中的事物,而不是某种必须自己跟踪内存地址的机器语言。
这是学习 C# 等高级语言的首批优势之一。
编译器和构建程序
这也解释了为什么在运行程序之前必须构建它。CSC(C# 编译器)运行并检查高级语言 C#,并将其转换为 CPU(中央处理器)理解的语言。**注意**:从 C# 到处理器实际上还有另一个中间步骤,但这里我正在简化事情。
让我们将这两个变量名声明添加到我们的程序中。
我只是将它们添加到了 Main()
函数的顶部,这样我们就可以根据需要使用它们。
请注意,此时它们仅被声明用于使用,因为它们有名称和类型,但它们没有值。
添加这些,保存文件并再次运行程序。
$ dot net run one two
当你运行程序时,你会得到两个编译器警告。
编译器告诉你,你声明了它们但从未使用过它们。这不会阻止你的程序运行。如果它是一个错误,编译器会阻止你。但由于它只是一个警告,它允许你的程序运行。
这个警告只是一个健全性检查,因为编译器想知道为什么你将从未使用的东西添加到程序中。删除多余/未使用的代码是一个最佳实践,所以它会让你知道这些类型的事情。
使用字符串变量
要使用 string
变量,我们所要做的就是将其设置为一个值。
我们可以使用字符串字面量(任何用双引号符号括起来的字符)来初始化变量。
你可以在声明变量的同一行初始化变量,也可以稍后初始化它。
我修改了代码,现在它看起来像下面这样:
string firstName = "Ted";
string lastName;
lastName = "Lastonian";
如果你再次运行,你会看到编译器再次给我们一个警告,但它略有不同。
它现在警告我们变量确实被赋值了,但值从未使用过。编译器有时可能像一个脾气暴躁的老头,对一切都发出警告。
理解错误和警告:更好的程序员
然而,编译器只是试图让一切正常运行,你越习惯于理解警告和错误的含义,你就会成为越优秀的程序员。
这种能力可能是区分专业开发人员和业余开发人员的主要因素之一。在现实世界的软件中,问题总是会发生,你越擅长处理它们,你的技能就越有价值。
你阅读编译器发出的晦涩错误和警告信息越多,你就能越好越快地确定问题所在。
让我们使用这两个变量来编写一些输出,然后你就会看到编译器不再抱怨了。
我们将添加这一行:
Console.WriteLine($"{firstName} {lastName} was here.");
整个源代码列表如下:
你可以看到我们正在使用字符串插值器创建我们的字符串。我们将每个想要输出的变量名都用它自己的一对花括号括起来。现在当我们运行它时,我们看到以下内容:
其他数据类型
现在我们知道 C# 支持字符串类型,但它还允许我们使用哪些其他类型呢?
以下是一些其他数据类型:
int counter; // integer
double patientTempCelsius; decimal value, floating point (double precision)
bool isDoorClosed; // boolean - true or false value
char middleInitial; // single character value
注释符号 //
请注意,在每一行的末尾,我都添加了两个斜杠,然后是对数据类型的注释。
这两个斜杠告诉编译器忽略该行的其余部分。
这意味着两个斜杠之后的所有内容都只是其他开发人员(或您自己以后)的注释。编译器将完全忽略这两个斜杠之后的所有内容,所以即使这些内容会导致错误,编译器也会忽略它,就好像这些字符根本不存在一样。
当您想删除代码行但将其保留在源文件中以备将来使用时,注释代码的能力也很好。
如果你不希望该行被程序考虑,只需在该行开头添加两个斜杠。
// int x = 50;
// Console.WriteLine("This will never appear in the output.");
开发人员选择数据类型
当我们设计程序来完成我们想要的自动化工作时,我们会选择数据类型。
我们根据将存储在其中的内容以及我们需要对数据执行的操作类型来选择数据类型。
如果我们需要向用户显示信息,我们会选择字符串类型。
如果我们需要在程序中计数某些事物,那么我们将选择整数类型。
如果我们需要存储项目的价格,以便以后可以对数字进行计算,我们需要一个浮点类型(用于存储与美元和美分相关的十进制数 - 1.99)。
例如,假设我们有以下内容:
float unitPrice = 2.04f; // notice the f after the numbers
int numberUnitsAvailable = 282;
double totalCost = unitPrice * numberUnitsAvailable;
Console.WriteLine($"Total cost is {totalCost}");
如果你运行那段代码,你会看到类似以下内容:
unitPrice
后面的奇怪的 f
(2.04)是我们添加的字面值,它是一种确保编译器将该值视为浮点值的方式。由于小数运算符(.)在其他地方使用,编译器可能认为您以不同的方式使用它,因此我们通过提供该 f
来明确告诉它我们正在将 2.04 用作 float
值。
按最大值选择数据类型
然而,我们也会根据值将增长到的大小限制来选择数据类型。
例如,上面声明为 int
的常规整数最大使用 4 字节内存。
四个字节是三十二位。一个四字节或三十二位数字只能保存范围内的有符号值:
-2,147,483,648 到 2,147,483,647
如果你需要存储大于 2,147,483,647(略超过 20 亿)的值,那么你需要选择下一个大小的整数,称为 long
。
你声明 long
的方式与声明 int
相同。
long federalDeficitDollars;
一个 long 是 8 字节(64 位),可以容纳更大(和更小)的数字:
-9,223,372,036,854,775,808 到 9,223,372,036,854,775,807
这超过 9 百亿亿。
我们将在后续内容中讨论更多数据类型,但这足以让您有一个大致的了解。
一个变量中的多个值:数组
到目前为止,我们能够在一个变量中存储一个值。
我们可以在一个整数变量中存储一个整数。在一个字符串变量中存储一个字符串。
现在你对数据类型有了更好的了解,让我们来谈谈一种数据结构,它允许你在一个变量中存储多个值。
数组是元素的列表。元素类型都将相同。
有时你希望将值列表一起存储,这将使在程序中迭代列表更容易。
有时,只是将所有元素放在一个名称下更方便,因为所有元素都是相同的数据类型,并且是相似的值。
string arg[]
数组就是一个完美的例子。
该项是用户发送到程序的项目列表。它们在程序命令行上提供,因此都相似。
你可以用你目前学过的任何数据类型(以及许多其他类型)创建数组。
要声明一个数组,你只需在类型之后和变量名称之前添加开/闭方括号。
int [] studentsPerClassCount;
float [] distances;
string [] lastNames;
编译器会理解你正在创建这些类型的数组,你将通过一个名称引用它们。每当你看到方括号时,你就应该开始考虑数组。
那是因为你也将使用方括号 []
从数组中获取值。
首先,让我们学习如何初始化数组中的值。
要初始化,你只需创建一个用花括号括起来的列表,其中每个值用逗号分隔。
int [] studentsPerClassCount = {30,23,17,31,34,12,58,32};
float [] distances = {3.23f, 17.389f, 38.37f, 1.23f};
string [] lastNames = {"smith", "jones", "jackson"}
一旦你的数组用值初始化完毕,你如何取出值来使用呢?
数组是索引的
每个数组只是同类型数据的列表。
每个数组项都存储在数组内的一个特定数组索引处。
获取特定索引处值的语法如下所示:
<arrayVariableName>[indexValue]
你提供数组变量名,后跟一个开方括号 [
、你要获取的项目的索引值,然后是一个闭方括号 ]
。
数组是基于 0 索引的
这意味着数组中的第一个项目是零。索引是基于起始数字 0,而不是 1。
因此,要获取上面 distances 数组中的第一个值,你只需执行以下操作:
Console.WriteLine(distances[0]);
这是此时的完整代码列表。
这是你运行程序时收到的输出。
最后一个输出 3.23 是 distances 数组的第一个元素。
我教你所有这些关于数组的知识,只是为了让你进入下一个部分。这是关于数组真正酷的部分。
遍历整个数组
你可以遍历整个数组。这意味着你可以运行代码,获取数组的每个元素并对其进行一些操作。
C# 语言和 C# 运行时具有一些智能。它们了解你的所有代码。它们知道你的数据类型是什么,但也明白特殊类型可以执行特殊操作。
C# 语言控制结构
C# 语言还提供了一些控制结构(if
语句,foreach
语句——我们即将学习,while
语句等等)。
这些控制结构允许您实际控制代码的运行方式。
你已经看到了一个控制结构的例子,就是我们之前处理的简单 if
语句。它控制 if
语句主体中的代码是否运行。
现在我们将看到一个新的控制结构,称为 foreach
语句。
ForEach 控制语句
foreach
控制语句与数组类型(称为集合)等特殊类型一起工作,以确定代码是否会运行。
在我们的例子中,我们只希望在数组包含一个或多个元素时才运行代码。
而且,如果我们的数组包含多个元素,我们希望控制结构运行多次。然而,我们也不希望它尝试访问超出数组元素数量的索引。那会导致我们的代码出错。
例如,如果我尝试获取 distances[30]
的值,就会得到一个错误。
distances 数组只包含 4 个元素,所以 distances[30]
未定义。
foreach
控制语句会检查所有这些,并确保它不会超出数组的边界。
这就是遍历目标数组中元素的确切数量的样子。
foreach (float f in distances){
Console.WriteLine($"value : {f}");
}
我们使用控制结构的名称 (foreach
),然后在开括号和闭括号 ()
内部,我们为 distances 数组中存在的每个 float
变量定义一个新的 float
,名为 f
。
这是 foreach
结构的通用版本:
foreach (type <variableName> in <array>){
}
如果你要遍历 string
数组,你会这样做:
foreach (string LName in lastNames){
Console.WriteLine(LName);
}
请注意,你声明了一个数组类型的新变量,然后在 foreach
循环结构内部,你使用新声明的名称。
再次,这是我们目前的整个代码列表。
这是我们的输出:
你可以看到每个距离和每个 lastNames
都输出到控制台。
所有这一切都是为了 Args
我们费了这么大的周折,只是为了向您展示如何获取 args
值。
args
是一个字符串数组,所以现在你知道你可以创建一个 foreach
循环并遍历用户在命令行中传入的每个项目。
让我们添加以下内容:
foreach (string cmd in args){
Console.WriteLine($"cmd: {cmd}");
}
我将 foreach
循环放在 Main
方法的更靠近顶部的位置,并且我将注释掉我们目前使用的测试代码,以便只有与 args
相关的部分才会实际运行。
这是我的代码现在的样子:
VSC 语法着色
你可以看到 VSC 为你做的语法着色将所有注释设置为绿色。这样,你就可以轻松地看到代码的哪一部分将运行,哪一部分只是注释。
当你运行代码时,你会看到类似以下内容(取决于你提供给程序的命令行参数)。
好处是,如果你不提供任何参数,那么 foreach
语句将理解 args
数组中没有元素,并且不会运行 foreach
语句的主体。
摘要
这是一个相当长的章节,你学到了很多关于控制台的基础知识以及 Main
函数的工作原理。你还学到了很多关于数据类型的基础知识,我们甚至还涉及了字符串以及如何从终端获取输入以及如何通过字符串插值进行一些漂亮的格式化输出。
历史
- 2020年8月16日:首次发布