C--编程零基础入门指南-全-

C# 编程零基础入门指南(全)

原文:C# Programming for Absolute Beginners

协议:CC BY-NC-SA 4.0

一、做好准备

亲爱的读者,欢迎您开始学习编程之旅!电脑、平板电脑、手机和许多其他电子设备都是可编程的,会完全按照人类程序员告诉他们的去做。

编程是一个完全基于逻辑的世界。在这方面,它在人类活动中是独一无二的。如果你喜欢逻辑——例如,你喜欢解谜或者你习惯于寻找你周围有意义的秩序——那么你会喜欢编程。

C# 语言

在本书中,您将创建一些真正的程序,为此,您需要学习一种编程语言,这种语言为计算机提供指令。编程语言提供了计算机和人类之间的交互。它们足够严格,以至于绝对愚蠢的计算机可以理解它们,但它们也足够人性化,以至于程序员可以使用它们编写代码。

随着时间的推移,许多编程语言被创造出来,并且有许多在今天被使用。每种语言都有其优点和缺点。

对于这本书,我选择了 C# 编程语言,这是我职业发展和教学的首选语言。它大约有 20 年的历史,这意味着它的创造者在开发它时可以避免旧语言的已知缺陷。此外,它现在是一种经过时间考验的语言,不会轻易被一些新的时尚所取代。

C# 实际上是微软的旗舰语言。它是相当通用的——你可以用它来编写各种程序,从传统的控制台和桌面应用,通过网站和服务到移动开发,无论是商业还是娱乐。它最初诞生于 Windows,近年来迅速传播到其他平台,如 Linux、Mac、Android 和 iOS。

我希望你会有一个美好的时光,你会发现它在你未来的职业/业余生活中有很多用途!

这本书是给谁的

这本书主要是为那些没有或仅有有限编程知识的人准备的。为了从本书中获得最大收益,你应该熟练使用计算机——你应该能够安装程序,知道什么是文件或文件夹,等等。

但是,由于本书对所包含的主题进行了深入的覆盖,如果您是一名中级程序员或已经掌握了另一种编程语言并希望从 C# 开始,您也可能会从本书中受益。你将比完全的初学者更快地阅读这本书。

这本书与其他书有何不同

我写这本书是基于我 15 年来教授各种学生、教师、业余爱好者和其他人编程的经验。对他们中的许多人来说,这是他们第一次接触这个主题。我在工作时密切观察他们,多年来,我积累了大量关于人们如何学习、什么对他们来说容易、什么需要更多关注的信息。

在本书中,你将受益于这些知识。本书与同类书籍的不同之处主要体现在以下几个方面:

  • 解释的速度——也就是进入新话题的速度——要适当慢一些,这样你才不会在开始后不久就迷失了方向。专家作者的一个常见谬误是认为初学者的东西微不足道。事实上,这对他们来说是微不足道的。但不是给读者看的。我已经做了相当大的努力来避免这种情况,并花足够的时间在印心者认为容易的事情上。

  • 我相信,为了成功地掌握所有的新思想,你需要看到它们在略有不同的情况下反复使用,这就是你在这里找到的。写这些例子是为了让你循序渐进,强化你已经知道的东西,并且总是增加一点新的信息或观点。

  • 大量的例子让我即使停留在初级水平也能深入这个主题。许多入门书籍通过一两个教科书上的例子展示了一个新的概念,然后继续前进。这里不是这样的。所选的例子来自真实的编程。它们通常代表了我在开发真实软件时发现自己所处的各种情况的核心。我涵盖了提炼到初级水平的核心主题。

  • 我已经用捷克语写了几本编程书籍,并且发现许多读者更喜欢编码示例而不是解释性文本。这大概反映了我们这个信息超载的现代。这就是为什么我用简明的、面向任务的方法写了这本书。你会发现这里说的最少,做的最多。好好享受吧!

如何使用这本书

在我开始告诉你如何准备你的电脑之前,这里有一些关于你如何使用这本书以获得最大效用的提示:

  • 这本书包含许多练习。这些不是练习你已经学过的东西的任务。这些任务构成了这本书的主要内容。这意味着你不应该在阅读完任务后试图去解决问题。你应该做的是阅读这个任务是关于什么的,看看它的说明性截图,然后立即去研究它的解决方案。

  • 你不应该只是阅读解决方案。强烈建议你在电脑上输入它们并让它们工作。如果你亲自尝试每一件事,这些练习会对你的理解产生更大的影响。

  • 如果你不能进行一些练习,你可以在 https://github.com/apress/csharp-programming-for-absolute-begs 查看附带的源代码。此外,你可能想在 http://modernizrogramovani.cz/en/ 访问我的网站。

  • 在每个任务中,试着理解其解决方案的逻辑。此外,尝试自己修改任务也很有帮助。不要害怕使用代码。这不是一个化学实验室;你不能炸掉你的房子!

  • 我在解决方案中加入了很多评论。实际上,代码的每个逻辑部分都有一个空行和解释其用途的注释。请密切注意这些评论;它们是主要的暗示,正好位于它们所解释的地方。只有更长的解释,我把它放在了代码之外,放在了书的正文中。

  • 在每一章的结尾,你可以放松下来,阅读它的摘要。然后,在进入下一章之前,你可以将它与你所学的主题进行比较。

在你的电脑上安装什么

介绍够了。让我们继续讨论如何准备,或者更确切地说,你应该如何准备你的电脑。

发展环境

要阅读这本书,你需要在电脑上安装一个程序,即所谓的集成开发环境(IDE)。

什么是 IDE?嗯,要在电脑上进行任何活动,你需要合适的软件。要写文字,你要用文字处理器;要浏览网页,你使用浏览器;等等。同样的,为了创建程序,你需要使用专门的软件来简化编程,这个软件就是开发环境。换句话说,它是一个“为编程而编程的程序”。

它通常被称为“集成”开发环境,因为它将程序员的所有活动——使用智能编辑器编写代码、将程序构建成计算机可用的形式、启动和测试程序、查看计算机内存等——都集中到一个地方,并提供工具帮助。

可视化工作室

对于 C# 来说,最好的开发环境是 Visual Studio。在撰写本文时,最新的生产版本是 2019 年,并在一个名为 Community 的免费版本中提供(见图 1-1 )。一分钟后,您将学习如何安装它。在本书中,我将使用 2021 年 1 月发布的 Visual Studio 版本,其中包括计划在下一个生产版本中发布的功能预览。NET 5 平台和 C# 9.0 语言等等。

img/458464_2_En_1_Fig1_HTML.jpg

图 1-1

Visual Studio 社区

Windows 版本

Visual Studio 需要 Windows 操作系统。如果你在网上搜索 Visual Studio 系统需求,你会发现支持的 Windows 版本(见图 1-2 )。

img/458464_2_En_1_Fig2_HTML.jpg

图 1-2

支持的 Windows 版本

如您所见,您不需要安装最新最好的 Windows 版本。截至 2021 年 1 月,您甚至可以安装带有 Service Pack 1 的 Windows 7。

此外,要使用这本书,你甚至不需要最新的 Visual Studio 版本。书中几乎所有的例子都可以在旧版本上很好地运行。

非 Windows 操作系统

如果您的计算机上没有 Windows 操作系统,您会很高兴听到 Visual Studio 代码开发环境。这是一个免费的多平台 IDE,也可以在 Linux 或 Mac 上运行,允许你在这些系统上用 C# 编程。

在本书的例子中,我将使用安装在 Windows 上的 Visual Studio 社区。我建议你也这样做。如果这对您来说不可行,请使用 Visual Studio 代码,同时考虑到有些内容可能与您在书中看到的略有不同。

装置

现在你知道要安装什么了——Visual Studio 社区——所以,请继续吧!将你的网页浏览器指向 http://visualstudio.com ,寻找类似“下载 Visual Studio”的东西,一定要选择“社区”版。单击按钮或链接,安装程序开始下载。

在安装过程中,会出现一个屏幕,显示您可以选择的不同组件(参见图 1-3 )。

img/458464_2_En_1_Fig3_HTML.png

图 1-3

安装 Visual Studio

一定要选择”。NET 桌面开发”并单击“安装”按钮。之后,安装应该会顺利运行。

免费注册

安装 Visual Studio 后,您应该在第一次启动它时注册您的副本(免费)。在相应的屏幕上,单击“登录”按钮并输入您的 Microsoft 帐户凭据。如果您还没有微软帐户,只需点击“注册”链接即可获得一个(见图 1-4 )。

img/458464_2_En_1_Fig4_HTML.jpg

图 1-4

注册您的 visual studio 副本

如果您在首次启动 Visual Studio 时跳过此步骤,您可以稍后通过选择“帮助➤在 Visual Studio 中注册产品”来注册。

更新和反馈

信息技术日新月异。在你读这本书的时候,有些事情可能会不同。如果 Visual Studio 出现了重要的变化,您可以通过 http://moderniProgramovani.cz/en/ 查看我的网站以获得最新信息。

此外,我欢迎任何关于这本书的反馈——你对它的改进建议,你学习它的感受,你用它教学的经验,等等。我的邮箱是vystavel@moderniProgramovani.cz。谢谢大家!

摘要

在这本书里,你将学习编程,特别是 C# 编程语言。您将通过许多实践练习学习用 C# 编写代码,这些练习将引导您走向越来越复杂的主题。为了能够按照练习进行,您应该按照以下方式准备您的计算机:

  • 您需要一台装有 Windows 操作系统的计算机(至少装有 Service Pack 1 的 Windows 7)。

  • 您需要安装一个合适的开发环境。在本书中,我将与免费的 Visual Studio 社区一起工作。

二、你的第一个程序

现在你已经准备好了你的电脑,让我们开始编程吧!在这一章中,你将用 C# 语言创建你的第一个程序,并学习你需要执行的所有步骤。

看到它的实际应用

在这一章中,你将创建一个显示信息“我开始用 C# 编程”的程序给用户(见图 2-1 )。

img/458464_2_En_2_Fig1_HTML.jpg

图 2-1

你的第一个程序

创建项目

你通过创建一个新的项目来开始每一个新的程序,所以让我们现在就开始吧。

启动 Visual Studio

启动 Visual Studio。应出现类似图 2-2 的开始屏幕。

img/458464_2_En_2_Fig2_HTML.jpg

图 2-2

Visual Studio 开始屏幕

向下拖动滚动条,选择“创建新项目”动作(见图 2-3 )。

img/458464_2_En_2_Fig4_HTML.jpg

图 2-4

选择项目模板

img/458464_2_En_2_Fig3_HTML.jpg

图 2-3

为新项目选择

创建新项目

出现的对话框(见图 2-4 )要求您选择一个新的项目模板。选择控制台应用 C# 模板,然后按下一步按钮。

在下一个对话框中(见图 2-5 ),键入“我的第一个程序”作为新的项目名称,并按下创建按钮。

img/458464_2_En_2_Fig5_HTML.jpg

图 2-5

输入新项目的名称

编写程序代码

最重要的步骤是编写程序代码,所以请继续阅读。

开发环境的外观

项目创建后,Visual Studio 看起来如图 2-6 。

img/458464_2_En_2_Fig6_HTML.jpg

图 2-6

Visual Studio 的外观

开发环境窗口的主要部分被源代码编辑器占据。在其中,Program.cs文件被打开,正如标签的标题所暗示的。Program.cs是新控制台项目的主文件。如您所见,它已经包含了一些源代码。

您可能想知道这些代码是从哪里来的。你还没有写任何一行代码!答案是,当您选择控制台应用模板时,Visual Studio 生成了代码。正如您在创建项目时看到的,Visual Studio 包含许多不同的模板;这些模板是不同类型程序的现成项目框架。

你可以看到代码中包含了一些奇怪的单词,比如usingnamespaceclass等等。我现在不打算解释这些,因为此时你不需要详细理解它们。但是 Visual Studio 需要这些行,所以不要管它们。你需要知道的是在哪里写你自己的陈述,这是我接下来要解释的。

知道在哪里写陈述

你在包含单词Main的行后的花括号中写程序语句(见图 2-7 )。

img/458464_2_En_2_Fig7_HTML.jpg

图 2-7

你写陈述的地方

在所有以前的 Visual Studio 版本中,大括号之间的空间是空白的。现在,IDE 创建者决定包含一行代码,您可以在以后删除或修改它。

编写代码

在这种情况下,在主行后的花括号中键入以下语句。确保准确地键入您在这里看到的。小写和大写之间的区别很重要,分号也很重要!****

// Output of text to the user
Console.WriteLine("I am starting to program in C#.");

// Waiting for Enter
Console.ReadLine();

Visual Studio 现在看起来如图 2-8 所示。

img/458464_2_En_2_Fig8_HTML.jpg

图 2-8

输入您的第一个代码语句

请仔细检查你是否和我在同一个地方输入了语句。同样,它们必须在括号之间。另外,要小心括号。不要意外删除其中任何一个。

Program.cs的源代码现在看起来像这样:

using System;

namespace My_first_program
{
    class Program
    {
        static void Main(string[] args)
        {
            // Output of text to the user
            Console.WriteLine("I am starting to program in C#.");

            // Waiting for Enter
            Console.ReadLine();
        }
    }
}

理解你的第一句话

这些语句有什么作用?

  • 向用户输出(写)一行。

  • Console.ReadLine一般来说,读取用户用键盘输入的一行文本。然而,在这种情况下,该语句的目的是让您的程序在一切完成后等待用户按 Enter 键。换句话说,您不希望程序窗口立即消失。

  • 两个斜线(//)后面的所有内容都将被忽略,直到对应行的末尾。此文本包含您的备注/评论。Visual Studio 将它们涂成绿色。

使用智能感知

您可能已经注意到,当您键入时,Visual Studio 会为您提供可用的可能性(参见图 2-9 )。您可以使用鼠标或使用箭头键然后按 Tab 键来选择一个选项。

img/458464_2_En_2_Fig9_HTML.jpg

图 2-9

使用智能感知

Visual Studio 中为您提供这些提示的部分称为智能感知。尽量习惯依赖它。这是避免不必要的错别字的最好方法。

保存项目

你已经写了几行代码,所以你可能想保存它们。根据 Visual Studio 的默认设置,项目会在每次程序启动前自动保存。但是,有时您希望手动保存更改。在这种情况下,从 Visual Studio 菜单中选择“文件”“➤”“全部保存”,或者单击 Ctrl+S

启动您的程序

写完程序后,你通常希望启动它,看看它是否“运行”,并检查它是否如你所愿。

做好准备。您的第一个计划发布的伟大时刻即将到来!从 Visual Studio 菜单中选择调试➤开始调试,或者直接按 F5 键。

Visual Studio 构建并启动您的程序(参见图 2-10 )。该程序输出指定的文本,并等待按下回车键,这正是你的编程方式。

img/458464_2_En_2_Fig10_HTML.jpg

图 2-10

启动您的程序

现在在用户的角色中,按回车键。程序终止,“黑窗口”消失。实际上,这是 Visual Studio 到目前为止的行为。在目前的版本中,增加了一个新的措施来防止控制台窗口消失,正如你在点击 Enter 键后所看到的那样(图 2-11 )。

img/458464_2_En_2_Fig11_HTML.jpg

图 2-11

防止窗户消失的附加措施

因为Console.ReadLine()防止窗口在 Visual Studio 之外运行时消失,我将在我们所有的程序中包含这个声明。如果您愿意,您可以根据图 2-11 的消息中给出的说明禁用新的 Visual Studio measure。

在 Visual Studio 中,选择“工具”“➤选项”菜单。在对话框中,点击调试组,然后勾选“调试停止时自动关闭控制台”选项,按 OK 确认(见图 2-12 )。

img/458464_2_En_2_Fig12_HTML.jpg

图 2-12

禁用新度量

注意

根据您计算机的默认设置,您的程序将以黑底白字显示,如图 2-10 所示。然而,为了更好的可读性,我将在浅色背景上以黑色字体显示所有后来的截图(见图 2-11 )。

更改文本大小

你认为输出的文本太小了吗?你需要放大你的程序将要使用的字体吗?

如果是,点击已启动程序“黑屏”左上角的标题栏图标,选择默认(见图 2-13 )。

img/458464_2_En_2_Fig13_HTML.jpg

图 2-13

选择默认设置

然后点击字体选项卡,根据自己的喜好更改字体,点击确定按钮确认更改(见图 2-14 )。

img/458464_2_En_2_Fig14_HTML.jpg

图 2-14

更改字体

下一个程序启动时,将使用新字体。

处理错误

如果您没有按照我向您展示的方式编写语句,或者您将它们写在了错误的位置,程序构建将会因错误而不成功地终止。

让我们试试这个!用Console.WriteLine语句删除行尾的分号。

当您尝试启动您的程序(通过按 F5 键)时,试验终止,出现错误对话框(参见图 2-15 )。

img/458464_2_En_2_Fig15_HTML.jpg

图 2-15

出现错误

在这个对话框中,总是点击 No;你不想运行某个旧版本的程序(如果它存在的话)。

点击否后,错误列表窗格出现在底部(见图 2-16 )。

img/458464_2_En_2_Fig16_HTML.jpg

图 2-16

错误表

把删除的分号退回去,一切又好了。以后可能更难发现自己做错了什么,尤其是在编程生涯的初期。没关系——我的观点是,除非你犯了所有可能的错误,否则你不可能成为某个领域的专家。

完成你的工作

您已经完成了程序开发的所有基本步骤。在你未来的每一个项目中,你都将沿着同样的路线前进。

你现在需要学习如何终止你的工作,以及如何在以后继续工作。前者简单;您可以通过从菜单中选择“文件”“➤”“关闭解决方案”或关闭整个 Visual Studio 程序来完成此项目的工作。

恢复您的工作

当您稍后想要回到您的项目时,可以使用下列方法之一在 Visual Studio 中重新打开它:

img/458464_2_En_2_Fig17_HTML.jpg

图 2-17

使用起始页

  • 从起始页:这是 Visual Studio 启动后立即出现的页面,包含指向您最近项目的链接(参见图 2-17 )。只需点击正确的选项。

img/458464_2_En_2_Fig19_HTML.jpg

图 2-19

显示扩展

img/458464_2_En_2_Fig18_HTML.jpg

图 2-18

用“打开项目”对话框打开程序

  • 从打开项目对话框:从 Visual Studio 菜单中选择文件➤打开➤项目/解决方案。打开项目对话框出现,您应该在其中找到您的项目(参见图 2-18 )。具体来说,就是查找扩展名为.sln的文件。如果看不到文件扩展名,请在 Windows 文件资源管理器中打开它们的显示(在“查看”选项卡上,选中“文件扩展名”复选框),如图 2-19 所示。

img/458464_2_En_2_Fig20_HTML.jpg

图 2-20

从文件菜单打开您的项目

  • 从最近的项目菜单:选择文件➤最近的项目和解决方案。Visual Studio 会记住您最近从事的项目。选择合适的项目即可(见图 2-20 )。

转移您的工作

您可能还对如何将项目从您的电脑传输到其他地方感兴趣,以便您以后可以在不同的电脑上处理它。答案很简单。如果您使用的是闪存驱动器、OneDrive 或类似设备,只需转移项目的整个文件夹。

使用解决方案浏览器

将项目转移到您应该熟悉的另一台计算机上有一个重要问题。有时,项目是在没有源代码编辑器的情况下打开的(见图 2-21 )。

img/458464_2_En_2_Fig21_HTML.jpg

图 2-21

在没有源代码编辑器的情况下打开项目

在这种情况下你会怎么做?有一个名为“解决方案资源管理器”的窗格(子窗口),通常位于 Visual Studio 窗口的右侧。只需双击你的源代码文件Program.cs(见图 2-22 )。

img/458464_2_En_2_Fig22_HTML.jpg

图 2-22

通过解决方案资源管理器打开源代码

连解决方案资源管理器都消失了吗?没问题!在任何时候,您都可以使用菜单选择视图➤解决方案资源管理器来显示它(参见图 2-23 )。

img/458464_2_En_2_Fig23_HTML.jpg

图 2-23

打开解决方案资源管理器

摘要

在本章中,您编写了自己的第一个程序,并开始学习如何使用 Visual Studio 开发环境。你经历了程序开发的所有步骤,基本上是这样的:

  • 创建项目

  • 编辑源代码

  • 保存源代码

  • 启动程序

  • 检测和排除可能的错误

  • 将您的项目转移到您的另一台电脑上,以便在其他地方使用**

三、处理输出

你已经知道了用 C# 语言开发程序时应该采取的所有主要步骤。此外,您已经看到了重要的语句Console.WriteLine,它在您的用户屏幕上显示数据。在本章中,您将扩展对这一陈述的了解。我还将向您展示输出的其他可能性。

产生数字输出

您已经知道如何显示一些文本。在本节中,您将学习如何显示数字。

工作

您将编写一个显示数字 37 的程序(参见图 3-1 )。

img/458464_2_En_3_Fig1_HTML.jpg

图 3-1

行动中的计划

解决办法

在 Visual Studio 中,创建名为 Numeric Output 的新项目。代码类似于您在第二章中编写的前一个程序,如下所示:

static void Main(string[] args)
{
    // Output of a number to the user
    Console.WriteLine(37);

    // Waiting for Enter
    Console.ReadLine();
}

Note

在本例中,以及本书中的所有后续示例中,我只向您展示了带有Main单词的行之后的代码块。这是您控制的代码块;换句话说,它是你改变的代码块。其余的Program.cs源代码应该保持原样,就像你在上一章的第一个程序中那样。

为了确保您理解我的意思,整个源代码看起来像这样:

using System;

namespace Numeric_output
{
    class Program
    {
        static void Main(string[] args)
        {
            // Output of a number to the user
            Console.WriteLine(37);

            // Waiting for Enter
            Console.ReadLine();
        }
    }
}

但是,同样,这是你最后一次看到完整的源代码。没有必要在我每次展示一个例子时都重复 Visual Studio 生成的代码,因为您永远不会更改它。如果您有疑问,可以参考本书附带的完整示例项目。

输入代码后,使用 F5 键启动程序。要终止程序,请按回车键。

讨论

与文本不同,数字不用引号括起来。

当然,你可以用引号将“37”括起来,但是数字 37 和文本“37”之间有很大的区别——你可以用数字来计算。这就是为什么你现在正在学习如何正确处理数字。

进行计算

接下来的任务是做一个简单的计算。

工作

你要向用户显示 1 加 1 是多少(见图 3-2 )。

img/458464_2_En_3_Fig2_HTML.jpg

图 3-2

1 加 1

解决办法

代码如下:

static void Main(string[] args)
{
    // Output of a calculation
    Console.WriteLine(1 + 1);

    // Waiting for Enter
    Console.ReadLine();
}

输入并启动程序!

注意

在编程中,这种计算(一般是值的组合)称为一个表达式

进行更复杂的计算

当然,你不需要一台电脑来做 1 对 1 的加法。但是 1 加 2 乘以 3 呢?你觉得这又是可笑的鸡毛蒜皮吗?请稍等片刻,因为即使在这种简单的情况下也很容易出错!

工作

您将创建一个程序来将 1 加 2 乘以 3。

解决办法

代码如下:

static void Main(string[] args)
{
    // Multiplication has greater priority
    Console.WriteLine(1 + 2*3);

    // Forcing priority using parentheses
    Console.WriteLine((1 + 2)*3);

    // Waiting for Enter
    Console.ReadLine();
}

启动后的程序如图 3-3 所示。

img/458464_2_En_3_Fig3_HTML.jpg

图 3-3

做更复杂的计算

讨论

请注意该程序的以下内容:

  • 此任务的目的是向您展示,您始终必须知道到底需要计算什么。在这个例子中,你必须决定是先做加法还是先做乘法。

  • 在基本的数学规则中,乘法和除法的优先级高于加法或减法。编程和数学是一样的。如果你首先想把 1 加到 2 上,然后乘以 3,你需要用括号把 1 和 2 括起来。

  • 我没有在乘法符号(星号)周围使用空格,但这与计算顺序无关。我觉得这样更好。在 C# 中,空格和换行符无关紧要。(当然中间不要断一个字。)

  • 最后,该示例显示计算机按照编写的顺序执行程序语句。这意味着自上而下。

连接文本

现在,您将发现加号运算符(+)也可以用于文本,而不仅仅是数字。换句话说,它将文本添加在一起。

工作

任务是探索如何将文本加在一起(见图 3-4 )。

img/458464_2_En_3_Fig4_HTML.jpg

图 3-4

连接文本

解决办法

代码如下:

static void Main(string[] args)
{
    // Normal text
    Console.WriteLine("I have started to program");

    // Also normal text
    Console.WriteLine(" in C#.");

    // Joining two texts using plus sign
    Console.WriteLine("I have started to program" + " in C#.");

    // Waiting for Enter
    Console.ReadLine();
}

注意介词中前的空格!

输出特殊字符

有时候,你需要输出一个特殊的字符到屏幕上。以下是一些例子:

  • 输出回车结束一行。

  • 输出引号。(C# 中的引号用作文本分隔符,因此必须特殊对待。)

  • 输出一个 Unicode 字符(当然,如果你的字体知道怎么画的话)。

工作

现在你要写一个程序来演示如何处理特殊字符。

解决办法

要处理特殊字符,可以使用转义序列。在 C# 中,转义序列以反斜杠开头。

static void Main(string[] args)
{

    // Multiline output
    Console.WriteLine("First line\r\nSecond line");

    // I prefer specifying "Enter" in more human form
    Console.WriteLine("First line" + Environment.NewLine + "Second line");

    // Text containing a quote
    Console.WriteLine("The letter started so sweet: \"My Darling\"");

    // Unicode characters, in this case Greek beta
    Console.WriteLine("If the font knows, here is Greek beta: \u03B2");

    // Backslashes themselves need to be doubled
    Console.WriteLine("Path to desktop on my computer: " + "C:\\Users\\vystavel\\Desktop");

    // Waiting for Enter
    Console.ReadLine();
}

结果应该如图 3-5 所示。

img/458464_2_En_3_Fig5_HTML.jpg

图 3-5

使用特殊字符

讨论

请注意该程序的以下内容:

  • 在 C# 中,文本中的反斜杠引入了所谓的转义序列。但是如果你想输出一个反斜杠呢?那你需要加倍。在 Windows 操作系统中处理文件路径时,经常会出现这种情况。

  • 控制台应用甚至会将简单的\n识别为行结束符(表示回车)。然而,在许多其他 C# 程序中,您需要“整个回车”,这用\r\n表示。这就是你在这个程序中使用它的原因。你还使用了Environment.NewLine,这绝对是最好的选择,因为它可读性很好。

使用预格式化文本

有时,你可能想一次显示多行文本(见图 3-6 )。

img/458464_2_En_3_Fig6_HTML.jpg

图 3-6

多行文本

工作

您将创建一个程序来显示多行文本。

解决办法

在文本的左引号前加上 at ( @)符号,如下所示:

static void Main(string[] args)
{
    // Bob Dylan...
    Console.WriteLine(@" Yes, and how many times can a man turn his head and pretend that he just doesn’t see?");

    // Waiting for Enter
    Console.ReadLine();
}

注意

at ( @)符号也关闭转义序列。这就是为什么在 Windows 中处理文件路径时你会发现它很有用(前面提到过);在这种情况下,您不必将每个反斜杠都加倍。

将 1 和 1 相加

在下一个任务中,你将回到 1 加 1 的问题。你想知道为什么我要回到如此琐碎的任务上吗?嗯,即使做一些简单的 1 加 1 的事情也会出错。让我想想。

工作

任务是探索将两个数字放在一起的不同方法(见图 3-7 )。

img/458464_2_En_3_Fig7_HTML.jpg

图 3-7

把数字放在一起

解决办法

代码如下:

        static void Main(string[] args)
        {
            // Pay special attention when mixing texts with numbers!
            Console.WriteLine(
@"Senior math test
==================

One and one is:");
            Console.WriteLine("a) " + 1 + 1);
            Console.WriteLine("b) " + (1 + 1));
            Console.WriteLine("c) " + "mostly fun");

            // Waiting for Enter
            Console.ReadLine();
        }

讨论

当您将数字与文本混合使用时,结果可能会与您预期的不同!

让我们考虑第一个答案(a)。计算机从左到右计算整个表达式。首先,它接受文本a)和一个数字(第一个 1)。它将它们连接在一起成为a) 1。然后,它获取这个新文本和最后一个数字(第二个 1),并再次将它们连接在一起以获得文本a) 11

第二个答案(b)不一样。圆括号使计算机首先执行数字的加法,然后才加入左边的文字。

有时,预先计算中间结果并将它们存储在变量中可能更透明。这是你下一章要研究的内容。当然,正如你将要看到的,变量有更多的用途。

摘要

在这一章中,您探索了Console.WriteLine语句为不同类型的输出提供的几种可能性。具体来说,您已经了解了以下内容:

  • 除了文本,您还可以在程序中使用数字。与文本不同,数字不用引号括起来。

  • 您可以将几个值组合成表达式。为此,您可以使用诸如+-*这样的操作符。对于数字,他们做普通的算术。加号运算符也适用于文本,在这种情况下,它将两段文本连接成一段。

  • 在计算中,你必须时刻注意计算结果的顺序。乘法和除法优先于加法和减法。若要强制不同的评估顺序,请使用括号。

  • 引号或换行符等特殊字符使用以反斜杠开头的转义序列输出。

  • 通过在多行文本前添加 at ( @)符号,可以方便地输出预格式化的多行文本。

四、使用变量

在这一章中,你将学习所有关于变量的知识。一个变量是计算机内存中一个程序可以存储东西的指定位置。可以是你想要的任何东西。事实上,你可以根据需要在你的程序中设置任意多的变量。

本章将从一些简单的例子开始,但最终你会发现变量对编程来说是绝对重要的。

存储文本

第一项任务将向您介绍变量。您将学习如何使用它们执行一些基本操作。

工作

您将创建一个名为message的变量。之后,您将在其中存储一些文本。最后,您将向用户显示变量的值。

解决办法

代码如下:

static void Main(string[] args)
{
    // Declaration of a variable to store text
    string message;

    // Storing a value in prepared variable (assignment statement)
    message = "I can't live with you.";

    // Another variable (initialized with some value)
    string anotherMessage = "I can't live without you.";

    // Output of variables
    Console.WriteLine(message);
    Console.WriteLine(anotherMessage);

    // Waiting for Enter
    Console.ReadLine();
}

讨论

现在我们来讨论解决方案。

变量声明

如果要使用一个变量,需要先声明(创建)它。

变量声明语句的一般语法如下:

类型名称【空间】变量名称【半结肠】

在这种情况下,它的内容如下:

string message;

类型表示您想要存储在变量中的值的类别。在这种情况下,您希望存储文本,这就是您使用名为string的类型的原因。

供选择的

还有一种方法可以编写变量声明语句。在分号前面,可以用等号和变量的初始值。

以下是该语法的一个示例:

string anotherMessage = "I can't live without you.";

赋值语句

代码中还有一点需要解释。第二个声明如下:

message = "I can't live with you.";

这存储了一个值(文本“我不能和你一起生活。”)在预备变量(message)中,它被称为赋值语句。当你想存放东西的时候,你可以用它。

赋值语句的一般语法如下:

  • WHERE(TO STORE)= WHAT(TO STORE);

存储数字

在下一个任务中,您将学习存储数字而不是文本的变量。

工作

您将创建(声明)一个名为number的变量。之后,你会在里面储存一些数字。最后,您将向用户显示变量的值。

解决办法

数值的数据类型称为int。严格地说,这是用于整数数(整数)的数据类型。不久你就会明白区分整数和小数在编程中的重要性。

static void Main(string[] args)
{
    // Variable for storing number (with initial value)
    int number = -12;

    // Output of value of the variable
    Console.WriteLine("Value of the variable: " + number);

    // Waiting for Enter
    Console.ReadLine();
}

不要忘记,输入的数字不带引号

将 1 和 1 相加

什么事?又把 1 和 1 相加?你可能认为我要疯了!

工作

在前一章中,我告诉过你,当把数字和文本结合起来时,变量可以给你提供更大的确定性。现在我回到这个建议。

解决办法

代码如下:

      static void Main(string[] args)
        {
            // Precalculation of result (into a variable)
            int sum = 1 + 1;
            // Output to the user
            Console.WriteLine(
@"Answer to Senior math test
=========================

One and one is: " + sum);

            // Waiting for Enter
            Console.ReadLine();
        }
    }

程序运行结果见图 4-1 。

img/458464_2_En_4_Fig1_HTML.jpg

图 4-1

1 加 1 计划的结果

讨论

请将 1 加 1 的计算与上一章的计算进行比较。这里,您将结果显式存储在一个变量中。这可以让你避免可能出现的评估顺序问题和得到 11 的错误答案。

使用变量进行计算

在下一个任务中,您将学习如何同时使用几个变量。

工作

你将在两个变量中存储一些数字。之后你会把他们的和算进第三个。

解决办法

代码如下:

static void Main(string[] args)
{
    // 1\. SOLUTION
    // Values to be summed
    int firstNumber = 42;
    int secondNumber = 11;

    // Calculating
    int sum = firstNumber + secondNumber;

    // Output
    Console.WriteLine("Sum is: " + sum);

    // 2\. SOLUTION
    // Declaring all variables at once
    int thirdNumber, fourthNumber, newSum;

    // Values to be summed
    thirdNumber = 42;
    fourthNumber = 11;

    // Calculating
    newSum = thirdNumber + fourthNumber;

    // Output
    Console.WriteLine("Calculated another way: " + newSum);

    // Waiting for Enter
    Console.ReadLine();
}

讨论

两个(备选)解决方案显示了您经常会遇到的两种情况:

  • 你声明一个变量,然后立即在其中存储一个值。

  • 你首先声明一个变量,然后在这个变量中存储一个值。

组装一个大组合

通常,您需要将几个值组合成输出。在本任务中,您将学习如何操作。

工作

我将通过一个足球比赛结果的例子向您展示如何组装复杂的文本(图 4-2 )。

img/458464_2_En_4_Fig2_HTML.jpg

图 4-2

大组合计划

在本例中,您有一些固定文本、一些(潜在的)可变文本和一些(潜在的)可变数字。这是典型的现实情况。

解决办法

要存储(潜在的)变量值,可以使用变量。当然,这些值在这个简单的程序中实际上是固定的,但是通常您会从其他地方获得它们(比如用户、文件、数据库或 web 服务)。你将在本书的后面学习如何从用户那里获得输入。

static void Main(string[] args)
{
    // Data in variables
    string club1 = "FC Liverpool";
    string club2 = "Manchester United";
    int goals1 = 3;
    int goals2 = 2;

    // Output of match result
    Console.WriteLine(
        "Match " + club1 + " - " + club2 +
        " ended with result " +
        goals1 + ":" + goals2 + ".");

    // Waiting for Enter
    Console.ReadLine();
}

讨论

在解决方案中,您应该特别注意以下几点:

  • 您使用不同数据类型的变量来存储不同种类的值。

  • 您正在从由八个加号连接在一起的九个部分构建显示的消息。信息的某些部分是固定的,而其他部分是可变的。

使用十进制数字

在编程中,你需要彻底区分整数和小数。你已经知道如何处理整数,所以现在你将看到小数。

工作

在这项任务中,我将向您展示一些如何处理小数的示例。

解决办法

在 C# 中,对于十进制数有一种叫做double的类型。代码如下:

static void Main(string[] args)
{
    // IN CODE, decimal separator is always DOT regardless of computer language settings
    double piApproximately = 3.14;

    // Pi is already available in C#
    double piMorePrecisely = Math.PI;

    // Decimal numbers have always limited precision
    double notCompletelyOne = 0.999999999999999999;

    // Outputs
    Console.WriteLine("Pi value from our code: " + piApproximately);
    Console.WriteLine("Pi value from C#: " + piMorePrecisely);
    Console.WriteLine("This should not be exact one: " + notCompletelyOne);

    // Waiting for Enter
    Console.ReadLine();
}

讨论

请注意以下几点:

img/458464_2_En_4_Fig3_HTML.jpg

图 4-3

十进制数字程序的结果

  • 在代码中,您总是需要使用小数点作为数字的整数和小数部分之间的分隔符。

  • 但是,输出取决于您的 Windows 设置。正如你在图 4-3 中看到的,我电脑上的输出使用逗号作为小数点分隔符,因为我的电脑设置为捷克语。

  • 你也可以看到十进制数没有无限的精度。它们在大约 15 个有效数字后四舍五入。

使用逻辑值

在编程中,你经常会用到逻辑值,也就是“是”和“否”的值。

工作

在这项任务中,我将向您展示如何使用逻辑值。

解决办法

在 C# 中,逻辑值的类型称为bool。值“是”写成true,值“否”写成false。下面是代码:

static void Main(string[] args)
{
    // Two logical (Boolean) variables
    bool thePrettiestGirlLovesMe = true;
    bool iAmHungry = false;

    // Use exclamation mark to negate logical value
    bool iAmNotHungry = !iAmHungry;

    // Output
    Console.WriteLine("She loves me: " + thePrettiestGirlLovesMe);
    Console.WriteLine("I am hungry: " + iAmHungry);
    Console.WriteLine("I am not hungry: " + iAmNotHungry);

    // Waiting for Enter
    Console.ReadLine();
}

讨论

请注意,每当您需要否定一个逻辑值时(将它从“是”翻转到“否”,然后再翻转回来),您都会使用感叹号。

摘要

在这一章中,你已经了解了变量的重要概念。在每一个真实的程序中,你都需要临时存储值(计算结果、用户输入等。)在计算机的内存中,这正是你使用变量的目的。变量是内存中的一个位置,它有一个引用它的名称和它的数据类型,以明确你将在其中存储什么类型的数据。

具体来说,您学到了以下内容:

  • 在使用变量之前,必须先声明它。合适的说法是string message;

  • 要在变量中存储一个值,可以使用赋值语句格式where = what;。比如message = "Some text";

  • 在 C# 中,文本的数据类型是string

  • 整数的数据类型是int

  • 在编程中,与通常的用法相反,必须注意区分整数和小数。

  • 十进制数的数据类型是double

  • 有一种称为bool的特殊数据类型,用于存储所谓的逻辑值truefalse,它们在计算机上相当于“是”和“否”

五、使用对象

类型为stringintdoublebool的变量总是包含一个值——文本、一个数字或一个是/否值。然而,这样的“原子”值可以被分组到称为对象的集合中。单个对象可以包含多个值,这些值被称为其组件成员。例如,分组可以发展到一个对象内部可以包含几个其他对象。在这一章中,你将学习关于物体的知识。

现在几点了?

您将遇到的第一个对象是一个DateTime对象,它包含单个时间实例的各种组件,比如日、月、年、小时、分钟、秒等等。

工作

您将编写一个向用户显示当前日期和时间的程序(参见图 5-1 )。

img/458464_2_En_5_Fig1_HTML.jpg

图 5-1

显示当前日期和时间

在这个任务中,你将了解DateTime类型的物体。

解决办法

代码如下:

static void Main(string[] args)
{
    // Variable of DateTime type, at first empty
    DateTime now;

    // Storing of current date and time into our variable
    now = DateTime.Now;

    // Output
    Console.WriteLine("Now is " + now);

    // Waiting for Enter
    Console.ReadLine();
}

今天是几号?

让我们进一步研究一下DateTime对象。

工作

假设您只对今天的日期感兴趣,不包括时间部分(见图 5-2 )。

img/458464_2_En_5_Fig2_HTML.jpg

图 5-2

仅显示日期

在许多程序中,今天的日期和当前的时间相差很大!

解决办法

代码如下:

static void Main(string[] args)
{
    // Variable of DateTime type, at first empty
    DateTime today;

    // Storing of today's date (without time component)
    today = DateTime.Today;

    // Output
    Console.WriteLine("Today is " + today);

    // Waiting for Enter
    Console.ReadLine();
}

使用日期组件

你可能想知道提到的对象的组件在哪里。让我们看看DateTime对象的组件。如果将一个DateTime类型的变量附加一个点,Visual Studio IntelliSense 会显示所有可能的可用组件。

工作

您将了解到DateTime对象的各种组件。

解决办法

代码如下:

static void Main(string[] args)
{
    // Current date and time (using single statement)
    DateTime now = DateTime.Now;

    // Picking up individual components
    int day = now.Day;
    int month = now.Month;
    int year = now.Year;
    int hours = now.Hour;
    int minutes = now.Minute;
    int seconds = now.Second;
    DateTime justDateWithoutTime = now.Date;

    // Output
    Console.WriteLine("Day: " + day);
    Console.WriteLine("Month: " + month);
    Console.WriteLine("Year: " + year);
    Console.WriteLine("Hours: " + hours);
    Console.WriteLine("Minutes: " + minutes);
    Console.WriteLine("Seconds: " + seconds);
    Console.WriteLine("Date component: " + justDateWithoutTime);

    // Formatting output our way
    Console.WriteLine("Our output: " +
        year + ", " + month + "/" + day +
        " " +
        hours + " hours " + minutes + " minutes");

    // Waiting for Enter
    Console.ReadLine();
}

图 5-3 显示了DateTime物体的组成部分。

img/458464_2_En_5_Fig3_HTML.jpg

图 5-3

日期时间对象的组件

使用名称空间

好了,现在你已经见到了你的第一个对象,我应该告诉你一些关于名称空间的事情。

重要用途

在最后一个项目仍然打开的情况下,使用两个斜杠注释掉Program.cs源代码中的第一行(using System;)(参见图 5-4 )。您也可以删除该行。然而,要回到最初的版本,更方便的是只注释掉这一行。

img/458464_2_En_5_Fig4_HTML.jpg

图 5-4

注释掉第一行

一瞬间,源代码中出现了大量的红色波浪。当您尝试使用 F5 键启动您的程序时,它将无法启动(参见图 5-5 )。

img/458464_2_En_5_Fig5_HTML.jpg

图 5-5

获取错误

提醒您一下,在出现的错误对话框中,总是单击“否”。

出现的错误列表窗格显示了大量错误——突然,Visual Studio“不知道”任何一个DateTimeConsole(参见图 5-6 )。

img/458464_2_En_5_Fig6_HTML.jpg

图 5-6

错误列表窗格

using线挺重要的吧?接下来我会解释原因。

名称空间

C# 中的几乎所有东西都属于某个更高层次的单元。在这种情况下,DateTimeConsole都属于System 名称空间。如果您想使用它们,您必须在源代码的顶部用一行using声明相应的名称空间。否则,Visual Studio 不理解它们。

为什么会有名称空间这样的东西?你要它们做什么?嗯,对象的名字并不是无限的,所以你需要指定你使用的是哪一个。例如,你不需要使用微软的DateTime类;你可以编写你自己的DateTime,或者你可以从另一个程序员那里购买一些美妙的DateTime。这就是为什么你需要一种方法来区分它们。这种方式是通过命名空间

每个对象类型都属于某个名称空间。例如,System名称空间是由微软“管理”的。如果我准备了自己的DateTime,我可能会把它放在RadekVystaveˇl.Books名称空间中。

好吧,也许没人需要自己做DateTime,但是还有更好的例子。例如,为具有图形用户界面的程序中的文本框控件准备的TextBox类存在于微软的至少四个版本中:

  • 对于 Windows 窗体技术中的桌面应用

  • 对于 WPF 科技的桌面应用

  • 对于 web 应用

  • 对于所谓的通用(面向触摸)应用

提到的每个文本框都属于一个单独的名称空间。

不使用

如果你现在删除你用来注释掉using System ;行的两个斜线,一切都会恢复到正常状态。然而,看看没有using的程序会是什么样子可能会很有趣,这也是你接下来要做的。

在您的源代码中,您需要用适当的名称空间,即System,用限定DateTimeConsole的所有出现的。从技术上讲,限定是通过在被限定的名称前面加上名称空间来执行的。

//using System;

namespace Date_components__without_using_
{
    class Program
    {
        static void Main(string[] args)
        {
            // Current date and time (using single statement)
            System.DateTime now = System.DateTime.Now;

            // Picking up individual components
            int day = now.Day;
            int month = now.Month;
            int year = now.Year;
            int hours = now.Hour;
            int minutes = now.Minute;
            int seconds = now.Second;
            System.DateTime justDateWithoutTime = now.Date;

            // Output
            System.Console.WriteLine("Day: " + day);
            System.Console.WriteLine("Month: " + month);
            System.Console.WriteLine("Year: " + year);
            System.Console.WriteLine("Hours: " + hours);
            System.Console.WriteLine("Minutes: " + minutes);
            System.Console.WriteLine("Seconds: " + seconds);
            System.Console.WriteLine("Date component: " + justDateWithoutTime);

            // Formatting output our way
            System.Console.WriteLine("Our output: " +
                year + ", " + month + "/" + day +
                " " +
                hours + " hours " + minutes + " minutes");

            // Waiting for Enter
            System.Console.ReadLine();
        }
    }
}

using s 更好,不是吗?

C# 9.0 极简程序

因为我们正在试验Main方法之外的东西,所以现在是了解 C# 9.0 创新的适当时机,它允许您省略“样板代码”,即namespaceclassMain行(当然,加上相应的括号)。

尽快回到本章中的“使用日期组件”,并再次在 Visual Studio 中打开适当的项目。

除了using行之外,删除所有不属于我们代码的内容。具体来说,您应该删除以namespaceclassMain开头的三行,加上三个左括号和三个右括号。您在 Visual Studio 中的代码编辑器将如图 5-7 所示。

img/458464_2_En_5_Fig7_HTML.jpg

图 5-7

省略“样板代码”

按 F5 键运行程序。起初,删除所有这些行似乎不是一个好主意(见图 5-8 )。

img/458464_2_En_5_Fig8_HTML.jpg

图 5-8

Visual Studio 抱怨 C# 版本

IDE 抱怨 C# 版本,至少在我用 Visual Studio 预览版的电脑上是这样。错误信息说我们需要 C# 9.0。因此,如果我们设法切换到 C# 9.0,我们的案例最终可能不会失败。

从 Visual Studio 菜单中,选择项目➤属性(参见图 5-9 )。

img/458464_2_En_5_Fig9_HTML.jpg

图 5-9

选择项目属性菜单

在出现的屏幕中,转到目标框架组合框并选择“”。NET 5.0”(见图 5-10 )。这将打开 C# 9.0。

img/458464_2_En_5_Fig10_HTML.jpg

图 5-10

改变目标

再次按 F5 键,程序现在应该可以顺利启动了。

使用环境对象

作为本章的总结,你将再看一眼你已经知道的Environment物体。从不同的角度看待事物是富有成效的。

工作

Environment对象包含关于程序“环境”的信息(例如,关于计算机和操作系统)。您已经看到了Environment.NewLine组件。现在,您将了解更多组件。

解决办法

代码如下:

static void Main(string[] args)
{
    // Displaying components of Environment object
    Console.WriteLine("Device name: " + Environment.MachineName);
    Console.WriteLine("64-bit system: " + Environment.Is64BitOperatingSystem);
    Console.WriteLine("User name: " + Environment.UserName);

    // Waiting for Enter
    Console.ReadLine();
}

与前面的程序相反,这里我没有将对象组件提取到变量中。我直接使用它们只是为了让你能看到使用它们的另一种可能的方式。

摘要

在这一章中,你已经熟悉了对象,它本质上是几个组件的聚合。与“原子”(单值)类型如intstring相反,对象通常包含许多值。

具体来说,您遇到了以下情况:

  • DateTime对象,可用于检索当前日期或时间

  • Environment对象,可用于检索程序的“环境”信息,如计算机名或用户名

您还学到了以下内容:

  • 对象可以存储在适当类型的变量中。

  • 一个对象的组件可以通过所谓的点符号来访问。您写下对象变量的名称并添加点,由于 Visual Studio IntelliSense,可用组件的列表会弹出。

  • 每个对象类型都属于某个名称空间。简单地说,名称空间可以被看作是相似对象类型的容器。

  • 一个重要的名称空间是System名称空间,它包含基本的对象类型,如DateTimeConsole

  • 您指出您想要使用一个特定的名称空间,在源代码的开头有一个using行。

  • 如果没有包含适当的using行,就必须完全限定类型名。这意味着您要在类型的名称前面加上名称空间的名称和一个点。

  • C# 9.0 允许您省略所谓的“样板”代码,即从项目模板中生成的代码,而不是由您编写的代码。

六、使用对象操作

从上一章你已经知道,一个对象是一种由几个“数据片段”组成的数据集合体您还知道,当您输入对象名称、点号和组件名称时,可以访问对象的单个组件。在这一章中,你会发现编程中的对象甚至更复杂。您将了解到,除了数据组件之外,对象还可以封装您可以使用相应对象执行的操作。通过几个任务,你将练习使用对象动作。

以文本显示月份

第一个任务将向您介绍可以使用DateTime对象执行的操作。

工作

你将编写一个程序,用文本显示当前日期,而不是用数字(或者,通常是长格式),如图 6-1 所示。

img/458464_2_En_6_Fig1_HTML.jpg

图 6-1

用文本显示当前日期和月份

您可以使用DateTime对象的ToLongDateString动作来完成这项任务。

解决办法

代码如下:

static void Main(string[] args)
{
    // Today's date
    DateTime today = DateTime.Today;

    // Output
    Console.WriteLine("Today is " + today.ToLongDateString());

    // Waiting for Enter
    Console.ReadLine();
}

讨论

请注意以下几点:

  • 当在 C# 中启动某个对象动作时,动作名称总是被括号(圆括号)所附加,即使它们之间没有任何内容。

  • 括号通常不是空的,而是包含一个参数(或多个参数),这是一些特定于动作的信息。例如,在Console.WriteLine动作的情况下,您在括号中指定想要显示的内容。

  • 您可以对对象执行的操作也被称为方法

  • ToLongDateString方法显示的月份名称取决于操作系统的语言设置。

明天展示

除了将日期转换成文本,对象还有更多可用的操作。日期运算尤其重要。

工作

你将编写一个显示明天日期的程序(见图 6-2 )。

img/458464_2_En_6_Fig2_HTML.jpg

图 6-2

显示明天的日期

解决办法

DateTime对象可以执行更多有趣的动作(方法),例如:

  • 使用AddDays进行日期运算

  • 使用ToShortDateString以短格式显示日期

代码如下:

static void Main(string[] args)
{
    // Today's date
    DateTime today = DateTime.Today;

    // Tomorrow's date
    DateTime tomorrow = today.AddDays(1);

    // Output
    Console.WriteLine("Today is " + today.ToShortDateString() + ".");
    Console.WriteLine("I will start learning on " + tomorrow.ToShortDateString() + ".");

    // Waiting for Enter
    Console.ReadLine();
}

显示特定日期

让我们继续讨论日期,看看什么是构造函数

工作

处理日期时,不必总是从今天的日期开始。你可以选择一些具体的日期(见图 6-3 )。

img/458464_2_En_6_Fig3_HTML.jpg

图 6-3

从特定的日期开始

解决办法

通过调用对象的构造函数,可以创建一个用特定日期初始化的DateTime对象。您输入new单词,键入名称(例如DateTime,并使用括号括起可能的参数。在这种情况下,参数是年、月和日。

static void Main(string[] args)
{
    // A specific date
    DateTime overlordDday = new DateTime(1944, 6, 6);

    // Output
    Console.WriteLine("D-Day (Overlord operation): " +
                        overlordDday.ToLongDateString() + ".");

    // Waiting for Enter
    Console.ReadLine();
}

滚动单个模具

约会够多了。现在你将学习如何处理偶然性或随机性。

工作

你将编写一个程序,将一个骰子“掷出”三次(见图 6-4 )。

img/458464_2_En_6_Fig4_HTML.jpg

图 6-4

滚动骰子

解决办法

要使用 chance,你需要一个随机数生成器。在 C# 中,您使用Random对象来实现这个目的。

在程序开始运行时,你首先通过调用其构造函数一次来创建一个Random对象,然后你重复调用其方法Next

static void Main(string[] args)
{
    // Creating random number generator object
    Random randomNumbers = new Random();

    // Repeatedly throwing a die
    int number1 = randomNumbers.Next(1, 6 + 1);
    int number2 = randomNumbers.Next(1, 6 + 1);
    int number3 = randomNumbers.Next(1, 6 + 1);

    // Output
    Console.WriteLine("Thrown numbers: " +
        number1 + ", " +
        number2 + ", " +
        number3);

    // Waiting for Enter
    Console.ReadLine();
}

注意

Next方法(动作)需要括号中的两个参数:

  • 生成数区间的下界

  • 上界增加 1 (对不起,我不是发明这种陌生感的人)

掷骰子

继续讨论随机数的话题,现在你将看到如何使用多个随机数序列。

工作

你将编写一个程序,将一对骰子掷出三次(见图 6-5 )。我会告诉你正确的方法和错误的方法。

img/458464_2_En_6_Fig5_HTML.jpg

图 6-5

掷骰子三次

解决办法

该解决方案的主要信息是使用单个随机数发生器。如果你几乎同时创建了两个,它们通常会生成相同的数字!代码如下:

static void Main(string[] args)
{
    // 1\. CORRECT SOLUTION
    // Creating random number generator object
    Random randomNumbers = new Random();

    // Repeatedly throwing two dice
    int correctNumber11 = randomNumbers.Next(1, 6 + 1);
    int correctNumber12 = randomNumbers.Next(1, 6 + 1);

    int correctNumber21 = randomNumbers.Next(1, 6 + 1);
    int correctNumber22 = randomNumbers.Next(1, 6 + 1);

    int correctNumber31 = randomNumbers.Next(1, 6 + 1);
    int correctNumber32 = randomNumbers.Next(1, 6 + 1);

    // Output
    Console.WriteLine("CORRECTLY");
    Console.WriteLine("Thrown couples: " +
        correctNumber11 + "-" + correctNumber12 + ", " +
        correctNumber21 + "-" + correctNumber22 + ", " +
        correctNumber31 + "-" + correctNumber32);

    // 2\. INCORRECT SOLUTION
    // Two random number generators
    Random randomNumbers1 = new Random();
    Random randomNumbers2 = new Random();

    // Repeatedly throwing two dice
    int incorrectNumber11 = randomNumbers1.Next(1, 6 + 1);
    int incorrectNumber12 = randomNumbers2.Next(1, 6 + 1);

    int incorrectNumber21 = randomNumbers1.Next(1, 6 + 1);
    int incorrectNumber22 = randomNumbers2.Next(1, 6 + 1);

    int incorrectNumber31 = randomNumbers1.Next(1, 6 + 1);
    int incorrectNumber32 = randomNumbers2.Next(1, 6 + 1);

    // Output
    Console.WriteLine(); // empty line
    Console.WriteLine("INCORRECTLY");
    Console.WriteLine("Thrown couples: " +
        incorrectNumber11 + "-" + incorrectNumber12 + ", " +
        incorrectNumber21 + "-" + incorrectNumber22 + ", " +
        incorrectNumber31 + "-" + incorrectNumber32);

    // Waiting for Enter
    Console.ReadLine();
}

获取桌面的路径

作为本章的结尾,你将学习另一个物体的动作。

工作

当您处理文件时,您可能希望在用户的桌面上创建一个文件。然而,每个人都有自己的桌面文件系统路径。我会告诉你如何找到那条路径(见图 6-6 )。

img/458464_2_En_6_Fig6_HTML.jpg

图 6-6

寻找道路

解决办法

可以用你的老朋友,Environment对象。代码如下:

static void Main(string[] args)
{
    // Finding path to the desktop
    string pathToDesktop = Environment.GetFolderPath(Environment.SpecialFolder.Desktop);

    // Output
    Console.WriteLine("Path to your desktop: " + pathToDesktop);

    // Waiting for Enter
    Console.ReadLine();
}

列举

要特别注意你对桌面感兴趣的方式。Desktop的值是被称为Environment.SpecialFolder枚举的值之一。

每当 Visual Studio 希望您输入枚举值时,它通常会为您提供相应的枚举。在这种情况下,当您从 IntelliSense 中选择GetFolderPath并随后键入左括号时,会立即弹出Environment.SpecialFolder枚举(参见图 6-7 )。

img/458464_2_En_6_Fig7_HTML.jpg

图 6-7

使用智能感知

使用 Tab 键选择提供的枚举,输入一个点,然后选择Desktop值。

摘要

本章的主要目的是向您展示对象是更复杂的实体,而不仅仅是数据组件的集合体。具体来说,它们通常包含方法,这些方法是您可以执行的操作,通常对对象的数据进行操作。

您已经熟悉了DateTimeRandomEnvironment对象的各种方法。具体来说,您学习了以下内容:

  • 使用ToLongDateStringToShortDateString方法将日期转换为文本

  • 日期运算的方法之一,即AddDays

  • 在指定范围内产生单个随机数的Next方法

  • GetFolderPath方法,可用于获取特殊文件夹的文件系统路径,如DesktopDocuments

您还了解了如何使用构造函数调用来创建对象。您输入了new单词,后面是对象类型的名称和括号。一些构造函数,比如Random对象,只需要空括号,而其他的,比如DateTime,需要你在括号之间指定一些值(年、月、日)。

在最后一个例子中,您还发现了所谓的枚举的用法,它本质上是一组预定义的(枚举的)值。Visual Studio 的 IntelliSense 在处理枚举时非常有用。

七、关于对象的更多信息

您已经看到一个对象充当数据聚集或容器。您已经看到了它所包含的数据作为它的属性,在对象名后面加上一个点就可以访问这些属性。访问一个属性意味着要么质疑它的价值,要么赋予它一个新的价值。

您还发现了(可能很多)动作可以与一个对象相关联。动作被称为方法,和属性一样,它们可以在对象名称后添加一个点来访问。此外,访问一个对象的特定方法需要在方法名中添加一对括号,括号内可能有参数值。访问一个方法(通常你会说调用它)意味着启动它实现的操作并执行它包含在里面的语句(在你不知道的情况下)。

这些是用对象编程的原则,它们是你在前两章中学习的基础。这一章将通过让你更深入地了解事物来充实你的知识。

现在让我们更深入地研究一下对象。

作为对象的文本

在 C# 中,即使是普通的文本也表现得像一个对象;你可以在文本上加一个点,这样就有很多可能性。让我们来看看。

工作

您将创建一个程序,显示文本的多个字符,将文本转换为大写,并检查文本是否包含特定的单词(图 7-1 )。

img/458464_2_En_7_Fig1_HTML.jpg

图 7-1

最终方案

解决办法

代码如下:

static void Main(string[] args)
{
    // Some text to try things on
    string text = "This is the last day of our acquaintance";

    // What e.g. can be done with texts
    Console.WriteLine("Original text: " + text);
    Console.WriteLine("Number of characters: " + text.Length);
    Console.WriteLine("In uppercase: " + text.ToUpper());
    Console.WriteLine("Does it contain word \"last\"? " + text.Contains("last"));

    // Waiting for Enter
    Console.ReadLine();
}

讨论

数据成员(如Length)没有括号,与方法(如ToUpperContains)相反,即使它们之间没有任何东西,也总是需要括号。

如何快速发现某个东西是否是方法(因此需要括号)?您可以通过查看智能感知中的紫色立方体或在工具提示中查找括号来完成此操作(参见图 7-2 )。

img/458464_2_En_7_Fig2_HTML.jpg

图 7-2

检查某个东西是否是一个方法

作为对象的数字

在前面的练习中,您看到了普通文本——一个类型为string的值——可以表现得像一个对象,并显示内部组件,如属性和方法。现在你会看到偶数可以表现得像物体一样,尽管它们的行为要稀疏得多。其实唯一值得一提的是转换成文本的动作。

工作

您将探索在一个数字变量上附加一个点后会弹出什么,并且您将学习如何将数字转换成文本。

解决办法

要将一个数字转换成它的文本表示,使用ToString方法。实际上,要将的任何东西转换成文本,C# 中总有可用的ToString方法(action)。

static void Main(string[] args)
{
    // Some number
    int number = 1234;

    // Conversion to text
    //string numberAsText = number; // DOES NOT WORK!
    string numberAsText = number.ToString();

    // Output
    Console.WriteLine("Output of number: " + number);
    Console.WriteLine("Output of text: " + numberAsText);

    // Waiting for Enter
    Console.ReadLine();
}

讨论

您可以看到类型为int的值不能直接赋给类型为string的变量。你得先把它转换成文本形式。

当然,在输出中,你看不到任何区别(见图 7-3 )。

img/458464_2_En_7_Fig3_HTML.jpg

图 7-3

输出

然而,很多时候你需要将一个数字转换成文本,而不是立即显示出来。然后,您将把值的文本形式存储在一个string类型的变量中,这就是您刚才看到的。

作为讨论的总结,我将告诉你为什么你看不到图 7-3 的两条线有任何不同:

  • Console.WriteLine方法将获得的所有内容转换成文本。它在幕后使用ToString转换来实现这一点。

  • 如果您使用加号将一些文本与一个数字连接起来,该数字会自动转换为 C# 中的文本。如果你想要更大的控制力,总是写下与数字相关的.ToString()

格式化数字

在上一个练习中,您忙于将数字转换成文本表示。然而,单个数字可以通过多种方式以文本形式表示。您现在将学习小数位数、舍入、千位分隔等等。

工作

在本练习中,你将看到几个使用ToString方法获得格式良好的数字输出的例子(见图 7-4 )。

img/458464_2_En_7_Fig4_HTML.jpg

图 7-4

格式良好的输出

解决办法

代码如下:

static void Main(string[] args)
{
    // Some money amounts and a number
    double amount = 1234.56;
    double anotherAmount = 789;
    int wholeNumber = 1234567;

    // Formatted outputs
    Console.WriteLine("Separating thousands and millions + money to cents");
    Console.WriteLine(amount.ToString("N2"));
    Console.WriteLine(anotherAmount.ToString("N2"));
    Console.WriteLine(wholeNumber.ToString("N0"));

    // Waiting for Enter
    Console.ReadLine();
}

与前面的练习相反,ToString方法调用现在在圆括号之间有一个参数。格式字符串指定了输出的样子。

在这里使用的格式字符串中,N表示需要千位分隔,二和零表示输出中的小数位数。

本地化输出

普通的数字格式(如前一个任务)根据 Windows 语言设置工作。但是,有时您不希望输出依赖于用户设置。您可能想要一个固定的语言设置,如美国、捷克或其他语言。

工作

在本练习中,您将学习两种不同语言风格的数字显示,捷克语和美语(见图 7-5 )。

img/458464_2_En_7_Fig5_HTML.jpg

图 7-5

两种不同的数字样式

如您所见,在捷克语中,您使用空格作为千位分隔符,使用逗号作为小数点分隔符。同样的逗号在美国格式中被用作千位分隔符,所以你可以想象让计算机决定使用什么语言(根据 Windows 设置)有时会导致混乱和不正确的程序行为。

解决办法

首先,在源代码顶部添加适当的using行(引用System.Globalization名称空间),如图 7-6 所示。

img/458464_2_En_7_Fig6_HTML.jpg

图 7-6

添加使用行

之后,像往常一样将代码输入到Main方法中:

static void Main(string[] args)
{
    // Whole and decimal number
    int wholeNumber = 1234567;
    double decimalNumber = 1234567.89;

    // Localization objects
    CultureInfo czech    = new CultureInfo("cs-CZ");
    CultureInfo american = new CultureInfo("en-US");

    // Localized output
    Console.WriteLine("Whole number - Czech: "    + wholeNumber.ToString("N0", czech));
    Console.WriteLine("Whole number - American: " + wholeNumber.ToString("N0", american));

    Console.WriteLine("Decimal number - Czech: "    + decimalNumber.ToString("N2", czech));
    Console.WriteLine("Decimal number - American: " + decimalNumber.ToString("N2", american));

    // Waiting for Enter
    Console.ReadLine();
}

结束语

为了在本书的范围内完成您对对象的了解,我将向您介绍更多的对象概念。如果你现在还不能完全理解它们,请不要担心。在你学习的这个阶段,仅仅了解他们是可以的。

静态对象

首先,我想让你们注意两种物体的存在。有DateTimeRandomCultureInfo等“经典”对象,也有ConsoleEnvironmentMath等“静态”对象。

你可以在你的程序中拥有任意多的经典对象。例如,变量todaytomorrow中有两个DateTime。变量randomNumbersrandomNumbers1randomNumbers2中也有三个Random

与经典对象相反,静态对象总是单一的——你只有一个Console,一个Environment,还有一个Math

此外,您总是按需创建经典对象,而静态对象在程序启动时就已经存在,无需您付出任何努力。

严格使用官方术语,我应该谈论“具有静态组件的类”而不是“静态对象”然而,我更喜欢后者,初学者友好的术语。近似美丽比完全丑陋要好。

班级

每一份关于物体的文件或教科书都大量使用了这个词。那么,这意味着什么呢?简单地说,是对象数据类型的同义词。你可以说“一个Random类的对象”,而不是“一个Random类型的对象”这意味着一个类也可以被看作是某一类对象的名字。

类和对象之间的关系

从另一个角度来看,类也是一个 C# 源代码,它定义了一个特定类型的对象包含什么以及它的行为方式。你也可以说类是对象的模板。比如Random类源代码(微软有,你没有)定义了所有 Random会有什么属性和什么方法。

因此,所有的Random对象都以同样的方式运行,因为它们都是从同一个模板或同一个类中创建的。所有的DateTime和所有的CultureInfo也是如此,以此类推。

换句话说,作为一个众所周知的面向对象编程准则,对象是一个类实例。单词 instance 表示一次实现或一次事件。

特殊班级

在本章中,您看到了文本、数字等也像对象一样。以下是它们对应的类:

|

数据类型

|

相应的类别

|
| --- | --- |
| string | String |
| int | Int32 |
| double | Double |
| bool | Boolean |
| ... | ... |

在 C# 中,可以交替使用stringStringintInt32等等。当然,您需要在源代码的顶部有一个using System;行,因为所有相应的类都属于那个特定的名称空间。

结构

在 C# 中,你也可能会遇到术语结构结构。什么是结构?

你可以把它们看作轻量级的类。在初学者的水平上,它们几乎与正常的类没有什么区别,所以你可能会在你长时间看到结构结构的地方简单地替换掉这个词。

例如,DateTime是一个结构的主要例子。然而,为了简单起见,在本书的每一处,我都将DateTime与普通的类如RandomCultureInfo同等对待。当你在本书的层次上工作时,你可能察觉到的唯一微妙的区别是DateTime对象不一定要被显式地创建,例如,通过一个构造函数调用。声明该类型的变量就足够了,尽管可能不实用。

摘要

在本章中,您学习了即使是普通的文本和数字也可以像对象一样工作。具体来说,您学习了以下内容:

  • Length属性,加上文本/字符串的ToUpperContains方法

  • ToString数字法

在后一种情况下,您还看到了由ToString方法生成的输出可以由格式字符串控制(如N2等)。)和语言规范(CultureInfo宾语)。将来,你会发现在 C# 中使用ToString方法绝对方便,不仅仅是数字。

您已经初步了解了对象编程术语。具体来说,请注意以下几点:

  • 与普通对象相反,静态对象总是存在于单个副本中。尽管你可以拥有尽可能多的DateTime,但你永远只有一个Console

  • 单词 class 是“对象数据类型”的同义词你会经常读到特定类的对象,这意味着“特定类型的对象”

八、输入

到目前为止,您的所有程序都在操作数据(数字、文本等),这些数据要么直接固定在源代码中,要么来自操作系统(日期、随机数等)。通常,程序从用户那里获取数据,这是你将在本章学到的。

文本输入

你将从最简单的例子开始学习输入。

工作

您将编写一个程序,接受用户的单行文本,并立即将输入的文本重复输出(见图 8-1 )。

img/458464_2_En_8_Fig1_HTML.jpg

图 8-1

完整的程序

解决办法

代码如下:

static void Main(string[] args)
{
    // Reading single line of text (until user presses Enter key)
    string input = Console.ReadLine();

    // Outputting the input
    Console.WriteLine(input);

    // Waiting for Enter
    Console.ReadLine();
}

当您使用 F5 键启动程序时,您会看到一个空屏幕。输入一个句子,并使用回车键将其发送到程序。

优质投入

在前面的程序中,用户可能不知道该做什么。你还没有告诉用户该做什么。在本练习中,您将改进输入过程。

工作

你将修改前面的程序,给用户一个提示,告诉他们应该做什么(见图 8-2 )。

img/458464_2_En_8_Fig2_HTML.jpg

图 8-2

改进的程序

解决办法

代码如下:

static void Main(string[] args)
{
    // Hinting user what we want from her
    Console.Write("Enter a sentence (and press Enter): ");

    // Reading line of text
    string input = Console.ReadLine();

    // Repeating to the output
    Console.WriteLine("You have entered: " + input);

    // Waiting for Enter
    Console.ReadLine();
}

讨论

Console.Write不会将光标移动到下一行,这与Console.WriteLine相反,到目前为止您一直在使用Console.WriteLine

数字输入

在前面的练习中,您参与了来自用户的文本信息的输入。现在你将转到数字上,它们同样重要。

工作

您将编写一个程序,从用户那里获取一个数字,将其存储在一个数值变量中,最后向用户重复该数字(见图 8-3 )。

img/458464_2_En_8_Fig3_HTML.jpg

图 8-3

从屏幕上读取一个数字

解决办法

总是读取文本,即使它的含义是一个数字。如果你想保存一个实数(例如,int类型的值),你必须使用Convert.ToInt32调用来制造它。

static void Main(string[] args)
{
    // Prompting the user
    Console.Write("How old are you? ");

    // Reading line of text
    string input = Console.ReadLine();

    // CONVERTING TO NUMBER (of entered text)
    int enteredNumber = Convert.ToInt32(input);

    // Output of entered number
    Console.WriteLine("Your age: " + enteredNumber);

    // Waiting for Enter
    Console.ReadLine();
}

讨论

严格地说,你实际上还不需要一个实数,因为你没有对这些数进行任何计算。但是,这将在下一个练习中改变。在这里,您探索了最简单形式的数字输入。

用输入的数字计算

现在,您将使用用户输入的值进行第一次计算。

工作

您将编写一个程序,接受用户的出生年份,然后计算他们的年龄(见图 8-4 )。

img/458464_2_En_8_Fig4_HTML.jpg

图 8-4

计算年龄

解决办法

以下是解决方案:

static void Main(string[] args)
{
    // Prompting the user
    Console.Write("Enter year of your birth: ");

    // Reading line of text
    string input = Console.ReadLine();

    // CONVERING TO NUMBER (of entered text)
    int yearOfBirth = Convert.ToInt32(input);

    // Finding this year
    DateTime today = DateTime.Today;
    int thisYear = today.Year;

    // Calculating age
    int age = thisYear - yearOfBirth;

    // Outputting the result
    Console.WriteLine("This year you are/will be: " + age);

    // Waiting for Enter
    Console.ReadLine();
}

还有十个

让我们继续计算。

工作

您将编写一个程序,从用户那里接受一个数字。之后,它显示一个比输入的数字大十的数字(见图 8-5 )。

img/458464_2_En_8_Fig5_HTML.jpg

图 8-5

给一个数加 10

解决办法

代码如下:

static void Main(string[] args)
{
    // Number input
    Console.Write("Enter a number: ");
    string input = Console.ReadLine();
    int number = Convert.ToInt32(input);

    // Calculating
    int result = number + 10;

    // Displaying the result
    Console.WriteLine("Number greater by ten: " + result);

    // Waiting for Enter
    Console.ReadLine();
}

添加

现在,您将进一步执行这一步骤,并使用来自用户的两个数字进行计算。

工作

您将编写一个程序,将用户输入的两个数字相加(见图 8-6 )。

img/458464_2_En_8_Fig6_HTML.jpg

图 8-6

将两个数相加

解决办法

代码如下:

static void Main(string[] args)
{
    // Input of 1\. number
    Console.Write("Enter 1\. number: ");
    string input1 = Console.ReadLine();
    int number1 = Convert.ToInt32(input1);

    // Input of 2\. number
    Console.Write("Enter 2\. number: ");
    string input2 = Console.ReadLine();
    int number2 = Convert.ToInt32(input2);

    // Calculating
    int result = number1 + number2;

    // Result output
    Console.WriteLine("Sum of entered numbers is: " + result);

    // Waiting for Enter
    Console.ReadLine();
}

输入不正确

在以前有数字的程序中,如果用户输入了数字以外的东西,程序会以一个运行时错误终止。然而,生产程序不应该这样。现在,您将学习如何处理运行时错误。

工作

在本练习中,您将修改之前的程序,使其正确处理来自用户的非数字输入(参见图 8-7 )。

img/458464_2_En_8_Fig7_HTML.jpg

图 8-7

为错误提供反馈

解决办法

让您的上一个项目保持打开状态,或者如果您已经关闭了它,请再次打开它。在接下来的内容中,您将编辑项目的Program.cs源代码;具体来说,您将在适当的位置插入一个try-catch构造。

用鼠标选中Main的整个内部,不包括最后一条语句(等待回车),如图 8-8 所示。然后,右键单击所选块中的任意位置,并从上下文菜单中选择“代码段”,然后选择“包围”。

img/458464_2_En_8_Fig8_HTML.jpg

图 8-8

选择环绕方式

在弹出的小窗格中,选择 Try(见图 8-9 )。

img/458464_2_En_8_Fig9_HTML.jpg

图 8-9

选择尝试

发生了什么

发生了什么事?Visual Studio 将选中的行包装到try块中,该块由单词try和一对花括号组成。它还在try块之后插入了一个catch块,其中包括单词catch和一对花括号。

捕捉部分的内部

删除catch块中的语句throw,并输入以下语句:

Console.WriteLine("Incorrect input - cannot calculate");

完全解

以下是完整的解决方案:

static void Main(string[] args)
{
    try
    {
        // Input of 1\. number
        Console.Write("Enter 1\. number: ");
        string input1 = Console.ReadLine();
        int number1 = Convert.ToInt32(input1);

        // Input of 2\. number
        Console.Write("Enter 2\. number: ");
        string input2 = Console.ReadLine();
        int number2 = Convert.ToInt32(input2);

        // Calculating
        int result = number1 + number2;

        // Result output
        Console.WriteLine("Sum of entered numbers is: " + result);
    }
    catch (Exception)
    {
        Console.WriteLine("Incorrect input - cannot calculate");
    }

    // Waiting for Enter
    Console.ReadLine();
}

测试

现在你可以测试你的程序的数字输入和无意义输入。

说明

try块中的语句以一种“试验模式”执行:

  • 当它们全部成功时,try块中的执行正常进行,之后跳过catch块。

  • 当一条语句失败时,跳过剩余的try块,而执行catch块中的语句。

摘要

在这一章中,你进入了一个新的编程技能水平。到目前为止,您只考虑了程序的输出。这里,您开始处理来自用户的输入,首先是文本输入,然后是数字输入。

具体来说,您学到了以下内容:

  • 使用Console.ReadLine方法调用从用户那里获得文本输入。

  • 在请求输入前向用户显示提示。为此,您使用了Console.Write方法,它与它的姐妹Console.WriteLine不同,它不终止一行。

  • 使用Convert.ToInt32方法将数字的文本输入转换成实际的数字表示,然后用它进行各种计算。

在最后一个练习中,您考虑了运行时错误的重要情况,比如非数字输入。您学会了使用try-catch构造来处理它们。该结构由两部分组成:

  • try块包围着“试验中”执行的语句如果一切顺利,try块不会改变任何东西,在它完成之后,程序的执行会在整个try-catch构造之后立即继续。

  • try程序块处理过程中出现错误时,catch程序块包围专门执行的语句。在有catch块的情况下,try块中失败的语句不会导致运行时错误和程序终止。相反,错误被“捕获”,并启动指定的替代操作。

九、数字

在前一章中,您学习了一般的输入,特别是数字输入。您还对用户输入的数字进行了一些简单的计算。在这一章中,你将更详细地了解数字。毕竟电脑之所以叫电脑,是因为计算频繁!

十进制输入

您将从从用户处读取一个十进制数的任务开始。

工作

您将编写一个程序,从用户那里接受一个十进制数,并立即在屏幕上重复它(见图 9-1 )。

img/458464_2_En_9_Fig1_HTML.jpg

图 9-1

最终方案

解决办法

整数和小数的输入有两个区别:

  • 您可以通过调用Convert.ToDouble方法将文本输入转换成相应的数字。

  • 要存储转换后的数字,可以使用类型为double的变量。

代码如下:

static void Main(string[] args)
{
    // Decimal input
    Console.Write("Enter a decimal number: ");
    string input = Console.ReadLine();
    double decimalNumber = Convert.ToDouble(input);

    // Repeating entered number to the output
    Console.WriteLine("You have entered number " + decimalNumber);

    // Waiting for Enter
    Console.ReadLine();
}

本地化数字输入

在上一个练习中,用户根据 Windows 语言设置输入小数点分隔符。这意味着英语中的小数点。然而,它在其他语言中可能有其他的意思。例如,在捷克语中,逗号用作小数点分隔符。

在当前的练习中,我将向您展示如何在特定的本地化环境中强制输入数字,而不管 Windows 设置如何。您在本书的前面做了一个关于本地化输出的类似任务;现在你要专注于输入。

工作

任务是写一个程序,读取一个十进制数,有两种固定的语言设置,美国和捷克。

解决办法

要处理特定的本地化,请使用CultureInfo对象。另外,请不要忘记在源代码的顶部插入using System.Globalization;行。

代码如下:

static void Main(string[] args)
{
    // AMERICAN
    CultureInfo american = new CultureInfo("en-US");
    try
    {
        // Input
        Console.Write("Enter American decimal number: ");
        string input = Console.ReadLine();
        double number = Convert.ToDouble(input, american);

        // Output
        Console.WriteLine("You have entered " + number);
    }
    catch (Exception)
    {
        // Error message
        Console.WriteLine("Incorrect input");
    }

    // CZECH
    CultureInfo czech = new CultureInfo("cs-CZ");
    try
    {
        // Input
        Console.WriteLine();
        Console.Write("Enter Czech decimal number: ");
        string input = Console.ReadLine();
        double number = Convert.ToDouble(input, czech);

        // Output
        Console.WriteLine("You have entered " + number);
    }
    catch (Exception)
    {
        // Error message
        Console.WriteLine("Incorrect input");
    }

    // Waiting for Enter
    Console.ReadLine();
}

测试和结论

以下部分介绍了这是如何工作的。

用小数点测试

运行您的程序,输入一个带小数点的数字两次(见图 9-2 )。

img/458464_2_En_9_Fig2_HTML.jpg

图 9-2

输入两个带小数点的数字

当使用美国本地化时,程序接受点作为小数点分隔符。同时,当使用捷克语本地化时,它拒绝小数点,因为点在捷克语中不是有效的小数点分隔符。

用十进制逗号测试

再次运行你的程序,这次输入一个带小数点的数字两次(见图 9-3 )。

img/458464_2_En_9_Fig3_HTML.jpg

图 9-3

用逗号输入一个数字两次

现在程序接受十进制逗号作为捷克语中的有效分隔符。

当使用美国本地化时,程序看不到任何十进制数。它只是忽略逗号,将用户输入转换成一个整数!

进一步的结论

在本书中,我用数字中的小数点显示输出。这是因为我没有在输出语句中指定任何本地化,并且我的 Windows 设置当前被设置为美国英语。两个测试都表明,如果你不够小心,十进制输入可能会出卖你。只是提醒你,如果你直接在你的 C# 源代码中输入一个十进制数,无论你的设置如何,你都应该使用小数点。

基本算术

在这一章中,您将与数字打交道,因此这是执行所有四种基本算术运算的好时机。

工作

您将编写一个程序,从用户那里接受两个十进制数,并显示它们的加、减、乘、除的结果(见图 9-4 )。

img/458464_2_En_9_Fig4_HTML.jpg

图 9-4

做基本算术

解决办法

代码如下:

static void Main(string[] args)
{
    // Inputs
    Console.Write("Enter first number: ");
    string input1 = Console.ReadLine();
    double number1 = Convert.ToDouble(input1);

    Console.Write("Enter second number: ");
    string input2 = Console.ReadLine();
    double number2 = Convert.ToDouble(input2);

    // Calculations
    double sum = number1 + number2;
    double difference = number1 - number2;
    double product = number1 * number2;
    double quotient = number1 / number2;

    // Output
    Console.WriteLine("Sum is " + sum);
    Console.WriteLine("Difference is " + difference);
    Console.WriteLine("Product is " + product);
    Console.WriteLine("Quotient is " + quotient);

    // Waiting for Enter
    Console.ReadLine();
}

数学函数

当您进行工程或财务计算时,您通常需要比上一个练习中显示的四个基本操作更复杂的操作。现在您将看到如何使用内置的(预定义的)数学函数来执行复杂的运算。

工作

为了让您体验一下可用的数学函数,您将在该任务中计算输入数字的正弦和平方根(参见图 9-5 )。

img/458464_2_En_9_Fig5_HTML.jpg

图 9-5

计算正弦和平方根

解决办法

代码如下:

static void Main(string[] args)
{
    // Input of angle
    Console.Write("Enter an angle in degrees: ");
    string input = Console.ReadLine();
    double angleInDegrees = Convert.ToDouble(input);

    // Calculation and output of sine value
    double angleInRadians = angleInDegrees * Math.PI / 180;
    double result = Math.Sin(angleInRadians);
    Console.WriteLine("Sine of the angle is: " + result);

    // Input of a positive number
    Console.WriteLine();
    Console.Write("Enter a positive number: ");
    input = Console.ReadLine();
    double number = Convert.ToDouble(input);

    // Calculation and output of square root
    Console.WriteLine("Square root of the number is: " + Math.Sqrt(number));

    // Waiting for Enter
    Console.ReadLine();
}

讨论

请注意以下几点:

  • 要计算数学函数的值,可以使用Math对象;它包含许多有用的函数,不仅仅是上面显示的那些。

  • Sin功能要求以弧度指定角度。如果您的输入是以度为单位的,这是通常的情况,那么您需要进行转换。

  • 有了第二个输入(一个数字),你就“回收”了之前已经使用过的变量input;由于不再需要存储值,您第二次使用了它。但是,这意味着您不必再次声明该变量。

  • 你不必“回收”变量;如今,变量不是宝贵的资源。但是如果你想的话你可以,这就是我展示给你的。

  • 与第一个计算相反,您没有将计算出的平方根存储到任何变量中。你直接把计算写进了输出语句(WriteLine)。

  • 如果用户输入一个负数,它的平方根无法计算,结果变成NaN(意思是“不是一个数”)。

整数除法

令人惊讶的是,在编程时,你经常需要使用整数除法,也就是带余数的除法。例如,33 除以 7 通常是 4.71428…或者是 4 加余数 5。

在各种计算平台上,你会做不同于“正常”除法的整数除法。不幸的是,在 C# 中,两种类型使用相同的运算符,斜杠(/)。它是这样工作的:

  • 如果在两个int类型的值之间加一个斜杠,这个斜杠执行整数除法。

  • 如果两个值中至少有一个是double类型,斜杠执行“正常”除法。

这种行为可能是难看的、难以发现的错误的来源。这个行为已经有 45 年的历史了,起源于 C 语言被创造出来的时候;不幸的是,一些较新的语言,如 C#,继承了这种行为。注意这一点,在使用斜线时要小心。

工作

在本练习中,您将探索用户输入的两个数字的“正常”除法和整数除法(参见图 9-6 )。

img/458464_2_En_9_Fig6_HTML.jpg

图 9-6

探索“正常”和整数除法

解决办法

下面是代码:

static void Main(string[] args)
{
    // Inputs
    Console.Write("Enter 1\. whole number (dividend): ");
    string input1 = Console.ReadLine();
    int number1 = Convert.ToInt32(input1);

    Console.Write("Enter 2\. whole number (divisor): ");
    string input2 = Console.ReadLine();
    int number2 = Convert.ToInt32(input2);

    // Integer calculations
    int integerQuotient = number1 / number2;
    int remainder = number1 % number2;

    // "Normal" calculations
    double number1double = number1;
    double number2double = number2;
    double normalQuotient = number1double / number2double;

    // Alternatively
    double normalQuotientAlternatively = (double)number1 / (double)number2;

    // Outputs
    Console.WriteLine("-----------------");
    Console.WriteLine("Integer quotient: " + integerQuotient +
        " with remainder " + remainder);
    Console.WriteLine("\"Normal\" quotient : " + normalQuotient);
    Console.WriteLine("\"Normal\" quotient (alternatively): " + normalQuotientAlternatively);

    // Waiting for Enter
    Console.ReadLine();
}

讨论

请注意以下几点:

  • 要计算余数,可以使用%运算符(百分号)。

  • 我已经向您展示了两种将输入的值强制为double s 以实现“正常”除法的方法。

    • 类型为double的变量赋值。

    • 型投double;您可以在括号中加上目标类型的值。

摘要

在这一章中,你更详细地研究了数字。你已经知道在计算中整数和小数的区别,你知道如何阅读整数;在这一章中,你学习了如何阅读小数。您还发现,阅读十进制数字对语言很敏感,如果不小心,可能会导致令人惊讶的结果。如果不指定要使用的语言,将使用 Windows 语言设置读取数字。具体来说,您学习了以下内容:

  • 使用Convert.ToDouble方法将文本用户输入转换成实际的十进制数

  • 将转换后的值存储在double类型的变量中

  • 使用作为第二个参数传递给转换方法的CultureInfo对象实施语言设置

此外,您还学习了如何使用运算符+、-、*和/进行基本运算,以及如何使用(static) Math对象的内置数学函数进行更复杂的运算。

最后,您探索了整数除法及其与“普通”除法的比较,并了解了斜线运算符的一些复杂行为,它执行以下操作:

  • 与两个整数一起使用时的整数除法

  • 至少有一个数字是小数时的“正常”除法

您了解了如何对整数进行“正常”除法运算:

  • 要么在计算前将它们赋给double类型的变量

  • 在计算中对它们进行类型转换

十、经济计算

在这一章中,你将学习如何数钱。这很简单,但是你需要运用一些常识。

货币兑换

执行简单的经济计算通常意味着进行货币转换,这将在本节中尝试。

工作

接受欧元金额和欧元汇率后,您将把金额转换成美元(见图 10-1 )。

img/458464_2_En_10_Fig1_HTML.jpg

图 10-1

转换成美元

解决办法

代码如下:

static void Main(string[] args)
{
    // Inputs
    Console.Write("Enter amount in euros: ");
    string inputEuros = Console.ReadLine();
    double amountEuros = Convert.ToDouble(inputEuros);

    Console.Write("Enter euro exchange rate (how many dollars per 1 euro): ");
    string inputExchangeRate = Console.ReadLine();
    double euroEchangeRate = Convert.ToDouble(inputExchangeRate);

    // Calculation
    double amountDollars = amountEuros * euroEchangeRate;

    // Output
    Console.WriteLine();
    Console.WriteLine("Amount in dollars: " + amountDollars);

    // Waiting for Enter
    Console.ReadLine();
}

总价

在本练习中,您将计算订单的总价。

工作

假设你的一个顾客买了几样东西,其中一些可能买了很多次。你需要计算包括运费在内的总价。在这个程序中,为了简单起见,两个产品的价格和数量以及运输价格将直接固定在源代码中(见图 10-2 )。

img/458464_2_En_10_Fig2_HTML.jpg

图 10-2

计算总成本

解决办法

下面是代码:

static void Main(string[] args)
{
    // Fixed values
    const double bookPrice = 29.8;
    const double dvdPrice = 9.9;
    const double shipmentPrice = 25;

    // Inputs
    Console.WriteLine("Order");
    Console.WriteLine("-----");

    Console.Write("Product \"C# Programming for Absolute Beginners (book)\" - enter number of pieces: ");
    string inputBookPieces = Console.ReadLine();
    int bookPieces = Convert.ToInt32(inputBookPieces);

    Console.Write("Product \"All Quiet on Western Front (DVD)\" - enter number of pieces: ");
    string inputDvdPieces = Console.ReadLine();
    int dvdPieces = Convert.ToInt32(inputDvdPieces);

    // Calculations
    double totalForBook = bookPrice * bookPieces;
    double totalForDvd = dvdPrice * dvdPieces;
    double totalForOrder = totalForBook + totalForDvd + shipmentPrice;

    // Outputs
    Console.WriteLine();
    Console.WriteLine("Order calculation");
    Console.WriteLine("-----------------");
    Console.WriteLine("Book: " + totalForBook);
    Console.WriteLine("Dvd: " + totalForDvd);
    Console.WriteLine("Shipment: " + shipmentPrice);
    Console.WriteLine("TOTAL: " + totalForOrder);

    // Waiting for Enter
    Console.ReadLine();
}

讨论

在固定“变量”前加上const表示它们是常量,是在程序运行过程中不会改变的值。Visual Studio 不允许您为这些“变量”赋新值

就我个人而言,我不经常使用const。我只是想给你看一下,以防你在工作过程中看到它。

佣金

在资本主义中,最重要的不是创造、生产或种植什么东西。最重要的是卖!销售人员通常会得到佣金,所以你必须学会如何计算。

工作

您将编写一个程序,接受产品的价格,然后计算商家、分销商和生产商的佣金百分比。从数据中还计算出三方的收益分成(见图 10-3 )。

img/458464_2_En_10_Fig3_HTML.jpg

图 10-3

计算佣金

解决办法

代码如下:

static void Main(string[] args)
{
    // Inputs
    Console.Write("Enter customer price of product: ");
    string inputPrice = Console.ReadLine();
    double customerPrice = Convert.ToDouble(inputPrice);

    Console.Write("Enter merchant commission (percents): ");
    string inputMerchantPercents = Console.ReadLine();
    int merchantPercents = Convert.ToInt32(inputMerchantPercents);

    Console.Write("Enter distributor commission (percents): ");
    string inputDistributorPercents = Console.ReadLine();
    int distributorPercents = Convert.ToInt32(inputDistributorPercents);

    // Calculations
    double coefficient1 = 1 - merchantPercents / 100.0;
    double coefficient2 = 1 - distributorPercents / 100.0;
    double wholesalePrice = customerPrice * coefficient1;
    double priceAfterCommissionSubtraction = wholesalePrice * coefficient2;
    double merchantIncome = customerPrice - wholesalePrice;
    double distributorIncome = wholesalePrice - priceAfterCommissionSubtraction;
    double producerIncome = priceAfterCommissionSubtraction;

    // Outputs
    Console.WriteLine();
    Console.WriteLine("Income division");
    Console.WriteLine("----------------");
    Console.WriteLine("Merchant: " + merchantIncome);
    Console.WriteLine("Distributor: " + distributorIncome);
    Console.WriteLine("Producer: " + producerIncome);

    // Waiting for Enter
    Console.ReadLine();
}

讨论

有时,佣金百分比可能是小数。我在这个例子中选择了整数,因为我想用一个实际的例子向你展示如何正确地划分整数。如您所知,要执行“普通”除法,您至少需要一个数字(在斜杠前面或后面)作为double。这就是你使用100.0的原因。

如果你用100来代替,结果会令人惊讶(见图 10-4 )。

img/458464_2_En_10_Fig4_HTML.jpg

图 10-4

佣金百分比,不正确

你知道为什么会这样吗?都是因为四舍五入。

舍入

金额通常四舍五入到美分。我将向您展示如何做到这一点,以及仅仅为了输出而舍入和为了进一步计算而舍入之间的区别。差别很小,但有时很重要。你可能会漏掉一分钱,给别人带来麻烦。

工作

在用户输入两个货币金额(可能以超过两位小数的方式计算)后,程序将以百分比精度显示它们,将它们四舍五入到美分,最后将原始值与四舍五入值进行比较(图 10-5 )。

img/458464_2_En_10_Fig5_HTML.jpg

图 10-5

舍入程序

解决办法

代码如下:

static void Main(string[] args)
{
    // For simplicity, inputs are fixed in program
    // Some amounts, e.g. after commission calculations, cent fractions are possible
    double amount1 = 1234.567;
    double amount2 = 9.876;

    // Displaying inputs (original values)
    Console.WriteLine("First amount (original value): " + amount1);
    Console.WriteLine("Second amount (original value): " + amount2);
    Console.WriteLine();

    // Rounding just for output
    Console.WriteLine("First amount displayed with cent precision: " + amount1.ToString("N2"));
    Console.WriteLine("Second amount displayed with cent precision: " + amount2.ToString("N2"));
    Console.WriteLine();

    // Rounding for further calculations + informative output
    double roundedAmount1 = Math.Round(amount1, 2); // 2 = two decimal places
    double roundedAmount2 = Math.Round(amount2, 2);

    Console.WriteLine("First amount rounded to cents: " + roundedAmount1);
    Console.WriteLine("Second amount rounded to cents: " + roundedAmount2);
    Console.WriteLine();

    // Calculations
    double sumOfOriginalAmounts = amount1 + amount2;
    double sumOfRoundedAmounts = roundedAmount1 + roundedAmount2;

    // Calculation outputs
    Console.WriteLine("Sum of original amounts: " + sumOfOriginalAmounts.ToString("N2"));
    Console.WriteLine("Sum of rounded amounts: " + sumOfRoundedAmounts.ToString("N2"));
    Console.WriteLine("On invoice, we need sum of rounded amounts");

    // Waiting for Enter
    Console.ReadLine();
}

进一步舍入

有时,舍入会更复杂。

工作

在这项任务中,我将向您展示如何四舍五入到美元,四舍五入到数百美元,始终向下舍入,始终向上舍入(见图 10-6 和 10-7 )。

img/458464_2_En_10_Fig7_HTML.jpg

图 10-7

另一个数字四舍五入到美元,美分,数百美元

img/458464_2_En_10_Fig6_HTML.jpg

图 10-6

更复杂的舍入

解决办法

代码如下:

static void Main(string[] args)
{
    // Input
    Console.Write("Enter (decimal) amount in dollars: ");
    string input = Console.ReadLine();
    double amount = Convert.ToDouble(input);

    // To dollars
    double nearest    = Math.Round(amount);
    double alwaysDown = Math.Floor(amount);
    double alwaysUp   = Math.Ceiling(amount);

    Console.WriteLine();
    Console.WriteLine("To dollars");
    Console.WriteLine("----------");
    Console.WriteLine("Nearest    : " + nearest);
    Console.WriteLine("Always down: " + alwaysDown);
    Console.WriteLine("Always up  : " + alwaysUp);

    // To cents
    nearest    = Math.Round(amount, 2);
    alwaysDown = Math.Floor(100 * amount) / 100;
    alwaysUp   = Math.Ceiling(100 * amount) / 100;

    Console.WriteLine();
    Console.WriteLine("To cents");
    Console.WriteLine("--------");
    Console.WriteLine("Nearest    : " + nearest);
    Console.WriteLine("Always down: " + alwaysDown);
    Console.WriteLine("Always up  : " + alwaysUp);

    // To hundreds of dollars
    nearest    = 100 * Math.Round(amount / 100);
    alwaysDown = 100 * Math.Floor(amount / 100);
    alwaysUp   = 100 * Math.Ceiling(amount / 100);

    Console.WriteLine();
    Console.WriteLine("To hundreds of dollars");
    Console.WriteLine("----------------------");
    Console.WriteLine("Nearest    : " + nearest);
    Console.WriteLine("Always down: " + alwaysDown);
    Console.WriteLine("Always up  : " + alwaysUp);

    // Waiting for Enter
    Console.ReadLine();
}

讨论

当然,如果您愿意,也可以使用value.ToString("N2")显示带分的舍入值。

增值税

在欧洲,我们有一个很好的东西叫增值税。每个人都乐于为商品支付更多的钱,如果这能让政客们有更多的预算……实际上,是为了什么?

工作

在该任务中,您将创建一个简单的增值税计算器(参见图 10-8 )。该程序从客户购买产品的价格开始,计算不含增值税(商家从购买中获得)的价格以及增值税本身(商家转移给税务管理员的金额)。

img/458464_2_En_10_Fig8_HTML.jpg

图 10-8

计算增值税

分析

如果你想编写一个程序,你必须首先理解这个程序的本质。那么,欧洲增值税是如何运作的呢?

计算的基础是不含增值税的价格。为了得到这个价格,加上适当的百分比部分(例如,21%),你得到客户支付的价格。重要的是,百分比是根据不含增值税的价格计算的,而不是根据客户价格计算的(见图 10-9 )!

img/458464_2_En_10_Fig9_HTML.jpg

图 10-9

了解增值税的工作原理

例如,如果增值税率为 21%,您需要将客户价格除以 1.21,以获得不含增值税的价格。对于税率的一般值,您可以通过将适当的分数加到 1 来计算除数。

解决办法

代码如下:

static void Main(string[] args)
{
    // Inputs
    Console.Write("Enter customer price of a product: ");
    string inputPrice = Console.ReadLine();
    double customerPrice = Convert.ToDouble(inputPrice);

    Console.Write("Enter VAT rate in %: ");
    string inputVatRate = Console.ReadLine();
    double vatRate = Convert.ToDouble(inputVatRate);

    // Calculations
    double divisor = 1 + vatRate / 100.0;
    double calculatedPriceWithoutVat = customerPrice / divisor;
    double priceWithoutVat = Math.Round(calculatedPriceWithoutVat, 2);
    double vat = customerPrice - priceWithoutVat;

    // Outputs
    Console.WriteLine();
    Console.WriteLine("Price without VAT: " + priceWithoutVat.ToString("N2"));
    Console.WriteLine("VAT: " + vat.ToString("N2"));

    // Waiting for Enter
    Console.ReadLine();
}

摘要

在这一章中,你练习了经济世界中各种真实例子的计算。在这样的计算中,最重要的是首先理解现实世界的问题。为了理解在没有程序的情况下如何得到结果,从一支铅笔、一张纸和一个计算器开始。适当地组织你的程序,将整个计算分成小块,并为你的变量使用描述性的名字,通常也是有帮助的。

除此之外,你还学习了如何舍入。具体来说,您学习了几个内置的数学函数:

  • 您知道如何使用Math.Round进行最常见的舍入,换句话说,舍入到最接近的整数。您可以在方法调用的第二个参数中指定所需的小数位数。

  • 您知道如何使用Math.Floor始终向下舍入,换句话说,舍入到小于或等于被舍入数字的最大整数。

  • 您知道如何使用Math.Ceiling始终向上舍入,换句话说,舍入到大于或等于被舍入数字的最小整数。

您还学习了如何舍入到百位数的技巧,包括在舍入前除以 100,然后乘以相同的数。

十一、使用日期的计算

在上一章中,您练习了经济领域的计算。你也经常需要计算日期。假设您需要设置发票的到期日期。或者假设您想要计算发票过期多少天。或者,您可能需要知道某个特定时期(如一个月或一个季度)的第一天和最后一天。在这一章中,你将学习如何计算日期。

日期输入

首先,您将学习如何从用户那里读取日期。我也将向您展示一些简单的日期算法。

工作

在这个任务中,您将获得一个基于用户输入的DateTime对象。之后,您将计算下一天和前一天(见图 11-1 )。

img/458464_2_En_11_Fig1_HTML.jpg

图 11-1

计算下一天和前一天

解决办法

重点是使用Convert.ToDateTime方法。如果用户输入一个不存在的日子(例如,非闰年的 2 月 29 日),该方法会导致一个运行时错误,您可以使用try-catch构造来处理这个错误。

static void Main(string[] args)
{
    try
    {
        // Text input of date
        Console.Write("Enter date: ");
        string input = Console.ReadLine();

        // Conversion to DateTime object
        DateTime enteredDate = Convert.ToDateTime(input);

        // Some calculations
        DateTime followingDay = enteredDate.AddDays(1);
        DateTime previousDay  = enteredDate.AddDays(-1);

        // Outputs
        Console.WriteLine();
        Console.WriteLine("Entered day  : " + enteredDate.ToLongDateString());
        Console.WriteLine("Following day: " + followingDay.ToLongDateString());
        Console.WriteLine("Previous day : " + previousDay.ToLongDateString());
    }
    catch (Exception)
    {
        // Treating incorrect input
        Console.WriteLine("Incorrect input");
    }

    // Waiting for Enter
    Console.ReadLine();
}

讨论

也可以用两个参数而不是一个来调用Convert.ToDateTime方法。第二个参数是语言设置,也就是你已经知道的CultureInfo对象。这与其他转换方法类似。

单月

现在,您将练习使用DateTime组件,并使用构造函数调用创建该对象。

工作

用户输入日期。该程序显示输入日期所在月份的第一天和最后一天(见图 11-2 )。

img/458464_2_En_11_Fig2_HTML.jpg

图 11-2

计算每月的第一天和最后一天

解决办法

代码如下:

static void Main(string[] args)
{
    // Date input
    Console.Write("Enter a date: ");
    string input = Console.ReadLine();
    DateTime enteredDate = Convert.ToDateTime(input);

    // Calculations
    int enteredYear = enteredDate.Year;
    int enteredMonth = enteredDate.Month;

    DateTime firstDayOfMonth = new DateTime(enteredYear, enteredMonth, 1);
    DateTime lastDayOfMonth = firstDayOfMonth.AddMonths(1).AddDays(-1);

    // Outputs
    Console.WriteLine();
    Console.WriteLine("Corresponding month: " +
        "from " + firstDayOfMonth.ToShortDateString() +
        " to " + lastDayOfMonth.ToShortDateString());

    // Waiting for Enter
    Console.ReadLine();
}

讨论

请注意以下几点:

  • 根据前面的练习,您使用Convert.ToDateTime方法调用从用户那里获得一个DateTime对象。

  • 您开始从输入的日期中选择月份和年份。为此,您可以使用MonthYear属性。

  • 使用这些数字,您可以很容易地组合出一个月的第一天,因为它的天数始终是一。

  • 一个月的最后一天并不容易,因为月份的长度不同。诀窍就是加一个月,减一天!

  • 注意,我没有在任何地方存储AddMonth的结果。我直接调用AddDays代替它。这被称为方法链接

  • 为了简单起见,我在这里不处理不正确输入的可能性。

四分之一

继续讨论日期,我将向您展示一些有趣的技巧,您有时必须使用这些技巧来获得正确的结果。

工作

对于输入的日期,该程序将显示该日期所属季度的开始、结束和编号(从一到四)(见图 11-3 和 11-4 )。

img/458464_2_En_11_Fig4_HTML.jpg

图 11-4

显示相应的季度,另一个例子

img/458464_2_En_11_Fig3_HTML.jpg

图 11-3

显示相应的季度

分析

这项任务的关键是确定季度的数字。接下来是该季度的第一个月。

季度编号

您需要将月份数转换为季度数,如下所示:

  • 第 1、2 或 3 个月= 1

  • 第 4、5 或 6 个月= 2

  • 第 7、8 或 9 个月= 3

  • 第 10、11 或 12 个月= 4

这是使用整数除法的一个很好的例子。您可以看到,首先需要在月份数字上加 2,然后执行整数除以 3:

int numberOfQuarter = (enteredMonth + 2) / 3;

季度的第一个月数字

如果您已经有了该季度的编号,您将得到该季度的第一个月,如下所示:

  • 第一季度为 1 月

  • 第二季度为 4 月

  • 第三季度 7 月

  • 第四季度 10 月

你可能意识到季度的数字必须乘以三。为了得到正确的结果,你需要连续减去两个:

int monthOfQuarterStart = 3 * numberOfQuarter - 2;

第一天和最后一天

有了第一个月的时间,你可以按照与上一个练习相似的步骤进行。要获得第一天,您可以使用将天数设置为 1 的DateTime构造函数。要得到最后一天,你要加上三个月,减去一天。

解决办法

代码如下:

static void Main(string[] args)
{
    // Date input
    Console.Write("Enter a date: ");
    string input = Console.ReadLine();
    DateTime enteredDate = Convert.ToDateTime(input);

    // Calculations
    int enteredYear = enteredDate.Year;
    int enteredMonth = enteredDate.Month;

    int numberOfQuarter = (enteredMonth + 2) / 3;
    int monthOfQuarterStart = 3 * numberOfQuarter - 2;
    DateTime firstDayOfQuarter = new DateTime(enteredYear, monthOfQuarterStart, 1);
    DateTime lastDayOfQuarter = firstDayOfQuarter.AddMonths(3).AddDays(-1);

    // Outputs
    Console.WriteLine();
    Console.WriteLine("Corresponding quarter: " +
        "number-" + numberOfQuarter +
        ", from " + firstDayOfQuarter.ToShortDateString() +
        " to " + lastDayOfQuarter.ToShortDateString());

    // Waiting for Enter
    Console.ReadLine();
}

日期差异

您经常需要计算两个特定日期之间的时间跨度,换句话说,在输入的日期之间经过了多少天或多少年。这就是你现在要学习的内容。

工作

用户输入他们的出生日期。该程序显示世界有多少天乐于拥有它们(见图 11-5 )。

img/458464_2_En_11_Fig5_HTML.jpg

图 11-5

计算还能活多少天

解决办法

如您所见,您需要从今天的日期中减去出生日期。当您减去日期时,结果是一个TimeSpan对象。有了这个对象,您可以使用它的许多属性中的一个。在本练习中,您将使用Days属性。

代码如下:

static void Main(string[] args)
{
    // Input
    Console.Write("Enter your date of birth: ");
    string input = Console.ReadLine();
    DateTime dateOfBirth = Convert.ToDateTime(input);

    // Today
    DateTime today = DateTime.Today;

    // Date difference
    TimeSpan difference = today - dateOfBirth;
    int numberOfDays = difference.Days;

    // Output
    Console.WriteLine();
    Console.WriteLine("Today is: " + today.ToShortDateString());
    Console.WriteLine("The world likes you for this number of days: " + numberOfDays.ToString("N0"));

    // Waiting for Enter
    Console.ReadLine();
}

时区和 UTC

如果您想要存储某件事情发生的时刻(例如,记录订单、问题等),您可能会对夏令时感到意外。或者,也许更重要的是,假设您正在创建一个将在全球范围内运行的程序。你必须适应不同的时区。

为了处理这些情况,了解如何使用协调世界时(UTC)是很好的,协调世界时是指不含食品添加剂的零子午线时间。对不起,我的意思是不受夏令时限制。UTC 是独立于时区的。

熟悉除了日期和时间之外还包含时区信息的DateTimeOffset对象也很有好处。

工作

在本练习中,我将向您展示如何使用 UTC 和包含在DateTimeOffset对象中的时区。您将创建一个处理当前时间的程序(参见图 11-6 )。

img/458464_2_En_11_Fig6_HTML.jpg

图 11-6

日期时间关闭对象

解决办法

代码如下:

static void Main(string[] args)
{
    // Current time serves as input
    DateTime now = DateTime.Now;
    DateTime utcNow = DateTime.UtcNow;
    DateTimeOffset completeInstant = DateTimeOffset.Now;
    DateTimeOffset utcCompleteInstant = DateTimeOffset.UtcNow;

    // Outputs
    Console.WriteLine("Now: " + now);
    Console.WriteLine("UTC now: " + utcNow);
    Console.WriteLine("Now (including time zone): " + completeInstant);
    Console.WriteLine("Time zone (offset against UTC): " + completeInstant.Offset.TotalHours);
    Console.WriteLine("UTC now (including time zone): " + utcCompleteInstant);

    // Waiting for Enter
    Console.ReadLine();
}

请注意,一些变量属于DateTime类型,而其他变量属于DateTimeOffset类型。

摘要

在这一章里,你相当彻底地学会了如何用日期做计算。

首先使用Convert.ToDateTime方法从用户处获取日期,然后使用DateTime的构造函数调用(new DateTime …)从指定的年、月、日获取日期。

在您的计算中,您适当地使用了DateTime对象的各种属性,如DayMonthYear,以及它的方法,如AddDays。此外,为了计算一个季度的数字,你使用了整数除法。

此外,您还熟悉了如何计算任意两个给定日期之间的差异,以及如何处理结果。具体来说,您使用了TimeSpan对象。

最后,我们讨论了 UTC 和时区,以便于程序跨多个时区运行,并正确处理夏令时带来的时间跳跃。具体来说,您了解了DateTimeOffset对象。

十二、理解不同种类的数字

在这一章中,你将学习一些关于数字和计算的更高级的主题,比如更多的数字类型、内存消耗和溢出。如果此时你不需要这么多的细节,你可以安全地跳过这一章或者只是浏览一下。

更多数字类型

你已经知道在计算中整数和小数是有区别的。使用int类型表示整数,使用double类型表示小数。

但是 C# 中还有其他数字数据类型。虽然它们中的许多主要是因为历史原因而存在,并且你可能永远不会使用它们,但至少了解它们是有好处的。

工作

您将编写一个程序,显示所有 C# 数值数据类型的概述。对于每种类型,其可能值的范围将被打印出来(见图 12-1 )。

img/458464_2_En_12_Fig1_HTML.jpg

图 12-1

打印所有数字数据类型

解决办法

代码如下:

static void Main(string[] args)
{
    // Immediately outputs
    Console.WriteLine("Signed whole numbers");
    Console.WriteLine("--------------------");
    Console.WriteLine("sbyte:  " + sbyte.MinValue + " to " + sbyte.MaxValue);
    Console.WriteLine("short:  " + short.MinValue + " to " + short.MaxValue);

    Console.WriteLine("int:    " + int.MinValue + " to " + int.MaxValue);
    Console.WriteLine("long:   " + long.MinValue + " to " + long.MaxValue);
    Console.WriteLine();

    Console.WriteLine("Unsigned whole numbers");
    Console.WriteLine("----------------------");
    Console.WriteLine("byte:   " + byte.MinValue + " to " + byte.MaxValue);
    Console.WriteLine("ushort: " + ushort.MinValue + " to " + ushort.MaxValue);
    Console.WriteLine("unit: " + uint.MinValue + " to " + uint.MaxValue);
    Console.WriteLine("ulong: " + ulong.MinValue + " to " + ulong.MaxValue);
    Console.WriteLine();

    Console.WriteLine("Basic decimal numbers");
    Console.WriteLine("---------------------");
    Console.WriteLine("float:  " + float.MinValue + " to " + float.MaxValue);
    Console.WriteLine("double: " + double.MinValue + " to " + double.MaxValue);
    Console.WriteLine();

    Console.WriteLine("Exact decimal numbers");
    Console.WriteLine("---------------------");
    Console.WriteLine("decimal:  " + decimal.MinValue + " to " + decimal.MaxValue);

    // Waiting for Enter
    Console.ReadLine();
}

注意

为了显示范围,我使用了所有数字数据类型的MinValueMaxValue属性。

讨论

下面几节讨论这个程序。

无符号数字

程序打印出来的结果显示,有些数据类型不允许存储负数!然而,除了从文件、数据库或 web 服务中读取二进制数据时使用的byte类型,这些无符号数字很少被使用。

与有符号的数字相反,无符号的数字通常以一个 u 开头,意思是“无符号的”类似地,有符号类型sbytes 开始,意味着更重要的byte的“有符号”变体。

十进制数字

十进制类型范围以科学记数法显示(也称为指数记数法)。比如最大的float数显示为 3.4E+38,表示 3.4 乘以 10 的 38 次方。这是一个很大的数字,不是吗?

十进制类型的精度也不同。float型存储大约 7 位有效数字的十进制值,而double型提供大约 15 位有效数字的精度,而decimal型提供 28 位有效数字。

特殊类型十进制

decimal数据类型有些特殊。由于以下原因,最好在处理货币时使用它:

  • 它精确地存储分数值。例如,12.80 的金额将被精确地存储为 12.80,而不是像 12.7999999999 这样的数字,使用其他类型可能会出现这种情况。

  • 因为有大量的有效数字,decimal数据类型允许您表示大量的钱,并且仍然保持分的精度。

然而,这两个理由并不像看起来那样令人信服。如果您正确地执行了舍入,您可以用double类型准确地存储美分。而且坦率的说,除了double 15 位数够不够钱的问题,你通常还需要解决其他问题!

此外,许多事情用double类型更容易,这就是为什么我在本书中更喜欢用double表示小数。

最后一点:使用decimal类型的计算比使用double类型的计算慢得多(事实上,慢了几百倍)。如果您只处理几个数字,这并不重要,但是在大型数据集中,这种差异可能非常显著。

内存消耗

如果您对位和字节有所了解,您可能会想到,由于相应类型可用的内存空间不同,类型范围也会有所不同。这是完全正确的,您将在本节中了解更多信息。

工作

在本节中,您将编写一个程序,告诉您每种类型使用多少字节的内存(见图 12-2 )。

img/458464_2_En_12_Fig2_HTML.jpg

图 12-2

显示每种类型使用的字节数

解决办法

代码如下:

static void Main(string[] args)
{
    // Outputs
    Console.WriteLine("Whole numbers");
    Console.WriteLine("-------------");
    Console.WriteLine("byte:   " + sizeof(byte));
    Console.WriteLine("sbyte:  " + sizeof(sbyte));
    Console.WriteLine();
    Console.WriteLine("short:  " + sizeof(short));
    Console.WriteLine("ushort: " + sizeof(ushort));
    Console.WriteLine();
    Console.WriteLine("int:    " + sizeof(int));
    Console.WriteLine("uint:   " + sizeof(uint));
    Console.WriteLine();
    Console.WriteLine("long:   " + sizeof(long));
    Console.WriteLine("ulong:  " + sizeof(ulong));
    Console.WriteLine();
    Console.WriteLine("Decimal numbers");
    Console.WriteLine("---------------");
    Console.WriteLine("float:    " + sizeof(float));
    Console.WriteLine("double:   " + sizeof(double));
    Console.WriteLine("decimal:  " + sizeof(decimal));
    Console.WriteLine();

    // Waiting for Enter
    Console.ReadLine();
}

连接

可以连接当前和以前程序的结果。比如我们来讨论一下重要的int型。它使用 4 字节或 32 位内存。这意味着可能值的 2 的 32 次方,超过 40 亿。int是有符号类型,所以正数有二十亿,负数有二十亿。它的无符号对应物uint有所有 40 亿个正数的值(当然,还有零)。

讨论

您可能会对数字数据类型的多样性感到困惑。为了帮助您理解它们,以下是您应该何时使用每种方法的总结:

  • int:对于固有整数值的常规工作(例如,某物的计数)。

  • double:用于处理可能是小数的值(如测量值)或数学运算值的常规工作。钱数也大多可以。

  • byte:用于处理二进制数据。

  • long:用于大整数值,如文件大小、支付标识(如可能需要十位数字)或常规(整)值的乘法结果。

  • 金额的常见选择。

其他类型不经常使用。

泛滥

当程序计算出一个不“适合”适当类型范围的值时,发生的情况称为溢出。你的程序的行为可能非常奇怪,如图 12-3 所示。

img/458464_2_En_12_Fig3_HTML.jpg

图 12-3

泛滥

因为乘法通常会产生很大的数字,所以溢出尤其会在乘法时发生。

工作

在本节中,您将编写一个程序,尝试计算一百万乘以一百万。

解决办法

代码如下:

static void Main(string[] args)
{
    // Multiplying million by million
    int million = 1000000;
    int result = million * million;
    long resultInLong = million * million;

    // Outputs
    Console.WriteLine("Million times million: " + result);
    Console.WriteLine("also in long: " + resultInLong);

    // Waiting for Enter
    Console.ReadLine();
}

讨论

程序做的事情完全出乎意料。你需要意识到这种异常。

实际发生了什么?这个程序将一百万乘以一百万。结果太大,不适合 32 位有符号int类型的正负 20 亿范围。所以,计算机简单地丢弃了高位,导致完全无意义。

请注意,即使将结果存储在一个long类型的变量中,也会得到同样的无意义结果。在计算过程中,会出现丢弃大于 32 位的无意义数据。根据 C# 规则,int乘以int就是int,不管你把结果存储在哪里。

处理溢出

前一个程序显示了不正确的结果。现在你将看到对此能做些什么。

工作

以下是如何处理溢出问题的两种可能性:

  • 如果你不期望一个很大的值,但它还是出现了,程序至少应该崩溃或者让你知道这个问题。显示一个无意义的值是最坏的选择。用户信任他们的计算机,并且会因为相信不正确的结果而做出错误的决定。

  • 如果您认为int可能不够,您可以使用以下解决方案进行正确计算。

解决办法

新项目源代码如下:

static void Main(string[] args)
{
    // 0\. Preparation
    int million = 1000000;

    // 1\. Crash at least, we do not
    //    definitely want a nonsense
    Console.WriteLine("1\. calculation");
    try
    {
        long result = million * million;
        Console.WriteLine("Million times million:" + result);
    }
    catch (Exception)
    {
        Console.WriteLine("I cannot calculate this.");
    }

    // 2\. Correct calculation of a big value
    Console.WriteLine("2\. calculation");
    long millionInLong = million;
    long correctResult = millionInLong * millionInLong;
    Console.WriteLine("Million times million: " + correctResult.ToString("N0"));

    // 3\. Alternative calculation of a big valule
    Console.WriteLine("3\. calculation");
    long correctResultAlternatively = (long)million * (long)million;
    Console.WriteLine("Million times million: " + correctResultAlternatively.ToString("N0"));

    // Waiting for Enter
    Console.ReadLine();
}

注意

然而,这个代码并不能解决所有问题。当你立即启动程序时,第一次计算仍然是错误的。人们有时会把try-catch当成一种灵丹妙药,但绝对不是。你需要别的东西,正如接下来要讨论的。

Visual Studio 中的设置

您需要在 Visual Studio 中设置您的项目,以便它报告程序溢出,而不是掩盖它。

从 Visual Studio 菜单中,选择项目,然后选择属性(参见图 12-4 )。

img/458464_2_En_12_Fig4_HTML.jpg

图 12-4

开启属性

接下来选择 Build 选项卡,垂直(也可能水平)滚动,这样你就可以看到高级按钮(它真的被隐藏了!),然后点击该按钮(参见图 12-5 )。

img/458464_2_En_12_Fig5_HTML.jpg

图 12-5

构建选项卡

在出现的对话框中,选择“检查算术溢出/下溢”复选框,并点击确定按钮确认(见图 12-6 )。

img/458464_2_En_12_Fig6_HTML.jpg

图 12-6

“检查算术溢出/下溢”复选框

您的项目现在终于可以运行了。

结果

现在程序按照预期运行,如图 12-7 所示。

img/458464_2_En_12_Fig7_HTML.jpg

图 12-7

用一百万乘以一百万

第一种选择

第一个计算正确地报告了一个问题。省略try-catch构造会导致运行时错误,但至少它不会显示不正确的结果。

其他选择

在计算开始之前,正确的计算将百万转换成long类型。以下是执行这种转换的两种方法:

  • 将百万分配给类型为long的变量

  • 使用带有(long)million的显式类型转换

如果不需要精确的整数运算,也可以使用double类型进行计算。除非解决一些很奇特的数学,double没有溢出的机会。

摘要

在本章中,您学习了高级数字计算。您必须了解 C# 中所有可用的数字数据类型。类型的不同之处在于它们允许整数还是小数,并且它们允许的值的范围也不同。小数的类型在存储数字的精度上也互不相同。

初级水平,有intdouble的知识就够了;你可以一直只使用它们。当你变得更有经验时,你也可以使用下面的方法:

  • 用于大整数的long类型,如文件大小、十位数的支付数字或中等大小数字的乘法结果

  • 用于处理货币的decimal类型

  • 用于处理二进制数据的byte类型

你也研究了溢出的问题。当计算出的值太大而不适合特定数据类型的范围时,就会产生无意义的结果。Visual Studio 的默认行为是照常继续。然而,现在您知道如何更改设置以至少导致运行时错误,因为继续得到不正确的结果是最坏的选择。

最好的替代方法是通过选择具有适当范围的数据类型来完全避免溢出。但是,请记住,改变用于存储结果的变量类型可能还不够。例如,int乘以int始终是int,最大值约为 20 亿,不管你把它存储在哪里。在计算之前将数字转换成long类型可能是合适的。

十三、累积值

到目前为止,您已经处理了存储了一个以后要使用的值的变量。初始赋值后,变量的值没有改变。现在,您已经准备好进入下一步,即研究在程序运行期间变量值发生变化的情况,换句话说,就是从旧值中确定新值。

十更,重访

首先,你将回到在第八章中学习的给一个数加 10 的任务。该程序的目标是呈现一个比用户输入的数字大 10 的值(见图 13-1 )。

img/458464_2_En_13_Fig1_HTML.jpg

图 13-1

显示用户号码加 10

工作

你现在将以一种新的方式解决这项任务;具体来说,您将把计算结果存储在最初存储输入数字的同一个变量中。

这不一定是更好的解决方案,但是您将在后面的章节中学习如何进一步构建它。

解决办法

代码如下:

static void Main(string[] args)
{
    // Input
    Console.Write("Enter a number: ");
    string input = Console.ReadLine();
    int number = Convert.ToInt32(input);

    // Calculation
    number = number + 10;

    // Result output
    Console.WriteLine("Number ten more greater is: " + number);

    // Waiting for Enter
    Console.ReadLine();
}

讨论

解决方案的核心语句如下:number = number + 10;。这个声明是不寻常的,因为同一个东西——变量number—出现在等号的两边!

计算机执行语句是这样的:“取变量number的当前值,加十,将结果存储为变量number的新值。”因此,该语句的净结果是将number的价值增加了 10。

复合赋值

做同样的事情有一个很好的捷径,叫做复合赋值。你现在要研究这个。

工作

您将使用更简洁的复合赋值来解决上一个练习。

解决办法

代码如下:

static void Main(string[] args)
{
    // Input
    Console.Write("Enter a number: ");
    string input = Console.ReadLine();
    int number = Convert.ToInt32(input);

    // Calculation using compound assignment
    number += 10; // same as number = number + 10;

    // Result output
    Console.WriteLine("Number ten more greater is: " + number);

    // Waiting for Enter
    Console.ReadLine();
}

注意

在这段代码中,您使用了复合赋值操作符(+=),这是一个快捷方式,其功能与前面的解决方案相同。你会在所有 C 系列编程语言中看到复合赋值。

进一步的复合赋值

你喜欢复合作业吗?在处理其他算术运算时,还可以使用更多类似的赋值。

工作

我将向你展示一个结合减法、乘法和除法来说明复合赋值的程序(见图 13-2 )。

img/458464_2_En_13_Fig2_HTML.jpg

图 13-2

复合赋值

解决办法

代码如下:

static void Main(string[] args)

{
    // Input
    Console.Write("Enter a number: ");
    string input = Console.ReadLine();
    int number = Convert.ToInt32(input);
    Console.WriteLine();

    // With subtraction
    number -= 5; // same as number = number - 5;
    Console.WriteLine("After decrease by 5: " + number);

    // With multiplication
    number *= 10; // same as number = number * 10;
    Console.WriteLine("Ten times greater: " + number);

    // With division
    number /= 2; // same as number = number / 2;
    Console.WriteLine("Decreased to one half: " + number);

    // Waiting for Enter
    Console.ReadLine();
}

注意

程序每次都使用相同的变量!

这里的除法是整数除法,因为number和 2 都是int

递增和递减

到目前为止,变量最频繁的变化是一个一个的变化。这就是为什么有特殊的超级简洁的方法来进行这样的计算。

工作

现在你将熟悉递增运算符(++)和递减运算符(--),如图 13-3 所示。

img/458464_2_En_13_Fig3_HTML.jpg

图 13-3

递增和递减运算符

解决办法

代码如下:

static void Main(string[] args)
{
    // Input
    Console.Write("Enter a number: ");
    string input = Console.ReadLine();
    int number = Convert.ToInt32(input);

    // Increasing by 1 using INCREMENT OPERATOR
    number++; // same as number = number + 1;
    Console.WriteLine("Increased by 1: " + number);

    // Decreasing by 1 using DECREMENT OPERATOR
    number--; // same as number = number - 1;
    Console.WriteLine("Back again: " + number);

    // Waiting for Enter
    Console.ReadLine();
}

复合赋值和文本

由于+操作符可以用于文本,所以您也可以将复合赋值操作符(+=)用于文本。你可能会经常用到它。

工作

该任务将使你熟悉使用复合赋值的文本连接(见图 13-4 )。

img/458464_2_En_13_Fig4_HTML.jpg

图 13-4

使用复合赋值的文本连接

解决办法

代码如下:

static void Main(string[] args)
{
    // Initial value (empty text)
    string books = "";

    // Appending
    books += "Homage to Catalonia" + Environment.NewLine;
    books += "Silent Spring" + Environment.NewLine;
    books += "The beat of a different drum" + Environment.NewLine;

    // Output
    Console.WriteLine("Valuable books");
    Console.WriteLine("--------------");
    Console.WriteLine(books);

    // Waiting for Enter
    Console.ReadLine();
}

渐进求和

渐进求和是对大量值求和的一个重要原则。这意味着不是在一个语句中一次性将它们相加,而是逐个相加,在一个特殊的变量中逐步累积中间结果。

工作

你将编写一个程序,逐步将三个输入的数字相加。当然,在一行中一次性将三个数相加会更方便。然而,我想用一个简单的例子来说明渐进求和的重要原理,并让你在讨论更复杂的话题,即循环之前习惯这个想法(见图 13-5 )。

img/458464_2_En_13_Fig5_HTML.jpg

图 13-5

逐步累加三个输入的数字

解决办法

代码如下:

static void Main(string[] args)
{
    // Preparation - variable to accumulate intemediate result
    int sum = 0;

    // Input - 1\. number
    Console.Write("Enter first number: ");
    string input = Console.ReadLine();
    int number = Convert.ToInt32(input);

    // Adding first number to intermediate result
    sum += number;

    // Input - 2\. number
    Console.Write("Enter second number: ");
    input = Console.ReadLine();
    number = Convert.ToInt32(input);

    // Adding second number to intermediate result
    sum += number;

    // Input - 3\. number
    Console.Write("Enter third number: ");
    input = Console.ReadLine();
    number = Convert.ToInt32(input);

    // Adding third number to intermediate result
    sum += number;

    // Output
    Console.WriteLine();
    Console.WriteLine("Sum of entered numbers: " + sum);

    // Waiting for Enter
    Console.ReadLine();
}

多文本连接

同样,因为+操作符也可以用于文本,所以您可以将渐进求和的原则扩展到文本。在这种情况下,不妨称之为渐进积累

工作

你将编写一个程序,逐步积累用户输入的名字。做两次积累会很有意思;第一个是原顺序,第二个是倒顺序。

为简单起见,您将只使用三个值(见图 13-6 )。

img/458464_2_En_13_Fig6_HTML.jpg

图 13-6

逐渐积累名字

解决办法

代码如下:

static void Main(string[] args)
{
    // Preparation - variables to accumulate intermediate results
    string inOriginalOrder = "";
    string inReversedOrder = "";

    // Input of the first person
    Console.Write("Enter first person: ");
    string person = Console.ReadLine();

    // Appending the first person to intermediate result
    inOriginalOrder += person + Environment.NewLine;
    inReversedOrder = person + Environment.NewLine + inReversedOrder;

    // Input of the second person
    Console.Write("Enter second person: ");
    person = Console.ReadLine();

    // Appending the second person to intermediate result
    inOriginalOrder += person + Environment.NewLine;
    inReversedOrder = person + Environment.NewLine + inReversedOrder;

    // Input of the third person
    Console.Write("Enter third person: ");
    person = Console.ReadLine();

    // Appending the third person to intermediate result
    inOriginalOrder += person + Environment.NewLine;
    inReversedOrder = person + Environment.NewLine + inReversedOrder;

    // Output
    Console.WriteLine();
    Console.WriteLine("Entered persons");
    Console.WriteLine("---------------");
    Console.WriteLine(inOriginalOrder);
    Console.WriteLine("In reversed order");
    Console.WriteLine("-----------------");
    Console.WriteLine(inReversedOrder);

    // Waiting for Enter
    Console.ReadLine();
}

注意

有趣的是,当以相反的顺序连接人名时,复合赋值没有帮助。

摘要

本章的中心主题是同一变量中值的累积。与迄今为止的程序相反,这里的程序重复地改变变量的值,通常使用它的初始值,并以某种方式修改它。具体来说,您学习了以下内容:

  • variable = variable + change;这样的语句获取variable的当前值,加上change,并将结果存储为variable的新值

  • 复合赋值,如variable += change;,它是前面语句的简短等效

  • 与其他算术运算的复合赋值:-=*=/=

  • 带文本的复合赋值(仅限+=)

  • 使用variable++;variable--;的超短符号增加(加 1)和减少(减 1)变量

在本章的最后,你已经熟悉了渐进求和(和渐进累加)的原理,这意味着一个接一个地对数字求和,同时将中间结果存储在一个特殊的变量中。这个原则主要用在对大量值求和时,在本书后面研究循环时,你会体会到它的极端重要性。

十四、基本工具

你已经完成了这本书的两部分。在接下来的两部分中,您将学习更复杂的主题,例如处理条件和循环。为了让你正确理解这些主题,在这一章中,我将介绍一些对你的编程有很大帮助的工具。

智能感知

您知道我将介绍的第一个工具:Visual Studio IntelliSense。每当您开始键入任何内容时,Visual Studio 都会立即为您提供完成文本的选项。当您选择其中一个选项时,开发环境会在工具提示中向您显示关于该选项的更多细节——该选项做什么,它需要什么参数,等等。

我在这一章中介绍智能感知,因为它经常被初级程序员使用。我建议您习惯于使用智能感知来完成您键入的几乎每个单词。你将省去大量的错别字。

探索可能性

使用智能感知也是探索当代计算平台提供的所有可能性的一种方式。

在过去,你必须研究手册或书籍中的可能性。他们中的一些人有足够的能力回答一个程序员几乎所有的问题。然而,今天有如此多的可能性,甚至一本 1000 页的书也不能把它们全部展示出来。

你还需要书(肯定是这本!)为您提供可靠而系统的指导,使您能够理解其中的原理,但您可能希望探索那里没有提到的进一步的可能性。智能感知是一个工具,可以向你展示其中的许多。

例子

你在寻找操纵文本的可能性吗?创建一个string类型的变量,并输入其名称后跟一个点(见图 14-1 )。

img/458464_2_En_14_Fig1_HTML.jpg

图 14-1

输入变量名和点号

当你直接输入string后跟一个点时,你可以找到更多的可能性(见图 14-2 )。

img/458464_2_En_14_Fig2_HTML.jpg

图 14-2

输入数据类型和点号

你在寻找可以和约会对象一起做的事情吗?输入DateTime变量,然后输入一个点(见图 14-3 )。

img/458464_2_En_14_Fig3_HTML.jpg

图 14-3

输入日期时间和一个点

像文本一样,你可以输入DateTime和一个点,得到一些有用的提示(见图 14-4 )。

img/458464_2_En_14_Fig4_HTML.jpg

图 14-4

从智能感知中获得一些提示

注意

使用列表底部的图标可以缩小 IntelliSense 为您提供的可能性列表。如果您只想查看属性、方法等等,请单击适当的图标。

快捷键

有时,当您执行某项操作时,智能感知会消失。在这种情况下,您可能会发现 Ctrl+J 或其他键盘快捷键很有用。要获得他们的列表,在 Visual Studio 的菜单栏中,选择编辑➤智能感知➤列表成员(见图 14-5 )。

img/458464_2_En_14_Fig5_HTML.jpg

图 14-5

列表成员的快捷键

文件

智能感知可以给你很多提示。它还可以显示您搜索的功能的基本用法。但是,您可以找到所有详细信息的地方是在线文档。

docs.microsoft.com

位于http:// docs.microsoft.com的网站是寻找任何关于 C# 和其他微软技术的最佳起点(见图 14-6 )。在页面上,单击“文档”,然后直接转到“C#”。

img/458464_2_En_14_Fig6_HTML.jpg

图 14-6

Microsoft 文档

搜索

您可以使用提供的链接使用文档网站。然而,更常见的是,你要搜索特定的东西。例如,在搜索字段中输入控制台类别并按回车键(参见图 14-7 )。

img/458464_2_En_14_Fig7_HTML.jpg

图 14-7

正在搜索“控制台类”

你感兴趣的链接往往是第一个返回的(见图 14-8 )。

img/458464_2_En_14_Fig8_HTML.jpg

图 14-8

查看“控制台类”的文档

特定类页面

点击正确的链接后,你会找到关于某个特定类的所有东西,比如本例中的Console

当我开始熟悉一些我还不知道的类时,我通常会寻找“通用操作”和“备注”部分,它们涵盖了关于类使用的基本信息和进一步细节的入口点(见图 14-9 和 14-10 )。

img/458464_2_En_14_Fig10_HTML.jpg

图 14-10

在备注部分获取更多详细信息

img/458464_2_En_14_Fig9_HTML.jpg

图 14-9

在通用操作部分获取介绍性信息

普通搜索

作为本节文档的总结,请注意,您也可以在 Microsoft 文档网站之外执行搜索。只需使用您最喜欢的网络浏览器进行搜索。

然而,与文档网站搜索相反,普通搜索更频繁地显示不相关的结果。为了弥补这一点,您可以使用编程语言名称来细化您的查询,例如在您最喜欢的 web 浏览器的搜索字段中输入控制台类 C#

调试工具

除了 IntelliSense 和文档之外,您还会发现其他一些有用的工具。具体来说,你可以使用调试工具,可以说,这些工具允许你查看计算机内部。您可以使用它们来查看计算机如何执行单个命令,内存中存储了哪些值,等等。

这些就是你现在要研究的工具。最初,开发它们是为了方便程序调试,换句话说,是为了查找和消除错误。然而,作为说明性工具,它们可能更有助于您理解各种编程结构。

项目

最好在实践中尝试所有的工具,所以请从第八章开始在 Visual Studio 中打开“处理不正确输入”项目。程序将两个数相加,并使用try-catch处理可能的输入错误。

单步执行代码

计算机工作速度如此之快,以至于不可能以千兆赫的速度跟上它。这就是为什么逐步执行代码通常是有帮助的,这迫使计算机根据你的命令一次执行一条语句。

转到您打开的“处理不正确的输入”项目,并使用 F10 键启动它,而不是通常的 F5。当然,选择调试➤单步调试菜单也是一样的(见图 14-11 )。

img/458464_2_En_14_Fig11_HTML.jpg

图 14-11

使用 F10 键启动程序

现在,每当您按下 F10 时,就会一次执行一个单独的程序语句。一直以来,使用黄色箭头和黄色背景,开发环境表示将在下一步中执行的语句。

现在只要玩踏脚。尝试用户输入正确数据的情况以及错误输入的情况。IDE 将向您展示try-catch或其他任何东西是如何工作的。这可以让你亲眼看到程序是如何运行的。

终止步进

当你找到你要找的东西时,你不必一步一步地通过程序到最后一条语句。还有其他选择:

  • 使用 F5 键(或调试➤继续),你可以继续正常的程序执行(没有步进)。

  • 使用 Shift+F5 键(或调试➤停止调试),可以终止程序执行。

断点

我已经介绍了在您已经过了您感兴趣的点之后,如果您不想逐句通过您的代码,该怎么办。还有一种情况。说你不想在到达感兴趣的地方之前做任何事情。在这种情况下,您可以使用一个断点

单击计算机要停止的语句并按 F9 键(或者,右键单击并选择断点➤插入断点)。断点已被放置的事实用暗红色背景和在行首的暗红色项目符号表示(见图 14-12 )。

img/458464_2_En_14_Fig12_HTML.jpg

图 14-12

插入的断点

使用断点

如果您的程序仍在运行,请使用 Shift+F5 组合键终止它。现在以常规方式启动程序,换句话说,使用 F5 键。它会正常运行,到达断点就停止。Visual Studio 在程序的窗口前弹出。

之后,您可以逐句通过代码或只是看一些东西,并使用 F5 键来进一步运行您的程序,直到它的结尾或直到下一个断点。

移除断点

要删除不再需要的断点,请在特定行上再次按 F9,或者右键单击并从上下文菜单中选择断点➤删除断点。

内存检查

每当程序挂起时(从断点、单步执行等),您可以检查程序可用的内存,并浏览各个变量的值。

为了便于内存检查,Visual Studio 在其窗口底部显示了“汽车”、“局部变量”和“监视”窗格。如果没有这些窗格,您可以使用调试➤窗口菜单来显示它们。

“自动”和“局部变量”窗格自动选择要显示的变量,而“监视”窗格则根据您希望看到的内容手动填充。您可以在窗格中输入特定变量的名称,或者您可以在代码中右键单击变量并从上下文菜单中选择添加监视(参见图 14-13 )。

img/458464_2_En_14_Fig13_HTML.jpg

图 14-13

选择添加观察

所选变量的当前值出现在监视窗口中(参见图 14-14 )。

img/458464_2_En_14_Fig14_HTML.jpg

图 14-14

使用监视窗口

C# 交互式

我在这里提到的最后一个工具可以帮助你在交互模式下学习 C# 语句。

这是什么?

到目前为止,你总是不得不写一个有几个语句的程序,然后启动它来看它的运行。交互模式允许您输入单个 C# 语句并立即运行它们。通过这种方式,您可以更快地探索一些 C# 特性。

如何推出?

您可以通过选择查看➤其他窗口➤ C# 交互来启动交互模式(参见图 14-15 )。您甚至不需要创建/打开项目。

img/458464_2_En_14_Fig15_HTML.jpg

图 14-15

切换到交互模式

图 14-16 显示了一个交互会话的例子——我声明了一个数字变量,增加了 10,并显示了它的值。

img/458464_2_En_14_Fig16_HTML.jpg

图 14-16

示例交互式会话

笔记

请注意以下几点:

  • 如果需要输入多行语句,可以用 Shift+enter 来终止行,而不是简单的 Enter。

  • 使用#help显示关于如何使用交互模式的简明信息。

摘要

本章向您介绍了可以在编程中使用的工具,包括 IntelliSense、文档、调试工具和交互模式。

当您在 Visual Studio 编辑器中键入时,IntelliSense 会显示可用可能性和相应工具提示的列表。你已经知道如何使用它了。在这里,我介绍了 IntelliSense 作为探索巨大的 C# 世界的一种方式。例如,如果您想要操作文本,并且您不知道相应的方法是如何调用的,那么您可以在一个string变量后面加上一个点,然后浏览各种可能性。您还可以在类型名称后面加上一个点,以获得更多的可能性。

您了解了 Microsoft 文档网站,该网站包含所有 Microsoft 编程技术的宝贵信息,包括 C# 语言和。NET 平台。您通常在此网站上执行全文搜索。

在这一章中,我揭示了一些调试工具,它们可以让你看到“计算机内部”您可以观察各个语句是如何执行的,检查变量值,等等。具体来说,您学习了如何单步执行代码、设置断点和检查内存。

交互模式是一种快速输入 C# 语句并查看它们的功能的方式。这是一个探索新特性的好工具。

十五、条件入门

到目前为止,一个程序的语句总是从开始执行到结束,而不管其他任何事情,只是在轮到它们的时候执行。在这一章中,整个新世界将开始展现自己,因为你将学习程序语句的条件执行。这意味着您将根据是否满足某个条件来处理可能执行也可能不执行的语句。

密码输入

您的第一个带条件的程序将评估一个密码。用户可能被允许也可能不被允许进入系统,这取决于他们是否输入了正确的密码。

工作

您将编写一个程序,提示用户输入密码,然后评估输入的密码是否正确。为了简单起见,正确的密码将直接在代码中指定(见图 15-1 和 15-2 )。

img/458464_2_En_15_Fig2_HTML.jpg

图 15-2

正确的密码

img/458464_2_En_15_Fig1_HTML.jpg

图 15-1

密码不正确

分析

让我们更详细地看看这个程序。

程序

在这个程序中,当两个密码(输入的和存储的)一致时,执行一些活动,当它们不一致时,执行不同的活动。在这种情况下,你可以通过适当的信息允许或拒绝用户(见图 15-3 )。

img/458464_2_En_15_Fig3_HTML.jpg

图 15-3

程序流程

程序分支

一般来说,程序分支意味着根据条件的满足采取不同的路径(见图 15-4 )。

img/458464_2_En_15_Fig4_HTML.jpg

图 15-4

分支

句法

对于分支,C# 使用如下所示的if-else结构:

if (condition)
{
  Statements to perform when the condition holds
}
else
{
  Statements to perform otherwise
}

解决办法

代码如下:

static void Main(string[] args)
{
    // Input
    Console.Write("Enter password: ");
    string enteredPassword = Console.ReadLine();

    // Password check
    if (enteredPassword == "friend")
    {
        Console.WriteLine("Password is OK, please enter");
    }
    else
    {
        Console.WriteLine("Incorrect password");
    }

    // Waiting for Enter
    Console.ReadLine();
}

讨论

为了阐明这个条件,我使用了一个等式测试,使用两个等号输入。如果比较值相同,测试评估为true,条件被认为满足,并且执行if分支中的语句。如果比较值不同,测试评估为false,条件被认为不满足,并且执行else分支中的语句。

试验

现在你可以检查程序是如何执行的!除了执行一个普通的程序运行,你还可以单步执行代码,就像你在上一章学到的那样。

反向条件

为了让你更熟悉情况,从不同的角度看它们是很有用的。继续讨论密码问题,让我们从另一个角度来看。

工作

现在的任务是交替地解决前面的练习,也就是把条件反过来。换句话说,你将测试不平等而不是平等。

解决办法

下面是代码:

static void Main(string[] args)
{
    // Correct password
    string correctPassword = "friend";

    // Input
    Console.Write("Enter password: ");
    string enteredPassword = Console.ReadLine();

    // Password check
    if (enteredPassword != correctPassword)
    {
        Console.WriteLine("Incorrect password");
    }
    else
    {
        Console.WriteLine("Password is OK, please enter");
    }

    // Waiting for Enter
    Console.ReadLine();
}

讨论

在这个练习中,我使用了一个不等式测试,它是用一个感叹号后跟一个等号键入的。当比较值不一致时,测试返回真。

长度检查

虽然两个文本只能通过比较来确定它们是相同还是不同,但是两个数字也可以通过比较来确定哪一个更长(或更短)。让我们来看看。

工作

在本节中,您将学习一个程序中的数字比较,该程序评估输入的文本是否最多四个字符长(参见图 15-5 和 15-6 )。

img/458464_2_En_15_Fig6_HTML.jpg

图 15-6

长文本

img/458464_2_En_15_Fig5_HTML.jpg

图 15-5

短文本

解决办法

据推测,您应该确定输入文本的字符数,并将其与数字 4 进行比较。在第七章中,你学习了如何确定文本的字符数——使用它的Length属性(程序是“文本作为对象”)。无论如何,如果您不记得属性的名称,您可以在文本变量的末尾添加一个点,并浏览 IntelliSense 可能性以查看什么可能是合适的,如前一章所述。

代码如下:

static void Main(string[] args)
{
    // Input
    Console.Write("Enter a word: ");
    string word = Console.ReadLine();

    // Determining length
    int wordLength = word.Length;

    // Checking length
    if (wordLength <= 4)
    {
        Console.WriteLine("Word is short (at most 4 characters)");
    }
    else
    {
        Console.WriteLine("Word is long (more than 4 characters)");
    }

    // Waiting for Enter
    Console.ReadLine();
}

注意

我在这个解决方案中使用了一个小于或等于运算符,看起来像这样:<=

正数

在本节中,您将获得更多关于数字比较的练习。

工作

您将编写一个程序来评估用户输入的数字是否为正数(参见图 15-7 和 15-8 )。

img/458464_2_En_15_Fig8_HTML.jpg

图 15-8

这不是积极的

img/458464_2_En_15_Fig7_HTML.jpg

图 15-7

它是积极的

解决办法

代码如下:

static void Main(string[] args)
{
    // Input
    Console.Write("Enter a number: ");
    string input = Console.ReadLine();
    int number = Convert.ToInt32(input);

    // Evaluation
    if (number > 0)
    {
        Console.WriteLine("The number is positive");
    }
    else
    {
        Console.WriteLine("The number is NOT positive");
    }

    // Waiting for Enter
    Console.ReadLine();
}

讨论

我使用了一个大于运算符来比较输入的数字和零。

当用户输入 0 时,你认为程序会做什么?它检查条件0 > 0并发现它不满足。因此,它显示该数字不是正数。这就是相当不寻常的信息措辞(“…非正面”)的原因,如图 15-9 所示。我没有用过“……是否定的”。

img/458464_2_En_15_Fig9_HTML.jpg

图 15-9

零的结果

奇数和偶数

让我们进行另一个数字比较。

工作

你现在的任务是编写一个程序,评估用户输入的数字是奇数还是偶数(见图 15-10 和 15-11 )。

img/458464_2_En_15_Fig11_HTML.jpg

图 15-11

确定奇数

img/458464_2_En_15_Fig10_HTML.jpg

图 15-10

确定偶数

解决办法

解决方案的核心是确定输入的数除以 2 的余数。如果余数为零,则数字为偶数。如果有余数,这个数就是奇数。

代码如下:

static void Main(string[] args)
{
    // Input
    Console.Write("Enter a number: ");
    string input = Console.ReadLine();
    int number = Convert.ToInt32(input);

    // Remainder calculation
    int remainderAfterDivisionByTwo = number % 2;

    // Evaluation
    if (remainderAfterDivisionByTwo == 0)
    {
        Console.WriteLine("The number is even");
    }
    else
    {
        Console.WriteLine("The number is odd");
    }

    // Waiting for Enter
    Console.ReadLine();
}

大小写无关

你已经知道两个文本可以比较,看看他们是否相等或不相等。这种比较区分大小写。换句话说,霍比特人霍比特人被认为是不同的单词。然而,您经常需要不区分大小写的比较,我现在将向您展示这一点。

工作

在这个程序中,用户将输入两个名字,你将评估它们是相同还是不同,不考虑小写和大写的区别(见图 15-12 和 15-13 )。

img/458464_2_En_15_Fig13_HTML.jpg

图 15-13

不同的名字

img/458464_2_En_15_Fig12_HTML.jpg

图 15-12

相同的名字

解决办法

该解决方案的核心是在进行比较之前将两段文本都转换成小写。为此,您可以使用ToLower方法调用。

代码如下:

static void Main(string[] args)
{
    // Inputs
    Console.Write("Enter a name: ");
    string name1 = Console.ReadLine();

    Console.Write("Enter another name: ");
    string name2 = Console.ReadLine();

    // Converting to small letters
    string name1inSmall = name1.ToLower();
    string name2inSmall = name2.ToLower();

    // Evaluating
    if (name1inSmall == name2inSmall)
    {
        Console.WriteLine("You have entered the same names");
    }
    else
    {
        Console.WriteLine("You have entered different names");
    }

    // Waiting for Enter
    Console.ReadLine();
}

不带牙套

如果分支只包含一条语句,C# 允许您省略ifelse分支周围的括号。一般来说,我不推荐这种做法,因为它可能会产生误导。我现在给你看这个,只是为了让你意识到它。

工作

您将再次解决之前的练习,这一次没有括号。

解决办法

代码如下:

static void Main(string[] args)
{
    // Inputs
    Console.Write("Enter a name: ");
    string name1 = Console.ReadLine();

    Console.Write("Enter another name: ");
    string name2 = Console.ReadLine();

    // Converting to small letters
    string name1inSmall = name1.ToLower();
    string name2inSmall = name2.ToLower();

    // Evaluating
    // BRANCHES NOT DELIMITED BY BRACES (CURLY BRACKETS)
    if (name1inSmall == name2inSmall)
        Console.WriteLine("You have entered the same names");
    else
        Console.WriteLine("You have entered different names");

    // Waiting for Enter
    Console.ReadLine();
}

两个数中较大的一个

程序员的一项常见任务是找出两个数字中哪个更大(或者更小,类似地)。

工作

你现在的任务是编写一个程序,要求用户输入两个数字,然后说出两个数字中哪个更大(见图 15-14 )。

img/458464_2_En_15_Fig14_HTML.jpg

图 15-14

确定哪个数字更大

解决办法

代码如下:

static void Main(string[] args)
{
    // Inputs
    Console.Write("Enter first number: ");
    string input1 = Console.ReadLine();
    int number1 = Convert.ToInt32(input1);

    Console.Write("Enter second number: ");
    string input2 = Console.ReadLine();
    int number2 = Convert.ToInt32(input2);

    // Evaluating
    int greater;
    if (number1 > number2)
    {
        greater = number1;
    }
    else
    {
        greater = number2;
    }

    // Output
    Console.WriteLine("Greater of entered numbers is: " + greater);

    // Waiting for Enter
    Console.ReadLine();
}

没有 else 分支

在之前的练习中,您总是有两个分支——if分支和else分支。换句话说,你总是处于非此即彼的境地。然而,需要注意的是,如果你愿意,可以省略else分支。这意味着如果一个条件满足了,你就做一些事情,如果没有满足,你就什么都不做。看一看!

工作

在上一个练习中,您将greater变量设置为第一个值或第二个值。

现在你将以不同的方式解决同样的任务。首先,您将直接将greater变量设置为第一个值,然后如果第二个值更大,您将更改最终结果。

解决办法

下面是代码:

static void Main(string[] args)
{
    // Inputs
    Console.Write("Enter first number: ");
    string input1 = Console.ReadLine();
    int number1 = Convert.ToInt32(input1);

    Console.Write("Enter second number: ");
    string input2 = Console.ReadLine();
    int number2 = Convert.ToInt32(input2);

    // Evaluating
    int greater = number1;
    if (number2 > greater)
    {
        greater = number2;
    }

    // Output
    Console.WriteLine("Greater of entered numbers is: " + greater);

    // Waiting for Enter
    Console.ReadLine();
}

使用内置函数

在这本书里,我经常从不同的角度向你展示事物。我坚信这能增进你的理解。对于当前寻找两个值中较大值的问题,我将向您展示第三种解决方法。任务如此频繁,事实上,有一个方便的内置功能。

工作

您将使用内置函数Math.Max解决前面的练习。

解决办法

代码如下:

static void Main(string[] args)
{
    // Inputs
    Console.Write("Enter first number: ");
    string input1 = Console.ReadLine();
    int number1 = Convert.ToInt32(input1);

    Console.Write("Enter second number: ");
    string input2 = Console.ReadLine();
    int number2 = Convert.ToInt32(input2);

    // Evaluating
    int greater = Math.Max(number1, number2);

    // Output
    Console.WriteLine("Greater of entered numbers is: " + greater);

    // Waiting for Enter
    Console.ReadLine();
}

摘要

在这一章中,你开始学习程序语句的条件执行,这意味着一个或多个语句的执行或不执行可以通过一些测试来决定。您看到了以下测试示例:

  • ==操作符测试两个文本或两个数字的相等性

  • !=运算符测试两个文本或两个数字的不等式

  • >(或<)运算符测试一个数是否大于(或小于)另一个数

最后一个测试可以用>=(或<=)运算符扩展为“大于或等于”(或“小于或等于”)。

为了在代码中使用条件执行,您学习了if-else构造。这由一个条件和两个分支组成。如果条件被评估为真(满足),则执行if分支中的语句。如果条件被评估为假(未满足),则执行else分支中的语句。

您了解到,如果一个分支由一条语句组成,C# 语法允许您省略分支周围的大括号,尽管我不鼓励您这样做,因为当人们后来将一个分支扩展到几条语句时,经常会忘记包括大括号。

更重要的是,您了解到如果您愿意,可以省略else分支。这意味着没有替代行动——当条件不满足时,什么也不做。

作为奖励,您学习了有用的内置函数Math.Max。(你大概能猜到有个类似的函数叫Math.Min。)

十六、实际情况

在前一章中,你学习了程序语句的条件执行。在这一章中,你将加深对这一主题的了解。我将向您展示如何在您的编程生涯中迟早会遇到的几个简单任务中使用条件和分支。

附加扩展名

有时,您想向用户询问文件名,但您不知道用户输入的文件名是否带有扩展名。

工作

您将编写一个程序,将扩展名.png附加到输入的文件名上,除非该扩展名已经是输入的一部分(见图 16-1 和 16-2 )。

img/458464_2_En_16_Fig2_HTML.jpg

图 16-2

不追加。png 扩展

img/458464_2_En_16_Fig1_HTML.jpg

图 16-1

追加。png 扩展

解决办法

代码如下:

static void Main(string[] args)
{
    // Input
    Console.Write("Enter image name: ");
    string fileName = Console.ReadLine();

    // Appending extension (ONLY IN CASE OF NEED)
    if (!fileName.ToLower().EndsWith(".png"))
    {
        fileName += ".png";
    }

    // Output
    Console.WriteLine("We are going to use name: " + fileName);

    // Waiting for Enter
    Console.ReadLine();
}

讨论

让我们稍微讨论一下这个程序。

延伸检测

当前练习中最有趣的一点是找出输入的文件名是否以特定的扩展名结尾:

  • 首先,将文件名转换成小写,这样就不必区分.png.PNG

  • 您使用方法EndsWith来确定文本是否以特定的内容结束。在这种情况下,如果文本以.png结尾,方法调用将返回true。否则返回false

  • 使用!操作符对EndsWith方法返回的结果求反。感叹号将true变为false,反之亦然。这意味着你实际上是在问“文本不是以.png结尾的吗?”而不是“是不是以.png结尾?”。

输入条件

请注意,在指定条件时,不一定要输入比较。例如,您不必总是使用“小于”。如果条件评估为布尔值,如truefalse,这就足够了。

如果条件评估为true,则认为满足,并且执行if分支中的语句。

如果条件评估为false,则认为没有满足,并且执行else分支中的语句(或者在缺少else分支的情况下不执行任何操作)。

缺少 else 分支

示例程序缺少一个else分支。如果输入的名字以.png结尾,EndsWith方法会找到它并返回true。如果得到false,则认为条件没有满足,所以不会执行附加扩展名的语句,输入的名称保持不变。

链接

注意ToLowerEndsWith方法的链接。小写转换的输出不存储在任何变量中。相反,它作为链中下一个方法的输入,换句话说,EndsWith

头和尾

让我们再做一些关于条件的练习。

工作

你要写一个扔一次硬币的程序(见图 16-3 )。

img/458464_2_En_16_Fig3_HTML.jpg

图 16-3

扔硬币

解决办法

解决方案的核心是生成一个随机数——零或一——并随后将其转换为正面或反面。

代码如下:

static void Main(string[] args)
{
    // Random number generator
    Random randomNumbers = new Random();

    // Random number 0/1 and its transformation
    int randomNumber = randomNumbers.Next(0, 1 + 1);
    if (randomNumber == 0)
    {
        Console.WriteLine("Head tossed");
    }
    else
    {
        Console.WriteLine("Tail tossed");
    }

    // Waiting for Enter
    Console.ReadLine();
}

讨论

我只想提醒您,Next方法要求指定一个随机数范围的上限,并且已经增加了 1。这就是为什么你在前一个程序中写了1+1。当然,你也可以直接写2,但是1+1在我看来更符合逻辑,声明1为上限,并添加(奇怪的)必需的1

截止日期检查

俗话说,“永远不要相信用户”。这意味着作为一名程序员,你必须经常检查生产软件中用户输入的数据。

你需要检查用户数据通常不是因为恶意使用,因为 99.9%的用户没有任何滥用你的软件的意图。用户只会犯错误。这就是为什么你应该检查他们的输入并提示他们改正。

因此,现在您将学习如何实现一些输入检查。

工作

您将编写一个程序,提示用户输入订单截止日期,如果用户输入的是过去的日期,则会给出警告(见图 16-4 )。

img/458464_2_En_16_Fig4_HTML.jpg

图 16-4

查看日期

解决办法

代码如下:

static void Main(string[] args)
{
    // Input
    Console.Write("Enter order deadline: ");
    string input = Console.ReadLine();
    DateTime enteredDeadline = Convert.ToDateTime(input);

    // Checking entered value
    DateTime today = DateTime.Today;
    if (enteredDeadline < today)
    {
        Console.WriteLine("Error! You have entered date in the past.");
    }
    else
    {
        Console.WriteLine("Deadline accepted.");
    }

    // Waiting for Enter
    Console.ReadLine();
}

讨论

请注意以下几点:

  • 要将文本形式输入的日期转换成DateTime对象,可以使用Convert.ToDateTime方法调用。

  • 如果输入不存在的日期,转换将失败。您可以使用try-catch来处理这个问题。

  • 与数字转换类似,Convert.ToDateTime可以接受第二个参数,指定转换所用的语言。

发票日期检查

让我们再做一个检查用户输入数据的练习。

工作

我国的增值税(VAT)法规要求发票开具日期不能早于付款日期,同时也不能晚于付款后 15 天。

当前的任务是执行这两项检查(见图 16-5 、 16-6 和 16-7 )。

img/458464_2_En_16_Fig7_HTML.jpg

图 16-7

接受日期

img/458464_2_En_16_Fig6_HTML.jpg

图 16-6

约会太晚

img/458464_2_En_16_Fig5_HTML.jpg

图 16-5

约会太早

解决办法

以下是解决方案:

static void Main(string[] args)
{
    // Inputs
    Console.Write("Payment date: ");
    string inputPayment = Console.ReadLine();
    DateTime paymentDate = Convert.ToDateTime(inputPayment);

    Console.Write("Invoice date: ");
    string inputInvoice = Console.ReadLine();
    DateTime invoiceDate = Convert.ToDateTime(inputInvoice);

    // Checking
    bool ok = true;
    if (invoiceDate < paymentDate)
    {
        Console.WriteLine("Invoice date cannot precede payment date.");
        ok = false;
    }
    if (invoiceDate > paymentDate.AddDays(15))
    {
        Console.WriteLine("Invoice cannot be issued later than 15 days after payment.");
        ok = false;
    }
    if (ok)
    {
        Console.WriteLine("Dates accepted.");
    }

    // Waiting for Enter
    Console.ReadLine();
}

讨论

在这个解决方案中,您使用了一个名为ok的辅助变量。变量监控一切是否正常。一开始,你把它设置为true。如果执行的任何检查失败,您将该值切换到false。如果变量在两次检查后都保持true,您就知道一切正常,并向用户显示一条确认消息。

西班牙语星期几

现在,您将学习如何将代码的执行分成多个分支。

工作

您将编写一个程序,显示用户输入的日期的西班牙语版本的星期几(月、月、日等等)(见图 16-8 )。

img/458464_2_En_16_Fig8_HTML.jpg

图 16-8

用西班牙语显示日期

解决办法

您可以使用DateTime对象的DayOfWeek属性来查找星期几。可以使用一系列条件进行到西班牙语的转换。

代码如下:

static void Main(string[] args)
{
    // Input
    Console.Write("Enter a date: ");
    string input = Console.ReadLine();
    DateTime enteredDate = Convert.ToDateTime(input);

    // Finding day of week (in enumeration)
    DayOfWeek dayOfWeek = enteredDate.DayOfWeek;

    // Spanish texts
    string spanish = "";
    if (dayOfWeek == DayOfWeek.Monday)
        spanish = "Lunes";
    if (dayOfWeek == DayOfWeek.Tuesday)
        spanish = "Martes";
    if (dayOfWeek == DayOfWeek.Wednesday)
        spanish = "Miercoles";
    if (dayOfWeek == DayOfWeek.Thursday)
        spanish = "Jueves";
    if (dayOfWeek == DayOfWeek.Friday)
        spanish = "Viernes";
    if (dayOfWeek == DayOfWeek.Saturday)
        spanish = "Sábado";
    if (dayOfWeek == DayOfWeek.Sunday)
        spanish = "Domingo";

    // Output
    Console.WriteLine(spanish);
    if (enteredDate == new DateTime(1945, 5, 8))
        Console.WriteLine("The happiest day of the 20th century.");

    // Waiting for Enter
    Console.ReadLine();
}

讨论

请注意以下几点:

img/458464_2_En_16_Fig9_HTML.jpg

图 16-9

使用 DayOfWeek 枚举

  • 您已经省略了围绕单个if分支的大括号。您可以这样做,因为每个分支中只有一个语句。我通常不这样做,但是在这种有许多简单的if的情况下,对我来说这似乎会使代码更整洁。

  • 一周中的每一天都是DayOfWeek枚举的成员。在输入两个等号后,只要按下键盘上的空格键,Visual Studio 就会为您提供枚举(参见图 16-9 )。使用 Visual Studio 提供的功能!

交换语句

对于某些多重分支的情况,C# 中也有一个switch语句。现在你将学习如何使用它。

工作

您将使用switch语句解决最后一个任务。

解决办法

下面是代码:

static void Main(string[] args)
{
    // Input
    Console.Write("Enter a date: ");
    string input = Console.ReadLine();
    DateTime enteredDate = Convert.ToDateTime(input);

    // Finding day of week (in enumeration)
    DayOfWeek dayOfWeek = enteredDate.DayOfWeek;

    // Spanish texts
    string spanish = "";
    switch (dayOfWeek)
    {
        case DayOfWeek.Monday:
            spanish = "Lunes";
            break;
        case DayOfWeek.Tuesday:
            spanish = "Martes";
            break;
        case DayOfWeek.Wednesday:
            spanish = "Miercoles";
            break;
        case DayOfWeek.Thursday:
            spanish = "Jueves";
            break;
        case DayOfWeek.Friday:
            spanish = "Viernes";
            break;
        case DayOfWeek.Saturday:
            spanish = "Sábado";
            break;
        case DayOfWeek.Sunday:
            spanish = "Domingo";
            break;
    }

    // Output
    Console.WriteLine(spanish);
    if (enteredDate == new DateTime(1945, 5, 8))
        Console.WriteLine("The happiest day of the 20th century.");

    // Waiting for Enter
    Console.ReadLine();
}

讨论

如果重复的分支总是基于相同的值,您可以使用switch语句作为if序列的替换。这是本例中的dayOfWeek变量的值。

至于语法,switch关键字后面(在括号中)是一个变量(或表达式),它的值决定了执行将采用哪个分支。各个分支以关键字case开始,后跟控制变量的特定值和一个冒号。你应该用break关键字结束每个分支。

摘要

在这一章中,你为各种实际任务编写了有条件执行的程序。具体来说,您学到了以下内容:

  • 输入不带任何关系运算符的条件,如<==等。条件简单地评估为bool类型。当评估结果为true时,视为完成。

  • 使用!运算符否定条件。

  • 将随机数转换成另一种数据,如正面/反面对。

  • 对用户输入执行各种检查,尤其是日期。

  • 通过使用一系列if语句或使用switch语句,将程序分支到几个可选的执行路径。

十七、复合条件

你现在已经有了一些用公式表达条件,并用它们来解决实际问题的经验。对于更复杂的问题,你经常需要的是把两个或更多的部分条件组合起来。这就是你将在本章学习的内容。

是或否

复合条件的第一个用途是检查用户输入是否属于允许的选项之一。

工作

您将编写一个程序来检查用户输入的还是否。所有其他输入都将被视为不正确(参见图 17-1 和 17-2 )。

img/458464_2_En_17_Fig2_HTML.jpg

图 17-2

是啊!

img/458464_2_En_17_Fig1_HTML.jpg

图 17-1

可以接受的回答但是很难过

解决办法

代码如下:

static void Main(string[] args)
{
    // Input
    Console.Write("Do you love me? ");
    string input = Console.ReadLine();

    // Evaluating
    string inputInSmall = input.ToLower();
    if (inputInSmall == "yes" || inputInSmall == "no")
    {
        Console.WriteLine("OK.");
    }
    else
    {
        Console.WriteLine("Say it straight!");
    }

    // Waiting for Enter
    Console.ReadLine();
}

讨论

请注意以下几点:

  • 为了忽略大小写的差异,您将输入转换为小写字母。

  • 使用的条件是复合条件。它由使用条件 OR 运算符连接的两个部分条件组成,类型为||(两条垂直线)。

  • 如果部分条件的至少一个被满足,则条件被满足(并且if分支被执行)。这意味着用户输入了否。在这种情况下,选项是互斥的。但是,您会遇到两个条件同时满足的情况。

  • 如果第一或第二部分条件都不满足,则条件不满足(并且执行else分支)。换句话说,用户输入了除是或否之外的内容。

用户名和密码

现在,您将看到应该同时满足的部分条件。

工作

您将编写一个程序,检查用户是否输入了正确的用户名(Orwell) 并同时输入了正确的密码(Catalonia)。用户名不区分大小写,这意味着它可以是小写和大写(参见图 17-3 和 17-4 )。

img/458464_2_En_17_Fig4_HTML.jpg

图 17-4

正确的用户名和密码

img/458464_2_En_17_Fig3_HTML.jpg

图 17-3

用户名正确,但密码不正确

解决办法

代码如下:

static void Main(string[] args)
{
    // Correct values
    string correctUsername = "Orwell";
    string correctPassword = "Catalonia";

    // Inputs
    Console.Write("User name: ");
    string enteredUserName = Console.ReadLine();

    Console.Write("Password: ");
    string enteredPassword = Console.ReadLine();

    // Evaluating
    if (enteredUserName.ToLower() == correctUsername.ToLower() &&
        enteredPassword == correctPassword)
    {
        Console.WriteLine("Thanks for your books!");
    }
    else
    {
        Console.WriteLine("Could not log you in.");
    }

    // Waiting for Enter
    Console.ReadLine();
}

讨论

请注意以下几点:

  • 使用的条件也是一个复合条件。它的部分条件使用条件 AND 运算符连接,该运算符的类型为&&(两个&符号)。

  • 如果两个部分条件同时满足,则条件满足(执行if分支)。这意味着用户必须输入正确的用户名和密码。

  • 不满足条件(并因此执行else分支),不满足任何一个部分条件就足够了。

裤子

您甚至可以组合几个 AND 和 OR 操作符来得到一个真正复杂的复合条件。看一看!

工作

您将修改前面的任务,以允许两个可能的用户登录。两者都有自己的密码。

解决办法

代码如下:

static void Main(string[] args)
{
    // Correct values
    string correctUsername1 = "Orwell";
    string correctPassword1 = "Catalonia";

    string correctUsername2 = "Blair";
    string correctPassword2 = "1984";

    // Inputs
    Console.Write("User name: ");
    string enteredUsername = Console.ReadLine();

    Console.Write("Password: ");
    string enteredPassword = Console.ReadLine();

    // Evalulating
    if (enteredUsername.ToLower() == correctUsername1.ToLower() &&
        enteredPassword == correctPassword1 ||
        enteredUsername.ToLower() == correctUsername2.ToLower() &&
        enteredPassword == correctPassword2)
    {
        Console.WriteLine("Thanks for your books!");
    }
    else
    {
        Console.WriteLine("Could not log you in.");
    }

    // Waiting for Enter
    Console.ReadLine();
}

讨论

请注意以下几点:

  • 您可以将两个条件运算符:AND 与 OR 结合使用。

  • 满足完整条件要求用户输入正确的第一用户名和正确的第一密码,或者输入正确的第二用户名和正确的第二密码。

  • 与 or 运算符相比,条件有意为 AND 运算符使用更高优先级的(优先级)。具体来说,首先评估两个潜在用户,然后对部分结果进行“或”运算。

** 如果需要不同的求值顺序,只需像数学一样使用圆括号(圆括号)即可。*

*## 条件的预先计算

前面练习中的复合条件已经相当复杂了。要理解这一点,阅读时必须集中注意力。在类似的情况下,预先计算(提前计算)部分条件可能更好。这就是我现在要给你看的。

工作

任务和上一个是一样的,但是解决方案会不一样。

解决办法

代码如下:

static void Main(string[] args)
{
    // Correct values
    string correctUsername1 = "Orwell";
    string correctPassword1 = "Catalonia";

    string correctUsername2 = "Blair";
    string correctPassword2 = "1984";

    // Inputs
    Console.Write("User name: ");
    string enteredUsername = Console.ReadLine();
    Console.Write("Password: ");
    string enteredPassword = Console.ReadLine();

    // Evaluating
    bool user1ok = enteredUsername.ToLower() == correctUsername1.ToLower() &&
                    enteredPassword == correctPassword1;
    bool user2ok = enteredUsername.ToLower() == correctUsername2.ToLower() &&
                    enteredPassword == correctPassword2;
    if (user1ok || user2ok)
    {
        Console.WriteLine("Thanks for your books!");
    }
    else
    {
        Console.WriteLine("Could not log you in.");
    }

    // Waiting for Enter
    Console.ReadLine();
}

讨论

请注意以下几点:

  • 您一个接一个地检查两个用户。这样,主条件就可以用一种清晰简洁的方式写出来。

  • 部分条件被预先计算成bool类型的变量。当条件满足时,相应变量的值被设置为true

是或否反转

你已经在第十五章中学习了反转条件。在那一章中,条件很简单。现在,您将逆转一个复合条件,这有点棘手,需要更加小心。

工作

你将再次回到本章开头的“是或否”项目。出于练习复合条件的目的,考虑如何颠倒原始条件来交换ifelse分支。

解决办法

下面是代码:

static void Main(string[] args)
{
    // Input
    Console.Write("Do you love me? ");
    string input = Console.ReadLine();

    // Evaluating
    string inputInSmall = input.ToLower();
    if (inputInSmall != "yes" && inputInSmall != "no")
    {
        Console.WriteLine("Say it straight!");
    }
    else
    {
        Console.WriteLine("OK.");
    }

    // Waiting for Enter
    Console.ReadLine();
}

讨论

请注意以下几点:

  • 您现在检查的不是正确的输入,而是不正确的输入。

  • 当输入既不等于“是”也不等于“否”时,则输入不正确。

  • 颠倒条件导致 OR 运算符变为 AND 运算符。它还导致平等变成不平等。

等级检查

现在我想把你的注意力转向一个属于特定集合或特定范围的数字的频繁测试。以下两项任务与此相关。

工作

用户输入学生的成绩。然后,程序将检查输入的数字是否在可能值 1、2、3、4 或 5 的集合中(见图 17-5 和 17-6 )。

img/458464_2_En_17_Fig6_HTML.jpg

图 17-6

不在范围内

img/458464_2_En_17_Fig5_HTML.jpg

图 17-5

在范围内

解决办法

这个条件可以用列举单个选择的公式来表达。为了简单起见,我不检查可能的非数字输入。您可以像往常一样使用try-catch自行处理。

代码如下:

static void Main(string[] args)
{
    // Input
    Console.Write("Enter a grade: ");
    string input = Console.ReadLine();
    int grade = Convert.ToInt32(input);

    // Evaluating
    if (grade == 1 ||
        grade == 2 ||
        grade == 3 ||
        grade == 4 ||
        grade == 5)
    {
        Console.WriteLine("Input OK.");
    }
    else
    {
        Console.WriteLine("Incorrect input.");
    }

    // Waiting for Enter
    Console.ReadLine();
}

更好的范围检查

允许的数字(可能的等级)实际上构成了一个一到五的范围(一个连续的没有间隙的范围)。在这种情况下,您可以使用更好的方法来检查一个数字是否属于特定的范围。

工作

任务是使用范围检查解决前面的练习。

解决办法

当一个数大于或等于下界,同时又小于或等于上界时,它就属于由其下界和上界给定的范围。

代码如下:

static void Main(string[] args)
{
    // Input
    Console.Write("Enter a grade: ");
    string input = Console.ReadLine();
    int grade = Convert.ToInt32(input);

    // Evaluating
    if (grade >= 1 && grade <= 5)
    {
        Console.WriteLine("Input OK.");
    }
    else
    {
        Console.WriteLine("Incorrect input.");
    }

    // Waiting for Enter
    Console.ReadLine();
}

摘要

本章向您介绍了复合条件的主题。您已经了解到,if语句条件可以通过使用条件 AND 和条件 OR 操作符将几个部分条件连接在一起来复合。

在 C# 中,AND 运算符被写成&&,当两个部分条件都满足时,它的计算结果为true。另一方面,OR 运算符被写成||,当至少一个部分条件满足时,它的计算结果为true

您还看到了大量的部分条件组合成一个条件。在这种情况下,运算符优先级的问题很重要。如果没有括号,则和总是在或之前计算。但是,请注意,这些条件可能会变得相当复杂和难以理解。建议预先分别计算整个条件的各个部分,并将它们临时存储在bool类型的变量中。

我还试图让你注意到逆转复合条件的问题,这需要额外的关注和专注才能做好。具体来说,您了解到在反转时,and 被切换为 or(反之亦然),等式变为不等式(反之亦然)。

最后,我向您展示了如何检查一个数字是属于指定的集合还是指定的范围。在后一种情况下,您对范围的下限和上限执行了同步测试。*

十八、多重条件

继续讨论条件这个话题,现在我们来看更复杂的例子。在这一章中,你将遇到可以在一个程序中使用几个条件来解决的任务。

足球

首先,您将详细考虑可选执行路径的三个分支的典型情况。

工作

您将准备一个程序,用户在其中输入关于足球比赛的数据:双方的进球数。然后,程序评估匹配结果。它显示第一个俱乐部赢了,第二个俱乐部赢了,还是平局(见图 18-1 、 18-2 和 18-3 )。

img/458464_2_En_18_Fig3_HTML.jpg

图 18-3

第二个俱乐部赢了

img/458464_2_En_18_Fig2_HTML.jpg

图 18-2

这是一个平局

img/458464_2_En_18_Fig1_HTML.jpg

图 18-1

第一个俱乐部赢了

分析

您可以连续使用三个条件来解决任务,每个条件考虑一个特定的匹配结果(参见图 18-4 )。

img/458464_2_En_18_Fig4_HTML.jpg

图 18-4

程序流程

解决办法

代码如下:

static void Main(string[] args)
{
    // Inputs
    Console.Write("Goals scored by Liverpool: ");
    string inputLiverpool = Console.ReadLine();
    int goalsLiverpool = Convert.ToInt32(inputLiverpool);

    Console.Write("Goals scored by Manchester: ");
    string inputManchester = Console.ReadLine();
    int goalsManchester = Convert.ToInt32(inputManchester);

    // Evaluating
    if (goalsLiverpool > goalsManchester)
    {
        Console.WriteLine("Liverpool won.");
    }

    if (goalsLiverpool == goalsManchester)
    {
        Console.WriteLine("Tie.");
    }

    if (goalsLiverpool < goalsManchester)
    {
        Console.WriteLine("Manchester won.");
    }

    // Waiting for Enter
    Console.ReadLine();
}

足球或者

为了给你展示另一个观点,你将用另一种方法解决前面的练习。之前,您连续使用了三个条件。这一次,您将第二个条件嵌套到第一个条件中。

分析

如图 18-5 所示,程序将首先分支到以下选项:

img/458464_2_En_18_Fig5_HTML.jpg

图 18-5

程序流程

  • 利物浦赢了。

  • 利物浦没有赢。

另一个选项“利物浦没有赢”,将进一步分为以下内容:

  • 领带。

  • 曼彻斯特赢了。

解决办法

代码如下:

static void Main(string[] args)
{
    // Inputs
    Console.Write("Goals scored by Liverpool: ");
    string inputLiverpool = Console.ReadLine();
    int goalsLiverpool = Convert.ToInt32(inputLiverpool);

    Console.Write("Goals scored by Manchester: ");
    string inputManchester = Console.ReadLine();
    int goalsManchester = Convert.ToInt32(inputManchester);

    // Evaluating
    if (goalsLiverpool > goalsManchester)
    {
        // Here we know Liverpool won. We can display the result.
        Console.WriteLine("Liverpool won.");
    }
    else
    {
        // Here we know Liverpool did not win. We will decide
        //   between tie and victorious Manchester
        if (goalsLiverpool == goalsManchester)
        {
            Console.WriteLine("Tie.");
        }
        else
        {
            Console.WriteLine("Manchester won.");
        }
    }

    // Waiting for Enter
    Console.ReadLine();
}

最少三个数字

下一个示例使用条件执行来比较三个数字。

工作

你将编写一个程序,找出用户输入的三个数字中最小的一个(见图 18-6 )。

img/458464_2_En_18_Fig6_HTML.jpg

图 18-6

寻找最小的数字

分析

该任务可以通过对所有输入数字的后续处理来解决。您将使用一个辅助变量来存储到目前为止找到的最小值。

开始时,第一个输入的数字成为最小值。在第二步中,您将第二个数字与最小值进行比较。如果前者小于后者,则前者成为最小值。最后,对第三个数字执行相同的程序。

解决办法

下面是代码:

static void Main(string[] args)
{
    // Inputs
    Console.Write("Enter 1\. number: ");
    string input1 = Console.ReadLine();
    int number1 = Convert.ToInt32(input1);

    Console.Write("Enter 2\. number: ");
    string input2 = Console.ReadLine();
    int number2 = Convert.ToInt32(input2);

    Console.Write("Enter 3\. number: ");
    string input3 = Console.ReadLine();
    int number3 = Convert.ToInt32(input3);

    // At the beginning, we set 1st number as minimum
    int minimum = number1;

    // Is not 2nd number less than present minimum?
    if (number2 < minimum)
    {
        minimum = number2;
    }

    // Is not 3rd number less than present minimum?
    if (number3 < minimum)
    {
        minimum = number3;
    }

    // Output
    Console.WriteLine("The least of entered numbers is " + minimum);

    // Waiting for Enter
    Console.ReadLine();
}

内置函数的最小值

您可以使用 C# 中现成的Math.Min函数来解决前面的练习。函数本身确定两个数字中最小的一个。我将向你展示如何使用三个数字的情况。

解决办法

首先,确定第一个和第二个数字中最小的一个。然后,结果将与第三个“竞争”。

代码如下:

static void Main(string[] args)
{
    // Inputs
    Console.Write("Enter 1\. number: ");
    string input1 = Console.ReadLine();
    int number1 = Convert.ToInt32(input1);

    Console.Write("Enter 2\. number: ");
    string input2 = Console.ReadLine();
    int number2 = Convert.ToInt32(input2);

    Console.Write("Enter 3\. number: ");
    string input3 = Console.ReadLine();
    int number3 = Convert.ToInt32(input3);

    // Evaluating
    int min12 = Math.Min(number1, number2);
    int minimum = Math.Min(min12, number3);

    // Output
    Console.WriteLine("The least of entered numbers is " + minimum);

    // Waiting for Enter
    Console.ReadLine();
}

线性方程

这个练习会涉及到一些数学知识。

工作

您将编写一个程序来求解一个线性方程,换句话说,一个类型为 ax + b = 0 的方程。

比如 2x + 6 = 0 是一个线性方程,2 是 a ,6 是 b

解决方案显然是-3。当你用-3 代替 x 时,左边变成零,换句话说,等于右边。

用户以系数 ab 的形式输入要求解的方程。然后程序计算并显示其解(见图 18-7 )。

img/458464_2_En_18_Fig7_HTML.jpg

图 18-7

计算并显示其解

分析

无论何时你想编程任何东西,你都需要先了解现实世界的问题。换句话说,你需要知道不用电脑怎么解。

以下是如何解线性方程的数学提示:

  • 如果 a 不为零,显而易见的解是 -b/a

  • a 等于零的情况是一种数学上的好奇。方程退化为一个奇怪的“没有 x 的方程”或者伪方程 b = 0 。这样一个“等式”

    • 对于等于零的 b 有无穷多个解(不管 x 如何,它总是成立)

    • 对于非零的 b 没有解(没有 x 可以满足这个方程)

解决办法

代码如下:

static void Main(string[] args)
{
    // Inputs
    Console.Write("Enter a: ");
    string inputA = Console.ReadLine();
    double a = Convert.ToDouble(inputA);

    Console.Write("Enter b: ");
    string inputB = Console.ReadLine();
    double b = Convert.ToDouble(inputB);

    // Solving the equation
    if (a != 0)
    {
        // a is non-zero, the equation has "normal" solution
        double solution = -b / a;
        Console.WriteLine("Solution is x=" + solution);
    }
    else
    {
        // a is zero, result depends on b
        if (b == 0)
        {
            Console.WriteLine("The equation \"is solved\" by any x");
        }
        else
        {
            Console.WriteLine("The equation does not have a solution");
        }
    }

    // Waiting for Enter
    Console.ReadLine();
}

二次方程

继续数学,下一个练习涉及一个更难的二次方程。

工作

你会写一个程序来解一个二次方程,换句话说就是一个类似ax2+bx+c = 0的方程。二次方程的一个例子是x2-x- 2 = 0其中 a 为 1, b 为-1, c 为-2。提到的方程有两个解:-1 和 2。替换左边两个零中的任何一个。

待解方程将以系数 abc 的形式输入。程序计算并显示其解(见图 18-8 )。

img/458464_2_En_18_Fig8_HTML.jpg

图 18-8

解二次方程

为了简单起见,您不会考虑 a 等于零的情况,这将把任务转移到前一个线性方程。

分析

很久以前,有人聪明地设计出了一个解二次方程的程序。你可能在学校就知道了。你先算一下所谓的判别式:

$$ D={b}²-4 ac $$

然后,解决方案根据判别值进行分支。

  • 对于 D > 0 ,该方程有两个解,由下式给出:

$$ {x}_{1,2}=\frac{-b\pm \sqrt{D}}{2a} $$

  • 对于 D = 0 ,同样的公式适用于两个解重合的情况。

  • 对于 D < 0 ,该方程没有实数解。

解决办法

代码如下:

static void Main(string[] args)
{
    // Inputs
    Console.Write("Enter a: ");
    string input = Console.ReadLine();
    double a = Convert.ToDouble(input);

    Console.Write("Enter b: ");
    string inputB = Console.ReadLine();
    double b = Convert.ToDouble(inputB);

    Console.Write("Enter c: ");
    string inputC = Console.ReadLine();
    double c = Convert.ToDouble(inputC);

    // Solving + output
    double d = b * b - 4 * a * c;
    if (d > 0)
    {
        double x1 = (-b - Math.Sqrt(d)) / (2 * a);
        double x2 = (-b + Math.Sqrt(d)) / (2 * a);
        Console.WriteLine("The equation has two solutions:");
        Console.WriteLine(x1);
        Console.WriteLine(x2);
    }
    if (d == 0)
    {
        double x = -b / (2 * a);
        Console.WriteLine("The equation has a single solution: " + x);
    }
    if (d < 0)
    {
        Console.WriteLine("The equation does not have a solution");
    }

    // Waiting for Enter
    Console.ReadLine();
}

讨论

这个练习最有趣的一点是你用代码输入公式的方式。请注意,分子和分母必须用括号括起来,以确定计算的正确顺序!数学公式不包含它们,因为数学家使用分数。

计算判别式时,我不使用括号;我只是依靠乘法优先于减法。

试验

为了检查程序计算是否正确,您可以编写更多的代码作为测试;将溶液替换为 x 后,左侧应为零。

摘要

在本章中,您看到了几个使用多个条件来完成指定任务的例子。

首先,你用两种方法解决了足球比赛评估。第一个使用三个简单的if语句一个接一个地考虑各个可能性。第二个使用了嵌套在另一个分支中的分支。

您进一步连续练习了多个条件,以找到三个数字中最小的一个。为此,您在一个辅助变量中存储了一个“迄今为止最小”值。

然后使用内置函数Math.Min解决了相同的任务。您已经知道函数决定了两个值中的最小值。在这里,我向您展示了一个有趣的案例,告诉您如何将它用于三个数字。

在最后两个任务中,你练习了数学中的多个条件,即解线性和二次方程。最后一项任务让您有机会看到用代码编写的更复杂的计算。

十九、高级条件

本书的第三部分以几个关于条件执行的任务结束,这些任务可能被认为是高级的。首先,你将学习条件运算符,然后你将编写一个包含几个复杂条件的程序,最后你将学习一条重要的格言:当你想测试某个东西时,你必须确定它存在。

条件运算符

在许多情况下,if-else结构可以用条件操作符代替,这将根据条件是否满足而产生两个值中的一个。如果你知道 Excel 的IF函数,你会发现条件运算符很熟悉。

工作

你将使用条件运算符(见图 19-1 )解决前面的“头尾”任务(来自第十六章)。

img/458464_2_En_19_Fig1_HTML.jpg

图 19-1

使用条件运算符

解决办法

代码如下:

static void Main(string[] args)
{
    // Random number generator
    Random randomNumbers = new Random();

    // Random number 0/1 and its transformation
    int randomNumber = randomNumbers.Next(0, 1 + 1);
    string message = randomNumber == 0 ? "Head tossed" : "Tail tossed";
    Console.WriteLine(message);

    // Waiting for Enter
    Console.ReadLine();
}

讨论

条件运算符(?:)的语法如下所示:

  • condition ? yesValue : noValue

这种表达式的结果如下:

  • yes 值如果条件成立(满足)

  • 新值否则

该计划

在这种情况下,条件是randomNumber变量相对于零的相等测试。如果为真,message变量被赋予“头被甩”的文本。否则,它将被分配“抛尾”文本。

术语

条件运算符也被称为三元运算符,因为它是唯一接受三个操作数(它处理的值)的运算符:条件、 yesValuenoValue

汇总评估

现在,您将在现实情况下练习更复杂的条件。

工作

任务是编写一个对学生进行总结评估的程序(见图 19-2 )。用户输入四个科目的等级(从一到五,其中一个是最好的)。用户还指定所考虑的学生是否无故缺席。然后,程序会给出一个总结性的评估,这是一种总体评分:

img/458464_2_En_19_Fig2_HTML.jpg

图 19-2

对学生的总结评价

  • 优秀的

  • 好的

  • 不成功的

细节

我在第十八章中强调,为了能够编写任何东西,你需要准确理解正在解决的任务。在当前练习中,您需要为汇总评估指定确切的标准。

在以下情况下,学生的评估为优秀

  • 所有成绩的算术平均值不高于 1.5。

  • 该学生没有任何成绩低于 2 分。

  • 这个学生没有任何无故缺席。

当学生的成绩中至少有一项是 5 分时,他们被认为是不及格。

在所有其他情况下,总结评估为良好

你现在大概可以猜到,这个程序不会是无足轻重的。

解决办法

代码如下:

static void Main(string[] args)
{
    // 1\. PREPARATIONS
    string errorMessage = "Incorrect input";
    int mathematics, informationTechnology, science, english;
    bool hasUnexcusedAbsences;

    // 2\. INPUTS
    try
    {
        Console.WriteLine("Enter grades from individual subjects:");

        Console.Write("Mathematics: ");
        string input = Console.ReadLine();
        mathematics = Convert.ToInt32(input);
        if (mathematics < 1 || mathematics > 5)
        {
            Console.WriteLine(errorMessage);
            return;
        }

        Console.Write("Information technology: ");
        input = Console.ReadLine();
        informationTechnology = Convert.ToInt32(input);
        if (informationTechnology < 1 || informationTechnology > 5)
        {
            Console.WriteLine(errorMessage);
            return;
        }

        Console.Write("Science: ");
        input = Console.ReadLine();
        science = Convert.ToInt32(input);
        if (science < 1 || science > 5)
        {
            Console.WriteLine(errorMessage);
            return;
        }

        Console.Write("English: ");
        input = Console.ReadLine();
        english = Convert.ToInt32(input);
        if (english < 1 || english > 5)
        {
            Console.WriteLine(errorMessage);
            return;
        }

        Console.Write("Any unexcused absences? (yes/no): ");
        input = Console.ReadLine();
        input = input.ToLower(); // not distinguishing upper/lower
        if (input != "yes" && input != "no")
        {
            Console.WriteLine(errorMessage);
            return;
        }
        hasUnexcusedAbsences = input == "yes";
    }
    catch (Exception)
    {
        Console.WriteLine(errorMessage);
        return;
    }

    // 3\. EVALUATION
    // You need arithmetic average of all the grades
    double average = (mathematics + informationTechnology + science + english) / 4.0;
    string message;

    // Testing all conditions for excellence
    if (average < 1.5001 &&
        mathematics <= 2 && informationTechnology <= 2 &&
        science <= 2 && english <= 2 &&
        !hasUnexcusedAbsences)
    {
        message = "Excellent";
    }
    else
    {
        // Here you know the result is not excellent, so testing the other two possibilities
        if (mathematics == 5 || informationTechnology == 5 ||
            science == 5 || english == 5)
        {
            message = "Failed";
        }
        else
        {
            message = "Good";
        }
    }

    // 4\. OUTPUT
    Console.WriteLine("Summary evaluation: " + message);

    // Waiting for Enter
    Console.ReadLine();
}

讨论

以下部分解释了该程序。

等级输入

在本练习中,您非常关心输入数据检查。一个try-catch包装了整个输入部分。你还需要检查分数是否属于一到五的范围。

请注意,坡度小于 1 或大于 5 表示有错误。您使用了||操作符(“至少一个”)。

程序终止

错误的输入会立即终止程序。这里使用了return语句来终止子程序。但是在Main内部使用时,直接终止整个程序。

是/否输入

要输入学生是否无故缺席,用户可以输入 yes 或 no。yes 和 no 的输入差异表示有错误。我使用了&&操作符(“同时”)。

在检查之前,您将输入转换成小写,这样它就不会介意用户使用大写字母。

有趣的是包含单等号和双等号的行(单等号用于赋值,双等号用于比较):

hasUnexcusedAbsences = input == "yes";

根据等式是否成立,==操作符的“工作”产生一个truefalse值。然后将结果值赋给bool类型的hasUnexcusedAbsences变量。

当心整数除法!

计算平均成绩时,将总和除以 4.0,而不是 4。您不希望计算机将斜杠视为整数除法运算符。这就是为什么你在避免intint除。

如果您只输入 4,那么 1,2,2,2 等级的情况将被错误地评估为优秀,因为平均值将被精确计算为 1,而不是正确的 1.75!

小数运算

你为什么按如下方式输入平均值的检查?

average < 1.5001

你为什么不用下面的?

average <= 1.5

这是因为十进制算术不一定要精确。有时,计算机可能会计算出类似于 1.500000000001 的值,而不是正确的 1.5。这就是为什么你在测试中使用稍微大一点的数字。

第二个性测试

许多程序崩溃是因为程序员在访问某个程序之前忘记测试它是否存在。这个任务将是你对这个常见问题的第一次了解。

工作

我将向您展示如何测试输入文本的第二个字符。假设一个产品标签必须在第二个位置有一个大写字母 X (见图 19-3 和 19-4 )。

img/458464_2_En_19_Fig4_HTML.jpg

图 19-4

测试第二个字符,不正确

img/458464_2_En_19_Fig3_HTML.jpg

图 19-3

测试第二个字符,对吗

为什么这样一个测试如此重要,以至于我决定让你熟悉它?您需要首先测试第二个字符是否存在。这是你会经常遇到的;在你发现某样东西存在之前,你将无法测试它!

在这种情况下,程序不得因空输入或输入过短而崩溃(见图 19-5 )。

img/458464_2_En_19_Fig5_HTML.jpg

图 19-5

标签不正确,不崩溃

解决办法

代码如下:

static void Main(string[] args)
{
    // Input
    Console.Write("Enter product label: ");
    string label = Console.ReadLine();

    // Evaluating
    if (label.Length >= 2 && label.Substring(1, 1) == "X")
    {
        Console.WriteLine("Label is OK");
    }
    else
    {
        Console.WriteLine("Incorrect label");
    }

    // Waiting for Enter
    Console.ReadLine();
}

讨论

以下部分解释了该程序。

获得角色

使用Substring方法访问第二个字符,该方法通常从给定文本中提取特定部分(子串)。该方法需要两个参数:

  • 所需子串的第一个字符的位置:位置编号从零开始,所以第二个字符位置是 1。

  • 所需子串的字符数:在这种情况下,你需要的只是单个字符,这就是为什么第二个参数也是一个。

存在测试

测试一个给定的字符是否等于某个东西有一个隐藏的陷阱:第二个字符根本不需要存在。当用户输入零个或一个字符时会出现这种情况。

在这种情况下,Substring(1,1)调用会导致运行时错误。

这意味着您必须首先测试文本是否至少有两个字符长。只有通过这个测试,你才能进入第二个角色。

代码中有一个复合条件,如下所示:

if (label.Length >= 2 && label.Substring(1, 1) == "X")

其功能依赖于&&操作员的短路评估。如果 AND 连接的第一个部分条件不成立,则第二个部分条件根本不会计算,因为它是无用的。即使它成立,也不会改变整体结果,因为 AND 运算符要求两部分同时成立。

在这种情况下,当一个标签的长度小于 2 时,那么会失败的Substring调用将被跳过,程序不会崩溃!

注意,对于||操作符也可以做类似的陈述。

一项实验

尽量省略第一个部分条件(长度检查)。然后输入一个字符作为用户。程序将因运行时错误而终止。这样你会发现第一个条件真的很重要。

摘要

在本章中,您学习了几个高级条件的示例。

您从所谓的条件运算符(?:)开始,它也被称为三元运算符,因为它与三个值一起工作。根据指定条件(第一个值,在问号之前)的满足情况,操作符的“工作”结果要么是 yesValue (第二个值,在问号和冒号之间),要么是 noValue (第三个值,在冒号之后)。对于某些类型的if-else情况,条件操作符是一种合适的快捷替代方式。

汇总求值的中间任务是一种对你所学的关于条件执行的所有东西的回顾。在这里,您遇到了各种测试和许多复合条件,以及使用!操作符的否定。

测试某些文本的第二个字符的最后一个任务向您展示了在探索第二个字符是什么之前测试它是否存在的重要性。这里你使用了短路条件评估。如果复合条件的结果在第一个部分条件被评估之后已经可以被决定,那么第二个部分条件被完全跳过。

二十、第一个循环

你正在进入这本书最难的章节。循环是一个强大的工具,所有程序员都像呼吸空气一样需要它。理解循环是不容易的,这就是为什么你会经历许多循环的练习。

重复相同的文本

循环是一种工具,用于高效地重复编写相同或更常见的类似活动。为了正确理解循环,你将两次解决一些任务,第一次不带循环,第二次带循环。您将从重复相同的活动开始,之后您将继续使用循环来重复类似的活动。

工作

你将编写一个程序,显示“我明天开始学习。”连续十次(见图 20-1 )。

img/458464_2_En_20_Fig1_HTML.jpg

图 20-1

十次重复

解决办法

代码如下:

static void Main(string[] args)
{
    // Output
    Console.WriteLine("I will start learning tomorrow.");
    Console.WriteLine("I will start learning tomorrow.");
    Console.WriteLine("I will start learning tomorrow.");
    Console.WriteLine("I will start learning tomorrow.");
    Console.WriteLine("I will start learning tomorrow.");

    Console.WriteLine("I will start learning tomorrow.");
    Console.WriteLine("I will start learning tomorrow.");
    Console.WriteLine("I will start learning tomorrow.");
    Console.WriteLine("I will start learning tomorrow.");
    Console.WriteLine("I will start learning tomorrow.");

    // Waiting for Enter
    Console.ReadLine();
}

使用循环的解决方案

想一想之前的练习。你能想象有人想要你改变显示的句子吗?你能想象重复一百次而不是十次吗?你能想象用户输入的重复次数吗?

要解决这些问题,你需要一个新的工具:循环。

解决办法

代码如下:

static void Main(string[] args)
{
    // Output
    for (int count = 0; count < 10; count++)
    {
        Console.WriteLine("I will start learning tomorrow.");
    }

    // Waiting for Enter
    Console.ReadLine();
}

for 循环的工作原理

你用for结构来表示重复。它的一般语法如下所示:

for (initializer; loopCondition; iterator)
{
    statement;
    statement;
    statement;
    ...
}

for循环是这样工作的:

  • initializer在进入循环前执行一次。

  • loopCondition在循环的每一个回合之前被评估。如果它成立,计算机就进入循环并执行它体内的语句。

  • iterator语句在循环的每一次循环完成后执行。之后,loopCondition再次求值。

图 20-2 显示了程序流程。

img/458464_2_En_20_Fig2_HTML.jpg

图 20-2

程序流程

循环

在这种情况下,所需的重复次数是通过计算到目前为止完成的循环圈数来实现的。为此,您可以使用 count 变量。

在开始时(初始化器),变量被设置为零。

完成每一次循环(迭代器)后,变量加 1。

只要(loopCondition)输出中的行数没有达到十,循环体(显示一行文本)就会重复。一旦count变量变为 10,条件(count < 10 或 10 < 10)将不再满足,循环将终止,计算机将继续执行循环后的语句。

自己去探索吧

您应该花时间探索循环的内部工作方式,以便彻底掌握它们。使用你已经知道的调试工具:步进并检查count变量。

小费

Visual Studio 可以帮你写一个for循环,不会出错。只需输入for,按两下 Tab 键,编辑生成的循环头即可。

选择重复次数

for循环允许您在事先不知道重复次数的情况下解决问题(在编写代码时)。

工作

您将修改之前的练习,让用户指定句子重复的次数(参见图 20-3 )。

img/458464_2_En_20_Fig3_HTML.jpg

图 20-3

让用户指定句子重复的次数

解决办法

代码如下:

static void Main(string[] args)
{
    // Input
    Console.Write("Enter number of repetitions: ");
    string input = Console.ReadLine();
    int howManyTimes = Convert.ToInt32(input);

    // Output
    for (int count = 0; count < howManyTimes; count++)
    {
        Console.WriteLine("I will start learning tomorrow.");
    }

    // Waiting for Enter
    Console.ReadLine();
}

讨论

请注意以下几点:

  • 与前一个任务相比,您用用户输入的变量值替换了固定的重复次数。

  • 仔细选择变量的名称,以存储所需的总重复次数;这里是howManyTimes。具体来说,您应该将它与存储当前重复次数的count变量区分开来。

反复投掷骰子

您将看到另一个重复相同活动的例子。

工作

你将编写一个投掷骰子 20 次的程序(见图 20-4 )。

img/458464_2_En_20_Fig4_HTML.jpg

图 20-4

投掷骰子 20 次

解决办法

代码如下:

static void Main(string[] args)
{
    // Random number generator
    Random randomNumbers = new Random();

    // Output
    for (int count = 0; count < 20; count++)
    {
        int thrown = randomNumbers.Next(1, 6 + 1);
        Console.Write(thrown.ToString() + " ");
    }

    // Waiting for Enter
    Console.ReadLine();
}

重复类似的台词

如果重复的活动不是相同的而是相似的呢?

工作

您将输出十个相似的行,不同之处仅在于打印的行号(见图 20-5 )。

img/458464_2_En_20_Fig5_HTML.jpg

图 20-5

输出十次类似的东西

没有循环的解决方案

同样,您可以从没有循环的解决方案开始,以体会循环的重要性。

代码如下:

static void Main(string[] args)
{
    // Output
    Console.WriteLine("My main to-do list:");

    Console.WriteLine("1\. To learn");
    Console.WriteLine("2\. To learn");
    Console.WriteLine("3\. To learn");
    Console.WriteLine("4\. To learn");
    Console.WriteLine("5\. To learn");
    Console.WriteLine("6\. To learn");
    Console.WriteLine("7\. To learn");
    Console.WriteLine("8\. To learn");
    Console.WriteLine("9\. To learn");
    Console.WriteLine("10\. To learn");

    // Waiting for Enter
    Console.ReadLine();
}

使用循环的解决方案

循环可以有效地解决这类问题。实际上,你会发现自己加入循环来重复相似的活动比重复完全相同的活动更频繁。

static void Main(string[] args)
{
    // Output
    Console.WriteLine("My main to-do list:");

    for (int taskNumber = 1; taskNumber <= 10; taskNumber++)
    {
        Console.WriteLine(taskNumber.ToString() + ". To learn");
    }

    // Waiting for Enter
    Console.ReadLine();
}

讨论

以下部分讨论了该计划。

决策变量

解决方案的核心是使用循环体内部的控制变量的值。在这个程序中,您将变量命名为taskNumber,并将它的值用于输出。

这就是如何在循环的第一段显示一个,在第二段显示两个,依此类推。

使用调试工具亲自检查情况。

循环从 1 开始

前面的练习(重复投掷骰子)使用了控制变量从 0 到 19 的循环。与此相反,这次从 1 开始比从 0 开始更方便。这一改变也导致了循环条件的改变。您使用了“小于或等于”测试,而不是“小于”测试。

摘要

本章向您介绍了循环的主题,这是一个强大的编程工具,允许您指定相同的重复,或者更常见的是,类似的活动。

对于循环,C# 提供了几种编程结构;在本章中,你学习了最基本的for循环。在代码中,for循环由一个控制循环的标题和一个主体组成,主体由用大括号括起来的要重复的语句组成。标题本身由分号分隔的三部分组成:

  • 初始化器是在循环开始“循环”之前要执行一次的语句

  • 循环条件是每次循环前评估的条件。如果满足(即评估为true),则执行循环体的另一轮语句。如果不满足(即评估为false),循环终止,程序继续执行循环后的语句。

  • 迭代器是循环每一次循环后要执行的语句。

为了更深入地理解for循环是如何工作的,一定要使用调试工具,比如步进和内存检查。

for循环通常由一个变量控制,其工作方式或多或少类似于循环圈数的计数器。这个变量叫做控制变量。在上一个任务中,您学习了如何在循环体中使用控制变量的值。

二十一、改善循环

正如您所了解的,循环是强大的,并且它们不是微不足道的。这就是为什么本书的其余章节都致力于更好地理解循环。让我们进行一些更难的练习。

选择文本

首先,你将回到上一章的课文重复练习,并对其进行改进。

工作

在“选择重复次数”部分,用户可以改变给定句子的重复次数。现在你将允许用户改变句子本身(见图 21-1 )。

img/458464_2_En_21_Fig1_HTML.jpg

图 21-1

改变句子

解决办法

代码如下:

static void Main(string[] args)
{
    // Inputs
    Console.Write("Enter text to repeat: ");
    string textToRepeat = Console.ReadLine();

    Console.Write("Enter number of repetitions: ");
    string input = Console.ReadLine();
    int howManyTimes = Convert.ToInt32(input);

    // Output
    for (int count = 0; count < howManyTimes; count++)
    {
        Console.WriteLine(textToRepeat);
    }

    // Waiting for Enter
    Console.ReadLine();
}

交替循环

通常,你需要重复一些活动。你做第一件事,然后第二件,再做第一件,以此类推。在代码中查看这样的任务是很有趣的。我将向你展示解决这个问题的三种方法。

工作

您将编写一个程序,在待办事项列表中的两个任务之间进行交替(参见图 21-2 )。

img/458464_2_En_21_Fig2_HTML.jpg

图 21-2

在两个任务之间交替

第一种解决方案

第一种解决方案是基于区分循环的控制变量(从 1 到 10)是奇数还是偶数。当它是奇数时,你显示“学习”当它是偶数时,你显示“约会”

奇数/偶数测试将通过检查整数除以 2 后的余数来执行。提醒您一下,余数是用 C# 中的百分号(%)运算符计算的。

static void Main(string[] args)
{
    // Output
    Console.WriteLine("My main to-do list:");

    for (int taskNumber = 1; taskNumber <= 10; taskNumber++)
    {
        string taskText = taskNumber % 2 != 0 ? "Learning" : "Dating";
        Console.WriteLine(taskNumber.ToString() + ". " + taskText);
    }

    // Waiting for Enter
    Console.ReadLine();
}

注意

您将奇数/偶数测试合并到一个条件(三元)运算符(?:)中。你也可以用普通的if-else

第二种解决方案

第二种解决方案是来回切换布尔值。

你有一个bool类型的变量,在循环的每一次循环中,你可以从true切换到false,反之亦然。当变量等于true时,显示第一个文本。当它是false时,你显示第二个。

static void Main(string[] args)
{
    // Preparations
    Console.WriteLine("My main to-do list:");
    bool learning = true;

    for (int taskNumber = 1; taskNumber <= 10; taskNumber++)
    {
        // Output
        string taskText = learning ? "Learning" : "Dating";
        Console.WriteLine(taskNumber.ToString() + ". " + taskText);

        // Toggling of the flag
        learning = !learning;
    }

    // Waiting for Enter
    Console.ReadLine();
}

笔记

请注意以下几点:

  • 条件不必作为learning == true输入。learning变量已经是bool类型的,这意味着你可以直接把它作为一个条件使用。当它是true时,条件成立。

  • 在进入循环之前,需要设置变量的初始值。初始值在循环的第一次循环中使用。在这种情况下,您将其设置为true

  • 使用求反运算符(!)从true切换到false,反之亦然。

第三种解决方案

解决方案的第三种方法是重复循环五次而不是十次,并且在循环的单个回合中执行“奇数活动”和“偶数活动”。

代码如下:

static void Main(string[] args)
{
    // Preparations
    Console.WriteLine("My main to-do list:");
    int taskNumber = 1;

    for (int coupleCount = 0; coupleCount < 5; coupleCount++)
    {
        // Couple output and adjusting task number
        Console.WriteLine(taskNumber.ToString() + ". Learning");
        taskNumber++;
        Console.WriteLine(taskNumber.ToString() + ". Dating");
        taskNumber++;
    }

    // Waiting for Enter
    Console.ReadLine();
}

石头剪刀布

在下一个练习中,您将看到for循环体中有许多语句。循环将代表游戏的单个回合。

工作

您将编写一个程序,与用户玩指定回合数的石头剪刀布游戏(见图 21-3 )。

img/458464_2_En_21_Fig3_HTML.jpg

图 21-3

游戏

得分类似于国际象棋:胜利得一分,平局得半分。

解决办法

代码如下:

static void Main(string[] args)
{
    // Preparations
    Random randomNumbers = new Random();

    double playerPoints = 0;
    double computerPoints = 0;

    int rock = 1, scissors = 2, paper = 3;

    // Inputs
    Console.Write("Enter your name: ");
    string playerName = Console.ReadLine();

    Console.Write("Enter number of game rounds: ");
    string input = Console.ReadLine();
    int totalRounds = Convert.ToInt32(input);

    Console.WriteLine();

    // Individual rounds
    for (int roundNumber = 0; roundNumber < totalRounds; roundNumber++)
    {
        // Computer chooses
        int computerChoice = randomNumbers.Next(1, 3 + 1);

        // Player chooses
        Console.Write("Enter R or S or P: ");
        string playerInput = Console.ReadLine();
        string playerInputUppercase = playerInput.ToUpper();
        int playerChoice = playerInputUppercase == "R" ?
            rock : (playerInputUppercase == "S" ? scissors : paper);

        // Round evaluation
        string message = "";
        if (computerChoice == rock && playerChoice == scissors ||
            computerChoice == scissors && playerChoice == paper ||
            computerChoice == paper && playerChoice == rock)
        {
            // Computer won
            computerPoints += 1;
            message = "I won";
        }
        else
        {
            // Tie or player won
            if (computerChoice == playerChoice)
            {
                // Tie
                computerPoints += 0.5;
                playerPoints += 0.5;
                message = "Tie";
            }
            else
            {
                // Player won
                playerPoints += 1;
                message = "You won";
            }
        }

        // Round output
        string playerChoiceInText = playerChoice == rock ?
            "Rock" : (playerChoice == scissors ? "Scissors" : "Paper");
        string computerChoiceInText = computerChoice == rock ?
            "Rock" : (computerChoice == scissors ? "Scissors" : "Paper");
        Console.WriteLine(playerName + ":Computer - " +
            playerChoiceInText + ":" + computerChoiceInText);
        Console.WriteLine(message);
        Console.WriteLine();
    } // End of loop for game round

    // Game evaluation
    Console.WriteLine("GAME OVER - OVERALL RESULT");
    Console.WriteLine(playerName + ":Computer - " +
        playerPoints.ToString() + ":" + computerPoints.ToString());

    // Waiting for Enter
    Console.ReadLine();
}

讨论

请注意以下几点:

  • 计算机用随机数“选择”:1 代表石头,2 代表剪刀,3 代表布。

  • 为了简单起见,当用户输入 R、S 或 P 以外的内容时,您将其视为“纸张”

  • 用户输入时不区分大小写。

  • 在一些地方,三重分支是使用两个嵌套的条件(三元)运算符解决的,而不是使用两个嵌套的if-else s。请仔细注意第一个条件运算符的noValue是如何使用另一个条件运算符指定的,该运算符用括号括起来。

  • 如果您不喜欢条件(三元)操作符,就不要使用它。它只是一个特殊if-else案例的捷径。我个人很喜欢,所以经常用。

摘要

在本章中,您继续学习了循环。第一个练习基本上是对你在前一章所学内容的提醒。您修改了以前的一项任务。

接下来,您了解了几种解决交替循环的方法,也就是说,循环重复相似的活动对。具体来说,您研究了以下解决方案:

  • 基于控制变量是奇数还是偶数的交替输出

  • 切换一个bool变量,指示您是否想要第一个输出

  • 在单圈中完成两个动作

石头剪子布游戏的最后一个例子实际上不是以循环为中心的。循环只是重复游戏回合的手段。一轮游戏是一个真实的,更复杂的程序的例子,你可以用你在本书中学到的知识来完成。

二十二、数字系列

一些编程任务简化为生成正则数列。这就是你本章要研究的内容。通过这种方式,您还可以更好地理解循环。

每隔一…

你已经能够生成一个简单的数列,比如从 1 到 10。现在,您将着手生成一个稍微复杂一点的序列。

工作

在该任务中,您将显示“每隔一个”的数字,直到 20(参见图 22-1 )。

img/458464_2_En_22_Fig1_HTML.jpg

图 22-1

每隔一个数字显示一次

解决办法

代码如下:

static void Main(string[] args)
{
    // Output
    for (int number = 2; number <= 20; number += 2)
    {
        Console.WriteLine(number);
    }

    // Waiting for Enter
    Console.ReadLine();
}

讨论

练习的最大难点是实现如何编写for循环的迭代器。因为您想将变量number增加 2,相应的语句将是number += 2

可选择的解决方案

用另一种方法解这道题很有趣。你可以有一个从 1 到 10 步进的普通循环,显示两倍的控制变量而不是变量本身。

static void Main(string[] args)
{
    // Output
    for (int line = 1; line <= 10; line++)
    {
        int displayedNumber = 2 * line;
        Console.WriteLine(displayedNumber);
    }

    // Waiting for Enter
    Console.ReadLine();
}

降级数

如果数列中的数字是递减的呢?那时很多事情都会改变。让我们来看看。

工作

在该任务中,您将显示从十到一的数字(参见图 22-2 )。

img/458464_2_En_22_Fig2_HTML.jpg

图 22-2

数字在下降

解决办法

代码如下:

static void Main(string[] args)
{
    // Output
    for (int number = 10; number >= 1; number--)
    {
        Console.WriteLine(number);
    }

    // Waiting for Enter
    Console.ReadLine();
}

讨论

请注意以下几点:

  • 循环的初始化器可能是最简单的。你从十点开始。

  • 迭代器也不难;数字会变小,这就是为什么你在每一轮结束的时候减一。

  • 最难的是循环条件。你必须以这样一种方式来制定它,只要你想让循环继续下去,它就成立,而当你想退出时,它就停止成立。正确的测试是number变量是否大于或等于 1。

十进制数字

带有十进制数字的序列可能会让您感到惊讶。

工作

在该任务中,您将生成一个从 9 到 0 的序列,数字在每一步中递减 0.9(见图 22-3 )。

img/458464_2_En_22_Fig3_HTML.jpg

图 22-3

减少 0.9

看似正确的解决方案

使用上一个练习的样式,您可以编写以下内容:

static void Main(string[] args)
{
    // Output
    for (double number = 9; number >= 0; number -= 0.9)
    {
        Console.WriteLine(number.ToString("N1"));
    }

    // Waiting for Enter
    Console.ReadLine();
}

测试

然而,测试揭示了序列中缺失的最后一个成员:零(见图 22-4 )。

img/458464_2_En_22_Fig4_HTML.jpg

图 22-4

缺少最后一个数字

这怎么可能呢?

错误的原因

这个练习展示了处理十进制数字是多么的棘手;你需要小心,因为十进制算术可能不精确!

当您忽略一个小数位(.ToString("N1"))的格式时,您可以感觉到原因。试试看(见图 22-5 )。

img/458464_2_En_22_Fig5_HTML.jpg

图 22-5

省略格式

您可以看到,预期的倒数第二个系列成员比它应该的稍少。进一步减去 0.9 会得到略低于零的值,这就是为什么预期的最后一个零没有显示出来。

正确的解决方案

使用十进制数时,您需要指定一个循环的终值,并留有一点自由空间。

因此,该练习的正确答案如下所示:

static void Main(string[] args)
{
    // Output
    for (double number = 9; number >= -0.0001; number -= 0.9)
    {
        Console.WriteLine(number.ToString("N1"));
    }

    // Waiting for Enter
    Console.ReadLine();
}

检查结果!

第二权力

现在,在一行中显示两个相连的数字怎么样?

工作

除了 1 到 10 系列的数字外,您还可以在每一行输出中显示相应的二次幂(见图 22-6 )。

img/458464_2_En_22_Fig6_HTML.jpg

图 22-6

显示第二功率

解决办法

代码如下:

static void Main(string[] args)
{
    // Output
    for (int number = 1; number <= 10; number++)
    {
        int secondPower = number * number;
        Console.WriteLine(number.ToString() + " " + secondPower.ToString());
    }

    // Waiting for Enter
    Console.ReadLine();
}

连续两次

让我们保持两个数在一条线上。

工作

在此任务中,您将生成一个 1–20 系列,每行输出中有几个数字(参见图 22-7 )。

img/458464_2_En_22_Fig7_HTML.jpg

图 22-7

在一行上显示多个数字

解决办法

这个练习非常类似于上一章的交替循环任务。它也可以通过多种方式解决。我选其中一个:你在奇数后面加一个空格,偶数后面加一个换行符。

代码如下:

static void Main(string[] args)
{
    // Output
    for (int number = 1; number <= 20; number++)
    {
        Console.Write(number);

        // What goes after the number depends on the even/odd test
        if (number % 2 != 0)
        {
            // Odd number, displaying space
            Console.Write(" ");
        }
        else
        {
            // Even number, new line
            Console.WriteLine();
        }
    }

    // Waiting for Enter
    Console.ReadLine();
}

两个独立系列

另一个有趣的例子是两个独立的数列。

工作

你将会有两个,有点随意的,成员数不同的数列。第一个是每步递减 2(111,109,…,97),第二个是每步递增 3(237,240,…,270)。

程序将在每一行显示第一个系列的数字和第二个系列的数字(见图 22-8 )。

img/458464_2_En_22_Fig8_HTML.jpg

图 22-8

更复杂的交替

解决办法

代码如下:

static void Main(string[] args)
{
    // Preparation
    int first = 111;

    // Output
    for (int second = 237; second <= 270; second += 3)
    {
        // Preparing first text
        string firstText = first >= 97 ?
            first.ToString().PadLeft(3) : "   ";

        // Actual output
        Console.WriteLine(firstText + " " + second.ToString());

        // Changing x
        first -= 2;
    }

    // Waiting for Enter
    Console.ReadLine();
}

讨论

请注意以下几点:

  • 使用循环的控制变量显示一个系列(较长的一个)。另一个用另一个自变量。

  • 在每一步中,你都要检查较短的系列是否还在继续。

  • 为了实现更好的格式化,您使用了PadLeft方法调用,该方法在参数的左边添加空格,以达到指定的字符总数。

摘要

在这一章中,你在生成各种数列的任务中练习了循环。具体来说,您学到了以下内容:

  • 当级数为 2 步时,如何编写循环的迭代器?

  • 如何在循环体中不直接显示控制变量,而是显示从中导出(计算)的值。

  • 如何在一个循环的迭代器中使用--操作符生成一个降序序列,并使用>=操作符指定循环条件,这样只要你想进行循环,它就会被满足。

  • 由于内存中十进制数的不精确表示,十进制数列需要特别小心。这意味着,例如,您需要在循环条件下提供额外的自由活动。

您还解决了在单个输出行中有两个数字的情况,以及两个独立系列的更困难的最终任务。

二十三、未知的重复次数

在所有你已经解决的循环中,你知道迭代的次数。有时候,当你写一个程序时,你并不知道它,仅仅是因为用户应该输入它。然而,在所有情况下,当一个循环开始时,已经确定了它将迭代多少次。

有时,在循环开始执行时,重复的次数是未知的。通常,您会关心循环应该继续还是终止的问题。

输入密码

第一项任务是登录。您事先不知道用户需要尝试多少次。

工作

您将反复要求用户输入密码,直到用户输入正确的密码(参见图 23-1 )。正确的密码将是的朋友

img/458464_2_En_23_Fig1_HTML.jpg

图 23-1

反复问一个问题

解决办法

代码如下:

static void Main(string[] args)
{
    string correctPassword = "friend";

    bool ok; // the variable must be declared outside of the loop!
    do
    {
        // Input
        Console.Write("Enter password: ");
        string enteredPassword = Console.ReadLine();

        // Evaluating
        ok = enteredPassword == correctPassword;
    } while (!ok); // loop repeats when the condition holds

    Console.WriteLine("Come inside, please...");

    // Waiting for Enter
    Console.ReadLine();
}

边做边施工

要编写循环,可以使用do-while构造。

计算机在单词do之后进入循环,执行它的语句,并询问“再来一次?”。如果在while字之后的条件成立,计算机返回到循环的开始,换句话说,在do字之后。等等。

while字后的条件被评估为未实现(false)时,循环终止。

这个案子

在这种情况下,程序会在每次输入后评估输入的密码。评估结果随后存储在一个名为okbool类型变量中。

如果输入的密码不正确,您希望循环继续。这就是为什么在while条件中使用否定运算符(感叹号)的原因。

循环外的变量

C# 要求循环条件中使用的所有变量都在循环之外声明。当您在内部声明它们时,它们在指定条件时不可见。

小费

Visual Studio 可以帮助您处理do-while循环。只需输入do,按两下 Tab 键。

等待下降

想象一下,计算机观察某个在大多数时间增长的量,任务是检测它减少(下降)的(可能很少)时刻。

在挖掘存储在文件或数据库中的大量数据时,您通常会遇到这样的问题。但是,您将根据用户输入的数据来解决这个问题。

工作

你将制作一个反复要求用户输入的程序(见图 23-2 )。每当用户输入一个小于前一个的数字时,程序将通知用户(并终止)。

img/458464_2_En_23_Fig2_HTML.jpg

图 23-2

当数字变小时终止

解决办法

解决方案的核心是记住以前的值,而不仅仅是当前输入的值。

代码如下:

static void Main(string[] args)
{
    // Preparations
    int previous = int.MinValue;
    bool ok;

    // Repeating until descend
    do
    {
        // Input
        Console.Write("Enter a value (number): ");
        string input = Console.ReadLine();
        int value = Convert.ToInt32(input);

        // Evaluating
        ok = value >= previous; // ok = still not descending

        // Storing for the next round of the loop
        previous = value;
    } while (ok);

    // Message to the user
    Console.WriteLine("Descend detected...");

    // Waiting for Enter
    Console.ReadLine();
}

讨论

第一个值有些特殊,因为它没有前任。它的缺失可以通过使用一些非常小的数字来模拟它来规避。C# 为您提供了int.MinValue,这是可以存储在int类型中的最小值,大约是负 20 亿。

直到年底的每个星期

让我们继续下一个练习,它与日期有关。

工作

任务是显示从今天开始到年底的日期,并以一周为单位进行(见图 23-3 )。

img/458464_2_En_23_Fig3_HTML.jpg

图 23-3

一年中,每次一周

解决办法

代码如下:

static void Main(string[] args)
{
    // Today
    DateTime today = DateTime.Today;
    int thisYear = today.Year;

    // Repeating
    DateTime date = today;
    do
    {
        // Output
        Console.WriteLine(date.ToLongDateString());

        // Preparing next output (a week later)
        date = date.AddDays(7);
    } while (date.Year == thisYear);

    // Waiting for Enter
    Console.ReadLine();
}

只要 6 号被抛出

随机数可以为您提供不确定循环终止的其他好例子。

工作

你将掷出一个骰子,只要有一个六,你就一直掷出这个骰子(见图 23-4 和 23-5 )。

img/458464_2_En_23_Fig5_HTML.jpg

图 23-5

只要你得到 6,就掷骰子

img/458464_2_En_23_Fig4_HTML.jpg

图 23-4

滚动一次骰子(没有六次,没有重复)

你可能知道一些使用这个原则的棋盘游戏。

解决办法

代码如下:

static void Main(string[] args)
{
    // Random number generator
    Random randomNumbers = new Random();

    // Throwing as long as we have six
    int thrown;
    do
    {
        thrown = randomNumbers.Next(1, 6 + 1);
        Console.WriteLine(thrown);
    } while (thrown == 6);

    // Waiting for Enter
    Console.ReadLine();
}

直到第二个六

这个任务是关于具有随机值的未知重复次数。

工作

你将编写一个程序,抛出一个骰子,直到第二次抛出 6(见图 23-6 )。

img/458464_2_En_23_Fig6_HTML.jpg

图 23-6

一直等到 6 出现两次

解决办法

你只需数一数掷出六个骰子的次数。

代码如下:

static void Main(string[] args)
{
    // Random number generator
    Random randomNumbers = new Random();

    // Throwing until the second six is thrown
    int howManySixes = 0;
    do
    {
        // Actual throwing
        int thrown = randomNumbers.Next(1, 6 + 1);
        Console.WriteLine(thrown);

        // Counting sixes
        if (thrown == 6)
        {
            howManySixes++;
        }
    } while (howManySixes < 2);

    // Waiting for Enter
    Console.ReadLine();
}

直到连续两个六

你知道为什么扔骰子的例子那么多吗?我小时候喜欢玩桌游,能看出来吗?

工作

在这个程序中,你将掷出一个骰子,直到连续两次掷出 6(见图 23-7 )。

img/458464_2_En_23_Fig7_HTML.jpg

图 23-7

连续两个六

解决办法

除了当前抛出的数字,您还需要跟踪前一个数字。这类似于“等待下降”部分中的程序。

如果当前和先前的数字都是 6,程序终止。

同样,第一个值是特定的,因为它没有前任。这就是为什么previous变量从零开始,这是一个永远不会出现在骰子上的值。

代码如下:

static void Main(string[] args)
{
    // Random number generator
    Random randomNumbers = new Random();

    // Preparations
    int previous = 0;
    bool ending;

    // Throwing until two sixes in a row
    do
    {
        // Actually throwing
        int thrown = randomNumbers.Next(1, 6 + 1);
        Console.WriteLine(thrown);

        // Evaluating
        ending = thrown == 6 && previous == 6;

        // Preparing for next round of the loop
        previous = thrown;
    } while (!ending);

    // Waiting for Enter
    Console.ReadLine();
}

摘要

在本章中,你学习了循环开始时不知道重复次数的循环。在 C# 中,这种循环可以使用do-while构造来编写。它的功能首先是执行身体的陈述,然后问,“再来一次?”评估条件,如果条件成立,就执行另一轮循环。

您还看到,要在do-while循环条件中使用某个变量,该变量必须在循环之外声明。

使用do-while循环时的一个常见错误是对其条件的错误表述。你必须小心,以这样的方式写,如果你想继续循环,条件应该评估为true

在本章的几个任务中,你需要一些来自前一轮循环的值。为此,您使用了一个特殊的变量来存储值。当然,第一轮循环需要特殊处理。

二十四、累积中间结果

在这一章中,你将学习使用循环处理大型数据集的重要案例。你会经常用一个循环去遍历大量的数据,去积累(聚合)一些中间结果,这些结果在循环终止后就成为最终结果。

输入数字的总和

此类别中的一个典型任务是对大量值求和。

工作

假设用户正在输入数字,最后一个数字是零。换句话说,用户通过输入零来表示他们完成了。然后程序显示所有输入数字的总和(见图 24-1 )。

img/458464_2_En_24_Fig1_HTML.jpg

图 24-1

将所有数字相加直到零

解决办法

解决方案的核心是积累中间结果。你必须把它保存在一个变量中,并把每个输入的数字都加到这个变量中。一旦用户终止输入,变量将包含所有输入值的总和。

代码如下:

static void Main(string[] args)
{
    // Preparations
    int sum = 0;
    int number;

    // Entering numbers until zero
    do
    {
        // Input
        Console.Write("Enter a number (0 = end): ");
        string input = Console.ReadLine();
        number = Convert.ToInt32(input);

        // Adding to intermediate sum
        sum += number;
    } while (number != 0);

    // Output
    Console.WriteLine("Sum of entered numbers is: " + sum.ToString());

    // Waiting for Enter
    Console.ReadLine();
}

输入数字的乘积

把输入的数字相乘而不是求和怎么样?你认为任务是一样的吗?也不完全是。

工作

在该程序中,用户输入数字,最后一个数字为零(见图 24-2 )。然后,程序显示所有输入数字的乘积,当然不包括最后的零,这将使所有数字为零。

img/458464_2_En_24_Fig2_HTML.jpg

图 24-2

将所有数字相乘

解决办法

代码如下:

static void Main(string[] args)
{
    // Preparations
    double product = 1;
    int number;

    // Entering numbers until zero
    do
    {
        // Input
        Console.Write("Enter a number (0 = end): ");
        string input = Console.ReadLine();
        number = Convert.ToInt32(input);

        // Accumulating in intermediate product (but not the last zero!)
        if (number != 0)
        {
            product *= number;
        }
    } while (number != 0);

    // Output
    Console.WriteLine("Product of entered numbers (excluding zero) is: " + product.ToString("N0"));

    // Waiting for Enter
    Console.ReadLine();
}

讨论

请注意以下几点:

  • product变量从一个与零相反的值开始,这是您在计算总和时使用的。

  • 更新产品时,需要注意不要包括最后的零。

  • 您在类型double中声明了product变量,以避免结果溢出。当你乘的时候,你很快得到大的数字。

最伟大的

处理大量数据时的另一个典型任务是搜索极值,换句话说,最大值或最小值。

工作

在这个程序中,用户输入十个数字。然后程序输出哪个最大(见图 24-3 )。

img/458464_2_En_24_Fig3_HTML.jpg

图 24-3

输出最大的数字

解决办法

你又要累加中间结果了。这一次,它将是“迄今为止”最大的数字。你必须特别注意第一个值;greatest变量在开始时被设置为最小可能值,以确定第一个输入的数字总是较大。

因为您期望输入中正好有十个值,所以在这里使用for循环更方便。

代码如下:

static void Main(string[] args)
{
    // Preparation
    int greatest = int.MinValue;

    // Input of ten numbers
    for (int order = 1; order <= 10; order++)
    {
        // Input
        Console.Write("Enter " + order.ToString() + ". number: ");
        string input = Console.ReadLine();
        int number = Convert.ToInt32(input);

        // Is it greater than the greatest so far?
        if (number > greatest)
        {
            greatest = number;
        }
    }

    // Output
    Console.WriteLine("The greatest of entered numbers was: " + greatest.ToString());

    // Waiting for Enter
    Console.ReadLine();
}

第二伟大的

第二大价值呢?这是一个困难得多的练习。

工作

任务是从十个输入的数字中选择第二大的数字(见图 24-4 )。

img/458464_2_En_24_Fig4_HTML.jpg

图 24-4

显示第二大的数字

解决办法

你需要记住并不断更新两个最大的数字。仅仅记住第二大奇迹是不够的。

这种情况类似于滑雪比赛,参赛者一个接一个地到达终点。在某个时刻,有人是第一;过了一会儿,又有人把第一名挤到了第二名。可能在以后的时间里,那个滑雪者甚至可能失去第二名,仅仅是因为有人会比他们更好,甚至比新的领先者更好。

代码如下:

static void Main(string[] args)
{
    // Preparation
    int greatest = int.MinValue;
    int secondGreatest = int.MinValue;

    // Input of ten numbers
    for (int order = 1; order <= 10; order++)
    {
        // Input
        Console.Write("Enter " + order.ToString() + ". number: ");
        string input = Console.ReadLine();
        int number = Convert.ToInt32(input);

        // Is it greater than the greatest so far?
        if (number > greatest)
        {
            // Moving so far greatest to the second place
            secondGreatest = greatest;

            // Entered number becomes the greatest so far
            greatest = number;
        }
        else
        {
            // We did not beat the greatest, will we beat the second greatest at least?
            if (number > secondGreatest)
            {
                secondGreatest = number;
            }
        }
    }

    // Output
    Console.WriteLine("The second greatest of entered numbers was: " + secondGreatest.ToString());

    // Waiting for Enter
    Console.ReadLine();
}

所有输入名称的输出

本章的最后一个练习与文本有关,特别是处理大量文本(见图 24-5 )。

img/458464_2_En_24_Fig5_HTML.jpg

图 24-5

以原始顺序打印,然后反转

工作

您将编写一个程序,重复读取用户输入的名称。空输入表示终止。然后程序重复所有输入的名字,首先以相同的顺序,然后以相反的顺序。

解决办法

为了让你能够在最后重复所有的名字,你需要在某个地方记住它们。你需要积累它们。一个变量将在它的末端累积它们(相同顺序的输出),另一个在它的开始累积它们(相反顺序的输出)。

代码如下:

static void Main(string[] args)
{
    // Preparation
    string inSameOrder = "";
    string inReversedOrder = "";
    bool terminating;

    // Repeating until blank input
    do
    {
        // Input
        Console.Write("Enter person: ");
        string person = Console.ReadLine();

        // Processing input
        terminating = person.Trim() == "";
        if (!terminating)
        {
            inSameOrder = inSameOrder + person + ", ";
            inReversedOrder = person + ", " + inReversedOrder;
        }
    } while (!terminating);

    // Removing trailing comma and space
    if (inSameOrder.EndsWith(", "))
    {
        int numberOfCharacters = inSameOrder.Length;
        inSameOrder = inSameOrder.Remove(numberOfCharacters - 2);
    }
    if (inReversedOrder.EndsWith(", "))
    {
        int numberOfCharacters = inReversedOrder.Length;
        inReversedOrder = inReversedOrder.Remove(numberOfCharacters - 2);
    }

    // Output
    Console.WriteLine("Entered persons: " + inSameOrder);
    Console.WriteLine("In reversed order: " + inReversedOrder);

    // Waiting for Enter
    Console.ReadLine();
}

讨论

请注意以下几点:

  • 您使用Trim方法来切断输入文本可能的前导或尾随空格,以便允许终止任何空白输入,包括几个空格。

  • 最后,你必须去掉两个累积文本中的最后两个字符。在此之前,您需要测试这两个字符(一个逗号和一个空格)是否出现在文本的末尾。如果用户通过输入空行立即终止程序,这些字符将不会出现。

  • 要测试文本是否以某个东西结尾,可以使用EndsWith方法。

摘要

循环最常见的用途之一是处理大量数据,无论是数字、文本还是整个对象。在循环体中,您处理单个数据,而循环确定所有数据都得到处理。

您通过求和、相乘和求极值的例子练习了处理大量数据。

最困难的练习是找出第二大数字,这需要仔细考虑根据数据可能出现的情况。

上一个任务向你展示了几种处理文本的方法:TrimEndsWithRemove

二十五、高级循环

在本章中,您将完成简单循环的主题。这在“不嵌套”的意义上是简单的,而不是“琐碎的”没有循环是微不足道的,尤其是本章中的循环。

这一章和整本书将以一个奖励结束:一个登月模拟游戏。如果你觉得本章的练习太难,就只玩游戏。

感谢上帝,今天是星期五

是时候熟悉一下while循环了,它是您已经熟悉的do-while循环的表亲。

工作

你将准备一个程序,显示最近的星期五的日期和剩余天数(见图 25-1 )。

img/458464_2_En_25_Fig1_HTML.jpg

图 25-1

显示最近的星期五

解决办法

代码如下:

static void Main(string[] args)
{
    // Today's date
    DateTime today = DateTime.Today;

    // Moving day after day until hit on Friday
    DateTime date = today;
    while (date.DayOfWeek != DayOfWeek.Friday)
    {
        date = date.AddDays(1);
    }

    // Calculating remaining days
    TimeSpan dateDifference = date - today;
    int daysRemaining = dateDifference.Days;

    // Outputs
    Console.WriteLine("Nearest Friday: " + date.ToShortDateString());
    Console.WriteLine("Remaining days: " + daysRemaining.ToString());
    if (daysRemaining == 0)
    {
        Console.WriteLine("Thanks God!");
    }

    // Waiting for Enter
    Console.ReadLine();
}

讨论

让我们更仔细地看看这个程序。

While 循环

要编写循环,您可以使用while构造,它在功能上类似于do-while,除了它的条件是在开始。因此,在进入循环之前,第一次对条件进行评估,如果条件不成立,循环体一次也不执行!

这个案子

在进入循环之前测试条件正是你需要做的。如果今天是星期五,您想让它保持原样。否则,你就是在加一天。

TimeSpan 对象

当你减去两个日期时,产生的结果总是一个TimeSpan对象。它的Days属性表示这两个日期之间的“时间跨度”已经过去了多少天。

力量

循环是数学练习中经常用到的。

工作

您将编写一个程序,在给定小数 x 和正整数 n 的情况下,计算数字 xn 次方(参见图 25-2 )。

img/458464_2_En_25_Fig2_HTML.jpg

图 25-2

计算 n 次方

只是提醒一下,210= 2×2×2×2×2×2×2×2 = 1024,这是最终产品中重复 10 次的数字 2。

解决办法

该任务可以通过重复乘以 x 来解决。这意味着你使用你已经学会的中间结果累积方法。

原则上,该解决方案与第二十四章“输入数字的乘积”一节中的解决方案非常接近:

static void Main(string[] args)
{
    // Inputs
    Console.Write("Enter x (number to be raised): ");
    string inputX = Console.ReadLine();
    double x = Convert.ToDouble(inputX);

    Console.Write("Enter n (power): ");
    string inputN = Console.ReadLine();
    int n = Convert.ToInt32(inputN);

    // Calculating
    double result = 1;
    for (int count = 0; count < n; count++)
    {
        result *= x;
    }

    // Output
    Console.WriteLine("x^n=" + result.ToString());

    // Waiting for Enter
    Console.ReadLine();
}

正弦

继续数学,你知道计算机实际上是如何进行计算的吗,例如,正弦函数?如果你对数学感兴趣,你可能会对它感兴趣。

为了完成这个任务,你可以使用所谓的泰勒展开式。有聪明人发现正弦函数在给定点 x ( x 是以弧度为单位的角度)的值可以计算成无穷级数的和:

$$ \sin x=x-\frac{x³}{3!}+\frac{x⁵}{5!}-\frac{x⁷}{7!}+\cdots $$

工作

现在的任务是编写一个程序,计算这个数列的和,并将结果与现成方法Math.Sin的值进行比较(见图 25-3 )。

img/458464_2_En_25_Fig3_HTML.jpg

图 25-3

计算正弦

如果您对如何计算正弦函数的值不感兴趣,请将此任务作为编写更难的循环的挑战。

分析

首先,你要分析计算。

无穷级数

要求和的级数是无穷的,但是你可能想知道如何对无穷多个数求和。

当然,你不能这样做。诀窍在于,您实际上不需要对该系列的无限个成员求和。在某一点上,它们变得如此之小,以至于它们的贡献远远落后于小数点。

出于实际原因,你只需要一个确定的精度,比如说 15 位小数;double型反正容纳不下更多的地方。因此,只要系列成员在小数点后第 15 位大于 1,就可以计算总和。

系列成员

所有的系列成员都是相似的。他们有一个奇数幂,奇数阶乘,和一个变化的符号。

提醒你一下,7!= 1 × 2 × 3 × … × 7.换句话说,阶乘是从 1 到给定数字的所有数字的乘积。

阶乘

可以用类似于本章前面计算幂的方法来计算阶乘,换句话说,就是在一个循环中逐步将所有的数相乘。

然而,你可以用更聪明的方法来做。你不需要从头开始计算每个阶乘。你总是可以从之前计算的结果中更快地得到它。

比如 7!= 7 × 6 × 5!。7 的阶乘可以通过 5 的阶乘乘以“缺失数”6 和 7 来计算。

力量

可以使用类似的技巧来计算每个串联成员的“功率部分”。功率不必从头开始计算。下一次幂就是上一次幂乘以 x 的平方。

比如 x7= x5×x2

解决办法

解决方案如下:

static void Main(string[] args)
{
    // Input
    Console.Write("Enter an angle in degrees: ");
    string input = Console.ReadLine();
    double angle = Convert.ToInt32(input);

    // Converting to radians
    double x = Math.PI / 180 * angle;

    // Preparations
    double member;
    double sum = 0;
    double tinyValue = 1e-15;

    double sign = 1;
    double power = x;
    double factorial = 1;
    double multiplier = 1;

    // Sum of the series
    do
    {
        // Calculating current member of the series
        member = sign * power / factorial;

        // Appending to sum
        sum += member;

        // Preparing next step
        sign *= -1;
        multiplier++;
        factorial *= multiplier;
        multiplier++;
        factorial *= multiplier;

        power *= x * x;

    } while (Math.Abs(member) > tinyValue);
    // Output

    Console.WriteLine("Our value: " + sum.ToString());
    Console.WriteLine("Math.Sin:  " + Math.Sin(x).ToString());

    // Waiting for Enter
    Console.ReadLine();
}

提高

您可以利用这个事实使计算更好,即对于 0 附近的 x 的值,级数收敛最快。使用正弦函数对称性,对大值 x 的计算可以转换成小值 x 的计算。

我会认为微软在Math.Sin的代码里有这一招。

月球登陆

自从阿波罗 11 号登月以来,创建登月舱着陆的模拟已经在各种编程平台上流行起来。所以,你要写一个类似的模拟作为本书的总结任务。

工作

你将编写一个模拟登月的程序。它将跟踪模块的高度 h ,月球表面,模块的速度 v ,以及着陆剩余燃料的质量 m F

用户的任务是软着陆(以尽可能小的速度)。在每一步中,代表着陆动作的一秒钟,用户根据百分比输入应该施加多少制动。百分比越高,速度越低,但同时消耗的燃料越多,如图 25-4 所示。

img/458464_2_En_25_Fig4_HTML.jpg

图 25-4

登月计划

一旦高度降到零度以下,登月舱就着陆了。程序通知用户着陆速度,并根据下表进行评估:

|

着陆速度

|

估价

|
| --- | --- |
| 小于 4 米/秒 | “软着陆” |
| 4-8 米/秒 | 硬着陆 |
| 大于 8 米/秒 | † |

如果在着陆结束前所有的燃油都消耗完了,程序开始忽略输入的刹车值,制动力设置为零。

物理模型

该计划将基于这里讨论的现实模型。

以下是初始值:

  • h = 50(米)

  • v = 8(米/秒)

  • mF= 35(公斤)

在代表着陆机动的一秒钟的每一步中,被跟踪的物理量的值将根据以下关系变化(意味着相应量的变化,如物理学中通常的那样):

h=–va/2

v = a

mF=–F/3000

在哪里

  • 制动力为制动百分比的 F = 360 ×

  • 朝向表面的加速度为a= 1.62-F/8000。

解决办法

代码如下:

static void Main(string[] args)
{
    // Initial values
    double h = 50, v = 8, mF = 35;

    // Preparation
    bool malfunction = false;

    // Repeating individual landing steps
    while (h >= 0)
    {
        // Displaying current values
        string height   = "Height: " + h.ToString("N1");
        string velocity = "Velocity: " + v.ToString("N1");
        string fuel     = "Fuel: " + mF.ToString("N1");
        Console.WriteLine(height + "  " + velocity + "  " + fuel);

        // Input
        Console.Write("Enter percentage of breaking (0-100): ");
        string input = Console.ReadLine();
        double percents = 0;
        try
        {
            percents = Convert.ToDouble(input);
            if (percents < 0 || percents > 100)
            {
                malfunction = true;
            }
        }
        catch (Exception)
        {
            malfunction = true;
        }
        if (malfunction)
        {
            percents = 0;
            Console.WriteLine("CONTROL MALFUNCTION!");
        }

        // Fuel check
        if (mF <= 0)
        {
            percents = 0;
            Console.WriteLine("NO FUEL!");
        }

        // Calculating new values
        double F = 360 * percents;
        double a = 1.62 - F / 8000;
        h -= v + a / 2;
        v += a;
        mF -= F / 3000;
        if (mF <= 0)
        {
            mF = 0;
        }

        // Output of an empty line
        Console.WriteLine();

    } // End of a single landing step

    // Output
    Console.WriteLine("Landing velocity: " + v.ToString("N1"));
    string evaluation = v < 4 ?
        "Soft landing, congratulations!" :
        (v <= 8 ? "Hard landing." : "Houston, crew is lost...");
    Console.WriteLine(evaluation);

    // Waiting for Enter
    Console.ReadLine();
}

摘要

本章以几个可能被认为是高级循环的例子来结束本书。

第一个练习可能是最容易的。它让你熟悉了while循环,这是你已经熟悉的do-while循环的近亲。唯一的区别是while循环在开始时就有它的条件,这意味着在第一次进入循环之前,它已经被求值了。随后,如果条件在开始时不评估为true,循环体将永远不会被执行。

这正是你所需要的。如果今天是星期五,你一次也不想执行循环体(多移动一天);你想和星期五在一起。

下一个任务把你转移到数学领域。您练习了重复乘法和渐进结果累加,以获得指定数字的 n 次方。

正弦任务可能是整本书中最难的一个。我在这里把它作为对有数学头脑的读者的奖励。你看到了计算机如何计算所谓的超越数学函数的值。

正弦值可以使用无限泰勒级数来计算。考虑到计算机中十进制数的有限精度,技巧是在有限个成员变得太小而无法给最终结果添加任何内容时截断序列。

该解决方案还向您展示了一些加快计算速度的技巧。您使用以前的系列成员来有效地计算下一个系列成员。

最终的登月任务结合了你在整本书中学到的许多东西,是一个你可以享受的轻松游戏!

个人笔记

既然这本书已经接近尾声,请允许我说几句个人的话。编程不仅仅是关于计算机、关键词和算法思维。对我来说,这是一生的激情和个人。

骰子

我已经注意到这本书里有很多模拟掷骰子的练习,因为我小时候玩过很多棋盘游戏。这些不仅仅是从商店购买的游戏。那时我发明了许多自己的游戏,其中大部分都是模拟体育赛事的。短跑、长跑、跳跃、自行车赛、足球等等都有不同的规则。这可能是成为一名程序员的良好准备。

正弦任务

我承认这一章的正弦任务大大超出了初学者的水平。我把它包括在内,是为了让你对你在编程这个奇妙的领域中可能的未来有所了解。

对我来说,这项任务也与个人有关。在我上学的某个时候,我想知道计算器是如何计算正弦的。我在想,函数值在计算器中被制成表格(“硬连线”)并进一步插值。后来我才发现另一条路,你看到的那条。

月球登陆

登月任务的简化版其实是我第一次接触编程。不,我没有编程;我是它的电脑。

当我年轻的时候,我读过一本杂志的特刊,向像我这样的年轻人解释编程。这期杂志里有一台纸电脑。那是一张写有代表变量的窗口的纸。在这些窗口中,你可以拉出纸条,在上面写下变量的值。给变量赋值?您只需拉动纸条隐藏旧值,并用铅笔在同一纸条上写下新值。

我用电子计算器完成了所有的计算。我正在执行程序的语句,我是计算机的 CPU,以 0.5 赫兹的惊人速度运行(是的,G 是故意省略的),使用巧克力棒可以提高到 0.6 赫兹。

那时,也就是 1982 年在捷克斯洛伐克,我登陆了那个登月舱可能有几百次,后来还在一个可编程计算器上使用了我的软件。也许我是当时最有经验的宇航员。不管怎样,这是一条非常激励人的编程之路。

最后的愿望

从宇宙的角度来看,我希望我已经把你带入了你自己的编程轨道。有时,我讲得有点深,所以也许你会喜欢回到练习题上来,把书看几遍。这是给你的宇宙站补充补给的一种方式。

我祝你在未来的编程中获得更多快乐和成功!

第一部分:数据

第二部分:计算

第三部分:条件

第四部分:循环

posted @ 2024-08-10 19:02  绝不原创的飞龙  阅读(7)  评论(0编辑  收藏  举报