编程基础-Java-C--和-Python-入门-全-

编程基础:Java、C# 和 Python 入门(全)

原文:Programming Basics: Getting Started with Java, C#, and Python

协议:CC BY-NC-SA 4.0

一、编程的基础

视频游戏、社交网络和你的活动手环有什么共同点?它们运行在一群(或多或少)程序员在很远很远的地方编写的软件上。在我们这个技术驱动的社会中,小工具和硬件只是硬币更明显的一面。在这一章中,我们将讨论编程的基础知识。我们还将看看数字系统的可见部分:硬件。

到底什么是编程?

基本上,编程是告诉数字设备,比如你的个人电脑,做什么的行为。我们键入由编程语言定义的命令列表,以便发生有用或有趣的事件。正确编程的计算机运行着世界上大部分的通信和在线服务。你可以提到像自动取款机、票阅读器和智能手机这样的小玩意,它们运行在某人用某种编程语言开发的软件上。

基本硬件缩减

作为一名初露头角的程序员,你将从理解你正在使用的普遍存在的电子设备中受益。最好至少对计算机内部最常见的组件有一个基本的了解。

计算机中的这些硬件组件代表了你的劳动力。作为一名程序员,你来主持这个节目。把编程的行为想象成告诉工厂工人要造什么。你制造应用程序,无论它们是大型复杂的软件项目,还是一些令人敬畏的编程书籍中的教程。

出于本书的目的,任何相对现代的台式机或笔记本电脑都可以。在尝试编程时,我们不需要任何昂贵的硬件。

1.中央处理器

自然,一个数字设备不能只靠软件运行;一个中央处理器(CPU) 是硬件“大脑”,它执行代码并使事情实际发生(见图 1-1 )。即使在一个不太复杂的电子产品中,所有指令都流向并通过一个 CPU(或一组 CPU)。由于体积非常小,自 20 世纪 70 年代以来,这些微芯片越来越成为我们生活的一部分。每个数字设备都有一个中央处理器,甚至可能是你的固定自行车/衣架。

img/507458_1_En_1_Fig1_HTML.jpg

图 1-1

早在 2005 年,数百万台电脑中使用的旧英特尔奔腾 4 CPU 的俯视图。图片由 Eric Gaba 提供。抄送-服务协议 3.0

2.硬盘(又称硬盘)

这个组件是用来永久存储数据的。在硬盘中,你会发现成千上万的文件,无论是图片、文本文件还是数据库。您的操作系统(如 Windows 或 macOS)也在硬盘的范围内。这些设备有两种类型:机械硬盘(见图 1-2 )和固态硬盘(SSD)。

img/507458_1_En_1_Fig2_HTML.jpg

图 1-2

Western Digital 机械硬盘的俯视图。由“Darkone”拍摄的图像经 CC BY-SA 2.5(creative commons . org/licenses/BY-SA/2 . 5/deed . en)许可

机械驱动器更实惠,但由于它们内部有移动部件,因此在过度振动和极端天气下,它们比固态硬盘更容易损坏。此外,固态硬盘通常运行速度更快。

3.视频卡

显卡负责显示系统的视觉效果,无论是纯文本还是现代视频游戏中令人眼花缭乱的 3D 图形。这些设备有多种配置和价格,从 30 美元的文字处理器恶魔到 1000 美元的游戏怪兽(见图 1-3 )。计算机显示器通常直接连接到视频卡。

img/507458_1_En_1_Fig3_HTML.jpg

图 1-3

2006 年的 Nvidia 7900GS 显卡

自 21 世纪初以来,视频卡业务基本上一直是两家数十亿美元的科技巨头英伟达AMD的垄断。然而,英特尔在这一领域也取得了进展。

4.随机存取存储器

随机存取存储器,俗称 RAM,用作计算机的临时存储。在物理上,它通常以棒状附件的形式出现(见图 1-4 )。当运行任何类型的软件时,您的计算机使用 RAM 来执行它。关闭你的设备将清空你的内存。相比之下,当关闭电脑时,写在硬盘上的数据不会被擦除。定期保存您的文档。

img/507458_1_En_1_Fig4_HTML.jpg

图 1-4

典型的内存块。图片由亨利·凯尔纳提供。CC BY-SA 4.0。来源: upload.wikimedia.org/wikipedia/commons/3/32/DDR3_RAM_53051.jpg

截至 2021 年,4 GB(即4gb)对于大多数用途来说已经足够了。视频编辑等高级用户将受益于 16 GB 或更大的 RAM。

5.母板

前面提到的所有四个硬件组件(即 CPU、显卡、硬盘和 RAM)在主板上组合在一起,形成一个工作的计算机单元。主板还具有用于键盘、鼠标和其他控制设备的连接器(参见图 1-5 )。

img/507458_1_En_1_Fig5_HTML.jpg

图 1-5

现代的个人电脑主板。图片由埃文-阿莫斯提供。CC BY-SA 3.0。来源: upload.wikimedia.org/wikipedia/commons/0/0c/A790GXH-128M-Motherboard.jpg

成为一名优秀程序员的三个要求

接下来,让我们讨论一下所有程序员为了在他们的职业中进步而应该拥有的一些个人优先事项,无论他们的起点可能是什么:

  1. 自信:问问自己这个,为什么要学编码?一些完全正确的答案包括“为了职业发展”、“保持我的能力”和“我想成为伟大事业的一部分。”现在,编程有时被外行人认为是一种可怕的活动。坐下来,置身事外,进入比特操作的世界,确实需要一些勇气。请记住,即使你是一个完全的初学者,你也可以在这个领域获得能力。自信来自经验。一行接一行,你将获得更多的良好氛围,并从编程书籍和在线教程中获得独立性。

  2. 正确的语言:不是所有人都能从流利的世界语或古典拉丁语中受益。当学习一门新语言时,我们倾向于学习一些有用的东西,如西班牙语或法语。同样,选择一种最适合你的意图的编程语言是至关重要的。如果你想最终为移动用户编写食谱应用程序,比如说,精通 1957 年的 FORTRAN 只能让你到此为止。为此,本书介绍了我们这个时代最流行的三种编程语言:Java、C# 和 Python。

  3. 耐心:在选择了你想专攻的编程语言之后,你只需要坚持下去。精通一门新语言需要六个月到一年的实践经验。这其实是个好消息。编码对失眠和无聊很有帮助。它还可以防止痴呆症,因为它确实在很大程度上激活了那些大脑突触。

初级程序员词汇表

我们现在将深入研究一些与神圣的编码爱好相关的基本术语。有数百个与各种编程技术和语言相关的术语和概念。然而,我们将只关注最相关的关键词,没有特定的顺序。

输入/输出

输入在编程的上下文中,是指我们输入数据,让计算机上运行的一个软件进行处理。这以键入文本、鼠标命令或各种类型的文件的形式出现。例如,文字处理程序(例如,Microsoft Office)通常将其输入主要作为通过击键提供的字母数字数据。输出是指经过软件处理的数据。在字处理器中,这通常是指与程序一起保存的文件。这种输出也可以指向打印机或其他设备。程序员的输出(尽管有二氧化碳和其他东西)通常是一个工作应用程序,无论它是一个完整的教程文件还是一个更大的项目。

算法

一个工作程序列表基本上构成了一个算法,指的是为解决问题而创建的一组步骤。大多数软件由许多子算法组成。在视频游戏的情况下,有显示图形、保存和加载游戏状态、播放音频文件等算法。

流程图

编程项目及其算法通常使用流程图来可视化,尤其是在团队环境中。在大多数情况下,这些都是演示基本程序流的好方法。

流程图仅由几个通用元素组成(见图 1-6 )。在它们最基本的形式中,它们使用四种符号。这些是终端(圆角矩形)流程(矩形)决策(菱形/菱形),以及流线(箭头)。结束符号用来表示程序流的开始和结束。任何操作和一般数据操作都由进程矩形表示。

img/507458_1_En_1_Fig6_HTML.jpg

图 1-6

一个非常简单的描述愚人节程序的流程图

大多数情况下,流程图是从上到下、从左到右解释的。早在 20 世纪 60 年代,美国国家标准协会(ANSI)就创建了流程图及其符号的标准。这组符号在 20 世纪 70 年代和 80 年代被国际标准化组织(ISO)扩展到 ??。为了这本书的目的,我们将坚持原著。

源代码

这个术语指的是组成每个软件项目的或多或少键入的编程清单的集合。作为程序员,你是源代码的创造者。简单的程序以单个源代码的形式出现,而复杂的软件,如操作系统(如 Windows ),可能由成千上万个清单组成,所有清单构成一个产品。

句法

一个语法是一组规则和原则,它们管理给定语言中的句子结构,包括在编程环境中。不同的编程语言对特定的操作使用不同的关键字。现在,看看用两种编程语言显示文本字符串的实际编程行:

表 1-1

两种编程语言之间语法差异的演示

|

爪哇

|

公式翻译程式语言(formula translator)

|
| --- | --- |
| System.out.print("你好!我喜欢蛋糕!”); | 1 打印*,“你好!我喜欢蛋糕!” |

Java,你可能已经知道了,是本书的主要语言之一。表 1-1 中使用的另一种编程语言叫做 FORTRAN。这种语言主要是为科学计算而设计的,由 IBM 在 20 世纪 50 年代创造。许多工业硬件都在 FORTRAN 上运行。甚至一些极客仍然用它来追求技术时尚(在某种程度上,我们也是如此)。

你可能会注意到我们在表 1-1 中的一个例子是以数字(1)开始的。在编码术语中,这被称为行号,这种做法早就被放弃了。通常,当代编程语言不需要行号。

例行的

编程环境中的例程是一个代码术语,它完成一项特定的任务,并被编码者随意反复调用。例如,一个程序可能包含一个播放声音效果的简单例程。代替每次需要所述声音效果时编写和重写代码,程序员将特别触发相同的代码(即,例程)。

根据上下文和使用的编程语言,例程有时也被称为子程序、函数、过程、方法。我们将在本书后面更详细地讨论术语。

文件格式

文件格式是一种编码数据的方法。到 2021 年,你已经在日常生活中遇到了很多 a 文件格式。数码照片、在 OpenOffice 中输入的情书以及那些时髦的 Excel 电子表格都代表了不同的文件格式。存放在硬盘上的图像文件(例如, apress_is_great.jpg )只能通过软件以图像的形式使用。同样,在照片编辑套件中打开 love-letter.doc 也不会给你带来最佳效果,最多显示些胡言乱语。大多数操作系统将不同的可用文件格式与正确的软件相关联,因此您可以安全地双击文件,并期望它们能够正常加载。

美国信息交换标准代码

美国信息交换标准码(ASCII) 是一种字符编码标准,用于分配字母、数字和其他字符,以供计算和其他数字设备使用。本质上,你现在读的是 ASCII 码。作为一名程序员,你会经常碰到这个术语。“ASCII 文件”通常被用作“人类可读文本文件”的简称该系统可追溯到 1963 年。

在今天的互联网上,最常用的字符编码标准是 UTF-8,它包括所有的 ASCII 字母数字以及许多其他符号。

样板代码

术语样板指的是或多或少自动插入到程序中的编程代码,几乎不需要编辑。当你在一个 C++环境中开始一个新项目时,当代的开发工具通常会为你提供运行程序所需的样板代码。如果没有,您可以相对安全地将旧工作项目中的样板代码复制粘贴到新项目中,以便开始工作。

软件框架

一个软件框架 是一组通用功能,通常可以节省编码人员很多时间。没有理由重新发明轮子,尤其是在软件项目中。框架包括各种焦点的各种软件库,包括文件操作例程、音频回放和 3D 图形例程(在 3D 视频游戏开发和其他高度可视化应用程序的情况下)。

出于本书的目的,我们不会深究任何复杂的软件框架,但是理解这个概念是很重要的。

全栈

一个全栈是组成一个完整工作的 web 应用程序的软件,比如一个在线数据库。一个 web 应用程序通常分为两个区域:一个前端和一个后端。前端是为用户做的;它包含使用应用程序所需的所有用户界面元素。后端由 web 服务器、框架和数据库组成。因此,全栈开发人员是指那些了解在线应用程序编码前端和后端的人。

最后

读完这一章,你有望对以下内容有所了解:

  • 计算机中的五个基本硬件组件

  • 成为程序员的三个主要要求

  • 一些基本的编程概念,包括源代码、语法和样板代码

  • 流程图指的是什么,它们的基本构件是什么

二、Java、C# 和 Python 101

在这一章中,我们将了解 2021 年(甚至更久)最流行的三种编程语言。将详细介绍和讨论与编程相关的几个概念。这些术语乍一看似乎令人生畏和/或笨拙,但是请注意,一旦掌握它们,它们实际上是相当简单的,并且提供了一条直接进入您的计算机核心的特殊路径。我们将首先向您介绍这三种令人惊叹的语言:Java、C# 和 Python。

爪哇

由 Sun 微系统公司创建并于 1995 年发布的 Java 很快成为一种被广泛采用的通用编程语言,尤其是在线环境(即云计算)。如今,用 Java 编写的软件驱动着无数的智能手机、数据中心和谷歌等无处不在的在线服务。截至 2021 年,对于新手和经验丰富的程序员来说,它是最受欢迎的编程语言之一。

虽然被称为 JavaScript 的编程语言与 Java 共享其前四个字母,但这两者几乎没有共同点。JavaScript 由互联网先驱网景在 90 年代创造,用于当时新的浏览器技术。这种语言在互联网上仍然很活跃,为无数网站提供了额外的功能和可用性。

C#

C#(读作 C 调)于 2002 年夏天向公众发布。该语言由微软开发,可用于创建从生产力软件到视频游戏的任何类型的现代应用程序。C# 与包括 Java 在内的其他几种流行的编程语言共享一些特性。

你可能听说过 C++,这是一种比 C# 更早也更复杂的语言。虽然 C++通常能够产生更有效的输出,但用 C# 开发移动和 web 应用程序更简单。至于 C++和 C# 的祖辈?向 C(有时也称为过程 C )问好,这是一种可以追溯到 1972 年的语言。

计算机编程语言

Python 于 1991 年发布,它的名字来自于 Monty Python,一个受欢迎的轻松娱乐电视节目。虽然 Python 的输出通常比用 C# 制作的软件慢,但 Python 在最近几年变得相当流行。它几乎可以处理任何事情,从制作简单的网络应用程序到较重的桌面软件。Python 甚至在视频游戏行业找到了一个游戏原型的位置。

关于 Python 值得注意的是它对空白/缩进(通过按空格键和/或 tab 键创建的不可见字符)的敏感方式。我们将在本书的后面部分触及这种独特的方法。

1,0,还有你

现在,你可能已经听过关于二进制数字和计算机如何喜欢它的演讲。是的,在最基本的层面上,微处理器确实咀嚼 1 和 0(它们真的挖掘字符串,比如 01011010110)。输入中央处理器的这个最接近的级别被称为机器语言机器代码的级别。在这个二进制级别之上有几个抽象层来帮助我们人类用计算机做我们的事情。

现在,在编程中,有高级语言低级语言 的区别。这与工具的质量无关。这种分类指的是一种编程语言与机器代码(即二进制代码)的接近程度。基本上,高级语言更接近人类语言,而低级语言对于外行人来说往往看起来相当晦涩。C# 和 Python 被认为是高级语言,而 C++则代表了所谓的中级语言,因为它为更高级的程序员提供了一些非常可靠的功能。

等等,你现在开始焦虑了吗?不要担心:在本书中,我们将只关注高层次的东西。不需要更深入的二进制/机器码知识。

编译和解释代码

计算机不能马上执行任何编程语言。比如 Java 和 Python 都是所谓的解释型语言。这意味着清单中的每一行都被实时地一步一步地“解释”成机器代码。与其他类型的语言(编译语言)相比,解释过程在程序中表现为较慢的执行速度。

在能够运行之前,编译语言中的清单需要经历一个叫做编译的过程。这是指在程序执行之前,将程序的全部源代码翻译成机器语言。谢天谢地,编译过程是自动化的,所以你不需要任何程序员到机器代码的翻译手册。这是任何好的编码开发环境的功能。参见表 2-1 了解编译和解释编程语言之间的主要区别。

表 2-1

编译语言和解释语言的主要区别

|   |

编译语言

|

解释语言

|
| --- | --- | --- |
| 例子 | C#,C++,C | Java、Python、JavaScript |
| 主要差异 | 整个代码列表在执行前被翻译成机器代码(即编译)编译时间会减慢开发速度构建更快的软件特定于平台(Windows、macOS、Linux 等)。) | 代码实时地一步一步翻译成机器代码快速制作原型构建较慢的软件独立于平台、近乎通用的兼容性非常适合在线应用 |

变量的魔力

变量是一种临时存储的形式,程序的用户在使用软件的时候可以使用它。变量通常使用设备中的随机存取存储器(RAM) 。这意味着,如果您关闭设备电源,存储在变量中的数据就会消失,除非先将其保存到硬盘等存储设备中。

变量可以用来存储游戏中玩家的名字,这只是一个基本的使用场景。从程序员的角度来看,变量在整个程序中被使用和重用。它们可以应用多种运算,从基本算术到复杂的数学公式。

每种编程语言中都有许多类型的变量。事实上,文本字符串、单个字母数字字符和不同范围的数值都有不同的变量类型。但是为什么要做这些区分呢?嗯,有时候你需要存储的只是一个字符,比如说字母“b”。在前面提到的场景中,为 19 位数值保留一种变量空间是对计算机内存的浪费,因此在大多数编程语言中有几种变量类型。

许多编程语言没有能够存储任何类型信息的通用变量。大多数情况下,程序员需要在使用前定义变量声明

计算机不擅长猜测。大多数编程语言会明确区分字符串快乐变量快乐变量。如果一个列表不起作用,可能只是在大写上有一些问题。

Python 中的变量

Python 有六种核心类型的数据类型:数字字符串列表元组集合字典。与 Java 和 C# 不同,Python 中的变量通常不需要类型声明。您只需分配一个值,它的类型就会自动设置。参见表 2-2 了解这些数据类型的概要。

表 2-2

Python 中的六种主要数据类型

|

数据类型

|

描述

|

示例定义

|
| --- | --- | --- |
| | Python 中的数字包括整数(整数)、浮点数和复数。可以在创建定义时进行计算 | 馅饼直径= 21 幸运数字= 5 + 1 圆环直径= 6.26 + 0.11 |
| 字符串 | 字符串是字母数字字符的连续集合。单引号和双引号在它们的定义中都被接受 | 索德伯里的雷金纳德爵士昵称= "注册"Color = "紫色" |
| 列表 | 列表由任何类型的值/变量组成。列表用方括号括起来,用单引号将字符串值括起来 | jolly_list = [ 1,2,3,4,5 ]happy_list = [ 'Hello ',123,' Orange' ] |
| 元组 | 与列表不同,元组是只读的,不能动态更新。元组用括号括起来 | 体面元组= ( 1,2,3)amazing_tuple = ( 1.12,“Ok”,456.5) |
| 设置 | 集合是使用花括号初始化的无序值的集合。在集合中,重复的值会被丢弃 | Fine_Animals = { '猫','蝙蝠','蝙蝠','鸟' }三个伟大的数字= { 1,2,3,3,3 } |
| 字典 | 字典是无序的键值对,用花括号括起来 | Friends = { 'name': 'Yolanda ',' age': 25 }cars = { 'make': 'Pinto ',' safety-level': 'great' } |

试用 Python

您实际上不需要安装任何特定的软件来尝试 Python、C# 和 Java 编程的一些基础知识。这些语言有很好的在线编程实验环境。首先,现在是访问这些服务并体验 Python 变量的好时机。试试这些,坚持你最喜欢的:

准备好编译你的第一行 Python 代码了吗?启动一个在线编译器,将清单 2-1 输入编程空间。准备删除“hello world”列表,默认情况下它可能在那里。当你完成输入后,点击编译器界面中的运行执行来查看结果。

Fine_Animals = { 'Cat', 'Bat', 'Bat', 'Bird' }
print("My favorite beasts:", Fine_Animals)

Listing 2-1Notice how text (i.e., “My favorite beasts”) can be displayed next to a variable using a comma in Python

在清单 2-1 中,我们首先定义了一个变量, Fine_Animals,,这是一个适合我们目的的名字。然后我们继续使用 print 命令输出它的内容。这个输出应该是说我最喜欢的野兽:{ '蝙蝠','猫','鸟' } (可能顺序不一)第二只“蝙蝠”怎么了?它消失了,因为我们定义了一个 Python 集合数据结构(熟悉表 2-2 ),其中不允许重复条目。

表 2-3

Python 中的一些显式类型转换函数

|

分配担任特定类型角色

|

函数调用

|

使用示例

|
| --- | --- | --- |
| 任何类型→整数 | int( ) | phone_number="5551234"``new_variable= int(phone_number)``print(new_variable) |
| 任何类型→浮点 | float( ) | wholenumber=522``floatnumber= float(wholenumber)``print(floatnumber) |
| 整数或浮点→字符串 | str( ) | float_variable=float(2.15)``string_variable= str(float_variable)``print(string_variable) |
| 字符串→列表 | 列表() | greeting="Hello"``a_list= list(greeting)``print(a_list) |
| 字符串→集合 | set( ) | fruit="Banana"``a_set= set(fruit)``print(a_set) |

操纵变量

有许多方法可以操作变量中的值。所有的基本算术(即加、减、乘、除、取幂和求根)都包含在任何编程语言中。下一个小清单用 Python 演示了这一点。清空您的编译器编程空间,如果您想查看它的运行情况,请键入清单 2-2 。

favorite_number = 21
print("My favorite number is", favorite_number)
favorite_number+=2
print("No, it was", favorite_number)
favorite_number-=1
print("Wait.. it's actually", favorite_number)

Listing 2-2The variable used is in bold

在清单 2-2 中,我们使用加法减法赋值操作符用于我们的算术目的(即,+=和-=)。下面的加法语句会产生相同的结果:favorite _ number = favorite _ number+2

Python 中的类型转换

有些场景要求程序将某些值解释为特定的变量类型。在用户需要输入各种数据类型的情况下,这可能是需要的。从一种数据类型转换到另一种数据类型的过程称为类型转换类型转换。幸运的是,Python 允许我们使用一些相当简单的方法来做到这一点。

现在,有两种类型的类型转换:隐式显式 你可能已经对前者有些熟悉了。隐式类型转换简单地说就是 Python 从您第一次分配给它的输入中推断出变量的类型(例如,一个数字或一串字符)。在这一节中,我们将重点关注显式类型转换,在这种转换中,程序员不仅将值赋给变量,还将其数据类型转换为另一种类型。Python 中的一些核心类型转换函数见表 2-3 。

变量比较

当然,还有其他利用变量内容的方法。为了制作可用的软件,我们经常需要比较价值。为此有六个通用运算符(见表 2-4 )。这些操作符适用于几乎所有的编程语言,包括本书中提到的三种。

表 2-4

Java、C#、Python 和大多数其他语言中的主要比较运算符

|

操作员名

|

运算符符号

|

使用示例

|
| --- | --- | --- |
| 等于 | == | if (name == "Johnny ")... |
| 不等于 | != | 如果(年龄!= 21) ... |
| 不到 | < | 如果(年龄 |
| 超过 | > | 如果(能量>最大能量)... |
| 小于或等于 | <= | if(宠物蛇< =允许蛇数量)... |
| 大于或等于 | >= | 如果(黄金> = 1000)... |

现在让我们来看看清单 2-3 中的一些比较在 Python 中是如何工作的。

print('Enter a number:')
YourNumber = input()
YourNumber = int(YourNumber)

if YourNumber > 10:
    print('Your number is greater than ten')
if YourNumber <= 10:
    print('Your number is ten or smaller')

Listing 2-3A simple listing in Python demonstrating comparison operators

Java 和 C# 中的变量声明

现在我们继续讨论 Java 和 C# 环境中的变量。与 Python 不同,这些编程语言要求我们手动定义变量的数据类型。有关 Java 和 C# 中一些主要变量类型的详细概述,请参见表 2-5 。如您所见,在大多数情况下,这两种语言的变量类型声明是相同的。

表 2-5

Java 和 C# 的主要变量类型及其声明

|

变量类型

|

Java 和 C#

|

变量范围

|
| --- | --- | --- |
| 整数(整数) | int | 从-2147483648 到 2147483647 |
| 性格;角色;字母 | 字符 | 单个字母数字字符(例如,B) |
| 浮点数 | 浮动 | 6 到 9 位小数之间的分数 |
| 双浮点数 | double | 最多 17 位小数的分数 |
| 布尔(逻辑运算符) | 布尔值 | 真或假(即 1 或 0) |
| 文本字符串 | 字符串(Java),字符串(C#) | 任意数量的字母数字字符 |

试用 Java 和 C#

不仅限于 Python,Java 和 C# 都有一些在线编译器。以下是一个选择,供您选择的乐趣。挑选一个你最喜欢的,这样你也可以用这些语言尝试一些实时编码。

在花括号、变量范围和代码块上

在本章中,你很快会遇到几个带花括号的代码清单。它们确实是许多编程语言中的重要元素。这些卷曲字符的作用是表示由成组声明和/或语句组成的代码块和。编译器或解释器软件将单个代码块作为单个指令读取。

此外,代码块可以限制变量的范围,即清单中特定变量有效的部分。两个不同的代码块可能无法访问彼此的变量空间。

Python 不像 Java 或 C# 那样处理花括号。相反,该语言在表示代码块时使用空白(即空白字符或缩进)。在 Python 中,花括号是为定义字典或集合数据类型而保留的。

爪哇第一次冒险

现在,在清单 2-3 中,我们用 Python 编写了一个程序,要求用户输入一个数字。然后程序在屏幕上显示一个基于这个输入的注释。让我们来看看 Java 是如何应对同样的考验的。这有点复杂,但不用担心。之后我们会分解它。

import java.util.Scanner;

public class HappyProgram {
    public static void main(String args[]) {

    Scanner input_a = new Scanner(System.in);
    System.out.print("Enter a number: ");
    int YourNumber = input_a.nextInt();

if (YourNumber > 10) System.out.println("Your number is greater than ten") ;
if (YourNumber <= 10) System.out.println("Your number is ten or smaller") ;
    }
}

Listing 2-4A simple listing in Java demonstrating user keyboard input

与 Python 相比,Java 确实需要更多的设置。用 Java 写的程序可以用所谓的 Java 包来扩展;这些基本上是数据容器,为你的项目添加新的特性和功能。清单 2-4 中的第一行为任何 Java 程序添加了交互功能,当我们需要用户输入时,它需要出现。

清单 2-4 只是将一个名为 java.util 的 Java 包合并到程序中。从这个包中,我们检索 scanner 函数,然后用它来读取键盘输入。在阅读本书的过程中,我们将浏览一些最常用的 Java 包。

现在让我们分解清单 2-4 中与用户输入相关的机制:

Scanner input_a = new Scanner(System.in);

这里发生的是我们创建了一个名为 input_a.扫描仪对象,我们可以将这个对象 happy_objectpink_tutu。然而,最好坚持至少一个有点逻辑的命名方案。继续前进,我们会遇到下面几行代码:

  System.out.print("Enter a number: ");
        int YourNumber = input_a.nextInt();

在前面的代码片段中,我们使用 Java 的标准打印函数显示了一条消息。请注意它与 Python 的不同之处。接下来,名为 YourNumber 的整数变量(即整数)被初始化。然后,它被发送给一个名为 nextInt( ) 的函数,该函数等待用户输入,期望得到一个整数。前面提到的函数是我们在清单第一行导入的 Java 包 java.util.Scanner 的一部分。

你可能已经注意到分号(;)在清单 2-4 中。Java 确实希望每个指令后面都有一个。此外,Java 语法要求在变量比较和大多数函数中使用括号。所有这些惯例也适用于 C#。

再次使用 C#

接下来,我们将浏览相同的清单;只是这次是用 C# 写的(见清单 2-5 )。您会发现它与 Java 中的有些相似,但是有一些关键的区别,我们将在后面讨论。

using System;

public class HappyProgram
{
      public static void Main()
      {
      Console.WriteLine("Enter a number: ");
      int YourNumber = Convert.ToInt16(Console.ReadLine());

      if (YourNumber > 10) Console.WriteLine("Your number is greater than ten");
      if (YourNumber <= 10) Console.WriteLine("Your number is ten or smaller");
      }
}

Listing 2-5A listing in C# demonstrating user keyboard input

清单中第一行 2-5 (即使用系统;)激活特定的名称空间。C# 中的名称空间是帮助你组织代码的容器元素。首先,它们可以帮你节省时间。没有系统名称空间,而是控制台。WriteLine* ,我们将输入*系统。每次我们在屏幕上打印东西的时候。人们也可以声明他们自己的自定义名称空间,这是我们将在本书后面做的事情。现在,你知道它们就足够了。**

大多数编程语言在源代码中需要特定的声明性语句,对于每个项目来说,这些语句通常不需要是唯一的。这被称为样板代码。例如,在清单 2-4 中,行public static void Main(String args[])和清单 2-5 , public static void Main( ) 可能分别被归类为 Java 和 C# 端的样板代码。我们将在后面的章节中详细讨论这个概念。

现在,与 Java 相比,C# 对它的许多函数使用了不同的词汇。为了在屏幕上打印文本,我们有控制台。writeline。用户输入,我们有控制台。ReadLine 如下行所示:

int YourNumber = Convert.ToInt16(Console.ReadLine());

这里发生的是我们初始化一个整数变量,YourNumber,并把它传递给一个转换函数 Convert。我们告诉它等待用户输入,并期待一个符号的 16 位整数值。这些是范围从-32,768 到 32,768 的整数。这为我们的用户最有可能输入的内容提供了足够的空间。

无符号 16 位整数携带 0 到 65,536 之间的值,这意味着它们不能存储负值。如果我们需要存储非常大的数字,我们可以选择使用 32 位整数,,它们也有带符号的(-2,147,483,647 到 2,147,483,647)和无符号的(0 到 4,294,967,295)对应项。出于本书的目的,我们将坚持使用较小的数字。

论类和面向对象编程

您可能已经注意到单词 class 在我们的列表中出现了几次。这是面向对象编程(OOP)中的一个关键概念,一个流行的编程范例。书中提到的三种语言都在不同程度上融入了 OOP。

任何真实世界或抽象场景都可以用 OOP 优雅地表达出来。这种方法中的两个基本构件被称为对象。简单来说,类定义了对象的蓝图。例如,你可能正在开发关于园艺的软件,并编写一个名为 Plant 的类。然后,您可以使用 Plant 类来调用称为对象的单个实例来填充您的虚拟花园。你以这种方式创建的各种不同的个体植物群将彼此共享特征和变量,正如它们的起源类中所定义的那样。以后更改工厂类的零件会影响属于该类的所有未来对象。您还可以创建 Plant 的子类,以满足您计划建模的各种玫瑰和郁金香(以及花园侏儒类)。

OOP 为软件开发者提供了许多好处。其中包括代码的可重用性和通过类封装的开发安全性。面向对象编程有许多迷人的方面。我们将在本书的后面更深入地探讨这个范例。

流量控制的基础:循环

除了变量之外,我们有一堆逻辑结构来处理我们的编码工作。这些结构构成了所谓的流量控制。当任何节目单被输入时,计算机会从上到下阅读它。通常,这个程序中的处理要重复许多次。因此,具备循环和条件程序流的能力是有意义的。

编程中的循环可能会向您介绍迭代的概念。迭代是将特定的步骤重复预定的次数,以获得想要的结果的过程。编程环境中重复的动作序列被称为循环。有许多方法可以创建这些循环,这也取决于所使用的语言。这些结构的示例实现见表 2-6 。

表 2-6

三种语言中的流控制示例

|

Java 中的“While-loop”

|

C# 中的“For 循环”

|

Python 中的“For-loop”

|
| --- | --- | --- |
| //让我们初始化一个变量 int I = 3; (i > 0) {System.out.println("三个 hello ");-我;} | //这是一个迷人的循环for(int I = 0;我<3;i++){控制台。WriteLine(“你好!”);} | #这是一个有趣的循环对于范围(10)内的 i:打印(“你好号码”,I) |

While 循环

表 2-6 中的第一个迭代方法是用 Java 演示的 while 循环。这种方法执行操作,直到满足 while 函数中的条件。在我们的例子中,当变量 i 大于零时循环运行。除了在屏幕上打印文本时命令语法的不同,表 2-6 中的 while 循环在 Java 和 C# 中都是一样的。

详细的 For 循环

表 2-6 中 C# 的例子可能看起来有些复杂。正在讨论的结构, for-loop ,是一种古老的技术,由现在将要讨论的几个元素组成。

头部(即从的开始的部分)包含关于主体(即由花括号包围的部分)将被执行多少次的指令。在我们的示例中,头部分如下所示:

  • 定义一个辅助数值变量 i 并赋予 i 零值(int I = 0;).

  • 只要变量 i 小于三(I<3;)之后,您将继续该程序。

  • i (i++)的值加一(1)。

同样,除了命令语法上的差异(即 System.out.println 与 Console。WriteLine),表 2-6 中间的例子在 Java 和 C# 中都是一样的。

Python 中的循环

您可能会注意到在我们的 Python 循环中明显缺少分号和花括号。包含 print 命令的行确实包含在循环的代码块中,只使用缩进。此外,Python 中的 for-loops 可以简单地使用一个叫做 range 的漂亮函数来设置迭代次数,而不是 Java 和 C# 中稍微复杂一些的结构。

有时我们需要记录代码的变更。虽然纸和笔都可以,但是在清单中做这个更好。要添加计算机无法解析的行,我们可以在清单中加入特殊字符。对于 Java 和 C#,我们使用两个正斜杠,对于 Python,我们使用散列标记,如清单 2-5 所示。

最后

读完这一章,你将有望理解以下内容:

  • 解释型和编译型编程语言的主要区别

  • 什么是编程环境中的变量以及如何操作它们

  • 如何使用 Java、C# 和 Python 定义和操作变量以及在屏幕上打印文本

  • 哪六个是通用变量比较运算符

  • 面向对象编程(OOP)的两个基本概念是什么

  • 编程流控制的基础,包括 if 语句和 for 循环

在第三章中,我们将超越在线编译器,为您提供一些优秀的可下载软件,并加深您对本章概念的理解。当谈到软件开发环境时,我们将涉及 Windows、macOS 和 Linux。

三、设置您的编程环境

本章致力于向您介绍集成开发环境的乐趣。虽然在线编程环境对您的前几份清单来说是不错的,但是在您自己的计算机上安装一些专用的编码软件会让您受益匪浅。幸运的是,有很多免费的 ide 可供你下载。我们将涵盖 2021 年三个最流行的操作系统的各种软件。但首先,我们将解决另一个基本概念:计算架构

关于计算体系结构

一个时钟周期代表一个 CPU 内执行的单个动作。在每个时钟周期中,CPU 执行基本的内部操作,这些操作构成了计算机生态系统中任何更大任务的一部分。现在,时钟速度指的是一个 CPU 每秒可以召集的时钟周期数,通常用千兆赫(Ghz)来表示。例如,一个 2.1 Ghz 的 CPU 每秒提供 21 亿个时钟周期。CPU 每秒提供的时钟周期越多,系统处理数据的速度就越快。

现在,术语计算架构被用来描述一个 CPU 每个时钟周期可以处理多少数据。目前市场上基本上有两种主要的计算架构:32 位和 64 位。后者正迅速成为大多数计算类型的事实架构。为 64 位架构编写的软件可以更好地利用系统资源,如 RAM,同时通常比 32 位架构执行得更快。

架构实现发生在硬件和软件中。只有 64 位 CPU 可以运行 64 位操作系统,同时仍然提供与旧的 32 位软件(和操作系统)的兼容性。然而,64 位操作系统通常甚至不能安装在具有 32 位 CPU 的计算机上。

到 21 世纪初,支持 64 位计算的 CPU 变得越来越流行。除非你使用的是真正的老式电脑,否则你很可能已经准备好了 64 位计算,至少在硬件方面是如此。有关这两种架构的概要,请参见表 3-1 。

大企业正在彻底放弃 32 位技术。事实上,从 macOS Catalina (10.15) 开始,苹果完全放弃了对 32 位软件的支持。微软也正在将 32 位世界抛在身后。截至 2020 年 5 月,任何现成的电脑都将只配备 64 位版本的 Windows 10 。在三大操作系统中,只有一些 Linux 发行版仍然广泛适用于 32 位硬件。

表 3-1

32 位和 64 位计算架构的比较

|   |

32 位架构

|

64 位架构

|
| --- | --- | --- |
| 特征 | 仅运行 32 位版本的操作系统,可以运行大多数传统的 16 位软件 | 运行 32 位和 64 位操作系统,通常不支持 16 位软件 |
| 每个系统的理论最大 RAM | 4 千兆字节或 4,294,967,296 字节 | 171 亿 GB 或 18,446,744,073,709,551,616 字节 |
| 系统中的典型 RAM 数量 | 1 到 4 GB 之间 | 介于 8 和 32 GB 之间 |

出于本书的目的,您不需要运行任何 64 位软件;32 位操作系统就可以了。但是,为了让您的设备经得起未来的考验,您应该考虑尽快迁移到 64 位操作系统。

解释了位派生单元

就像你现在可能知道的,计算的最小单位是比特。和其他量一样,仅仅用原子单位来衡量事物是不切实际的。因此,我们有字节、兆字节和千兆字节,仅举三个例子(见表 3-2 )。

公制单位(即使用 10 的幂乘数的单位)在个人计算的早期工作得很好。然而,使用这些乘数并不完全准确。例如,按照公制,1 千字节的文件实际上是 1024 字节,而不是 1000 (10 3 )。

典型的硬盘驱动器的额定容量为 250 GB(即 250,000,000,000 字节),实际容量为 232.8 GB。硬件厂商一般不会提这些东西。我想你可以走了。

1998 年,国际电工委员会(IEC) 创建了更精确的测量方案。新系统使用 2 的幂乘数。例如,一千字节变成了 1024 字节大小的千比字节(210= 1024)。这些新单元非常需要,因为它们在测量更大的数据池时更加精确。

表 3-2

最常用的公制和 IEC 数据存储单元的比较

|

米制单位

|

价值(公制)

|

IEC 单位

|

价值(国际电工委员会)

|
| --- | --- | --- | --- |
| 位(b) | 0 或 1(原子/最小单位) |   |   |
| 字节(b) | 八位 |   |   |
| 千字节(kB) | 1000 字节 | 基布利特(基布利特) | 1024 字节 |
| 兆字节 | 100 万字节 | Mebibyte (MiB) | 1,048,576 字节(1024 个字节) |
| 千兆字节 | 10 亿字节(十亿字节) | 吉布比特(gib) | 1,073,741,824 字节(1024 兆字节) |
| 兆兆字节 (TB) | 10 亿字节(一万亿字节) | 提比布 | 1099511627776 字节(1024) |

64 位多任务处理

与旧的 32 位架构相比,64 位计算的主要优势之一是大大改善了多任务处理能力。多任务指的是同时运行几个程序。一个典型的程序员可能在他们的电脑上有一个很棒的 IDE、 PhotoshopSpotify ,以及在 Firefox 中同时打开的 50 个标签。安装了大量千兆字节的 RAM 消除了通常与运行多个程序相关的许多不连贯和变慢的情况。RAM 升级可能是将个人电脑推向更高性能的最常见方式。

识别您的操作系统架构

您可能需要检查操作系统的架构是 32 位还是 64 位。又快又简单。

  • 在 Windows 10 中,进入设置系统关于。你会在这一页看到必要的细节。

  • 在 Linux 中,打开一个终端窗口,键入 arch 并按回车键显示您的系统架构。显示 x86_64 的输出意味着您拥有 64 位版本的 Linux。对于该操作系统的 32 位版本,显示 i386i686 的输出。

  • 至于 macOS,这个操作系统从 2009 年雪豹(10.6) 开始,每个版本都是 64 位(对 32 位软件有不同程度的向后兼容)。

为 Java 开发安装 Eclipse

好的开发环境提供搜索特性、语法突出显示,在某些情况下还支持多种编程语言。我们将首先为您设置一个专门为 Java 开发打造的环境,由 Eclipse Foundation 开发的强大的 Eclipse IDE (见图 3-1 )。

Eclipse 项目最初是由 IBM 在 2001 年 11 月创建的。Eclipse Foundation于 2004 年作为一个独立的非盈利组织出现。它是作为一个围绕 Eclipse 的开放透明的社区而创建的。

Eclipse 适用于 WindowsmacOSLinux。访问下面提供的下载页面,只需点击反映您正在运行的操作系统类型的链接。然而,有一个警告。Eclipse 的最新版本只适用于现代操作系统的 64 位版本。如果您仍然在使用 32 位操作系统,请查看 Eclipse 旧版本的专用链接。它们也很适合我们的目的。

img/507458_1_En_3_Fig1_HTML.jpg

图 3-1

行动中的 Eclipse

安装 Eclipse for Linux

Linux 存储库可能托管一个过时版本的 Eclipse。为了在 Linux 中安装最新版本,我们将使用 Canonical Ltd .提供的 Snapcraft 方法。这种方法应该适用于 Linux 的所有主要发行版。打开终端窗口,输入以下行:

  1. 首先,我们确保您的操作系统上安装了【JRE】在终端窗口中键入以下字符串:

  2. 接下来,我们使用 snappy 系统下载 Eclipse:

sudo apt install default-jre
Fedora Linux users might need to input the following Terminal-commands:
sudo dnf install java-latest-openjdk.x86_64
sudo dnf install java-latest-openjdk-devel.x86_64

sudo snap install --classic eclipse

在安装过程中,系统可能会提示您输入密码。成功安装后,您应该会看到以下消息:安装了来自 Snapcrafters 的 eclipse(版本信息)。

Eclipse 首次发布

启动 Eclipse 时,会提示您为 Eclipse 工作区选择一个目录。默认设置适用于大多数用途。让我们创建一个新项目。这是通过从顶部菜单栏导航到文件新建Java 项目来完成的。接下来,您应该会看到一个等待您的项目图块的窗口。输入一个并点击创建。

将出现一个窗口,询问您是否想要创建module-info.java。这个文件是 Java 的模块化功能使用的一个模块声明。对于简单的应用程序,如果你选择不要创建就好了。

您将被带到 Eclipse 中的主项目视图。我们手头上还没有一个实用的 Java 应用程序。现在,在左边,你会看到项目浏览器。左键单击您的项目名称。从顶部菜单中选择。输入这个新类的名称;它不必共享您的项目名称。

接下来,确保你已经勾选了public static void main(String args)旁边的复选框。这给了你的项目一个 Java main 方法。没有它,你无法执行你的项目。最后,点击完成进入你全新的 Java 代码清单。您现在可以在 main 方法下开始编码了。

C# 开发的好主意

现在,让我们回顾一下您的 C# 需求的一些选择。我们将从微软的 Visual Studio 开始,这是一个流行的多平台 IDE,充满了伟大的特性,包括动态错误下划线。该软件适用于 Windows 和 macOS。

为 Windows 和 Mac 设置 Visual Studio

让我们从 Windows 和 macOS 的 Visual Studio 安装开始。首先,您需要下载完全免费的社区版的正确安装程序。这是一个用于多种语言的健壮的 IDE,包括 C#。

启动 Visual Studio 安装程序后,您最终会看到一个关于工作负载的屏幕。这些基本上是指 Visual Studio 中提供的各种语言的不同实现场景。您将看到 C# 和其他语言的四类工作负载;分别是 Web &云、桌面&移动、游戏、其他工具集(见图 3-2 )。对于我们的编码需求,让我们勾选旁边的复选框。NET 桌面开发。这将很好地为我们设置 C# 语言。

最后点击窗口右下角的安装。您也可以选择在下载时安装,或者先下载所有必需的文件,然后再开始安装过程。

img/507458_1_En_3_Fig2_HTML.jpg

图 3-2

Microsoft Visual Studio Community Edition 的安装屏幕

。NET 是微软在 2002 年创建的一个软件框架,在专有许可下发布。它为 C#、C++和其他流行语言提供了简化的开发。该框架在 2016 年以的名义接受了一次大修。NET 核心。这一次,它以开源和跨平台的形式出现,包括对 macOS(以及在一定程度上对 Linux)的支持。此后,微软不再将新版本的名字改回. NET。

在 Visual Studio 中启动新项目

经过可能很长的安装过程后,Visual Studio 为您提供了登录开发人员帐户(或注册一个)的选项,以获得更多好处。请随意跳过这一步。接下来,您要为 IDE 选择一种配色方案,并单击启动 Visual Studio。

几分钟后,您将到达 Visual Studio 主窗口。当你点击创建新项目时,你会看到各种各样的编程模板,包括那些专门针对 C# 的模板。对于我们的需求来说,控制台 App 是合适的。过一会儿,Visual Studio 将创建您的项目文件,您可以开始在 main 函数下编码。

控制台与图形用户界面应用程序

在所谓的控制台应用程序和那些具有图形用户界面(GUI)的应用程序之间有一个普遍的区别。前者使用基于文本的基本界面,而后者为用户提供更多的视觉效果,通常还提供额外的输入方法(如鼠标或触控板)。控制台应用程序有时会利用基于文本的伪图形来模拟几何形状。

尽管外观古怪,但控制台应用程序为开发人员提供了很高的效率;毕竟,(音频)视觉所消耗的所有资源都可以用于程序的核心功能。

控制台应用程序遍布我们的操作系统。它们用于与网络相关的任务,以及潜在的大量自动化实例。主要的控制台应用有 macOS 中的终端Windows 控制台Linux 命令行。此外,有史以来最受欢迎的角色扮演视频游戏之一 Nethack、是一款真正的控制台应用程序。谁需要 3D 图形?

您可以通过在控制台应用程序领域中工作来学习所有基本的编程技巧。尽管我们将触及基于 GUI 的开发,但本书的重点主要是基于文本的环境。

Linux 中的 MonoDevelop 简介

截至 Q1 2021 年,Visual Studio 不可用于 Linux。然而, MonoDevelop 提供了在灵活的 IDE 中开始编写 C# 所需的一切。

从前面的下载页面(例如 Debian)导航到最接近您当前使用的 Linux 发行版和版本的 Mono 存储库。接下来,您将复制粘贴许多命令到您的终端窗口。根据您的硬件,您可能需要等待几分钟才能完成安装。之后,MonoDevelop 应该安全地驻留在您的应用程序文件夹中。

点击 MonoDevelop 图标打开 MonoDevelop。导航到文件新解决方案。一个新的窗口将会打开。点击。NET其他,选择控制台项目。最后点击右下角下一个的。将打开另一个窗口,提示您输入项目名称。输入您认为合适的内容,然后点击创建。**

接下来,您应该会看到一个用 C# 编写的通用 hello world 应用程序的清单。或者导航到运行启动而不调试,或者单击 MonoDevelop IDE 左上角的播放图标来执行列表。

在 MonoDevelop 中尝试运行/调试程序后,如果出现以“找不到指定文件”结尾的错误提示,您可能需要在终端窗口中执行以下附加行:

cd /usr/lib

sudo mkdir gnome-terminal

cd gnome-terminal

sudo ln -s /usr/libexec/gnome-terminal-server

现在,您已经准备好为 Linux 开发一些漂亮的控制台应用程序了。

Python 开发的 PyCharm

当使用 Python 进行开发时,有一个软件是最优秀的。PyCharm 是一个多平台 IDE,具有智能代码完成等优秀特性。该套件有付费版和免费版;我们将与后者合作。以下链接将引导您找到您的操作系统支持的 PyCharm 版本。

PyCharm 在 Windows 和 macOS 上的安装过程非常简单。对于前者,运行您下载的可执行文件,并按照屏幕上的指示进行操作。对于 macOS,只需双击图像文件(以结尾。dmg),并将 PyCharm 图标拖到应用程序文件夹中。

在 Windows 上安装 PyCharm 时,即使您的帐户没有密码保护,也可能会出现密码提示。只需单击“取消”继续安装。

要开始试用 PyCharm,请点击创建项目。然后,您将有机会命名您的项目,并为其选择一些其他选项。项目名称同时也是所有 PyCharm 项目文件的目录位置。当你选定了一个合适的标题后,点击窗口右下角的创建

为了使开始更容易,PyCharm 为您提供了创建欢迎脚本的选项。最好启用这个选项。创建新项目时,确保勾选了Create a main . py welcome script旁边的复选框。

第一次创建新项目时,PyCharm 可能会下载一些额外的组件。安装这些组件可能需要一段时间。当您的新项目文件准备好时,您将进入 PyCharm 的主编码视图。现在,您可以自由编辑文件 main.py 并体验这个令人惊叹的 Python IDE。

第一次打开新项目时,文件 main.py 将填充一些 Python 代码。你可以删除所有的代码,然后在它的位置键入你自己的代码。

为 Linux 安装 PyCharm

让 PyCharm 在您的 Linux 上运行的最佳方式是使用 snap 包。只需打开一个终端窗口,输入下面一行:

sudo snap install pycharm-community --classic

软件害虫:臭虫

你是否曾经因为电脑堵塞和/或文本文件丢失而结束了你的工作?你可能会在运行中遇到一个软件错误。软件环境中的 bug 意味着由错误的程序代码引起的小故障;它们可能是程序员的打字错误,或者更常见的是一些神秘的设计错误。所有这些操作系统的软件更新基本上都是为了修复错误(有时还会提供新功能)。参见表 3-3 了解最常见的软件错误和问题。

表 3-3

最常见的软件问题/错误类别

|

软件缺陷

|

例子

|

软件问题

|

例子

|
| --- | --- | --- | --- |
| 句法的 | 不正确地使用语言语法,例如,使用错误的变量比较运算符 | 连接 | 缺少用户界面功能,例如,缺少导航按钮或其他关键元素 |
| 算术 | 被零除,可变范围溢出 | 安全 | 薄弱的身份验证,关键系统组件的不必要暴露 |
| 逻辑学的 | 较差的程序流控制,例如无限循环或折衷循环 | 沟通 | 缺少用户文档,用户界面元素的标签不好,不直观的错误提示 |
| 资源 | 使用未初始化的变量,通过不释放不需要的变量空间来耗尽系统 RAM | 团队合作疏忽 | 代码元素/变量的标签不合逻辑,注释不当 |

关于调试和测试

调试是发现并修复软件中发现的 bug 的艺术。一个调试器为软件开发人员提供工具,在错误发生时,在它们可能对用户计算机造成严重破坏之前修复它们。这是 solid IDE 的另一个特性,自然属于本章介绍的每个解决方案。

调试范围从 IDE 编辑器中简单的错误突出显示到详尽的数据收集和分析。典型的调试组件允许程序员在软件运行时检查正在开发的软件,如有必要,可以一行一行地检查。代码编写的高级编程语言,如 Java 和 Python,通常更容易调试。低级语言,如过程 C,留给程序员更多的控制权;这可能会更频繁地导致内存损坏等问题。

繁重的调试对于较大的软件项目来说是绝对必要的。出于我们的需要,这个话题我们不必深究。在这一点上,你知道这个术语的意思就足够了。

调试与软件开发中有时被忽视的领域有关:测试。虽然测试包括调试,但还有更多。测试团队负责报告软件产品在其预期的使用场景下是否正常工作。然而,即使是大规模的测试过程也不能找到软件项目中的每一个缺陷。

软件测试可以大致分为功能非功能分支。前者侧重于将软件及其组件的功能与一组规范进行比较。非功能测试指的是与性能、可用性和安全性相关(但不限于此)的问题。本地化,包括非西方市场的开发,也是非功能软件测试的一部分。本地化涉及到结合流畅和适当的翻译语言,并关注不同的文化敏感性。

测试过程的细节取决于目标受众的类型。视频游戏开发人员,尤其是对于较小的团队,有时会在测试上偷懒(这对他们非常不利)。银行和金融领域的基本软件预计将接受最严格的测试。较大的软件项目需要一个高度组织化的测试团队。由于经济原因,大型软件的测试通常外包给国外的企业。

调试:基本方法

现在,我们将详细介绍一些最常见的代码调试方法:

  • 跟踪/打印调试:这种方法简单来说就是密切关注每一行代码的执行结果,将它们一个接一个地打印在屏幕上。可以密切注意变量和其他数据结构的内容,因为它们在程序执行过程中会发生变化。跟踪对于较小的项目非常有用,比如本书中的教程。

  • 记录和回放调试:使用这种方法,程序执行的部分被记录和回放,以检查其潜在的缺点。这不是指软件的外部或视觉回放;相反,它侧重于州一级的程序。

  • 事后分析 调试:这种方法包括分析程序崩溃后的日志数据。许多类型的软件在严重故障后会在磁盘上写入日志文件,包括大多数操作系统。然后可以检查这些文件,寻找导致崩溃的错误的线索。

  • 远程调试:调试不必在运行焦点程序的设备上进行。使用流行的网络方法,如 Wi-Fi 或 USB 电缆,不同角色和外形的设备可以连接在一起工作。为 Android 和 iOS 编写和调试软件时,远程完成是最常见的方法,因为开发机器几乎总是一台独立的全尺寸计算机。

  • Bug 聚类:每当发现大量 Bug 时,这是一个有用的方法。程序员首先识别错误中的所有共同特征。然后,问题被分成特定的组,这些组共享它们的属性,逻辑是,即使一个集群中的几个 bug 被解决了,其余的也应该随之而来。

  • 代码简化:有时消除 bug 的最佳策略是(或多或少暂时地)删除它们周围的功能代码部分。这显然对更隐秘/害羞的虫子最有效。当您还不确定什么不起作用时(例如,什么导致了崩溃),一个接一个地移除明显起作用的部分,并引诱 bug 出来。

最后

读完这一章,你会对以下内容有所了解:

  • 32 位和 64 位计算体系结构之间的主要区别,以及如何识别您的操作系统运行在哪个品种上

  • 如何定义最常见的位导出数据单元

  • 什么是集成开发环境(IDE) 以及如何为您的操作系统获得一个集成开发环境

  • 控制台应用和利用图形用户界面(GUI) 的应用之间的核心区别

  • 最常见的软件错误/问题类型

  • 什么是调试和软件测试

下一章将详细介绍面向对象编程(OOP)的奇迹。正如在第二章中提到的,这是一个非常重要的范例,每个初学的程序员都应该能够使用。

四、面向对象编程

这一章是关于面向对象编程的所有细节。这个不朽的范例改变了编程的世界,并且已经成为软件设计中某种事实上的方法。接下来,我们将讨论许多对面向对象范例至关重要的概念。在本章中,为了清晰起见,我们将主要使用 Java 语言来介绍与 OOP 相关的概念,当然,不会完全放弃 C# 和 Python。

过程范式与面向对象范式

正如在第二章中提到的,目前基本上有两种主要的编程范例:过程化和面向对象。20 世纪 70 年代末是计算机科学令人兴奋的时期。在此之前,大多数编程都是严格在过程语言领域中完成的,这种语言是在所谓的“自顶向下”的设计范式下运行的。基本上,使用这种方法,程序员最后处理细节。大多数焦点都集中在程序的主要功能上。

C++的发布拉开了面向对象革命的序幕。该语言于 1985 年发布,很快被广泛应用于大多数用途。使用“自下而上”的设计方法,C++和其他面向对象的语言专注于首先定义数据,这通常是根据现实生活中的现象建模的。此外,与它们的程序性对应物相比,它们提供了大量的附加功能。这包括封装,这是一种通过实现访问说明符将代码部分相互隔离的技术。接下来我们将更详细地研究封装和其他面向对象的细节。

参见表 4-1 了解过程式编程语言和面向对象编程语言的主要区别。

表 4-1

两种主要编程范例之间的主要区别

|   |

过程程序设计

|

面向对象编程

|
| --- | --- | --- |
| 示例语言 | (程序性)C,Pascal,BASIC | C#、C++、Java、Python |
| 基于 | 抽象世界 | 真实世界的场景 |
| 方法 | 自上而下:主要问题被分解成子过程 | 自下而上:首先创建数据类 |
| 强调 | 功能(即程序) | 数据(即类) |
| 遗产 | 不支持 | 支持 |
| 封装(数据安全) | 低:没有访问修饰符 | 高:多级访问修饰符 |
| 方法重载(多个方法同名) | 不支持 | 支持 |

类、继承和 UML

让我们重温一下面向对象编程(OOP)的最基本的概念,因为我们在第二章中只触及了其中的一些。我们将继续讨论对象的概念。到现在为止,你可能知道在 OOP 中,类是一种创建代码对象的蓝图。

现在,尤其是对于较大的软件项目,可以说,在编写一行代码之前,将笔放在纸上通常是一个好主意。可视化面向对象软件的最好和最流行的工具之一是使用通用建模语言(UML) 。由 Rational Software 在 20 世纪 90 年代中期创建,UML 已经成为软件工程中无处不在的工具。让我们用一个关于冰淇淋的类图来取样一些(见图 4-1 )。

img/507458_1_En_4_Fig1_HTML.jpg

图 4-1

一个简单的类图展示了 UML 的基础知识

图 4-1 (即冰淇淋)中最上面的盒子是一个抽象类。基本上,这些类不能用来创建对象。那为什么有他们?嗯,抽象类可以保存和普通类相同类型的信息。它们也可以有子类。抽象类的目的是为它的子类提供一些共享信息来继承和创建对象。在许多情况下,它们的使用简化了设计过程。

抽象类下面的三个盒子被称为子类。他们展示了继承,这是 OOP 中的一个关键概念。香草巧克力鬼椒接收它们的超类冰激凌内置的所有变量和方法。注意这些盒子是如何连接的。不同种类的箭头在 UML 中描述不同的事物。如图 4-1 所示,一个空的箭头意味着简单的继承。在 UML 中,人们还必须注意箭头的方向。

现在,你在 UML 中用斜体表示抽象类的名字;常规课程通常不会以任何方式风格化。接下来,一个类框列出了它的变量和它们的数据类型。变量前的加号(+)表示它是公共的,而减号(-)表示私有变量。最后,我们列出在我们的类中找到的任何方法,就像我们对图 4-1 中的 Display_Flavor( ) 和所有其他方法所做的那样。

除了我们到目前为止介绍的元素之外,UML 还有更多内容。我们将在本书后面的章节中更详细地介绍这种不可思议的建模语言。现在,只要你理解 UML 如何帮助你可视化一个软件项目就足够了。

包装

OOP 环境中的抽象封装的概念密切相关。前者指的是对程序的用户/编码者隐藏无关信息的技术。后者处理软件的内部工作;在封装中,数据和操作它们的函数都存储在类中。然后,每个类都有控件,可以从其他类访问这些控件的数据。让我们用 Java 来演示封装,好吗?(见清单 4-1 。)

GetSet 一般来说是 Java 和 OOP 中非常重要的关键词。与 return 命令一起,它们用于检索和定义类中的数据。提醒一下,函数(也叫方法)是一段只有被调用时才运行的代码。

public class Geezer {
  // The private-access modifier is used to limit access from other classes
  private String name;

  // The Get-function is used to retrieve name variable
  // Its naming convention is non-capitalized "get" and capitalized variable
  public String getName() {
    return name;
  }

  // The Set-function defines data
  public void setName(String newName) {
    this.name = newName;
  }
}

Listing 4-1A class definition in Java with Get- and Set-functions (file name Geezer.java)

清单 4-1 不能在 ide 或在线开发环境中执行,因为它缺少 Java main 方法。该列表仅用于演示目的。我们将很快进入实际可执行的 Java 代码。

我们通过给它一个名字来开始我们的类定义,这个名字也是它的文件名。在我们的例子中,应该是 Geezer。我们做的第一件事是定义一个类型为字符串的变量(显然是为了存储老人的名字)。变量定义前的关键字 private 被称为访问修饰符 。私有方法和变量只能在定义它们的类中访问(在我们的例子中,只能从 Geezer 中访问)。

getName 方法使用 return 关键字来检索一个怪老头的名字。该方法被创建为公共的和值字符串。这只是因为它应该返回一个字符串变量的值。

接下来让我们分解我们在清单 4-1 中定义的 setName 方法。关键字对 public void 指定了一个不返回值的方法。它通常适用于集合函数,因为它们用于定义变量,而不是从变量中检索值。Java 中的关键字 this 简单地寻址包含该方法的对象。

您的(潜在的)第一个 Java 对象

类是组织数据的好工具,但是它们能做的更多。使用 geezer 类作为起点,让我们添加代码来用 Java 创建一个实际的对象(参见清单 4-2 )。

public class Geezer {
  private String name;

  public String getName() {
    return name;
  }

  public void setName(String newName) {
    this.name = newName;
  }
    // We add a main-method so we can actually experiment with our class
    public static void main(String args[]) {
    // Next we create an object using the geezer-class as a blueprint
    // We'll call our object "some_geezer"
    // and use Java's built-in new-method to bring it to life
    Geezer some_geezer = new Geezer();
    // Next we invoke the setName-method to give our geezer a name
    some_geezer.setName("John");
    // Finally we access our geezer-object to print out a name
    // in two different ways
    System.out.println("Hello, my name is "+some_geezer.name+" the geezer!");
    System.out.println("Can you repeat that? "+"It's "+some_geezer.getName());
    }
}

Listing 4-2A class definition with a main method in Java (file name Geezer.java)

在清单 4-2 中,我们使用了一个所谓的点操作符(即 some_geezer.name )和 getName 方法从我们的对象中读取数据。点运算符也称为成员运算符。

public static void main(String[]args)是每个 Java 程序开始处理的地方,开始在屏幕上为用户显示一些东西。声明 String[ ] args 的部分基本上意味着程序接受来自执行它的人的单个文本字符串作为输入(例如,用户可以通过键入“我的程序你好”而不是“我的程序”来启动程序,这可能有各种效果)。

Java 字母汤:JDK、JVM 和 JRE

在继续 OOP 之前,让我们先看看 Java 开发中最重要的三个软件组件。毫无疑问,在您的编程冒险中,您会经常遇到这些术语。首先,有 Java 开发包(JDK) 这是用 Java 编码所需的类和工具的核心集合。JDK 有几个品种。

接下来,我们有了 Java 运行时环境(JRE) 该组件用于将 JDK 内部完成的代码输出与一些额外需要的软件库相结合,从而允许 Java 程序实际执行。

最后,我们有 Java 虚拟机 。在桌面 PC 上创建的 Java 程序可以在任何安装了 JVM 的设备上运行。因此,这种在专用虚拟机上运行 Java 的方法创建了很大程度的平台独立性。

你可能还记得第二章的内容,Java 是一种 ?? 解释语言。当执行用 Java 编写的程序时,编译器生成字节码。这是一种中间格式,需要 Java 虚拟机(JVM)来运行;没有它就不能启动字节码程序。由于 JVM 可用于大多数现代计算机和设备,这种方法使得 Java 几乎普遍独立于平台。

C# 中的对象

已经用 Java 创建了我们的(可能的)第一个对象,现在让我们用 C# 做同样的事情。参见清单 4-3 ,其功能与清单 4-2 完全相同。这将展示 Java 和 C# 在语法上的许多相似之处。

using System;

public class Geezer {
  private String name;

  public String getName() {
    return name;
  }

  public void setName(String newName) {
    this.name = newName;
  }

public static void Main(string[] args) {
    Geezer some_geezer = new Geezer();
    some_geezer.setName("John");
    // The next two lines differ the most between our Java and C# listings
    Console.WriteLine("Hello, my name is "+some_geezer.name+" the geezer!");
    Console.WriteLine("Can you repeat that? "+"It's "+some_geezer.getName());
    }
}

Listing 4-3A class definition with a main method in C# (file name Geezer.cs)

Java 和 OOP 中的静态和公共方法

我们现在将更深入地研究编写各种方法。OOP 中基本上有两种方法:静态公共。我们已经在清单 4-2 和 4-3 中试验了后者。

Note

一个方法实际上可以使用两个限定符,著名的 Java main 方法 public static void main()就是这样。

现在,这两个变种之间的主要区别是静态方法不需要使用对象来调用;您可以在没有任何特定类实例的情况下调用它们。然而,静态方法不能使用类变量,如清单 4-4 所示。

public class HappyMethods {
private int x=10, y=10;

  // A static method can't use our class variables
  static void myStaticMethod() {
    System.out.println("Hello! I'm a static method and I can't use x or y.");
    // System.out.println(x + " + " + y + " = " + (x+y));
    // The line above would return an error
  }

  // A public method can use our class variables for some rudimentary arithmetic
  public void myPublicMethod() {
    System.out.println(x + " + " + y + " = " + (x+y));
  }

  // Our main method
  public static void main(String[] args) {
    myStaticMethod(); // Call the static method
    HappyMethods myObj = new HappyMethods(); // Create an object of HappyMethods
    myObj.myPublicMethod(); // Call the object's public method
  }
}

Listing 4-4A class definition with a main method in Java (HappyMethods.java)

构造函数方法

对象从类定义中接收所有变量的初始值。但是,无论何时创建对象,都可以调用构造函数来设置全部或部分变量数据。

您可以通过定义接受额外属性的方法来创建新的构造函数。这些属性然后根据需要传递给每个对象,以替换类中定义的原始值。参见清单 4-5 中的示例。

构造函数方法必须与包含它的类同名。在我们的示例程序中,这两个构造函数都被命名为 Movie ,根据它们的原始类。

public class Movie {
  // Class variables and their default values
  private String title="Jack and Jill";
  private int release_year = 2011;

  // This is a default constructor. Java creates them automatically if need be
   public Movie() {
   }

  // This is a constructor-method for setting both movie title and release year
  public Movie(String name, int year) {
    release_year = year;
    title = name;
  }
  // This is a constructor for setting the movie title only
  public Movie(String name) {
    release_year = 2021;
    title = name;
  }

  public static void main(String[] args) {
    // Create three objects based on class "Movie"
    Movie myMovie = new Movie("Jack and Jill 2");
    Movie myMovie2 = new Movie("The Ridiculous 6", 2015);
    Movie myMovie3 = new Movie();
    // Display the data stored in these three objects
    System.out.println(myMovie.title + " (" + myMovie.release_year+")");
    System.out.println(myMovie2.title + " (" + myMovie2.release_year+")");
    System.out.println(myMovie3.title + " (" + myMovie3.release_year+")");
  }
}

Listing 4-5A listing in Java demonstrating the use of constructor methods (Movie.java)

现在,在清单 4-5 中,我们调用了三个基于电影的对象。所有这些对象都使用不同的构造方法来赋予它们生命。

第一个对象,处理理论上(但确实很精彩)的电影杰克和吉尔 2 ,是使用接受单个字符串的构造函数方法创建的。清单 4-5 中的第二个对象使用了更通用的构造函数,它接受一个字符串和一个整数。我们示例中的第三个也是最后一个对象是使用最基本的构造函数创建的,它不接受任何属性;它会将默认值(“Jack 和 Jill”和 2011)分配到我们的对象中,就像输入到类定义中一样。

重载方法

在 OOP 中,你可以拥有多个同名的方法,只要它们的参数的数量和数据类型不同(参见清单 4-6 )。

public class OverloadingFun {

// Method for returning a middle-initial as an integer
static int MakeName(int middle) {
  return middle;
}

// Method for combining three strings into a full name
static String MakeName(String first, String mid, String last) {
  return first + mid + last;
}

public static void main(String[] args) {

  // Define an integer using our first method
  int integer_initial = MakeName(80); // 80 stands for 'P' in the ASCII-system

  // Convert this integer into single character-type using typecasting
  char middle_initial=(char)integer_initial;

  // Convert the new character variable into a string
  String mid=String.valueOf(middle_initial);

  // Add all three names to create full name using the second method
  String fullname = MakeName("Rick ", mid, " Astley");

  System.out.println("Full name: " + fullname);
}
}

Listing 4-6A listing in Java demonstrating the overloading

of methods (OverloadingFun.java)

清单 4-6 有两个名为 MakeName 的方法。第一个方法接受整数值,而它的兄弟方法接受三个字符串。为了让后者完成它的工作,前面提到的整数值首先被转换成一个单字符变量。这是使用 Java 的类型转换功能完成的,并导致字母“P”被存储到变量 integer_initial 中。然后使用 Java 的value of-函数将这个变量转换成一个字符串。

最后,我们将三个字符串(包括中间的首字母)组合成一个受欢迎的英国流行歌手的名字。

更详细的访问修饰符

到目前为止,您已经在我们的清单中多次遇到了访问修饰符。接下来让我们回顾一下。毕竟,它们在 OOP 中非常重要。此外,对他们来说,除了私人的、公共的 ??,还有更多。参见表 4-2 和 4-3 分别了解 Java 和 C# 中最常见的访问修饰符。您会注意到两种语言有不同数量的访问修饰符,尽管它们基本上都遵循相同的 OOP 逻辑。此外,类可以访问 Java 中的包。

表 4-2

Java 中最常见的访问修饰符

|

访问修饰符

|

当与一起使用时

|

易接近

|
| --- | --- | --- |
| 公共 | 班级 | 可由其他类访问 |
| 受保护的 |   | 可由声明类和派生类以及同一包中的类访问 |
| 决赛 |   | 类不能被任何其他类继承 |
| 摘要 |   | 类是抽象的;它不能实例化对象 |
| 公共 | 变量、方法、构造函数 | 所有类都可以访问代码 |
| 私人 |   | 代码只能由声明的类访问 |
| 默认(即未指定) |   | 代码只能在同一个包中访问 |
| 决赛 |   | 变量和方法不能被修改 |

提醒一下,在 Java 中,指的是一组相关的类。我们在程序清单中使用 import 关键字,将特定的类(如import package . name . happy class)或完整的包(如 import package.name.* )引入到我们的项目中,以获得额外的功能。后一种类型的导入中使用的星号()称为通配符。*

为什么访问修饰符很重要

您可能想知道访问修饰符实际上提供了什么特定的用途;现在将是一个好时机来回顾一些场景,在这些场景中它们提供了明确的好处。一个这样的场景涉及团队合作。封装的数据可以防止大型项目出现人为错误。有了封装的代码,程序员不一定需要知道一个方法如何工作;重要的是产量。这通常会加快开发时间。

此外,从程序员的角度来看,正确使用访问修饰符使程序更具可读性。更新和维护封装的软件项目通常比那些过程化的(即非面向对象的)项目更直接。

记住,OOP 中的封装包含两层意思。首先,它是一个用来描述使用类将数据与方法配对的方法的术语。其次,它指的是使用访问修饰符在编程级别限制对数据的直接访问。

C# 程序集和访问修饰符

在我们开始 C# 访问修饰符稍微不同的前景之前,让我们回顾一个重要的相关概念。C# 中的一个程序集指的是你的项目的输出,就像在一个用户可执行文件中,通常带有文件扩展名。Windows 环境下的 exe (如 happyprogram.exe)。它是语言中最小的部署单位。程序集通常包含程序使用的其他资源,包括图像数据。它们还承载您的项目元数据,如其版本信息,以及执行程序所需的其他程序集的潜在列表。较大的项目可能包含许多程序集。

*现在,让我们回顾一下六个 C# 访问修饰符。它们现在看起来很容易互换,但是当你读完这本书的时候,你会对它们都很熟悉,并且知道它们是如何被需要的(见表 4-3 )。

表 4-3

C# 的六个访问修饰符

|

访问修饰符

|

易接近

|
| --- | --- |
| 公共 | 所有其他类都可以访问 |
| 私人 | 只能从声明的类中访问 |
| 受保护的 | 可从声明类内部和从该声明类派生的任何类内部访问 |
| 内部 | 访问仅限于当前程序集中定义的类 |
| 受保护的内部 | 访问权限仅限于当前程序集中定义的类和/或驻留在其他程序集中从它们派生的类 |
| 私人受保护 | 可从声明类和从该声明类派生的任何类中访问,但只能在同一程序集中访问 |

用 C# 访问类

让我们回到编码上来。到目前为止,本章的大部分内容都使用了 Java。接下来,让我们为 C# 创建一个原始的 OOP 清单来演示它对访问修饰符的处理(参见清单 4-7 )。

using System;

class Protected_Class {

   // Define a protected string variable, fruit
    protected String fruit;

    public Protected_Class()
    { fruit = "Noni fruit"; }
}

// Make a new derived class using the colon-operator
class Derived_Class : Protected_Class {

    // This method from Derived_Class can access Protected_Class
    public String getFruit()
    { return fruit; }
}

class Program {
    // Main execution begins below
    public static void Main(string[] args)
    {
               // Create two objects, one from each class
               Protected_Class Object1 = new Protected_Class();
               Derived_Class Object2 = new Derived_Class();
               // Display our string variable using a method from the derived class
               Console.WriteLine("Your favorite fruit is: {0}", Object2.getFruit());
    }
}

Listing 4-7A C# listing demonstrating inheritance and the use of access modifiers

为了清楚起见,清单 4-7 从创建一个我们称之为 Protected_Class 的类开始。这个类拥有一个构造函数,它设置了一个受保护的变量, fruit。接下来创建第二个类 Derived_Class ,并使用冒号(:)操作符继承第一个类的属性。Derived_Class 现在可以访问 Protected_Class 中的数据,甚至是它的 Protected 变量。这可以使用 get 方法来完成,所以我们将只为这个类创建一个,并将其命名为 getFruit()。

接下来,我们将继续讨论列出 4-7 的主要方法。这里,我们创建了两个对象,分别来自我们之前定义的每个类。请注意,用 C# 创建对象的语法与 Java 使用的语法完全相同。

我们的 main 方法中的最后一行显示了一条包含字符串变量内容的消息;这就是所谓的格式的字符串。在 C# 中,变量用花括号显示在文本中(C 派生语言一般真的很爱用)。元素 {0} 引用第一个(在本例中是唯一的)变量,我们将在消息旁边显示它。如果我们有第二个变量要在字符串中打印出来,我们会用 {1}— 等等。

再一次,使用 UML

为了让你对统一建模语言的奇迹有进一步的准备,让我们把清单 4-7 变成 UML,好吗?实际上,这种语言不仅仅是类图;使用 UML,我们还可以可视化对象。在图 4-2 中,我们有清单 4-7 的类图(左)和对象图。

img/507458_1_En_4_Fig2_HTML.jpg

图 4-2

UML 中清单 4-7 的类图(左)和对象图

你应该从这个相当简单的数字 4-2 中吸取一些东西。首先,一个类中受保护的成员用前面的 hashtag (#)符号表示。在我们的例子中,这指的是字符串变量水果。另外,UML 中的对象图使用一种特定的格式。头中有对象名、一个由空格包围的冒号操作符(😃,最后还有实例化该对象的类名。此外,标题带有完整的下划线。在 UML 中,对象图的变量被期望显示它们的内容;因此,我们有美味的“诺丽果”,因为这是类的构造方法所声明的。

UML 类图表示系统的整体,而对象图表示系统在特定时间点的详细状态。将前者视为蓝图,将后者视为运行中系统的快照。

受保护的访问:Java 与 C#

尽管 Java 和 C# 在语法和逻辑方法上非常接近,但还是有一些细微的差别,一开始可能会让人感到困惑。例如,两种语言对受保护的访问修饰符的处理是不同的。

在 Java 中, protected 相当于 C# 中的 protected internal ,因为它只能由声明类或派生类访问,或者由同一(在 Java 中)或程序集(在 C# 中)中的类访问。

从表 4-3 中你可能还记得,C# 中真正的保护的修饰符只能从声明类和从原始类派生的任何类中访问。

Python 和 OOP

我们没有忘记 Python,是的,它是最面向对象的。尽管这种语言支持所有主要的面向对象技术,但语法与 Java 和 C# 有很大不同。首先,大多数可爱的大括号仍然不存在。另外,Python 中的构造函数是用关键字 init( ) 定义的(每边有两个下划线)。

在 Python 中,空白成为一个非常重要的因素。正如第二章提到的,缩进是 Python 语法不可或缺的一部分,用于表示清单中的代码块。

现在,让我们用 Python 制作一个简单的类应用程序。在清单 4-8 中,我们创建了一个类和一个构造函数方法,并使用该类实例化了一个对象。

class Publisher:
           def __init__(self, name):
           self.name = name

"Create new object, cool_publisher, from the above class"
cool_publisher = Publisher("Apress")
"Display its name"
print(cool_publisher.name, "is cool!")

Listing 4-8A simple Python listing demonstrating OOP

仔细阅读清单 4-8 ,您会立即注意到 Java 和 C# 的区别。首先,类 Publisher 由三个独立的代码块组成,由三个不同级别的缩进所分隔。如果不遵循这种格式逻辑,Python 实际上会抛出一个错误。幸运的是,大多数 Python IDEs 会在适当的地方自动添加空白。

在 Python 中,类和构造函数声明以冒号(:)结尾。表达式 self 用于为它产生的每个对象寻址类的变量。用 Python 创建对象相当简单;我们给它们一个名字,并用我们选择的构造函数分配一个类。在清单 4-8 中,只有一个构造函数接受 publisher 类的唯一变量 name 的值。

接下来让我们用 Python 尝试一些稍微复杂一点的东西(参见清单 4-9 )。

"Create and initialize a global variable"
potato_count = 0
class Potato:
   "Make a constructor"
   def __init__(self, *args):
         global potato_count # point out potato_count is indeed global
         "country defaults to Finland if no value is given"
         self.country = "Finland"
         "Take the first argument as diameter"
         self.diameter = args[0]
         "Take the second argument as cultivar"
         self.cultivar = args[1]
         "Increase global variable value by one"
         potato_count += 1

         "If over two arguments are given assume the third one is for country"
         if len(args) > 2:
             self.country = args[2]

   "Make a method for displaying object information"
   def printInfo(self):
          print("My cultivar is", self.cultivar, "and my diameter is", self.diameter, "inches")
          #If the country-variable is not empty (!= "") display it"
          if self.country != "":
              print("I was grown in", self.country)

"Create three objects of class Potato"
potato1 = Potato(3, "Lemin kirjava")
potato2 = Potato(5, "French Fingerling", "France", "This does nothing")

potato1.printInfo()
potato2.printInfo()
print("Total potato-cultivars listed:", potato_count)

Listing 4-9A listing in Python demonstrating class construction

清单 4-9 的输出应该如下所示:

我的品种是 Lemin kirjava,我的直径是 3 英寸

我在芬兰长大

我的品种是法国鱼种,我的直径是 5 英寸

我在法国长大

列出的马铃薯品种总数:2

我们稍微复杂一点的 Python 清单引入了几个新概念。其中之一是全局变量。这些是指可以在 Python 列表中的任何地方使用的变量,包括方法内部和外部(任何类)。

接下来在清单 4-9 中,我们有一行 def init(self,args)😗 ,它是我们的 Potato 类的唯一构造函数。它不接受特定的数据类型,而是接受一个参数列表,如表达式 *args 所示。

Python 本身不支持方法重载,不像 Java 和 C#。如果我们为了重载的目的在一个类中输入任意数量的方法,这些方法中的每一个都会简单地覆盖前一个。

为了给变量赋值,我们使用 args[0] 作为第一个参数,使用 args[1] 作为第二个参数。可以看出,Python 从零开始计算参数。

现在,Python 有了一个方便的内置函数来确定列表和其他数据结构的长度, len 。这在我们的清单 if len(args) > 2: 中的下一行中使用,它的意思是“如果参数长度超过两个”基本上我们的程序最多接受三个参数;剩下的就干脆丢弃了。当我们给对象 potato2 总共四个参数时,这反过来被证明;最后一个论点没有效果。

至于我们的全局变量 potato_count ,每当从 potato 类实例化一个新对象时,它的值就增加 1。这自然相当准确地反映了土豆对象的总数。

Python 中的继承

在结束这一章之前,让我们再讨论一个重要的话题。Python 中的继承实现起来非常简单(参见清单 4-10 )。

class Computer:
  def __init__(self, *args):
     self.type = args[0]
     self.cpu = args[1]
  "Define a method for displaying type"
  def printType(self):
     print("I use a", self.type, "as my daily driver.")

"Create a child class, Desktop"
class Desktop(Computer):
  def __init__(self, *args):
     Computer.__init__(self, *args)
     self.color = args[2]

"Create an object using Desktop-class"
computer1 = Desktop("Commodore 64", "MOS 8500", "beige")

computer1.printType()
print("It has a", computer1.cpu, "CPU.")
print("It is a wonderful", computer1.color, "computer.")

Listing 4-10A Python listing demonstrating inheritance. The child class definition is in bold

在清单 4-10 中,行类 Desktop(Computer): 表示一个继承类 Desktop 的开始,该继承类继承了其原始类 Computer 的所有变量。在我们的例子中,这意味着字符串类型cpu 现在成为类桌面的一部分。此外,我们在继承的类中声明了一个额外的变量, color,,总共给它三个变量。

自然,Python 中的继承不仅仅适用于变量;方法也会被传递。在清单 4-10 中, printType 是一个源自计算机类的方法。但是,我们可以使用从 Desktop 类实例化的对象来调用它。

Python 中的属性绑定

在我们郑重地结束这一章之前,让我们再来看一下 Python(参见清单 4-11 )。

class ToiletPaper:
    pass

"Create two objects from class ToiletPaper"
type1 = ToiletPaper()
type2 = ToiletPaper()

"Add cost and brand -variables into the class"
type1.cost = 4
type1.brand = "Supersoft"
type2.cost = 2
type2.brand = "Sandpaper"

print("We sell", type1.brand, "and", type2.brand)
print("Their prices are", type1.cost, "and", type2.cost,"dollars, respectively")

Listing 4-11A Python listing demonstrating ad hoc class modification

在清单 4-11 中,我们使用关键字 pass 来创建一个没有变量或方法的类。在 Python 中,我们甚至可以实例化这些空白类,这就是接下来要做的。现在是适度有趣的部分,向临时对象修改问好。在 Python 中,通过引用空白类实例中不存在的变量,可以为所述实例创建新的数据结构。我们称之为属性绑定。

属性绑定也适用于类(参见清单 4-12 )。

class ToiletPaper:
    pass

"Create object and add cost and brand -variables into it"
type1 = ToiletPaper()
type1.cost = 400
type1.brand ="Sandpaper"

"Add cost and brand into the class"
ToiletPaper.cost = 4
ToiletPaper.brand = "Supersoft"

"Create an object from modified class ToiletPaper"
type2 = ToiletPaper()

print("We sell", type1.brand, "for", type1.cost, "dollars")
print("We also sell", type2.brand, "for", type2.cost, "dollars")

Listing 4-12A Python listing demonstrating attribute binding for classes

清单 4-12 再次从我们定义一个空类开始。然而,这一次我们将属性绑定到这个类,而不仅仅是它的对象。从清单的输出可以明显看出,向类中添加任何数据都不会覆盖对象以前的绑定。

最后

读完这一章,你将有望学到以下内容:

  • 过程式和面向对象编程(OOP)范例之间的主要区别

  • 在面向对象的上下文中,抽象、继承和封装指的是什么

  • 如何在 Java 和 C# 中定义类并基于它们创建对象

  • OOP 中公共方法和静态方法的区别以及访问修饰符的基础

  • 什么是构造函数,如何使用它们,以及如何重载方法

  • 统一建模语言的基础

这就结束了第四章,这是本书相当密集的部分。在下一章,我们将深入探讨一些高级的 Java 主题,比如文件操作和多线程。*

五、文件操作、多线程和其他 Java 设施

到目前为止,您可能已经相当熟悉了强大的面向对象语言 Java 中的基本编程元素。现在是时候转向更高级的概念了。在本章的后面,我们将从多线程和 Java 中的基本错误处理开始。继续,我们最终会谈到文件操作这个有趣的话题。

多线程:内核和线程

Java 支持多线程,多线程基本上是指将一个程序拆分成几个并发运行的部分。这通常会大大加快任何算法的执行时间。多线程有时也被称为并发

多线程是一个与处理器内核密切相关的概念,处理器内核指的是硬件方面:中央处理器单元(CPU)。比如说,2005 年以前的老一代 CPU 通常只有一个内核。你很难找到 2021 年制造的全新单核。如今,大多数 CPU 至少包含两个内核。这些核心处理你输入计算机的所有数据(见图 5-1 )。

img/507458_1_En_5_Fig1_HTML.jpg

图 5-1

演示双核 CPU 内部多线程的简单图表

一般来说,系统拥有的内核越多,处理数字的效率就越高。这取决于您的操作系统以及它是否支持多核计算;几乎所有现代操作系统都具备这样做的条件。然而,并非所有第三方软件都是为了充分利用多核处理而编写的。在这种情况下,软件只使用系统中的一个内核。

现在,并发运行线程的数量通常不等于 CPU 可支配的内核数量。记住,一个软件应用程序可以为不同的任务调用多个线程。这意味着单核系统可能会有几个线程同时运行。多核 CPU 提供的是在众多内核之间共享所有这些线程的工作负载的可能性。在具有六个或更多 CPU 内核的系统上,这可以极大地提高计算速度。

你可能偶尔会遇到术语超线程。这是指英特尔的专有技术,理论上,它可以提供两倍于处理器实际配备的 CPU 内核。超线程技术于 2002 年推出,它与操作系统协同工作,以增加可用内核的数量。英特尔声称,与没有超线程技术的相同 CPU 相比,配备超线程技术的 CPU 可以全面提升性能。然而,在现实生活中,超线程技术的优势在很大程度上取决于应用。这项技术确实在视频编辑和 3D 建模等 CPU 密集型场景下蓬勃发展。

用 Java 实现多线程

线程可以分为不同的状态,这些状态构成了一个线程生命周期。接下来,让我们详细了解这五个阶段:

  1. :一个线程诞生。

  2. Runnable :顾名思义,这个阶段的线程正在运行它的任务,无论它们是什么。

  3. 等待:当一个线程与其他线程一起工作,并且暂时完成了它们的处理,让其他线程接管时,这个线程就进入了这个阶段。此阶段中的线程正在等待其他线程的“同意”以恢复其工作负载。

  4. 定时等待:当线程暂时完成了自己的任务,但在将来某个特定的时间点又被需要时,线程就进入这个不活动的阶段。

  5. 终止:当一个线程完成了它所有的任务,它就死了。

    你可能听说过计算环境中的多任务处理。这个概念指的是共享同一组资源(如 CPU)的几个进程(即软件)。使用多线程,单个程序中的任务被分割成单独的执行线程。

让我们看一个用 Java 演示多线程的实际程序(见清单 5-1 )。

// Make HappyThreadClass inherit methods from Thread-class
// using the keyword "extends"
class HappyThreadClass extends Thread {

public HappyThreadClass(String str)
{
// The super-keyword is used to access data from a parent-class
// (Class "Thread" in this case)
super(str);
}
    // Make the thread do things while executing using the run-method
    public void run() {

    // Iterate each thread four times, displaying each step
    for (int i = 0; i < 4; i++) {
    System.out.println("Step "+ i + " of " + getName());
    }
    System.out.println("Done processing " + getName() + "!");
    }
    }

public class Thread_Demo {
// Add main method
public static void main(String[] args){

// Create and execute three threads
new HappyThreadClass("Thread A").start();
new HappyThreadClass("Thread B").start();
new HappyThreadClass("Thread C").start();
}
}

Listing 5-1A listing in Java demonstrating multithreading

在清单 5-1 的第二行,您会看到一个新的关键字,扩展了。在 Java 中,这个类继承了另一个类的方法和构造函数。在我们的例子中, HappyThreadClass 接收 Thread-class 的功能(在 Java SDK 中称为 Java.lang.Thread )。在后面的清单中,我们将看到一些从 thread 派生的方法,即 getName( )start( ) 方法。前者返回一个线程的名字,而后者执行一个线程。

虽然清单 5-1 的输出是以有序的方式呈现的,但是其中的三个线程是按照多线程的方法并发运行的(参见图 5-2 )。

此外,在连续几次运行清单时,您可能会发现不同的线程以不同的顺序显示它们的输出。对于一个不同步的程序来说,这是非常正常的。

img/507458_1_En_5_Fig2_HTML.jpg

图 5-2

清单 5-1 中程序流程的简化图

CPU 提供的处理能力是一种有限的资源。因此,多个执行线程被分配了优先级,这有助于操作系统对它们的处理进行优先级排序。这是一个基本上自动化的过程,取决于计算机资源的当前状态。

线程同步

尤其是在大型项目中,多线程绝对需要协同工作。幸运的是,Java 非常重视这种方法。线程同步在 Java 的上下文中是指控制多个线程对一个共享资源的访问。为什么要同步呢?如果没有这种方法,几个线程可能会试图同时访问相同的数据。这不利于数据的排序或稳定性。

要以简单的方式在 Java 中实现同步,可以使用语句 synchronized 。这种方法可以在三个层次上使用:静态方法、实例方法和代码块。以清单 5-1 为例。只要稍微修改一下函数声明(即 publicsynchronizedvoid run()),我们就能从程序中获得更有序的输出。

同步块

我们甚至不必同步整个方法;我们可以选择在哪里使用这项技术。同步代码块将一个监视器锁绑定到一个对象。同一对象种类的所有同步块只允许一个线程同时执行它们。

要添加简单的块级同步,可以用 synchronize(this) {...} 。括号带宾语;在我们的例子中,我们使用关键字 this 来指代它所属的(目前不存在的)方法。

论公平与饥饿

按照 Java 的说法,饥饿在一个线程(或一组线程)没有获得足够的 CPU 资源来正确执行时发生,因为在这种情况下,“果汁”被其他更贪婪的线程霸占了。那些宝贵的 CPU 资源的平均分配系统被称为公平。Java 饥饿的主要原因之一实际上在于基于块的同步。处理饥饿的主要方法是通过适当的线程优先级和使用

Java 中的锁

现在,Java 中的每个对象都有一个所谓的锁属性。这种机制只允许那些被明确授予权限的线程访问对象数据。在这样一个线程处理完一个对象后,它执行一个锁释放,这将锁返回到可用状态。在 Java 中,使用锁是一种比基于块的方法更复杂的同步形式(见表 5-1 )。

表 5-1

Java 中基于块的同步和基于锁的同步的主要区别

|   |

块同步

(如同步(this)...)

|

基于锁的同步

|
| --- | --- | --- |
| 公平 | 不支持 | 支持 |
| 等待状态 | 不能被打断 | 可以被打断 |
| 发布 | 自动的 | 指南 |
| 可以查询锁定 | 不 | 是 |

一些基本线程方法一览

现在是时候回顾一下在 Java.lang.Thread 中发现的一些关键方法了(见表 5-2 )。随着我们继续阅读本书,这些方法和许多其他方法将会越来越为你所熟悉。

表 5-2

Java 的 Thread-class (Java.lang.Thread)中的一些方法

|

方法

|

描述

|

示例

|
| --- | --- | --- |
| getName( ) | 返回线程的名称 | System.out.println("我是 a "+getName()); |
| setPriority() | 设置线程的优先级。取 1 到 10 之间的一个数 | orange . set priority(10);peach . set priority(1); |
| getPriority() | 返回线程的优先级 | system . out . println(" Priority for "+getName()+":"+getPriority()); |
| getState( ) | 返回线程的状态 | State state = currentThread()。getState();system . out . println(" Thread:"+current Thread()。getName());system . out . println(" State:"+State); |
| 中断( ) | 停止正在运行的线程 | some class . interrupt(); |
| 中断( ) | 返回线程是否被中断 | if (ShoeFactory.interrupted( ) ) {System.out.println("线程已被中断!");} |
| 当前线程( ) | 返回对当前正在执行的线程的引用 | thread gimme = thread . current thread();Thread niceThread = new Thread(这个,“好线程”);System.out.println("这是"+ gimme +"线程"; |
| 睡眠( ) | 将线程置于睡眠状态,持续特定的毫秒数 | 线程.睡眠(50);// 50 毫秒的休息 thread . sleep(1000);//一秒钟的小睡 |
| 等待( ) | 将线程置于等待状态,直到另一个线程对其执行 notify 方法 | System.out.println("等待..");尝试{wait();} catch(异常 e){ } |
| 通知( ) | 恢复处于等待状态的单个线程 | 已同步(bananaObject) {banana object . notify();处理完成!);} |
| 开始( ) | 执行线程 | 新 FruitThreads(“香蕉”)。start();my class car factory = new my class();car factory . start(); |

Try-Catch 块

为了处理 Java 中的错误,程序员可以实现 try-catch 块。监视 try 块中的代码是否有错误,如果有错误,程序将切换到 catch 块中的代码。这种方法也被称为异常处理。看一下清单 5-2 来看看机械师的动作。

public class TryCatchDemo {
  public static void main(String[ ] args) {
    int happyNumber = 20;

    try {
      // Divide twenty with zero (uh oh..)
      happyNumber /= 0;

      System.out.println(happyNumber);

    } catch (Exception e) {
      // Display our error message
      System.out.println("We have an error.");
    }
  }
}

Listing 5-2A simple demonstration of the try-catch block (i.e., exception handling) in Java

最后:更多有趣的尝试捕捉块

是的,Java 的 try-catch 块带来了更多的乐趣。无论前面的 try-catch 中是否出现异常,最终都会执行一个名为的可选块;这对于确保在任一事件之后运行任何特定的关键操作非常有用。参见清单 5-3 中一个在 Java 中使用 finally 块的简单例子。

public class TryCatchFinally
{
   public static void main (String[] args)
   {
     // Begin a try-catch block
     try {
     // happyArray is allocated to hold five integers
     // (The count starts from zero in Java)
     int happyArray[] = new int[4];

     // Be naughty and assign the value of 14 into
     // a non-existent sixth element in happyArray
     happyArray[5] = 14;
     }
     catch(ArrayIndexOutOfBoundsException e) {
     System.out.println("We encountered a medium-sized issue with an array..");
     }
     // Implement a finally-block which is executed regardless of
     // what happened in the try-catch block
     finally {
     System.out.println("You are reading this in Alan Partridge's voice!");
     }
   }
}

Listing 5-3A Java listing demonstrating a finally block during exception handling

抛出:您的自定义错误消息

为了创建我们自己的异常/错误消息,我们可以在 Java 中使用 throw 语句(参见清单 5-4 )。使用 throw,我们可以得到更详细的错误消息,指出代码中有问题的行。

public class FruitChecker {

  // Create method for checking type of fruit
  static void checkFruit(String fruit) {

    // Display our custom error message if fruit does not equal "Lemon"
    if (!fruit.equals("Lemon")) {
      throw new RuntimeException("Only lemons are accepted.");
    }
    else {
      System.out.println("Lemon detected. Thank you!");
    }
  }

  public static void main(String[] args) {
    checkFruit("Lemon");
    checkFruit("Orange");
  }
}

Listing 5-4A Java listing demonstrating the use of the throw statement

Java 中的异常基本上是程序执行过程中出错的信号。程序员可以实现多种类型的异常。

在清单 5-4 中,术语 RuntimeException 指的是一般类型的错误;我们选择扔掉一个,以防我们不会遇到柠檬。因为我们对“柠檬”和“橙子”都运行了 checkFruit()方法,所以我们既得到了无异常的消息,又得到了有异常的消息。参见清单 5-4 的输出:

检测到柠檬。谢谢大家!

线程“main”Java . lang . runtime 异常:只接受柠檬。

    at FruitChecker.checkFruit(FruitChecker.java:8)
    at FruitChecker.main(FruitChecker.java:17)

接下来让我们看看 Java 中其他一些常见的异常:

  • ArrayIndexOutOfBoundsException:当访问数组中不存在的索引项时(即索引超出了数组的分配大小),抛出该异常。

  • 算术异常:算术运算失败时抛出。

  • ClassNotFoundException :正如你可能猜到的,这是在试图访问一个无处可寻的类时抛出的。

  • NumberFormatException :当方法无法将字符串转换成数字时抛出。

  • NoSuchFieldException :当处理一个类中缺少的变量时抛出。

  • RuntimeException :如清单 5-3 所示,该异常用于程序执行过程中的任何一般性错误。

已检查和未检查的异常

Java 中基本上有两类异常:已检查未检查。前者用于处理程序无法理解的错误,例如文件操作或网络传输的问题。未检查的异常,有时也被称为运行时异常,处理编程逻辑中的问题,例如无效参数和不支持的操作。参见表 5-3 了解这两种异常之间的更详细的差异。

表 5-3

Java 中检查异常和未检查异常的主要区别

|   |

检查异常

|

未检查/运行时异常

|
| --- | --- | --- |
| 主要作用区域 | 外部,程序之外 | 内部,程序内 |
| 例题 | 文件访问或网络连接问题,外部数据库问题 | 有缺陷的程序逻辑;类定义、算术运算或变量转换的错误 |
| 检测 | 在程序编译期间 | 在程序执行期间 |
| 优先级 | 关键:不可忽视。程序将无法执行 | 严重:不应忽视程序将运行,但或多或少仍不稳定 |

我们将在本书的后面更详细地讨论 Java 异常处理。

Java 中的基本文件操作

基本上,文件操作是指读取、写入和删除存储在硬盘或固态硬盘等设备上的数据。为了建立一个准备好文件操作的 Java 项目,我们应该在清单的开头添加下面的包: import java.io.File 。实际上,让我们看看 Java 中一个简单的文件操作程序是什么样子的(见清单 5-5 )。

import java.io.File;

public class JollyFileClass {

  public static void main(String[] args) {
    // Use the imported File class to create a file object, happyfile
    File happyfile = new File("apress_is_great.txt");

    // Begin a try-catch block
    try {
      // Summon the createNewFile-method from File class
      boolean ok = happyfile.createNewFile();
      // ok is a boolean variable, meaning it holds either 'true' or 'false'
      if (ok == true) {
        System.out.println("File " + happyfile.getName() + " created.");
      }
      else {
        System.out.println("The file already exists.");
      }
    }
    // Display error message in the catch-block if necessary
    catch(Exception e) {
    System.out.println("We have an issue..");
    }
  }
}

Listing 5-5A Java listing for creating an empty text file

不仅仅局限于我们在清单 5-5 中检查的方法,java.io.File-package 提供了更多。参见表 5-4 了解我们即将探索的所有方法。此外,表 5-5 列出了一些与时间相关的格式标记,我们将在本章后面深入研究。Java 的格式标记显示解析成特定字符串模式的时间和日期。现在,你知道它们就足够了;它们中的许多起源于类Java . time . format . datetime formatter

表 5-4

java.io.File 提供的 Java 中文件操作的一些常用方法

| createNewFile() | 创建新文件 | 长度( ) | 以字节为单位返回文件的大小 | | mkdir() | 创建新目录 | getAbsolutePath() | 检索文件的绝对路径,例如 C:\Windows\hello.txt | | 删除( ) | 删除文件 | 可以读取( ) | 测试文件是否可读 | | 存在( ) | 测试文件是否存在 | canWrite() | 测试文件是否可写 |

用 Java 创建文本文件

言归正传,我们接下来做一个文本文件,好吗?参见清单 5-6 了解我们下一剂 Java 文件操作的良药。

import java.io.*;

 public class Main {
   public static void main(String args[]) {

   // Define a string we intend to write into a file
   String data = "Apress rules, OK?";

     try {
       // Create a Writer object using the FileWriter-class
       FileWriter writer1 = new FileWriter("happyfile.txt");
       // Writes a message to the file
       writer1.write(data);
       // Closes the writer
       writer1.close();
       // Open the created file
       File file1 = new File("happyfile.txt");
       // Create a new file input stream, "fileinput1"
       FileInputStream fileinput1 = new FileInputStream(file1);

System.out.println("The file " + file1.getName() + " says the following:");

int counter=0;
// Use a while-loop to display every text character in "happyfile.txt"
// Break out of "while" when encountering "-1", which signals the
// end of the file
while((counter = fileinput1.read())!=-1)
{
System.out.print((char)counter);
}
     // Close file input stream
     fileinput1.close();
     }
     catch (Exception e) {
       e.getStackTrace();
     }
  }
}

Listing 5-6A Java program demonstrating text-file creation and access

在第二个清单中,您将看到大量新的、可能令人恐惧的机制。我们现在以平静的方式一个接一个地看一遍。首先,*导入 Java . io . ;清单 5-6 中的提供了对 java.io 包中所有可用类的访问。

接下来,我们有 Filewriter 类。正如您所料,这个类提供了写入数据和创建新文件的方法。我们从 Filewriter 实例化一个对象,将其命名为 happyfile.txt.

在清单 5-6 中,我们为我们的文件提供了一个文件名和一个目录路径happyfile.txt 。带有字母 C 的部分指的是 Windows 根目录驱动器的最常见选择。您可以在在线编程环境中运行这个列表。然而,如果你使用硬盘,你不太可能在硬盘上写任何东西。

沿着清单向下,我们从 Filewriter 类调用 write 方法,将来自字符串数据的消息传递给它。我们的 happyfile.txt 现在包含了一条基于文本的消息。接下来,我们执行 close 方法;需要这样做,以便后续方法能够访问 happyfile.txt。

现在,我们再次打开我们的文件,这次是为了在屏幕上打印它的内容。我们首先使用 Java 的 File 类实例化一个对象,即File File 1 = new File(" c:\ \ happy File . txt ")。行file inputstream file input 1 = new file inputstream(file 1)让我们能够访问 Java 文件输入流类,以一个字节为增量提供文件读取功能。在我们的例子中,这意味着一次读取和显示一个文本文件(即 happyfile.txt)。为此,我们实现了一个 while 循环,它使用了 FileInputStream 类中的一个方法(例如, read )。

我们打印 happyfile.txt 的内容(同样,按照 FileInputStream 文件访问的实现方式,一次打印一个字符)。一个负一(-1)的值表示文件结束,中断了 while 循环。在这之后,就是清理时间了。我们关闭文件并退出程序。

Java 文件操作的更多冒险

让我们做一些更多的文件操作。不如我们查询文件属性,比如文件大小,并尝试删除一些文件(参见清单 5-7 )。

import java.io.*;

 public class Main2 {
   public static void main(String args[]) {
     // Open file for access
     File file1 = new File("c:\\happyfile.txt");
     // Begin try-catch block
     try {
       // First check if file exists
       if (file1.exists()) {
       // If so, display confirmation of existence
       System.out.println("The file " + file1.getName() + " exists!");
       // Summon length()-method and display file size
       System.out.println("This file is " + file1.length() + " bytes in size");
       // Display notification if the file is over 10 bytes/characters in size
       if(file1.length()>10) System.out.println("That's a lot of data!");
       // Display read/write information
       if (file1.canRead()) System.out.println("This file is readable.");
       if (file1.canWrite()) System.out.println("This file is writable.");
       System.out.println("Deleting " + file1.getName() + "...");
       // Summon delete()-method on file1
       file1.delete();
            // If file does not exist, display notification
       } else System.out.println("Can't find file. Nothing will be deleted.");
     }
     catch (Exception e) {
       e.getStackTrace();
     }
  }
}

Listing 5-7A program demonstrating file attribute query and deletion in Java

从清单 5-7 中可以看出,用 Java 实现基本的文件操作相当简单。但是有一些手术我们还是应该试验一下。我们使用第二种类型的文件流,并创建一个全新的目录,怎么样?(参见清单 5-8 。)

import java.io.*;
class FileStreamTesting {
   public static void main(String args[]) {
      // Begin try-catch block
      try {
      // Define a string we use as a name for a directory
      String directoryName = "HappyDirectory";
      File dir1 = new File(directoryName);
      // Create new directory into the Windows root-folder, C:
      dir1.mkdir();
      // Create an array of six characters, Publisher
      char Publisher[] = {'A','p','r','e','s','s'};
      // Instantiate an object from the OutputStream class, stream1
OutputStream stream1= new FileOutputStream("c://HappyDirectory//publisher.txt");
         for(int x = 0; x < Publisher.length ; x++) {
            // Write each character from our array into the file
            stream1.write(Publisher[x]);
         }
         stream1.close();

      } catch (IOException e) {
         System.out.print("An error has occurred.");
      }
   }
}

Listing 5-8A listing in Java demonstrating the use of file streams and directories

最后两个清单使用了 Java 的文件流类。清单 5-7 使用了 InputStream 类中的一个方法在屏幕上打印文本。在清单 5-8 中,我们有一个 Java 的 OutputStream 实例将一个由六个字符组成的数组(即 Publisher[ ] 的内容)写入一个文件。

在指定目录位置时,两个清单有不同的符号:在清单 5-7 中,我们使用反斜杠(即 c:\happyfile.txt )来表示目录路径,而在清单 5-8 中,我们使用正斜杠,如 c://HappyDirectory 。这两种方法都可以在 Java 中使用。

Java 约会

让我们面对现实吧:在这个世界上记录时间是相当重要的。没有理由不在 Java 中这样做。幸运的是,有一个很棒的类,叫做 LocalDate 。此外,Java . time . format . datetime formatter类用于按照程序员定义的定制格式显示日期。(我们在表 5-5 中遇到了这些格式标记)。清单 5-9 向您展示了这是如何完成的。

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
public class HappyDateDemo {
   public static void main(String args[]) {
        // Create an object using the Java LocalDate-class
        LocalDate happyDate = LocalDate.now();
        LocalDate fortnite = happyDate.minusDays(14);
        LocalDate tomorrow = happyDate.plusDays(14);
        System.out.println("Today's date is " + happyDate);
        System.out.println("Two weeks ago it was " + fortnite);
        System.out.println("Two weeks into the future it'll be.. " + tomorrow);
        // Create and display three different formatting patterns
        System.out.println("Today's date in format A: "+happyDate.format(DateTimeFormatter.ofPattern("d.M.uuuu")));
        System.out.println("Today's date in format B: "+happyDate.format(DateTimeFormatter.ofPattern("MMM d uuuu")));
        System.out.println("Today's date in format C: "+happyDate.format(DateTimeFormatter.ofPattern("d.MMM.uu")));
   }
}

Listing 5-9A program demonstrating date retrieval and formatting in Java

表 5-5

一些时间模式–来自 Java . time . format . datetime formatter 的格式化标记

| *d/d*d | 一月中的某一天 | *W* | 一个月中的第几周(0–5) | | *毫米* | 月 | *K* | 一天中的小时数(1–24) | | uuuu */uu* | 年份(完整或最后两位数字) | *K* | 一天中的小时,上午/下午(0–11) | | *w* | 一年中的一周(1–53) | *E* | 一周中的某一天(如星期一) |

更多关于约会:闰年和时间旅行

让我们探索更多 Java 的约会选项。我们不仅可以以一天为单位在时间上来回旅行;月和年也由我们支配。请参见清单 5-10 中这两个实例。此外,我们将探索如何在 Java 中发现闰年。

import java.time.LocalDate;
import java.time.LocalTime;

public class TimeTravel {
public static void main(String[] args) {
// Store current date into rightnow
LocalDate rightnow = LocalDate.now();
System.out.println("This is thirty weeks into the future: " + rightnow.plusWeeks(30));
System.out.println("This is fifty months into the past: " + rightnow.minusMonths(50));
System.out.println("This is 56 years into the past: " + rightnow.minusYears(56));
System.out.println("Whoa! This is 373 years into the future: " + rightnow.plusYears(373));
// Store a future date into futuredate1
LocalDate futuredate1 = LocalDate.of(2028,1,1);
// Store a past date into pastdate1
LocalDate pastdate1 = LocalDate.of(1728,1,1);
// Check for leap years
System.out.println("\nWas the year " + pastdate1.getYear() + " a leap year? " + pastdate1.isLeapYear());
System.out.println("Is the year " + rightnow.getYear() + " a leap year? " + rightnow.isLeapYear());
System.out.println("Is the year " + futuredate1.getYear() + " a leap year? " + futuredate1.isLeapYear());
     }
}

Listing 5-10A listing demonstrating the use of some of the methods in the LocalDate class in Java

Java 日历

当谈到 Java 的时间旅行时,有许多方法,特别是在公历中,公历仍然是自 1582 年以来最流行的组织我们日子的系统。我们可以很容易地获取过去和未来的日期,如清单 5-11 所示。

import java.util.Calendar;

public class HappyCalendar {

   public static void main(String[] args) {
   // Create an object using the Calendar-class
   Calendar calendar1 = Calendar.getInstance();
   // Display current date
   System.out.println("Current date: " + calendar1.getTime());
   // Take a week out of the date and display it
   calendar1.add(Calendar.DATE, -7);
   System.out.println("A week ago it was: " + calendar1.getTime());
   // Fast forward two months
  calendar1.add(Calendar.MONTH, 2);
  System.out.println("Two months into the future: " + calendar1.getTime());
   // Go ten years into the future
  calendar1.add(Calendar.YEAR, 10);
  System.out.println("Ten years into the future: " + calendar1.getTime());
  // Go a century back into the past
  calendar1.add(Calendar.YEAR, -100);
  System.out.println("A century ago: " + calendar1.getTime());
   }
 }

Listing 5-11A listing demonstrating the use of the Calendar

class in Java

自定义您的日历

基于 Java 的日历的乐趣不会停留在时间旅行上。让我们把事情变得真正有趣,创建我们自己的日期格式符号,即工作日和月份。为此,我们正在召唤一些令人兴奋的新方法和一个新类, Locale (参见清单 5-12 )。您会注意到这个清单导入了 java.util.Date 类,这是一个所谓的过时类,意味着它有一个更新的版本(在本例中是 java.util.LocalDate)。然而,在您的编码冒险中,您可能会不时地遇到这些更具历史意义的类。

import java.text.DateFormatSymbols;
import java.util.Date;
import java.util.Locale;
import java.text.SimpleDateFormat;

public class SassyCalendar {

public static void main(String [] args){
// Create a new object from class Locale
Locale locale1 = new Locale("en", "US");
// Create an object for a new set of date format symbols
DateFormatSymbols sassysymbols = new DateFormatSymbols(locale1);

// Rename days of the week using setWeekdays() method
sassysymbols.setWeekdays(new String[]{
        "",
        "Superior Sunday",
        "Mega Monday",
        "Tepid Tuesday",
        "Wonderful Wednesday",
        "Tedious Thursday",
        "Fun Friday",
        "Serious Saturday",
});
// Rename months using setMonths() method
sassysymbols.setMonths(new String[]{
        "",
        "Jolly January", "Fabulous February",
        "Marvelous March", "Amazing April",
        "Joyous June", "Jubilant July",
        "Affable August", "Silly September",
        "Odorous October", "Nasty November",
        "Delightful December",
});
// Choose a formatting pattern
String pattern = "EEEEE', in 'MMMMM yyyy'!'";
// Apply this pattern on our object, sassysymbols
SimpleDateFormat format1 = new SimpleDateFormat(pattern, sassysymbols);
// Create a new object using Date-class
String sassydate = format1.format(new Date());
System.out.println("Today is " + sassydate);
     }
}

Listing 5-12A Java listing demonstrating customized date symbol creation for a particular locale

在清单 5-12 中,我们迎合了特定的地区,美国,选择英语作为使用“US”和“en”的语言。我们将在本章的后面更深入地探讨本地化。

现在,方法 setWeekdays( )setMonths( ) 都接受字符串数组,这些字符串数组用于覆盖默认的格式符号(例如,星期一、一月等)。).这些数组中的第一项故意留空,因为它不考虑这些方法的工作。

谈到清单 5-12 中的格式模式,我们使用 E 来表示一周中的某天(现在已经修改过了),用 M 来表示月份,用 y 来表示年份。我们还在单引号内添加了一些空格和其他标点符号(例如,'!')。

如果我们在 2021 年 6 月 29 日运行清单 5-12 ,我们将得到以下输出:

今天是不温不火的星期二,在快乐的 2021 年 6 月!

Java 的国际化和本地化

国际化是指设计软件,使其无需大修即可本地化;本地化是为特定语言和/或地区创建内容的行为。就国际化而言,Java 是一种很棒的编程语言,它提供了相当动态地本地化日期、货币和数字的方法。

术语“国际化”输入起来可能有点麻烦。因此,软件人员有时更喜欢缩写 i18n 而不是本地化,另一方面,在知情人士中被称为 L10n 。你可能已经猜到了,这些节略中的数字代表了前面提到的两个术语中的字母数量。

清单 5-12 中简单提到的 Java Locale 类提供了本地化我们项目的选项。它让程序员有机会呈现本地化的消息或程序的其他部分。Locale 类用于标识对象,而不是对象本身的容器。

Locale 类有三个维度:语言国家变体。虽然前两个是不言自明的,但 variant 用于表示程序运行的操作系统的类型和/或版本。

现在,在 Java 中,一个应用程序实际上可以同时有几个活动的语言环境。例如,可以组合意大利日期格式和日本数字格式。因此,Java 是创建真正多文化应用程序的好选择。让我们看看清单 5-13 中关于如何在 Java 中使用本地化的演示。

import java.util.Locale;

public class LocalizationDemo {
 public static void main(String[] args) {

    // Create a generic English-speaking locale
    Locale locale1 = new Locale("en");
    // Create an English-speaking locale set in the UK
    Locale locale2 = new Locale("en", "UK");
    // Create a Finnish-speaking, Indian locale in Mumbai
    Locale locale3 = new Locale("fi", "IN", "Mumbai");

   System.out.println("Locale 1\. " + locale1.getDisplayName());
   System.out.println("Locale 2\. " + locale2.getDisplayName());
   System.out.println("Locale 3\. " + locale3.getDisplayName());

   // Retrieve user's current operating system locale information by creating
   // another instance of Locale-class and summoning the getDefault() method
   Locale yourLocale = Locale.getDefault();

  System.out.println("OS language: " + yourLocale.getDisplayLanguage());
  System.out.println("OS country: " + yourLocale.getDisplayCountry());
 }
}

Listing 5-13A listing demonstrating the three main dimensions of locales in Java (i.e., language, country, and variant)

在清单 5-13 中,我们首先创建三个不同的 locale 对象,并使用 Locale 类中的 getDisplayName( ) 方法显示它们的一些内容。

我们还调用了第四个对象来检查用户操作系统的地区设置。首先, getDefault( ) 方法用于检索这些数据,并将其插入到我们命名为 yourLocale 的对象中。然后,执行另外两个方法,即 getDisplayLanguage( )getDisplayCountry( ) ,来显示我们感兴趣的信息。

最后

读完这一章,你将有望学到以下内容:

  • 如何用 Java 打开、创建和删除文件

  • 多线程指的是什么以及它是如何在 Java 中实现的

  • 如何在 Java 中实现基本同步

  • try-catch 块的操作

  • 异常处理意味着什么以及如何实现

  • 检查异常和未检查异常的区别

  • Java 中日期相关类的一些基本用法

  • 基于 Java 的本地化基础

第六章将提供一些确实完全不同的东西;我们将探索强大的 Python 语言的更高级的功能。

六、现在是完全不同的东西:高级 Python

在前一章中我们已经深入了解了 Java 的世界,现在是时候用 Python 做同样的事情了。我们将从文件操作开始,转到多线程和本章后面的其他更高级的主题。这里的目的是为您提供 Python 中一些更深层机制的坚实基础,以便您以后根据需要进行构建。

Python 文件操作

为了在 Python 中打开文件,我们使用了名副其实的 open( ) 函数。它使用以下语法:【文件对象名=打开(文件名,访问模式,缓冲)。最后两个属性是可选的。Python 的 open()函数的一个简单示例如下:

happyfile = open("happytext.txt")

现在,Python 使用所谓的访问模式进行文件操作。不是所有的文件都需要写入或附加到文件中,有时你需要做的只是从一个文件中读取。Python 的文件访问模式列表见表 6-1 。

表 6-1

十二种 Python 文件访问模式

| r | 以只读模式打开文件。这是 Python 中的默认访问模式 | w+ | 以读/写模式打开文件;如果文件已经存在,则覆盖它 | | 元素铷的符号 | 以只读二进制模式打开文件 | wb+ | 以读/写二进制模式打开文件;如果文件已经存在,则覆盖它 | | r+ | 以读/写模式打开文件 | a | 打开要追加到的文件。如果文件不存在,则创建一个 | | rb+ | 以读/写二进制模式打开文件 | 腹肌 | 打开一个二进制文件以追加到。如果文件不存在,则创建一个 | | w | 以只写模式打开文件;如果文件已经存在,则覆盖它 | a+ | 打开文件进行追加和读取。如果文件不存在,则创建一个 | | 世界银行 | 以只写二进制模式打开文件;如果文件已经存在,则覆盖它 | ab+ | 以二进制模式打开文件进行追加和读取。如果文件不存在,则创建一个 |

缓冲在 Python 的文件操作上下文中,指的是将文件的一部分存储在临时内存区域,直到文件被完全加载的过程。基本上,零(0)值关闭缓冲,而一(1)值启用缓冲,例如, somefile = open("nobuffer.txt "," r ",1) 。如果没有给定值,Python 将使用系统的默认设置。通常保持缓冲是一个好主意,这样可以提高文件操作的速度。

Python 中的文件属性

Python 中基本上有四个主要的文件属性(其中一个大部分是历史感兴趣的,即 softspace )。如果您的项目包含哪怕是最少量的文件操作,您可能会对它们都非常熟悉。参见表 6-2 了解 Python 中这些属性的概要。

表 6-2

四个 Python 文件对象属性

|

属性

|

描述

|

例子

|
| --- | --- | --- |
| 。名字 | 返回一个文件名 | happyfile = open("happytext.txt")打印(happyfile.name) |
| 。方式 | 返回文件访问模式 | another file = open(" whacky text . txt ")打印(另一个文件.模式) |
| 。关闭的 | 如果文件关闭,返回“真” | apess _ file = open(" super text . txt ")apress_file.close()如果打印(apress_file.closed):print("文件关闭!") |
| 。软空间 | 如果 print 语句要在第一项前插入一个空格字符,则返回“false”。历史:从 Python 3.0 开始过时 | file5 = open("jollyfile.txt ")print("软空间集?"文件 5 .软空间) |

实用文件存取

文件需要在 Python 中保持打开状态才能被操作;设置为关闭的文件无法写入或检查其属性。参见清单 6-1 中关于文件访问以及如何用 Python 读取文件属性的演示。

# Import a special module, time, for the sleep() method
import time
file1 = open("apress.txt", "wb") # Create/open file
print("File-name: ", file1.name) # Start reading file attributes
print("Opening mode: ", file1.mode)
print("Is file closed? ", file1.closed)
time.sleep(1) # Sleep/delay for one second, for the suspense
file1.close() # Close file
print("Now we closed the file..");
time.sleep(1.5) # Sleep/delay for 1.5 seconds
print("File is closed now? ", file1.closed)

Listing 6-1A listing in Python demonstrating some basic file operations

Python 中的目录操作

目录或文件夹是任何操作系统的文件管理系统的重要组成部分;Python 也提供了处理它们的方法。接下来让我们看看一些目录操作(参见清单 6-2 )。

import os # import the os module for directory operations
print("Current directory:", os.getcwd()) # Print current directory using getcwd()
print("List of files:", os.listdir()) # List files in directory using listdir()
os.chdir('C:\\') # Change to the (most common) Windows root-directory
print("New directory location:", os.getcwd()) # Print current directory again

print("Let's make a new directory and call it JollyDirectory")
os.mkdir('JollyDirectory') # Make a new directory using mkdir()
print("List of files in JollyDirectory:", os.listdir('JollyDirectory'))

Listing 6-2A listing in Python demonstrating directory operations

至于重命名目录,我们会使用 os.rename("Somedirectory "," Newname") 。一旦一个目录不再需要,并且首先是空的文件,只需要OS . rmdir(" some directory ")就可以删除它。

文件名模式匹配

我们有办法在 Python 中找到匹配特定命名模式的文件。从清单 6-3 中可以明显看出,这些实现起来相当简单。

import os
import fnmatch

# Display all files in Windows root with .txt/.rtf extension
for filename in os.listdir('C:\\'):
     if filename.endswith('.txt') or filename.endswith('.rtf'):
         print(filename)
# Display all files with .txt extension starting with 'a'
for filename in os.listdir('C:\\'):
     if fnmatch.fnmatch(filename, 'a*.txt'):
        print(filename)

Listing 6-3A listing in Python demonstrating file name pattern matching

在清单 6-3 中,我们引入了两个方便的函数来定位具有特定命名约定的文件,即 endswith()fnmatch() 。后者提供了所谓的基于通配符的 Python 项目搜索(例如,文件)。txt* 还是????name.txt )。

用 Glob 搜索文件?

术语 globbing 指的是在特定目录(或者 Python 的当前工作目录,如果没有指定的话)中执行高度精确的文件搜索。术语 glob ,是 global 的缩写,起源于基于 Unix 的操作系统世界。在清单 6-4 中,您将看到这种方法得到了很好的应用。

import glob
# Using * pattern
print("\nGlobbing with wildcard pattern * (*.py)")
for name in glob.glob("*.py"):
    print(name)
# Using ? pattern
print("\nGlobbing with wildcard ? and * (??????.*)")
for name in glob.glob("??????.*"):
    print(name)
# Using [0-9] pattern
print("\nGlobbing with wildcard range [0-9]")
for name in glob.glob("*[0-9].*"):
    print(name)
# Using [b-x] pattern
print("\nGlobbing with wildcard range [b-x]")
for name in glob.glob("*[b-x].*"):
    print(name)

Listing 6-4A listing in Python demonstrating file searches by globbing

Python 中的日期

您可能还记得上一章中用 Java 显示时间和日历数据的强大工具。Python 在计时方面也提供了同样多的功能。这些功能驻留在 datetime 模块中(参见清单 6-5 )。

import datetime
from datetime import timedelta

time1 = datetime.datetime.now() # Create a datetime-object for right now
print("The time is:", time1) # Display current time unformatted
# Display formatted day and month (%A for day of the week, %B for month)
print("In other words it's a", time1.strftime("%A in %B"))
# Reduce time1.year-variable by ten
print("Ten years ago it was", time1.year-10)
# Use timedelta to move thirty days into the future
futuredate = datetime.timedelta(days=30)
futuredate += time1 # Add the current date to futuredate
print("Thirty days into the future it'll be", futuredate.strftime("%B"))

Listing 6-5A listing demonstrating some time and calendar functions in Python

虽然清单 6-5 相当简单,但我们应该更好地看看 Python 中的一些格式标记,以满足所有与日期相关的需求(见表 6-3 )。

表 6-3

Python 中一些常见的日期格式标记

| %A | 一周中的一整天(例如,星期一) | %B | 月份的全名(例如,三月) | | %a | 一周中较短的一天(例如,星期一) | %b | 月份的简称(例如,Mar) | | %Z | 时区(例如,UTC) | %H | 24 小时制(例如,18) | | %p | 上午/下午 | %I | 小时,12 小时制 |

正则表达式的威严

一个正则表达式,通常简称为 RegEx ,是一个字符序列,构成了字符串的搜索模式。导入一个简单的叫做 re 的代码模块允许我们在 Python 中执行正则表达式工作。您可以使用正则表达式来查找具有特定搜索模式的文件,以及在众多类型的文本文件中查找特定的术语。参见清单 6-6 首次演示它们是如何工作的。

import re
text1 = "Apress is the best publisher"
regex1 = re.findall("es", text1) # Create a RegEx-object with search-pattern
print("Looking for all instances of 'es'")
print("We found", len(regex1), "matches in", text1)

Listing 6-6A simple example of using regular expressions in Python

在清单 6-6 中,我们调用 findall 方法在字符串变量 text1 中寻找“es”的实例。我们还使用 Python 的 len()方法对存储在 list regex1 中的实例进行计数。现在,是时候看看 RegEx 魔术的另一个演示了(见清单 6-7 )。

正则表达式实际上可以追溯到 1951 年,当时是由美国数学家斯蒂芬·科尔·克莱尼(1909–1994)提出的。如今,正则表达式是许多流行的编程语言中的主要部分,包括 Perl、C# 和 Java。后两种语言中的正则表达式实现将在本书的后面部分探讨。

import re
# Summon search-method from the regex module, re
match1 = re.search('Apress', 'Apress is the best')
if match1: # This is shorthand for "if match1 == True:"
    print(match1) # Display object contents if match found

happytext = "My name is Jimmy" # Create a string variable
match2 = re.search('Jimmy', happytext)
if match2:
    print(match2)

# Use fullmatch-method from re on string "happytext"
match3 = re.fullmatch('Jimmy', happytext)
if match3:
    print("Match found for 'Jimmy'!") # This message will not display
else:
    print("No Match for 'Jimmy'")

match3 = re.fullmatch('My name is Jimmy', happytext)
if match3:
    print(match3)

# Use match-method
match4 = re.match('the', 'Apress is the best')
if match4:
    print(match4) # This message will not display
    # match() only looks for patterns from the beginning of a string
else:
    print("No Match for 'the'")
match5 = re.match('Apress', 'Apress is the best')
if match5:
    print(match5) # This message will display

Listing 6-7A listing in Python demonstrating search( ) and fullmatch( ) RegEx methods in Python

在清单 6-7 中,我们使用了 search()、match()和 fullmatch()。你可能会问它们之间有什么区别。search 方法遍历给定模式的整个字符串,而 fullmatch 仅在字符串完全反映模式时返回 true。match 方法只查找字符串开头的模式。

元字符

正则表达式最好与元字符一起使用。这些基本上是更高级的字符串相关搜索的构建块。参见表 6-4 中一些重要元字符的概要,以及清单 6-8 中它们用法的一点演示。

表 6-4

一些重要的 Python 元字符

| \w | 任何消息。通常指字母数字 | \s | 空白 | | \W | 任何非单词 | \S | 非空白 | | \d | 任何数字 | 。 | 任何单个字符 | | \D | 任何非数字 | * | 零个或多个字符 |
import re
match1 = re.search('.....', 'Hello there!')
if match1: # This is shorthand for "if match1 == True:"
    print(match1) # Displays "Hello"

match2 = re.search('\d..', 'ABC123')
if match2:
    print(match2) # Displays "123"

match3 = re.search('\D*', 'My name is Reginald123456.')
if match3:
    print(match3) # Displays "My name is Reginald"

match4 = re.search('y *\w*', 'Hello. How are you?')
if match4:
    print(match4) # Displays "you"

match5 = re.search('\S+', 'Hello. Whats up?')
if match5:
    print(match5) # Displays "Hello."

Listing 6-8A listing in Python demonstrating the use of metacharacters in regular expressions

在清单 6-8 中,对于 match4 ,我们使用元字符 \w ,它指的是寻找任何完整单词的匹配。如果没有这个字符,我们会看到输出“y”而不是“you”

让我们学习更多关于 Python 中元字符的知识。参见表 6-5 中的八个更重要的正则表达式标记,以及清单 6-9 中的第二个演示。

表 6-5

一些更重要的 Python 元字符

| \. | 文字点(例如,句号字符) | $ | 匹配行尾 | | ? | 零或一个字符 | { n } | 出现 n 次 | | + | 一个或多个字符 | [a-z] | 字符集 | | ^ | 匹配行首 | [0-9] | 数字字符集 |
import re
string1 = 'Beezow Doo-doo Zopittybop-bop-bop'
patterns = [r'Do*', # D and zero or more o's (*)
            r'Be+', # B and one or more e's (+)
            r'Do?', # D and zero or one o's (?)
            r'it{2}', # i and two t's
            r'[BDZ]\w*', # Look for full words starting with B, D, or Z
            r'^Be\w*', # Look for a full word starting with "Be"
            r'...$' # Look for the three last digits in the string
            ]
def discover_patterns(patterns, string1): # Create our method
    for pattern in patterns:
        newpattern = re.compile(pattern) # Summon compile()
        print('Looking for {} in'.format(pattern), string1)
        print(re.findall(newpattern, string1)) # Summon findall()

discover_patterns(patterns, string1) # Execute our method

Listing 6-9Another listing in Python demonstrating the use of metacharacters in regular expressions

清单 6-9 中的列表结构模式包含七个搜索,而字符串 1 存储我们的源材料。这两个数据结构将被输入到我们接下来创建的方法 discover_patterns 中。

在我们的这个新方法中,我们使用了 Python 的两个 RegEx 函数: compile( )findall( ) 。对于前者,我们将 RegEx 模式转换成模式对象,然后用于模式匹配。这种方法在重复使用搜索模式的情况下最为有效,比如数据库访问。

Findall 用于发现字符串中搜索模式的所有匹配项。清单中的 r' 让 Python 知道一个字符串被认为是“原始字符串”,这意味着其中的反斜杠将在没有特殊函数的情况下被解释。例如, \n 不会表示原始字符串中的换行符。

正则表达式带来更多欢乐

接下来让我们探索 RegEx 的更多高级特性。清单 6-10 展示了两个新方法的使用: group( )sub( ) (为了方便起见,以粗体显示)。此外,我们将和我们的老朋友在 RegEx 中使用一项新技术 search()。

import re
string1 = "Today's dessert: banana"
# Summon search() with four options for a match
choice1 = re.search(r"dessert.*(noni-fruit|banana|cake|toilet-paper)", string1)
if choice1:
    print("You'll be having this for", choice1.group(), "!")

string2 = "Have a great day"
string2 = re.sub('great', 'wonderful', string2)
print(string2) # Outputs: Have a wonderful day

string3 = 'what is going on?'
# Replace all letters between a and h with a capital X
string3 = re.sub('([a-h])s*', 'X', string3)
print(string3) # Outputs: wXXt is XoinX on?

Listing 6-10A listing in Python demonstrating the sub( ) method

清单 6-10 中的搜索方法用于比较和定位现在作为方法参数列出的字符串。换句话说,在字符串 1 中一共搜索了四种水果。它们由逻辑 or 运算符分隔,用竖线字符表示(即|)。这个方法是用来寻找这些字符串/水果的,但是它们只应该出现在字符串“dessert”的旁边。如果 string1 和 choice1 中的一个条目匹配,程序就会显示出来。

Python 中的并发和并行

并行处理是指同时执行一个以上的计算或指令。你可能还记得上一章中多线程的概念。像 Java 和 C# 一样,Python 能够处理多个执行线程。然而,还是有一些主要的区别。Python 的多线程实际上并不是以并行的方式运行线程。相反,它伪并发地执行这些线程。这源于 Python 实现了一个全局解释器锁(GIL) 。该机制用于同步线程,并确保整个 Python 项目仅在单个 CPU 上执行;它只是没有利用多核处理器的全部魅力。

虽然并发并行是相关的术语,但它们不是同一个术语。前者指的是同时运行独立任务的方法,而后者是将一个任务分成子任务,然后同时执行。

多重处理与多线程

Python 中线程的实际并行处理是通过使用多个进程实现的,所有进程都有自己的解释器和 GIL。这被称为多重处理

Python 对并发性的理解可能有点复杂(比如说,与 Java 相比)。现在,Python 中的进程不同于线程。尽管两者都是独立的代码执行序列,但还是有一些不同之处。一个进程往往比一个线程使用更多的系统内存。流程的生命周期通常也更难管理;它的创建和删除需要更多的资源。螺纹和工艺对比见表 6-6 。

表 6-6

Python 中线程和进程的主要区别(例如,多重处理和多线程)

|   |

过程

|

线

|
| --- | --- | --- |
| 使用单个全局解释器锁(GIL) | 不 | 是 |
| 多个 CPU 内核和/或 CPU | 支持 | 不支持 |
| 代码复杂性 | 不太复杂 | 更复杂 |
| RAM 占用空间 | 巨大的 | 驳船 |
| 可以被打断/杀死 | 是 | 不 |
| 最适合 | CPU 密集型应用、3D 渲染、科学建模、数据挖掘、加密货币 | 用户界面、网络应用 |

Python 有三个用于同时处理的代码模块:多处理、异步和线程。对于 CPU 密集型任务,多处理模块工作得最好。

用 Python 实现多线程

正如本章前面所讨论的,Python 中的多线程与全局解释器锁(GIL)机制密切相关。这种方法利用了互斥的原理(见图 6-1 )。由于 Python 多线程中的调度是由操作系统完成的,所以一些开销(即延迟)是不可避免的;这是多重处理通常不会遇到的问题。

img/507458_1_En_6_Fig1_HTML.jpg

图 6-1

使用三个线程的 Python 多线程的部分可视化

是时候实际一点了。让我们看看多线程是如何在 Python 中实现的(参见清单 6-11 )。

import threading
def happy_multiply(num, num2):
    print("Multiply", num, "with", num2, "=", (num * num2))
def happy_divide(num, num2):
    print("Divide", num, "with", num2, "=", (num / num2))

if __name__ == "__main__":
    # Create two threads
    thread1 = threading.Thread(target=happy_multiply, args=(10,2))
    thread2 = threading.Thread(target=happy_divide, args=(10,2))
    # Start threads..
    thread1.start()
    thread1.join() # ..and make sure thread 1 is fully executed
    thread2.start() # before we start thread 2
    thread2.join()
    print("All done!")

Listing 6-11A listing in Python demonstrating elementary use of the threading module

我们在清单 6-11 中创建了两个函数,分别是 happy_multiplyhappy_divide 。每个都有两个参数。对于前者,我们用 num 乘以 num2 ,而后者用 num 除以 num2 。然后结果被简单地打印在屏幕上。

现在,清单 6-11 中有一个函数您应该密切关注;Python 的 join( ) 方法确保线程在继续处理清单之前已经完全完成了处理。如果您删除了行 thread1.join( ) ,清单 6-4 的输出将会是一片混乱。

并发处理环境中的竞争条件是指两个或多个进程同时修改资源(如文件)的场景。最终结果取决于哪个进程先到达那里。这被认为是不理想的情况。

在 Python 中实现多重处理

img/507458_1_En_6_Fig2_HTML.jpg

图 6-2

使用三个进程的 Python 多重处理的部分可视化

尽管多处理方法通常会产生 CPU 效率高的代码,但仍然会出现一些小的开销问题。如果 Python 中的多处理项目有这些问题,它们通常发生在进程的初始化或终止期间(见图 6-2 )。

现在,为了简单演示 Python 的多处理模块,请参见清单 6-12 。

import time
import multiprocessing

def counter(): # Define a function, counter()
    name = multiprocessing.current_process().name
    print (name, "appears!")

    for i in range(3):
        time.sleep(1) # Delay for one second for dramatic effect
        print (name, i+1,"/ 3")

if __name__ == '__main__': # Define this listing as "main", the one to execute
    counter1 = multiprocessing.Process(name='Counter A', target=counter)
    counter2 = multiprocessing.Process(name='Counter B', target=counter)
    counter3 = multiprocessing.Process(target=counter) # No name given..

    counter1.start()
    counter2.start()
    counter3.start() # This nameless counter simply outputs "Process-3"

Listing 6-12A listing in Python demonstrating elementary use of the multiprocessing module

Python 中的每个进程都有一个名称变量,如清单 6-12 所示。如果没有定义,将自动分配一个通用标签进程-[进程号]

迭代器、生成器和协同程序

Python 中有三种重要的数据处理机制,它们有时会混淆: iterables、生成器协程列表(例如 happyList[0,1,2] )是简单的可迭代列表。它们可以根据需要随时阅读,不会出现任何问题。列表中的所有值都会保留,直到明确标记为删除。生成器是迭代器,只能被访问一次,因为它们不在内存中存储内容。生成器的创建类似于 Python 中的常用函数,只是它们用关键字 yield 代替了 return。

当读取大文件的属性时,比如它们的行数,生成器是很有用的。用于这种用途的生成器将产生不是最新的数据,因此不需要,从而避免内存错误并使 Python 程序更有效地运行。

协程是一种独特的函数类型,它可以将控制权让给调用函数,而无需结束它们在进程中的上下文;协程在后台平稳地维护它们的空闲状态。换句话说,它们被用于合作的多任务处理。

阿辛西奥:出类拔萃的那个?

Asyncio 是异步输入/输出的缩写,是 Python 中为编写并发运行代码而设计的模块。尽管与线程和多处理有相似之处,但它实际上代表了一种不同的方法,称为协同多任务处理。asyncio 模块使用在单线程中运行的单个进程提供了一种伪并发性。

在 Python 中使用异步代码意味着什么?用这种方法编写的代码可以安全地暂停,以便项目中的其他代码片段执行它们的任务。异步进程为其他进程提供停机时间来运行它们的进程;这就是如何使用 asyncio 模块实现一种类型的并发性。

异步事件循环

异步 Python 项目的主要组件是事件循环;我们从这个结构中运行我们的子流程。任务被安排在一个事件循环中,由一个线程管理。基本上,事件循环是为了协调任务,确保我们在使用 asyncio 模块时一切顺利。让我们用清单 6-13 来看看如何实际实现 asyncio 模块。

import asyncio

async def prime_number_checker(x): # Define a function which takes x as input
    # Go to sleep for one second, potentially letting other functions
    # do their thing while we're asleep
    await asyncio.sleep(1.0)
    message1 = "%d isn't a prime number.." % x # Set default message

    if x > 1:
        for i in range(2, x):
            # Apply the modulo-operator (%) on variable x.
            # If the ensuing remainder does not equal zero, update "message1"
            if (x % i) != 0:
                message1 = "%d is a prime number!" % x
                break # Break out of the loop
    return message1

async def print_result(y): # Define a function which takes y as input
    result = await prime_number_checker(y) # Await for other function to finish
    print(result) # Print results from function prime_number_checker()
happyloop = asyncio.get_event_loop()
# See if 2, 11, and 15 are prime numbers
happyloop.run_until_complete(print_result(2))
happyloop.run_until_complete(print_result(11))
happyloop.run_until_complete(print_result(15))
happyloop.close()

Listing 6-13A listing in Python demonstrating asynchronous chained coroutines and the use of an event loop

在清单 6-13 中,我们寻找质数(即只有两个因子的数:它们本身和 1)。我们定义了两个异步函数/协程, prime_number_checker(x)print_result(y) ,它们都带有关键字 async def ,因为这就是 asyncio 模块的工作方式。

现在,我们定义的第一个函数从行开始,等待 asyncio.sleep(1.0) 。与 Python 中常规的 sleep 方法相比,这种异步方法不会冻结程序;相反,它为其他任务在后台完成提供了一个指定的时间段。

第二个协程中的行result = await prime _ number _ checker(y)用于确保第一个函数已完成执行。在异步方法中, Await 确实是一个不言自明的关键关键词。

为了让我们的异步清单完全运行,我们需要使用事件循环。为此,我们创建了一个新的循环对象, happyloop,并调用了一个名为 asyncio.get_event_loop() 的函数。尽管事件循环管理有多种多样的函数,但为了清楚起见,我们在这个例子中只讨论前面提到的方法。

接下来,在清单 6-13 中,函数 run_until_complete 实际上执行我们的 print_result(y) 函数,直到其各自的任务全部完成。最后,我们通过调用 close 方法来结束我们的事件循环。

Python 中的并发性:概述

Python 提供的所有处理方法可能会让你感到困惑。因此,让我们回顾一下所涉及的一些核心概念来结束本章(见表 6-7 )。

表 6-7

Python 中并发编程的主要方法

| **多重处理** | 同时运行多个进程;为每个进程创建一个单独的带全局解释器锁(GIL)的 Python 解释器 | 可以在一个系统中使用多个 CPU 和/或 CPU 内核 | | **多线程** | 由操作系统确定优先级的计划任务执行。受限于单一的 GIL | 也叫*抢占式多任务处理。*在单个 CPU/内核上运行 | | **异步处理** | 由任务决定的日程安排受限于单一的 GIL | 又称*协同多任务处理。*运行在单个 CPU/内核上。在 Python 版中引入 |

Python Lambda 函数

把一个 lambda 函数想象成一个一次性函数。Lambda 函数是无名的,而且通常规模很小。它们的语法简单如下:

  • λ(自变量):(表达式)

下面是一个简单的 lambda 函数: sum1 = lambda x,y : x + y 。对于稍微复杂一点的例子,参见清单 6-14 。

def happymultiply(b):
  return lambda a : a * b

result1 = happymultiply(5)
result2 = happymultiply(10)
result3 = happymultiply(50)

print("Result 1:", result1(6))
print("Result 2:", result2(6))
print("Result 3:", result3(6))

Listing 6-14A listing demonstrating lambda functions in Python

现在,Python 中有三种方法可以很好地处理 lambda 函数: map( )filter( )reduce( ) 。Filter()获取一个列表,并创建一个包含所有返回 true 的元素的新列表。Map()还从它处理的 iterables 中创建新的列表;可以把它想象成一个无循环的迭代器函数。最后,我们有 reduce(),它将一个函数应用于一个列表(或其他可迭代的对象)并返回一个值,这有时也将我们从循环中解放出来(参见清单 6-15 )。

from functools import reduce # Import code module needed for reduce()

# Create a list containing seven values
middle_aged = [6, 120, 65, 40, 55, 57, 45]
# Display list before manipulations
print("Ages of all people in the study:", middle_aged)

# Filter list for ages between 40 and 59
middle_aged = list(filter(lambda age: age>=40 and age<60, middle_aged))
print("Middle aged people:", middle_aged)

# Summon map() and double the values in the list "middle_aged"
elderly = list(map(lambda x: x * 2, middle_aged))
print("They will live to:", elderly)

# Summon reduce() to add all elements in "middle_aged" together
total_years = reduce(lambda x, y: x + y, middle_aged)
print("Their combined time spent alive so far:", total_years, "years")

Listing 6-15A listing with a lambda function applied on a filtered list in Python

在 Python 中压缩

以著名的服装闭合机制拉链命名,Python 中的 zip 指的是一种将两个或多个可重复元素结合起来的方法。例如,在用 Python 创建字典数据结构时,压缩是一个很好的工具。不,这种 zip 与流行的归档文件格式 ZIP 没有任何关系,我们将在本章的后面回顾这种格式。

现在,Python 中的 zip 函数接受两个可迭代对象(例如列表)并返回一组元组(参见清单 6-16 )。

# Define two lists
letters1 = ['z', 'a', 'r', 'd', 'o', 'z']
numbers1 = [1, 2, 3, 4, 5, 6]

# Create zip object, zip1, using "letters1" and "numbers1"
zip1 = zip(letters1, numbers1)

for i in zip1: # Display zip contents
    print(i)
# Define new list, "letters2"
letters2 = ['Z', 'A', 'R', 'D', 'O', 'Z']

# Create second zip object, zip2, using "letters1", "numbers1", and "letters2"
zip2 = zip(letters2, numbers1, letters1)

for i in zip2: # Display second zip contents
    print(i)

Listing 6-16A listing in Python demonstrating the zip function

关于拉链的更多信息

在很多情况下,基本的拉链都不够用。其中之一涉及具有不完整元组的拉链。幸运的是,我们有一个叫做 zip_longest 的方法。这个方法在代码模块 itertools 中找到,用您选择的占位符数据填充任何缺失的元素。参见清单 6-17 进行演示。

from itertools import zip_longest
letterlist = ['a', 'b', 'c']
numberlist = [1, 2, 3, 4]
maximum = range(5) # Define zip length and store it into "maximum"
# Summon zip_longest()
zipped1 = zip_longest(numberlist, letterlist, maximum, fillvalue='blank')
# Display zip contents
for i in zipped1:
    print(i)

Listing 6-17A listing in Python demonstrating the zip_longest method

你可能想知道 Python 里的一个拉链能不能拉开。答案是响亮的“是”(见清单 6-18 )。

letters = ['A', 'B', 'C', 'D']
numbers = [1, 2, 3, 4]

zipped1 = zip(letters, numbers) # Zip the data
zipped_data = list(zipped1)
print("Here's the zip:", zipped_data)

a, b = zip(*zipped_data) # Unzip the data using the asterisk-operator
print("Next comes the data, unzipped!")
print('Letters:', a)
print('Numbers:', b)

Listing 6-18A listing in Python demonstrating unzipping.

使用另一个拉链

正如本章前面提到的, zip 是一个在计算环境中至少有两种流行含义的词。我们探讨了 Python 中的压缩(和解压缩),涵盖了第一个含义。现在是时候在任何 Python IDE 中使用另一种 ZIP 文件了,这是一种流行的归档文件格式。

现在,用 ZIP 软件压缩的目录(及其子目录)最终成为一个文件,其大小也往往比未压缩的内容小得多。对于基于网络的文件传输,这显然是一个很好的解决方案。自然,有几种文件压缩解决方案可用,但截至 2021 年,ZIP 仍然是一种无处不在的文件存档格式,可用于所有流行的操作系统。

ZIP 是由菲利普·卡兹和 ?? 的加里·康威在 1989 年创建的。ZIP 压缩技术最近被用作 Java Archive (JAR) 文件格式 和许多其他格式的基础。

Python 也喜欢它的 ZIP 文件。我们实际上可以在 Python IDE 中操作它们。有一个代码模块被恰当地称为 zipfile 。参见清单 6-19 中关于如何创建 ZIP 文件以及如何在 Python 中显示其内容的演示。

import os
from os.path import basename
from zipfile import ZipFile # Include ZipFile module

lookfor = "thread" # Create a variable for the string we plan to look for
# Define a new function, make_a_zip
def make_a_zip(dirName, zipFileName, filter):
   # Create a new ZipFile-object with attribute w for (over)writing
   with ZipFile(zipFileName, 'w') as zip_object1:
        # Start iteration of files in the directory specified
        for folderName, subfolders, filenames in os.walk(dirName):
            for file in filenames:
                if filter(file):
                    # Create full file-path
                    filePath = os.path.join(folderName, file)
                    # Add file to zip using write() method
                    zip_object1.write(filePath, basename(filePath))
# Summon make_a_zip() and give it the current directory to work with
# Also only look for filenames with the string "thread" in them
make_a_zip('..', 'happy.zip', lambda name: lookfor in name)
print("All files containing <<", lookfor, ">> in their name are now inside happy.zip!")
# Create a ZipFile Object and load happy.zip into it (using attribute r for reading)
with ZipFile('happy.zip', 'r') as zip_object2:
   # Summon namelist() and store contents in "file_in_zip" variable
   files_in_zip = zip_object2.namelist()
   # Iterate through files_in_zip and print each element
   print("\nThese files were stored in happy.zip:")
   for i in files_in_zip:
       print(i)

Listing 6-19A listing demonstrating the use of ZIP file archives from inside Python

在清单 6-19 中,您将看到 Python 的 ZIP 支持的两个主要机制在起作用:创建一个新的文件归档(即 happy.zip )和检索存储在其中的文件名。该示例还向您展示了在将文件收集到归档文件时如何使用基于通配符的功能。在这种情况下,我们将只查找文件名中带有字符串“thread”的文件。

ZipFile 一起使用的 with 语句基本上是为了让我们的清单更加清晰。首先,它们让我们不用使用关闭方法(例如, happyfile.close( ) ),这些方法通常在文件操作完成后才需要。

清单 6-19 将在您当前的 Python 目录中执行,因此您的结果可能会有所不同。

我们将在本书的后面回到 Python 语言的复杂性。

最后

读完这一章,你将有望学到以下内容:

  • 如何使用 Python 打开、创建和删除文件

  • Python 中的基本目录管理

  • 正则表达式(RegEx)指什么

  • 如何以及何时为正则表达式调用 match()、search()和 fullmatch()

  • 如何在 Python 中实现多线程和多处理,它们的主要区别是什么

  • 全局解释器锁(GIL)指的是什么

  • 使用 asyncio 的 Python 异步编程基础

  • lambda 函数有什么用

  • Python 中压缩和使用 ZIP 文件归档的基础知识

第七章将会看到我们探索 C# 语言更高级的一面。

七、C# 中的日历、区域性和多线程

这一章是献给最通用的语言之一:强大的 C#。到目前为止,您应该已经熟悉了这种语言的基本语法和集成开发环境的设置。我们将在此继续探索与这种健壮语言相关的一些更高级的主题,包括日历工作和多线程的基础知识。

C# 中的日期

与 Java 和 Python 一样,C# 提供了处理日期和日历所需的一切。让我们来看一个访问日期和日历的程序,好吗?(参见清单 7-1 。)

using System;
public class Example
{
   public static void Main()
   {  // Create a new DateTime-object, "happydate"
      DateTime happydate = new DateTime(1966, 2, 6, 5, 20, 0);
      // Display object data
      Console.WriteLine("Our special date is {0}", happydate.ToString());
      // Fetch and display year and day of the week for our date-object
      Console.WriteLine("Back in {0} it was {1}!", happydate.Year, happydate.DayOfWeek);
      // Create a second DateTime-object
             DateTime happydate2 = DateTime.Now;
             Console.WriteLine("As for right now, it's a {1} in {0}!", happydate2.Year, happydate2.DayOfWeek);
   }
}

Listing 7-1A listing in C# demonstrating the use of DateTime objects (the date in question is recording artist Rick Astley's date of birth)

我们通过创建一个我们选择称为 happydate 的对象来开始列出 7-1 。它应该是 C# DateTime 结构的一个实例,并使用特定的构造函数来设置年、月和时间。然后我们使用控制台显示这个日期。WriteLine 内部带有一个 {0} 格式标记,用于包含第一个参数,并且仅在这种情况下包含第一个参数。happydate 对象也使用 ToString 方法转换为当前活动的特定于文化的格式约定

在 C# 中,DateTime 结构是系统名称空间的一部分,因此不需要以任何其他方式将它们包含在我们的项目中。

C# 中的 DateTime 结构包括大量的属性,用于访问从年到毫秒的日历相关数据。纲要见表 7-1 。

表 7-1

C# 中 DateTime 结构包含的一些核心属性

| 现在 | 一天 | 毫秒 | | 日期 | 小时 | 时间日 | | 年 | 分钟 | 星期几 | | 月 | 第二 | 年复一天 |

系统。全球化名称空间

C# 非常重视全球文化这一概念。全球化是指为全球任何地方的用户设计应用程序。本地化指的是定制应用程序以符合特定文化需求的过程。在实践中,这些概念处理我们星球提供的不同日历、货币、语言和位置。在 C# 中,我们有大量的方法和属性用于显示本地化信息。他们中的许多人来自系统。全球化名称空间。

使用世界日历

C# 中的 Calendar 类包含总共 14 种不同的日历,以满足您的本地化需求。见表 7-2 浏览其中的八个。

表 7-2

C# 中包含的一些日历

| *公历日历* | 世界上使用最广泛的日历 | *JulianCalendar* | 俄罗斯东正教使用的 | | 希伯来语日历 | 以色列官方日历。也被称为犹太历 | 波斯历 | 伊朗和阿富汗官方日历 | | *女儿传说* | 也被称为伊斯兰日历 | *泰国历* | 在泰国使用。比公历早了 543 年 | | 日本历 | 类似于公历,根据日本现任天皇在位的年份添加年份指定 | *一种烷基化* | 在沙特阿拉伯使用,类似于回历 |

在清单 7-2 中,您会发现一个程序,它采用公历中设定的日期(即 2021 年 12 月 31 日)并将其转换成其他四种日历。我们将使用 GetYear()和 GetMonth()等方法来访问与日期相关的数据。我们还使用 ToString 方法为 Gregorian DateTime 对象指定了一个自定义格式模式。

using System;
using System.Globalization;
public class CalendarsExample
{
   public static void Main()
   {
             // Create a new Gregorian DateTime-object, date1
             DateTime date1 = new DateTime(2021, 12, 31, new GregorianCalendar());
             // Create four more objects for the four other types of calendars
             JapaneseCalendar japanese = new JapaneseCalendar();
             PersianCalendar persian = new PersianCalendar();
             HijriCalendar hijri = new HijriCalendar();
             ThaiBuddhistCalendar thai = new ThaiBuddhistCalendar();
Console.WriteLine("When the Gregorian Calendar says it's {0}..", date1.ToString("dd.MM.yyyy"));
Console.WriteLine("> The Japanese Calendar says it's year {0}.", japanese.GetYear(date1));
Console.WriteLine("> The Persian Calendar says it's the year {0} and the {1}th month of the year.", persian.GetYear(date1), persian.GetMonth(date1));
Console.WriteLine("> The Hijri Calendar says it's the year {0} and it's {1}.", hijri.GetYear(date1), hijri.GetDayOfWeek(date1));
Console.WriteLine("> The Thai Buddhist Calendar says it's the year {0} and the {1}th month of the year.", thai.GetYear(date1), thai.GetMonth(date1));
   }
}

Listing 7-2A listing demonstrating displaying information using five different calendars in C#

CultureInfo 类

CultureInfo 类为我们提供了在 C# 中本地化程序的方法。这包括显示适合特定地区的时间、日历和货币信息。要授予我们访问 CultureInfo 类的权限,我们需要让我们的程序再次使用名称空间系统。全球化

C# 使用语言文化代码进行本地化。在下面的清单中,我们使用了四个,即 en-USfi-FIse-SEes-ES 。此代码中的前两个字母代表一种语言,而第二个大写的字母对代表一个特定的地区。例如,德语( de) 可以用附加的文化代码表示,例如, de-AT ( 奥地利)、 de-CH (瑞士)和 de-LI (列支敦士登)。现在,请看清单 7-3 的演示。

C# 中的语言文化代码基于 ISO 3166-1 国家列表。截至 2021 年,总共支持 249 种语言文化代码。

using System;
using System.Globalization;
public class CultureInfoExample
{
   public static void Main()
   {
      // Create an array of four CultureInfo-objects called "jollycultures"
      CultureInfo[] jollycultures = new CultureInfo[] {
      new CultureInfo("en-US"), // US
      new CultureInfo("fi-FI"), // Finnish
      new CultureInfo("se-SE"), // Sami in Northern Sweden
      new CultureInfo("es-ES")}; // Spanish
// Create a new DateTime-object, "date1", assigning calendar-information into it
DateTime date1 = new DateTime(1952, 12, 1, 15, 30, 00);
// Display unformatted date
Console.WriteLine("The special date is " + date1 + " in your current locale.\n");
// Use the foreach-keyword to loop through all of the jollycultures-objects
// and display their contents, using "i" as a temporary iterator-variable
      foreach (CultureInfo i in jollycultures)
      {
             Console.WriteLine("This date in {0} is.. {1} ({2})", i.EnglishName, date1.ToString(i), i.Name);
      }
      }
}

Listing 7-3A listing demonstrating the use of localization in C# using the CultureInfo class

在清单 7-3 中,我们创建了 CultureInfo 类的四个实例来展示美国、芬兰、萨米(在瑞典北部)和西班牙的地区,因为它们与 C# 相关。

我们采用的解决方案是在显示 CultureInfo 对象时使用一个 foreach 元素。C# 中的 foreach 是 for 循环的替代方法,更适合在数组中迭代。例如,这个元素与 CultureInfo 的实例配合得非常好。

现在是时候看一个货币格式化的小例子了(参见清单 7-4 )。

using System.Globalization;
public class JollyCurrencies
{
     public static void Main()
     {
      int cash = 10000;
               // Display cash amount without currency-format
               Console.WriteLine("No currency-format: " + cash.ToString());
               // Set CurrentCulture to Finnish in Finland.
               Thread.CurrentThread.CurrentCulture = new CultureInfo("fi-FI");
               // Add the appropriate currency-format using "c"
               Console.WriteLine("Finnish currency-format: " + cash.ToString("c"));
               // Set CurrentCulture to Belarusian in Belarus and display string
      Thread.CurrentThread.CurrentCulture = new CultureInfo("be-BY");
      Console.WriteLine("Belarusian currency-format: " + cash.ToString("c"));
      // Set CurrentCulture to Chinese in People's Republic of China
      Thread.CurrentThread.CurrentCulture = new CultureInfo("zh-CN");
      Console.WriteLine("Chinese currency-format: " + cash.ToString("c"));
      }
}

Listing 7-4A listing demonstrating currency formats in the C# CultureInfo class

在清单 7-4 中,我们分别为芬兰语、白俄罗斯语和汉语创建并显示了三种不同的货币格式。方法 ToString("c") 用于指定一种货币。

C# 中的 File 类

C# 通过一个简单的叫做 File 的类为所有类型的文件操作提供了通用的工具。我们可以读取和显示文本文件,创建新文件,并检索多种类型的属性,如创建日期等等。

在清单 7-5 中,我们从 C# 中创建了一个新文件,我们称之为 apress.txt 。在里面,我们会写一条小信息(“Apress 是最好的出版商!”).不止于此,我们将继续读取并显示我们刚刚创建的文件的内容。

要使用 File 类,我们需要系统。我们程序中的 IO 命名空间。

using System;
using System.IO;
class HappyTextWriter
{
     static void Main(string[] args)
     {
     using (TextWriter happywriter = File.CreateText("c:\\apress.txt"))
               {
                  happywriter.WriteLine("Apress is the best publisher!");
               }
     Console.WriteLine("Textfile created in C:\n\nIt reads:");
     // Open the file we just created and display its contents
     using (TextReader happyreader1 = File.OpenText("c:\\apress.txt"))
               {
                  Console.WriteLine(happyreader1.ReadToEnd());
               }
            }
        }

Listing 7-5A listing demonstrating the TextWriter and TextReader classes in C#

除了文本文件,还有另一种文件格式可供我们使用。在清单 7-6 中,我们将使用另一个主要类型:二进制文件。这种格式非常通用,可以存储文本、数字以及我们可能用到的任何东西。在下一个清单中,我们将创建一个二进制文件,并在其中存储一个浮点数、一个文本字符串和一个布尔变量。同样,我们需要使用系统来利用文件类。IO 命名空间。

using System;
using System.IO;
class JollyBinaryTest {
               static void Main(string[] args)
       {
       string filename1 = "c:\\binarystuff.dat"; // Define our path and filename
/* Create a new BinaryWriter-object (writer1) and save three lines/types of  information into it */
using (BinaryWriter writer1 = new BinaryWriter(File.Open(filename1, FileMode.Create)))
{
writer1.Write(55.52F);  // Write a floating point number
writer1.Write("Oranges are great"); // Write a string
writer1.Write(true); // Write a boolean (true/false) variable
}
Console.WriteLine("Data written into binary file " + filename1 + "!\n");
/* Create a new BinaryReader-object (reader1) and use it to decipher the binary data in our file using the appropriate methods, e.g. ReadSingle() for the floating point */
using (BinaryReader reader1 = new BinaryReader(File.Open(filename1, FileMode.Open)))
{
Console.WriteLine("First line: " + reader1.ReadSingle() ); // Read a floating point
Console.WriteLine("Second line: " + reader1.ReadString() ); // Read a string
Console.WriteLine("Third line: " + reader1.ReadBoolean() ); // Read a boolean variable
}
}
}

Listing 7-6A listing demonstrating the BinaryWriter and BinaryReader classes in C#

当处理非特定的二进制文件时,习惯上使用文件扩展名。数据中的 dat

FileInfo 类

File 类在 C# 中有一个替代项;FileInfo 提供了更多的控制,在某些情况下更有用。让我们用清单 7-7 来看看如何用 FileInfo 访问文件属性。

using System;
using System.IO;
class FileInfoAttributeFun
{
public static void Main()
{
               string fileName1 = @"C:\apress.txt"; // Set our target file
               FileInfo ourfile = new FileInfo(fileName1);
               string na = ourfile.FullName;
    Console.WriteLine("Attributes for " + na + ":");
               string ex = ourfile.Extension;
    Console.WriteLine("File extension: " + ex);
               bool ro = ourfile.IsReadOnly;
               Console.WriteLine("Read-only file: " + ro);
               long sz = ourfile.Length;
    Console.WriteLine("Size: " + sz + " bytes");
               DateTime ct = ourfile.CreationTime;
    Console.WriteLine("Creation time: " + ct);
               DateTime la = ourfile.LastAccessTime;
               Console.WriteLine("Last access: " + la);
    }
}

Listing 7-7A listing demonstrating file-attribute access using the C# FileInfo class

清单 7-7 希望您在 Windows 硬盘的根目录下有一个文本文件 apress.txt 。您可以修改文件名 1 指向不同的位置和/或不同的文本文件,让清单 7-7 发挥它的魔力。

现在是时候看看 FileInfo 可以访问的一些最常用的属性了。纲要见表 7-3 。

表 7-3

C# 中 FileInfo 类包含的一些属性。

| 名字 | 返回文件的名称 | 存在 | 用于确定特定文件是否存在 | | 工作表名称 | 返回文件的全名和目录路径(例如,C:\MyFolder\myfile.txt) | 长度 | 以字节为单位返回文件的大小 | | 创造时间 | 返回或更改文件的“出生日期” | 目录 | 返回父目录的实例 | | 最后访问时间 | 返回或更改文件或目录的上次访问时间 | 属性 | 返回或更改文件的属性 |

接下来让我们来看一个更全面的 C# 文件操作的例子。在清单 7-8 中,我们更广泛地使用了 FileInfo 类。这包括使用 CopyTo 方法复制文件以及使用 Delete 方法删除文件。

using System;
using System.IO;
class FileInfoExample
{
public static void Main()
{
string filename1 = @"C:\wackyfile.txt"; // Path to main file
string file2 = @"c:\wacky_copy.txt"; // Path to copy of main file
FileInfo ourfile = new FileInfo(filename1); // Create new FileInfo-object, "ourfile"
FileInfo ourfile_copy = new FileInfo(file2); // Do the same for "ourfile_copy"
             // Use Exists-property to determine if file has already been created
             if(!ourfile.Exists) {
             File.Create(filename1).Dispose(); // Create file and disable file-lock on "ourfile"
             Console.WriteLine("File not found. Creating " + filename1);
             } else Console.WriteLine("File found.");
             // Display full name of our file
             Console.WriteLine("Attempting to make a copy of " + ourfile.FullName);
             // See if copy exists. If it doesn't, duplicate file
             if(!ourfile_copy.Exists) { ourfile.CopyTo(file2);
             Console.WriteLine("{0} has been copied as {1}", ourfile, file2);
             } else Console.WriteLine("File not copied. Duplicate already found.");
             // Prompt user for input
              Console.WriteLine("Would you like to delete these files? (y/n)");
              char input1 = (char)Console.Read(); // Assign a Console.Read() to character "input1"
              if(input1=='y') { // If user inputs 'y' delete both files
              Console.WriteLine("Deleting files..");
              ourfile.Delete();
              ourfile_copy.Delete();
              } else Console.WriteLine("Files in C:\\ not deleted.");
    }
}

Listing 7-8A listing in C# demonstrating some file operations found in the FileInfo class

让我们详细看一下清单 7-8 。首先,我们定义两个字符串:文件名 1文件 2 。这些字符串包含完整的文件路径,并指向 Windows 根目录 C:中的两个文件。我们也可以只保留路径,只保留文件名(即 wackyfile.txtwacky_copy.txt )。

接下来,我们使用 FileInfo 类创建一个对象,将其命名为 ourfile 。然后为文件副本创建另一个对象 ourfile_copy 。我们需要实例化 FileInfo,以便稍后使用类方法。

字符串值前的 at 符号,即@,表示 C# 中的文字字符串。这意味着后面的字符串不会被解析为任何转义序列,比如"\\"中的反斜杠。比较下面几行(最上面一行是文字字符串):

string file1 = @"c:\user\xerxes\documents\text\nincompoop.txt";

string file1 = "c:\\user\\xerxes\\documents\\text\\nincompoop.txt";

这句台词如果(!ourfile Exists) 是检查对象 ourfile 的不言自明的 Exists 属性。前面有一个感叹号,条件句是“如果我们的文件不存在。”继续前进,行档。创建(文件名 1)。dispose();包含两条指令。首先,Create 方法在您的存储系统上创建一个实际的文件。Dispose 方法主要释放文件的访问状态。如果没有它,我们可能会在程序后期操作这个文件时遇到问题。

现在,回到我们的条件句。它以关键字 else,结束,显示“如果我们的文件确实存在”,在这个场景中,它输出消息“找到文件”接下来,我们试图复制对象我们的文件。又到了一个条件从句的时候了。如果(!ourfile_copy 存在){ ourfile copy to(file 2);是否再次检查文件的存在,这一次关注我们之前定义的另一个文件, ourfile_copy 。如果不存在,我们调用 CopyTo 方法将我们的文件(即 wackyfile.txt )复制到 file2(即 wacky_copy.txt )上。在条件子句的末尾,我们又有了一个可选的 else 关键字。这一次,它显示消息“找不到文件。已找到重复项。

我们现在转到一些用户交互。如果用户希望删除原始文件及其副本,系统会提示用户按“y”(并点击 return)。这是通过 Delete 方法实现的。如果用户输入任何其他字符而不是“y”,文件将保持不变,并显示一条消息“C:\中的文件未删除”而是显示。

File 与 FileInfo:有什么区别?

C# 的 File 和 FileInfo 类非常相似。然而,它们在语言中保持独立是有原因的。当需要对一个文件进行多个操作时,fileInfo 类是更好的选择,而 File 最适合单个操作。FileInfo 还通过字节大小的文件操作提供了更多的手动控制。

File 中的方法是静态的,而 FileInfo 中的方法是基于实例的。静态方法需要更多的参数,比如完整的目录路径。反过来,在特定的场景下,比如为基于网络的环境编码,稍微更费力的文件类实际上可能产生更有效的结果(见表 7-4 )。

表 7-4

C# 中 File 和 FileInfo 类的主要区别

|

文件

|

文件关于

|
| --- | --- |
| 使用静态方法 | 使用基于实例的方法 |
| 不需要实例化 | 需要实例化 |
| 提供更多方法 | 提供较少的方法 |
| 在某些情况下可以提供更快的性能,例如网络流量 | 对文件读写操作有更好的控制 |

要观察 FileInfo 提供的一些手动控制,请参见清单 7-9 。

using System;
using System.IO;
// Create a new FileInfo object, "fileobj1"
FileInfo fileobj1 = new FileInfo(@"c:\apress.txt"); // This text-file should contain something
// Open the file for reading: this line works with FileInfo only; not with File
FileStream filestream1 = fileobj1.Open(FileMode.Open, FileAccess.Read);
// Create a byte array sharing the length of our filestream
byte[] bytearray1 = new byte[filestream1.Length];

// Set variable for a byte-counter and feed the length of our file into it
int howmanybytes = (int)bytearray1.Length;
int bytesread = 0;
// Read our file, a byte at a time using a while-loop
while (howmanybytes > 0)
{
    int i = filestream1.Read(bytearray1, bytesread, howmanybytes);
    if (i == 0) break; // Once we have zero bytes left, break loop
    howmanybytes-=i;
         bytesread+=i;
}
// Convert bytes into string which uses UTF-8 character encoding
string string1 = Encoding.UTF8.GetString(bytearray1);
Console.WriteLine(string1); // Display string

Listing 7-9A listing demonstrating manual control for reading data using FileInfo and FileStream classes in C#

FileStream 类允许我们在逐字节的基础上读写文件。这是使用清单 7-9 中的 Open 方法完成的。这是一个多用途的方法,需要几个属性;为了以只读模式打开我们的文件,我们输入参数 FileMode。打开并文件访问。读入 Open 方法。接下来,我们使用关键字 byte[ ]创建一个字节数组。提醒一下,数组是值的集合;它们可能由数字、字符、字符串或字节组成。在 C# 中,数组是用方括号定义的(第二个提醒是,每个字节由 8 位组成,经常用来表示字母字符)。

接下来,使用行 int howmany bytes =(int)bytearray 1。长度;我们创建了一个整数,howmanybytes,在其中我们应用了一个长度方法,以便找出我们的文件的字节大小。在随后的 while 循环中,使用 read 方法读取字节。

更准确地说,这个 while 循环将在变量 howmanybytes 保持大于零时执行。我们之前定义的文件流 filestream1 应用了 Read 方法,将字节数组(bytearray1)、目前读取的字节数(bytes read)和要读取的字节总数(howmanybytes)作为该方法的参数。这个输出被输入到变量 I 中。当 I 达到零时,我们使用 break 关键字中断这个循环。

清单 7-9 的最后步骤包括首先将 bytearray1 转换成可读的 Unicode 字符,并将其存储到 string1 中,然后显示该字符串。

使用 UTF-8 字符编码,存储在字符串中的每个拉丁字母数字字符通常占用两个字节,即 16 位。

C# 中的垃圾收集

垃圾收集(GC) 的概念基本上是指自动化的内存管理。这种机制可以根据程序的需要分配和释放 RAM 支持垃圾收集的编程语言将程序员从这些任务中解放出来。包括 C# 在内的大多数现代语言都支持开箱即用的 g C。这个特性也可以以附加软件库的形式添加到没有本地支持的语言中。

随着一个编程项目的增长,它通常包括越来越多的变量和其他数据结构,除非得到适当的处理,否则它们会耗尽 RAM。对程序员来说,管理这些资源可能有点麻烦。此外,受损的内存分配会导致稳定性问题。

现在,C# 中的垃圾回收在以下三种情况下被调用:

  1. 您的计算机物理内存不足。系统的内存越大,这种情况发生的频率就越低。

  2. 分配的对象使用的内存超过了特定的阈值。该阈值由垃圾收集组件实时更新。

  3. 一个 GC。程序员调用了 Collect 方法。对于较小的项目,这很少是必要的。

本机堆与托管堆

本机堆是由操作系统管理的动态分配的内存。每当一个程序被执行时,本机堆内存被动态地分配和释放。被称为托管堆的内存区域是一个不同的实体。每个单独的进程都有自己的托管堆。一个进程中的所有线程也共享这个内存空间。

C# 中的多线程

如您所料,多线程在 C# 中确实得到了很好的支持。参见清单 7-10 进行演示。首先,我们定义一个自定义方法, ChildThread(),来产生新的线程。我们稍后将创建三个线程,它们将同时使用 Sleep 方法,简单地暂停一个线程的处理。在我们的线程中,这些方法被赋予一个 0 到 6000 毫秒之间的随机值,最大延迟为 6 秒,按照基于线程的范例,所有这些方法都被同时计数。

程序由进程组成。线程是进程内部的实体,它们可以在最需要的时候被调度。在 C# 中,我们也可以对线程的创建和其他生命周期事件施加很多控制。

接下来,在我们的自定义方法中,我们检索一个相当冗长的属性,称为 System。threading . thread . current thread . managed threadid为每个正在运行的线程提供一个唯一的标识号,将其存储到 happyID 中。

设置线程信息时,变量 delay 除以一千(即乘以 0.001)以秒为单位显示。我们还利用 C# 数学类中的 Round 方法将延迟整数舍入到两位小数。

using System;
using System.Threading;
public static void ChildThread() {
Random randomnr = new Random(); // Create a new Random-object, "randomnr"
int delay = randomnr.Next(0, 6000); // Set a random delay between zero and six seconds using our random-object
/* Assign thread ID number, i.e.  System.Threading.Thread.CurrentThread.ManagedThreadId, into integer "happyID" */
int happyID = System.Threading.Thread.CurrentThread.ManagedThreadId;
         Console.WriteLine("Happy thread " + happyID + " starts!");
         // Round delay amount to two decimals when displaying it
         Console.WriteLine("Happy thread " + happyID + " is paused for {0} seconds..", Math.Round(delay * 0.001, 2) );
         Thread.Sleep(delay);
         Console.WriteLine("Happy thread " + happyID + " will now resume!");
      }

      static void Main(string[] args) {
         ThreadStart child = new ThreadStart(ChildThread);
         for(int i=0; i<3; ++i) { // Create a total of three child-threads
         Thread childThread = new Thread(child);
         childThread.Start(); // Commence execution of thread
       }
}

Listing 7-10A listing demonstrating how child threads are created in C#

列出 7-10 的主要方法相当简单。使用 for 循环,我们创建了三个 ChildThread 实例,使用 C# Thread 类中的 Start( ) 方法开始执行。

C# 线程中的锁和属性

像 Java 和 Python 一样,C# 为它的线程化应用程序提供了锁定机制。有了锁,我们可以让线程同步(参见清单 7-11 )。

using System;
using System.Threading;
    public class LockingThreads
    {
        public void OurThread()
        {
                 // Get a handle for the currently executing thread
                 // so we can retrieve its properties, such as Name
                 Thread wackyhandle = Thread.CurrentThread;
                 // Apply a lock to synchronize thread
                 lock(this) { // Locked code begins
                 Console.WriteLine("(This thread is " + wackyhandle.ThreadState+" with " + wackyhandle.Priority + " priority)");
             for(int i=0; i<3; ++i) {
Console.WriteLine(wackyhandle.Name + " has been working for " +i+ " hours");
        Thread.Sleep(400); // Wait for 0.4 seconds before next line
        }
        } // Locked code ends
     }
    }
       public static void Main()
       {
       LockingThreads jollythread = new LockingThreads();
       Thread thread1 = new Thread(new ThreadStart(jollythread.OurThread));
       Thread thread2 = new Thread(new ThreadStart(jollythread.OurThread));
       thread1.Name="Graham"; thread2.Name="Desdemona";
       // Both of these threads are synchronized / locked:
       // thread1 will be processed until completion before thread2 begins
       thread1.Start();
       thread2.Start();
       }

Listing 7-11A listing in C# demonstrating thread locking and accessing some thread properties

清单 7-11 的输出如下:

(This thread is Running with Normal priority)
Graham has been working for 0 hours
Graham has been working for 1 hours
Graham has been working for 2 hours
(This thread is Running with Normal priority)
Desdemona has been working for 0 hours
Desdemona has been working for 1 hours
Desdemona has been working for 2 hours

如果没有锁定机制,我们会在这个与工作相关的状态更新中看到关于 Graham 和 Desdemona 的线条交替出现。您还将看到线程类的三个属性被充分展示出来(即,线程状态优先级名称)。

睡眠与产量

到目前为止,我们已经在几个清单中探索了 Sleep 方法的使用。重申一下,这告诉线程在预定的持续时间内小睡/挂起自己,通常以毫秒为单位。

该是我们带来 C# 收益的时候了。这个关键字在 C# 中根据上下文有多种含义;我们将在下一章更详细地研究它们。在多线程环境中,Yield 告诉线程进入不确定等待状态。被放弃的线程将在需要时被重新激活;这可能在几毫秒内发生,也可能需要更长的时间。Yield 基本上将 CPU 从执行特定线程中解放出来,以便处理其他更紧急的线程。这种紧急程度最终取决于一个人的操作系统。

Sleep 方法实际上可以在一定程度上模拟 C# 的产出。年纪大。NET 框架(4.0 版之前)还不支持 Yield。在这些情况下,在 Sleep(0)中键入类似于 Yield。

现在让我们看看 Yield 在实际中的表现(参见清单 7-12 )。

using System;
using System.Threading;
public class AmazingThreads
{
private int counter; // Declare a counter-variable
    public void YieldThread()
    {
       Console.WriteLine("First thread is an infinite loop with Yield()");
       while(true) // Start an infinite loop
       {
       // Without Yield, the counter would trail off to much longer lengths
       Thread.Yield();
       ++counter;
       }
    }
    public void SecondThread()
    {
Console.WriteLine("Second thread informs you: First thread counter reached " + counter);
    }
}
public class YieldExample
{
    public static void Main()
    {
        AmazingThreads greatobject = new AmazingThreads();
        Thread thread1 = new Thread(new ThreadStart(greatobject.YieldThread));
        Thread thread2 = new Thread(new ThreadStart(greatobject.SecondThread));
        thread1.Start();
        thread2.Start();
    }
}

Listing 7-12A listing demonstrating the Yield method in C#

在清单 7-12 中,我们定义了一个类整数,计数器,以粗略记录第一个线程在让步开始之前的执行时间长度。然后我们启动两个线程,第一个线程进入无限循环。第二个线程显示存储在计数器变量中的数量。就收益率而言,我们可以预计,在大多数情况下,这一数据将远低于 2000 点。如果没有 Yield,计数器将显示高出几个数量级的读数,最终导致程序无响应。

同样,操作系统是根据其他线程的状态和优先级调度发出让步请求的线程的最终实体。

加入

C# 中线程工作最重要的方法之一叫做 Join 。使用这种方法,您可以让线程在其他线程完成处理时等待。自然,Join 只能被已经开始执行的线程调用(参见清单 7-13 )。

 using System;
 using System.Threading;
 static void OurFunction() { // Create a custom method
              for (int i = 0; i < 10; ++i)
              Console.Write(i + " ");
         }

static void Main(string[] args) {
                Thread thread1 = new Thread(OurFunction);
                thread1.Start();
                thread1.Join();
                // Join() makes sure a thread completes its processing
                // before the listing proceeds
                Console.Write("10"); // Finish the list with a number ten
}

Listing 7-13A basic demonstration of the Join method

in C#

清单 7-13 为我们提供了一个 Join 方法的基本示例。清单 7-13 的输出应该是 0 1 2 3 4 5 6 7 8 9 10 。如果没有 Join,您可能会得到意想不到的结果,例如 100 1 2 3 4 5 6 7 8 9 。这是因为最后的控制台。Write 没有被明确告知要等到 thread1 完成其处理。

在 C# 中实现异步

基本上,异步编程是一门艺术,让一个程序包含许多任务,这些任务不会互相冲突(或与其他程序冲突)。这种方法对程序员来说也更容易理解,因为它产生了相当清晰的代码布局。

C# 中异步处理的当前实现利用了一个名为 ThreadPool 的类。这个类采用了不同于 Thread 类的方法(在本章前面已经讨论过)。首先,ThreadPool 只创建低优先级的后台线程;给线程分配优先级不是这个类的一部分。

一个设计模式是一个可重用的解决方案,用于解决软件设计中特定环境下反复出现的问题。

现在,C# 中有三种主要的异步编程范例。其中只有一个 TAP 被微软推荐在 2021 年使用。

  • 异步编程模型(APM):APM 方法使用 IAsyncResult 接口及其 BeginOperationNameEndOperationName 方法。微软不再推荐使用这种有些费力的设计模式。

  • 基于事件的异步模式(EAP) : EAP 被设计来提供异步软件范例的好处,同时从程序员的角度来看保持事情相对优雅。这种设计模式也被认为是过时的方法。

  • 基于任务的异步模式(TAP) : TAP 是编写异步软件的最新最优雅的设计模式。与 APM 和 EAP 不同,TAP 使用单一方法来表示异步操作的启动和完成,并以任务任务<t 结果> 类为中心。

有关使用 TAP 的异步模式的示例,请参见清单 7-14 。

using System;
using System.Threading;
static async Task Main(string[] args) // Our small asynchronous main thread
       {
       await happyMethod(); // Await for happyMethod to do its thing
            Console.WriteLine("Program complete.");
       }

public static async Task happyMethod() // Create asynchronous Task happyMethod()
       {
         int fibo = await Fibonacci(); // Await for Fibonacci-method to complete
                  // and assign its results into integer "fibo"
         DisplayFibonacci(fibo); // Summon our most simple method
       }
public static async Task<int> Fibonacci()
// Create asynchronous Task Fibonacci(), which is to return an integer
       {
              int x = 0, y = 1, z = 0; // Define variables needed in our for-loop
              // This task is kept simple for the sake of clarity. In real life
              // you could do some heavy lifting here in concert with other
              // asynchronous tasks.
             await Task.Run(() =>
             {
                for (int i = 2; i < 5; i++) // Look for Fibonacci number
                {
        z = x + y;
        x = y;
        y = z;
              }
         });
       return z; // Return result, i.e. the fifth Fibonacci
       }

public static void DisplayFibonacci(int fibo) // Method for display the result
       {
           Console.WriteLine("The fifth Fibonacci number is " + fibo);
       }

Listing 7-14A demonstration of asynchronous processing in C# using the TAP design pattern

现在,清单 7-14 被分成四个方法,其中只有一个在本质上不是异步的(即 DisplayFibonacci )。前三种方法使用了异步/等待机制。通过在方法旁边使用定义 async ,我们指定一个方法是异步的。这允许我们使用 await 关键字,将控制权交给它的调用方法。

async/await 机制允许程序在后台处理潜在的繁重计算,这通常会减少用户界面的故障和/或加快网络文件传输。

创建方法时,TAP 有一些特定的关键字。任务是用来表示不返回值的方法。在我们的清单中,您还会发现一个任务< int > 的实例;这个表达式表示一个必须返回整数的方法。任务可以返回任何类型的变量。所有创建的异步任务都将被安排在由 ThreadPool 类管理的线程上运行。

命令等待任务。Run 使用 ThreadPool 类在自己的线程中启动一个任务。现在,基本上有三种方法来调用这个机制:

1        await Task.Run(() => SomeMethod );
2        await Task.Run(() => SomeOtherMethod(something) );
3        await Task.Run(() => {
         Console.WriteLine("Let's do something here");
         return 0;
         });

第一种方法接受以前声明的不带参数的方法;您只需传递一个方法的名称。第二种方法是采用参数的方法。我们也可以使用基于块的语法,正如第三个机制所演示的。和往常一样,必须注意圆括号、花括号和其他特殊字符的正确使用。正如您可能注意到的,清单 7-7 对其等待任务使用了基于块的方法。运行()

斐波纳契数列中,每个数字代表它前面两个数字的和,例如,0,1,1,2,3,5,8。这个概念是由意大利数学家莱昂纳多·博纳奇(约 1170 年,可能是公元 1250 年)在其开创性的著作 Liber Abaci 中引入的。在他生命中的某个时刻,波纳奇被取了一个绰号叫斐波那契。

最后

读完这一章,你可能会对以下内容有所了解:

  • C# 系统的一些常见用法。全球化名称空间及其一些类,包括 Calendar 和 CultureInfo

  • C# 中使用 file 和 FileInfo 类的基本文件操作

  • C# 中垃圾收集(GC)的基础知识

  • C# 中基本多线程的实现,包括一些最相关的方法(连接、启动、睡眠、产出)

  • 使用基于任务的异步设计模式(TAP)及其异步/等待机制的 C# 异步编程基础

第八章致力于 C#、Java 和 Python 提供的更好的技术,包括高级多线程。我们将进入制作有用的(但绝对是小规模的)应用程序的世界。

八、毕业日:稍大的编程项目

在这一章中,我们将研究几个比目前介绍的稍微大一点的 Python、C# 和 Java 编程项目。这些项目旨在演示本书中讨论的一些最相关的概念:变量、循环、文件访问和线程。如果你能够破译这些小项目的内部结构,你可以有把握地说,你已经对本书中介绍的三种编程语言的基础有了一定的理解。如果在这一点上你仍然不确定你作为一个程序员的技能,这一章可以帮助你澄清一些概念。

Python 的通用聊天模拟器

我们将从向世界介绍通用聊天模拟器开始这一章。这个程序将展示 Python 的以下特性,所有这些我们在本书前面都讨论过:

  • 文件操作

  • 字符串格式

  • 自定义函数定义(使用 def 关键字)

  • 变量(包括随机化)、列表和迭代

  • 基本异常处理

  • 元素枚举

  • 程序流程和简单循环

  • 日期和时间检索

像本章中的其他程序一样,通用聊天模拟器(UCS)是一个控制台应用程序,这意味着它只在文本模式下工作;为了保持简单,不使用图形元素。

现在,UCS 的两个关键元素以虚拟聊天主持人和他们的虚拟观众的形式出现,后者由一个共享的昵称“聊天”来描述。这两个虚拟角色每隔几秒钟就在屏幕上输出文本,文本的内容由三个外部的文本文件决定,因此很容易编辑。

开始使用 UCS 时,不需要键入整个清单;这个文件可以在 GitHub 上找到,这个免费下载的列表也有完整的注释。然而,为了彻底起见,现在让我们一个块一个块地检查程序。

第一部分:设置项目

首先,我们导入所需的代码模块。日期时间时间随机随 Python 而来。然而,对于我们的项目,我们还需要从 Python 包索引(PyPI) 中获取一个额外的模块。这是一个免费的在线知识库,用于扩展 Python 的功能。我们将使用 Abhijith Boppe 的资产 clrprint 。安装并导入 clrprint 模块后,我们可以用类似下面这样的代码行将彩色文本添加到 Python 项目中: clrprint("Hello!",clr='red') 。项目代码的第一部分见清单 8-1 。

只需在 macOS 终端、Windows shell 或 Linux 命令行中输入“pip 3 install clr print”即可安装 clrprint

# import three Python code-modules
import datetime
import time
import random
# also import the clrprint-module from the Python Package Index (PyPI)
from clrprint import *
name = "Apple representative"  # virtual chat host name
mood = 50  # moods: 50 = neutral, 0 = bad ending, 100 = good ending
score = messages_sent = 0  # assign zero to score and messages_sent
response = negative = positive = False  # assign False to three variables
negativetrigger = "orange"  # negative trigger word, used to reduce mood
positivetrigger = "apple"  # positive trigger word, used to grow mood
# Fetch current time and date using now() from datetime-class
initial_time = datetime.datetime.now()
# define a custom function, print_chat()
# variable "cap" decides how the chat messages are to be capitalized, if at all
# both 0 and -1 mean no changes in capitalization
# 1 means capitalize first letter of a line, 2 means capitalize all letters in a line
def print_chat(chat):
               cap = random.choice([-1, 0, 1, 2])
               if cap == 1:
               chat = chat.capitalize()  # summon capitalize() method
               elif cap == 2:
               chat = chat.upper()  # summon upper() method for ALL CAPS
               print("Chat: " + timer.strftime("<%A %H:%M> ") + chat)

Listing 8-1First part of the listing for the Universal Chatting Simulator in Python

接下来,我们定义一个名为 open_chatfile 的定制函数,它有两个参数, namedeletechar 。Name 只是表示我们要打开的文件名。Deletechar 用于表示我们要从这些列表中删除的字符;这是存在的,因此我们可以从聊天中删除所有换行符(例如, \n )以消除不必要的空行(参见清单 8-2 )。

我们还在函数中使用了一个临时列表结构 templist 。数据被输入到 templist 中,以便可以对其进行枚举并删除其换行符。使用 Python 的 readlines 方法读取文件内容。

def open_chatfile(name, deletechar):
     try:
         file = open(name, 'r')  # open file in read-only mode, "r"
         print("Loading file " + name)

         templist = file.readlines()  # read file into templist
         for i, element in enumerate(templist):  # enumerate the list
             templist[i] = element.replace(deletechar, '') # delete newlines
         return templist  # return the enumerated list
         file.close()  # closing the file is always a good practice
     except (FileNotFoundError, IOError):  # handle an exception
         # str() converts variables to string
         print("The file " + str(name) + " could not be read!")

Listing 8-2The second part of the listing for the Universal Chatting Simulator in Python

第二部分:展示高分

在我们开始记分之前,我们要定义三个列表用于 open_chatfile 函数;他们被称为中性情绪坏情绪聊天情绪。来自文本文件的数据需要存储在某个地方,这就是我们三个列表的用武之地(参见清单 8-3 )。

在 Python 中,我们可以使用赋值操作符在同一行用同一个变量初始化变量,包括列表,例如, a = b = 10happy = wonderful = [ ]

接下来我们实际使用我们的方法,召唤它三次打开文件 neutral.txtbad.txtchat.txt 。open_chatfile 的另外两个参数是我们之前创建的列表和换行符。现在,我们有三个列表,充满了发人深省的交流,为我们的虚拟聊天乐趣做好了准备。

在项目的这一部分结束时,我们将打开一个包含当前最高分的文本文件。是的,我们在通用聊天模拟器中有一个评分系统;稍后将详细介绍。我们通过创建一个 try 块来获取分数数据。提醒一下,try 块允许我们动态测试代码中的错误。加上 except 关键字(exception 的缩写),我们可以引入自己的错误消息。在清单 8-3 的 try 块中,试图加载文件 highscores.txt 。如果找不到这样的文件,异常块会告诉我们并创建文件。

# display program title in vibrant yellow using the clrprint-module
clrprint("Universal Chatting Simulator (UCS) ver 0.1.0 by Bob the Developer", clr='yellow')

# define three list-structures to be filled with data from text-files
neutralmoods = badmoods = chatmoods = []  # [] denotes an empty list
# insert textfile-data into the three lists using our custom function
neutralmoods = open_chatfile("neutral.txt", "\n")
badmoods = open_chatfile("bad.txt", "\n")
chatmoods = open_chatfile("chat.txt", "\n")
print("Your starting mood is " + str(mood) + "/100")

# display current highscore stored in "highscore.txt"
try:
     # open "highscore.txt" in r+ mode for both reading and writing
     with open("highscore.txt", "r+") as scorefile:
         hiscore = scorefile.read()
         print("CURRENT HIGHSCORE: %s" % hiscore)
# if highscore.txt is not found, create this file
except (FileNotFoundError, IOError):
     print("Highscores not found. Creating highscore.txt")

     scorefile = open("highscore.txt", "w")
     scorefile.write("0")  # write zero into "highscore.txt"
     scorefile.close()

Listing 8-3The third part of the listing for the Universal Chatting Simulator in Python

第三部分:不屈不挠的主循环

现在,我们进入主循环的领域。这是大部分处理和/或魔术发生的地方。UCS 在 while 循环中运行,其内容为“当可变情绪小于 100 时”如果情绪超过 100,循环就不再运行。此外,如果 mood 属于 break 关键字,则使用 break 关键字退出循环。

虚拟聊天者使用简单称为 chat 的变量在程序中获得他们的表示;从 chatmoods 列表中随机选择一个元素,并输入到该变量中,以在清单 8-4 中进一步显示。

你可能想知道关于情绪多变的喧闹是怎么回事。不仅 mood 用于中断主循环,而且如果它低于 30,程序将开始为我们的虚拟主机使用一组不同的行(即文件 bad.txt 中定义的更糟糕的行)。mood 整数的值受特定关键字的影响,即另外两个变量 negativetriggerpositivetrigger ,在前面的清单 8-1 中定义。如果程序检测到一个负面的触发词,“情绪”会减少 1 到 5 之间的一个数字。类似地,正触发字导致整数被加上一个 1 到 5 之间的数。为此,我们使用行 random.randint(1,5) 生成一个在期望范围内的整数,将其赋给另一个变量 moodchange

现在,如果在清单 8-4 中检测到一个触发字,有两个布尔变量会产生一个标志;这些简称为。这两个布尔值用来显示程序后面任何情绪变化的状态消息。

资本化和延迟

接下来,我们在程序中加入一些修饰性的变化。清单 8-4 可以用三种格式显示所有的聊天行:常规、首字母大写和全部大写。这是通过每个主循环检查一次变量 cap 的随机值来实现的。我们发布 cap 四个潜在值:-1、0、1 和 2。如果 cap 保持在 1 以下,一行颤振保持不变。值 1 使程序调用 Python 的大写()方法,正如它的名字所暗示的那样。最后,值 2 对应于 upper( ) 方法,基本上为我们的虚拟 chatters 打开了 caps lock(仅针对一行)。

对于一个模拟聊天的程序,必须实现一些延迟,以便观众可以充分享受适度有见地的评论流。在清单 8-4 中,我们通过应用 Python 奇妙的 sleep()方法并给它一个 1 到 3 之间的随机值来实现这一点,对应于相同的延迟秒数。

# main while-loop begins
while 1 < mood < 100:  # keep looping as long as mood remains under 100

     timer = datetime.datetime.now()  # Fetch current time and date

     chat = random.choice(chatmoods) # Select a random line from "chatmoods"

     if negativetrigger in chat:  # negative trigger word found
              # set variable "moodchange" between one and five
         moodchange = random.randint(1, 5)
         mood -= moodchange
              negative = True  # negative trigger word was found
     if positivetrigger in chat:  # positive trigger word found
         moodchange = random.randint(1, 5)
         mood += moodchange
         positive = True  # positive trigger word was found
            print_chat(chat) # summon function for printing chat's output

     messages_sent = messages_sent + 1
     if negative:  # same as "if negative == True:"
         if mood < 0:
             mood = 0
         clrprint("MOOD -" + str(moodchange) + "! Your current mood is
         " + str(mood) + "/100", clr='red')
         negative = False
     if positive:  # same as "if positive == True:"
         clrprint("MOOD +" + str(moodchange) + "! Your current mood is
         " + str(mood) + "/100", clr='green')
         positive = False
     # delay program between one to three seconds
     time.sleep(random.randint(1, 3))
     # set a 50% of chance for a response from our virtual chat host
     response = random.choice([True, False])
     if response:
         if mood > 30:
             clrprint(
                 name + ": " + timer.strftime("<%A %H:%M> ") +
                 random.choice(neutralmoods),
                 clr='white')
         else:
             clrprint(name + ": " + timer.strftime("<%A %H:%M> ") +
             random.choice(badmoods),
                      clr='white')

Listing 8-4The fourth part of the listing for the Universal Chatting Simulator in Python

第四部分:游戏结束

在一个非常繁忙的主循环之后,我们终于到达了聊天模拟器的最后一部分。它主要是通过添加一些路径分支来保持分数(见清单 8-5 )。

程序中的评分是基于虚拟聊天者发送的聊天消息的数量;每条信息值三分。这个模拟器程序有两种结局。在“坏”的一个中,观看者被简单地告知虚拟聊天主持人的情绪降到了零。另一个更乐观的结局给予观众价值 1000 分的“欣快奖励”。

# game over
score = messages_sent * 3  # make each message from chat worth 3 points

if mood < 1:  # bad ending
     clrprint("Your mood fell to 0/100!", clr='yellow')
else:  # good ending
     clrprint("Your mood reached 100/100!", clr='yellow')
     clrprint("Euphoria bonus: 1000", clr='yellow')
     score = score + 1000  # add euphoria bonus to score

timespent = timer - initial_time  # calculate elapsed time
print("Time spent in the chat: " + str(timespent))
print("Your chat sent " + str(messages_sent) + " messages")
print("SCORE: " + str(score))
# if current score is greater than saved highscore, save that to file
with open("highscore.txt", "r+") as scorefile:
     hiscore = scorefile.read()
     if not hiscore:  # if file is empty..
         hiscore = '0'  # ..assign zero into it
     if score > int(hiscore):  # if current score is greater than the
     stored highscore
         print("NEW HIGHSCORE MADE!")
         scorefile.seek(0)  # "rewind" to the beginning of the file
         scorefile.write(score)  # write the new score into "highscore.txt"
     else:
         print("CURRENT HIGHSCORE: %s" % hiscore)

Listing 8-5The fifth and final part of the listing for the Universal Chatting Simulator in Python

在清单 8-4 中,我们有很多方法在工作。作为回顾,让我们记录通用聊天模拟器使用的主要 Python 方法(见表 8-1 )。

表 8-1

通用聊天模拟器使用的一些 Python 方法

|

方法

|

描述

|

清单 8-4 中的使用示例

|
| --- | --- | --- |
| 打开( ) | 打开文件进行读取和/或写入 | 文件=打开(名称,' r ') |
| 阅读( ) | 读取打开文件的内容 | hiscore = scorefile.read() |
| seek() | 设置文件的位置 | scorefile.seek(0) |
| random.randint() | 返回一个随机整数 | neutralmoods[random.randint(0,len(neutralmoods) - 1)], |
| random.choice() | 随机选择一个元素 | response = random . choice([对,错]) |
| str() | 将变量转换为字符串 | print("SCORE: " + str(score)) |
| int() | 返回一个整数对象 | 如果 score > int(hiscore): |
| len() | 返回对象/字符串长度 |   |

C# 线程竞速器

为了让你回忆一下线程在 C# 中是如何工作的,请看一下清单 8-6 。在这个小清单中,发送了三个线程来运行五米短跑。该程序演示了 C# 语言的以下功能:

  • 对共享数据/变量的简单线程化和基于线程的访问

  • 使用锁定对象和互锁。减量( )方法

  • 将随机值赋给变量,包括字符串数组

  • 定义方法(例如 MakeNames)并从中检索数据

using System;
using System.Threading;
public class ThreadRacer
{
// Define an empty (i.e. null) string for holding the winner of the race
string winner = null;
// Define an integer for examining the threads' arrival order at finishing line
int place = 3;
// Create an object, happylock, for thread-locking purposes
static object happylock = new object();
      // Define Racer, a thread-based method
      public void Racer()
      {
            int distance = 5; // Set racing distance to 5

            // Assign variable "handle" with the current thread so we may read
            // thread properties, such as Name
            Thread handle = Thread.CurrentThread;
      // Loop while a thread's distance is greater than zero
while(distance > 0) {
Console.WriteLine(handle.Name+" is at " + distance + " meters from the goal.");
// Reduce racer's distance by one using an Interlocked.Decrement-method
Interlocked.Decrement(ref distance);
            }
// Summon our locking object
lock(happylock) {
// If the winner-variable is still set to "null", announce current thread
// as the winner
      if(winner == null) Console.WriteLine(handle.Name + " WON 1st place!");

// Use an Interlocked.Decrement-method to subtract 1 from variable "place"
         Interlocked.Decrement(ref place);
winner = handle.Name;
            if(place==1) Console.WriteLine(handle.Name + " came in 2ND..");
            if(place==0) Console.WriteLine(handle.Name + " came in 3RD..");
            }
        }
      public static string MakeNames() {
      // create new object, random0, using the C# Random class
      var random0 = new Random();
      // Create two string-arrays, first_names and last_names
      string[] first_names = { "Peter", "Paul", "Patrick", "Patricia", "Priscilla", "Pauline" };
      string[] last_names = { "Plonker", "Pillock", "Prat", "Pecker" };
      int f_index = random0.Next(first_names.Length);
      int l_index = random0.Next(last_names.Length);
      // return name as a string:
      return (first_names[f_index] + " " + last_names[l_index]);
      }
      public static void Main() // Main function
       {
       ThreadRacer racer = new ThreadRacer();
       Thread thread1 = new Thread(new ThreadStart(racer.Racer));
       Thread thread2 = new Thread(new ThreadStart(racer.Racer));
      Thread thread3 = new Thread(new ThreadStart(racer.Racer));
            thread1.Name = "[Racer A] " + MakeNames();
            thread2.Name = "[Racer B] " + MakeNames();
            thread3.Name = "[Racer C] " + MakeNames();
            Console.WriteLine("Welcome to THREAD RACER!\n");
            thread1.Start(); // Start threads
            thread2.Start();
            thread3.Start();
            }
    }

Listing 8-6A program demonstrating threading in C#

在清单 8-6 中,我们为线程同步制作了一个锁定对象 happylock。我们还使用了一种新方法,互锁。减量,将“距离”和“位置”变量减一。为了在基于线程的项目中进行有效和安全的添加,C# 还提供了一种称为 Interlocked 的方法。增量(我们在 Thread Racer 中不需要它)。在 C# 中处理基于线程的代码时,这两种方法通常可以取代标准的减法和加法运算符(例如- variable 或++variable)。

现在,我们在清单 8-6 中精心制作了一个我们自己的方法;这将是的成名之作。这个方法是用来演示数组的,更具体地说是字符串类型的数组。这些数组用名称填充,并作为单个字符串返回,供 main 函数使用。

Thread Racer 中的 f_indexl_index 变量用于存储字符串数组 first_nameslast_names 的所需索引位置。这些索引位置是通过在数组长度上应用下一个方法(一个随机数发生器)依次创建的。这些长度然后通过调用恰当命名的长度方法来推导。

下面是清单 8-6 中的一行代码:

int f_index = random0.Next(first_names.Length);

这是这样的:“让整数 f_index 等于一个随机数,其最大值为名字数组的长度。”脱口而出。

我们可以将方法的输出直接添加到变量中。以清单 8-6 中的这一行为例:线程 1。name = "[Racer A]"+MakeNames();其中我们将字符串“[Racer A]”与输出中发生的任何 MakeNames 结合起来,只使用了一个简单的加号运算符。

Thread Racer 的线程调度也是由操作系统执行的。这意味着过一会儿你可能会得到同样的结果。

C# 的快乐测验

接下来,清单 8-7 向您展示了一个基于控制台的小测验程序。它展示了这种优秀语言的以下特征:

  • 基本文件操作和线程

  • 字符串格式

  • 程序流、用户交互和循环

  • 变量和迭代

Jolly Quiz 使用两个独立的文件, questions.txtanswers.txt 。它们都是自上而下处理的,问题文件中的每一行都与答案文件中的同一行相对应。这种方法使添加新问题和修改现有问题变得容易,只需编辑这两个文本文件。

希望用户在程序执行期间键入他们的答案。一个线程对象在后台运行,独立于正在进行的测验,告诉用户“快点!”每四秒钟。每个正确答案加十分。在测验结束时,将向用户显示他们的分数以及正确答案的百分比。

using System;
using System.IO;
using System.Threading;
class JollyQuiz
{
       public static void Main()
      {
      // Declare three integer-variables
      int counter=0, score=0, percentage=0;
      // Declare a boolean-variable (takes: true or false)
      bool countdown=true;
      Console.WriteLine("Welcome to THE JOLLY QUIZ PROGRAM!\n");
      // Create a new thread-object, timerthread1
       Thread timerthread1 = new Thread(delegate() {
      Thread.Sleep(6000); // Sleep for 6 seconds at first
    while(true) { // Create an infinite loop inside our thread
          Console.WriteLine("HURRY UP!");
          Thread.Sleep(4000);
      // End thread processing if countdown is set to "false" in main program
      if(!countdown) break; // This reads: if countdown is NOT "true" exit loop
    }
       });
      // Open the question and answer -files. These should work from the
      // same directory as your project file
      var q = File.ReadAllLines("questions.txt");
      var a = File.ReadAllLines("answers.txt");
      timerthread1.Start(); // Start the timer-thread

      foreach(string lines in q) // Iterate through the questions-file
            {
            Console.WriteLine("{0}\nEnter response: ", lines);
      // Convert response to ALL CAPS to eliminate input capitalization issues
      // i.e. make "Helsinki" equally valid to "HeLSiNKI"
      string response = Console.ReadLine().ToUpper();
            ++counter;
            // Compare user input against correct answers
            if(response.Equals(a[counter-1])) {
            Console.WriteLine("{0} is correct!", response); score+=10;
      // use else if to determine whether the user wrote a wrong answer
      // or simply pressed enter
      } else if(response!="") Console.WriteLine("{0} is wrong..", response);
                  else Console.WriteLine("Type something next time!");
            }
      // No need to remind the user to hurry up after the quiz is over;
      // so we next tell timerthread1 to end processing
      countdown = false;
      Console.Write("\nYour score: {0}. ", score);
      percentage = score*100/(counter*10);
      Console.WriteLine("You knew/guessed {0}% right on the {1} questions presented.", percentage, counter);
      // Display shaming message for motivation after a poor score
      if(percentage < 50) Console.WriteLine("Shame on you!");
      // Display a different message for a more impressive score
      if(percentage >= 90) Console.WriteLine("Well done!");
      }
}

Listing 8-7A quiz program in C# demonstrating basic file operations and threading

你会注意到清单 8-6 和 8-7 有一个特殊的变量类型, var 。这是 C# 中的一个隐式变量定义,简单地说就是允许编译器程序决定变量的类型。对于 C# 中较小的项目,这种方法通常很好。

一本 Java 语言的食谱

接下来我们将带着一个名为美味芬兰食谱的小程序回到 Java 的世界。这个相当简单的清单基本上显示了容易定制的文本文件。

import java.io.File;
import java.io.FileNotFoundException;
import java.util.Scanner;

public class FinnishRecipes {
  // Define LoadText, a method for loading text-files
  // which takes a file path and filename as its arguments
  static void LoadText(String path1, String section) {
    try {
      File fileobject1 = new File(path1 + section);
      // Create a Scanner-object, reader1, for user input
      Scanner reader1 = new Scanner(fileobject1);
      while (reader1.hasNextLine()) {
        String output1 = reader1.nextLine();
        System.out.println(output1);
      }
       reader1.close();
    } catch (FileNotFoundException e) { // Throw exception/error if needed
      System.out.println("File " + section + " not found!");
    }
  }
  static void DisplayMenu() {
  System.out.println("DELICIOUS FINNISH RECIPES\nEnter choice:");
  System.out.println("[1] Salads");
  System.out.println("[2] Main Courses");
  System.out.println("[3] Custom recipes (make your own)");
  System.out.println("[4] Quit)");
  }
  // Define Main method
  public static void main(String[] args) {
    // Display main menu
    DisplayMenu();
    Scanner keyscanner1 = new Scanner(System.in);

    while(true) { // Create an infinite while-loop
    String input1 = keyscanner1.nextLine();
    // "C:\\" refers to the file-path to the most common Windows root-drive
    // modify this path as per your file locations
    if(input1.equals("1")) LoadText("","salads.txt");
    if(input1.equals("2")) LoadText("","maincourses.txt");
    if(input1.equals("3")) LoadText("","custom.txt");
    if(input1.equals("4")) break;

    // Display menu again if user inputs a plain enter
    if(input1.equals("")) DisplayMenu();
    } System.out.println("Have a great day!");
  }
}

Listing 8-8A program in Java demonstrating file access

在清单 8-8 中,我们创建了一个方法 LoadText ,它接受两个参数,即文件路径和名称。然后,它开始加载指定的文本文件,并在屏幕上逐行显示其内容。我们还有另一个方法, DisplayMenu ,它只是用来向用户显示可用的选项。两种方法都被定义为 void ,这意味着它们不返回任何信息。

清单 8-8 中有一个 try-catch 块,如果找不到文件,它会发出声音,也就是抛出一个异常。无限 while 循环使程序一直运行,直到用户在键盘上输入“4”,在这种情况下,执行 break 关键字。

Java 中的秒表

下面的程序就像一个简单的秒表。你输入几秒钟让它嘎吱作响,然后它会告诉你时间到了。这个程序代表了 Java 线程的一个非常基本的实现,由一个主线程组成。

import java.util.Scanner;
public class Counter {
  public static void main(String[] args) {
  int counter1=0;
  Scanner keyscanner1 = new Scanner(System.in);

  // Loop while counter1 equals zero
  while(counter1 <= 0) {
  System.out.println("Enter timer count (in seconds): ");
  String input1 = keyscanner1.nextLine();
  // begin a try-catch block
  try {
  // Convert "input1" to integer
  counter1 = Integer.parseInt(input1);
  } catch (NumberFormatException e) {
    System.out.println("Enter value in numbers only!");
  }
  if(counter1<0) System.out.println("(Positive numbers only!)");
  } // Loop end

  // Loop while counter is greater than zero
  while(counter1 > 0) {
  System.out.println("Time left: " + counter1 + " sec");
  try
  { Thread.sleep(1000); }
  catch(InterruptedException ex)
  { Thread.currentThread().interrupt(); }
  --counter1;
  } // Second loop end
  System.out.println("All done!");
  }
}

Listing 8-9A listing for a stopwatch program

in Java

清单 8-9 有两个 while 循环。第一个是从用户那里获取输入,并确保它符合特定的规则。主要地,输入仅由正数组成。如果输入了字母或其他非数字字符,程序会抛出一个异常,准确地说是一个 NumberFormatException 。此外,第一个循环拒绝接受负数,并在需要时发布一条消息。

在第二个循环中,我们使用带有 Thread.sleep(1000) 的一行来体验期望的延迟量。请注意,为了让 sleep 方法在 Java 中工作,您需要检查它是否有异常。在清单 8-9 中,我们为此使用了一个经典的 try-catch 块。

最后

读完本章后,您将会对 Python、C# 和 Java 的以下方面有所了解:

  • 数据结构、程序流程和基本文件操作

  • 初级线程技术

  • 使用 try-catch 块的异常处理

  • 功能定义和访问

第九章的代码会轻得多,都是关于一些可爱的图,也就是那些用通用建模语言(UML) 创建的图。

九、UML 类图

这一章致力于统一建模语言的奇迹,它是软件设计中一个相当普遍的工具——也当之无愧。在本书的前面,我们仅仅触及了 UML 的表面,现在我们将更深入地探索 UML 提供的更多可能性,因为它与类建模有关。

可视化面向对象的范例

我们在第四章中介绍了面向对象的范例。书中的大部分代码清单实际上都是这种范例,包括它们的类、对象和方法。将 UML 视为软件开发的预编码阶段的一个有价值的工具。你用它描绘出所有与 OOP 相关的机制,包括类和它们与其他类的关系。

通用建模语言有两个标准,即 UML 1.x(最初于 1996 年)和 UML 2.x(于 2005 年首次发布)。这两个标准都包含许多类型的图,几乎可以用于任何建模目的。

UML 图的类别

UML 图类型的整个范围超出了本书的范围。然而,知道 UML 有哪些变种是很有用的。概括地说,它通常分为两个主要类别,然后再分为许多子类。许多较大的项目可能需要以下大部分(非详尽的)图表列表;它们是互相排斥的。

  • 行为图:与结构图不同的是,行为图集中于描绘一个正在运行的系统。

    1. 用例:UML 中的参与者是与系统交互的一方。用例图描述了(人类)参与者和特定系统之间的交互。用例关注系统提供的特定功能。例如,一个人从 ATM 机取钱是用例图的一个潜在场景。

    2. 序列图:这些图关注的是对象发送消息的时间顺序。因此,当对象之间的交互被精确地建模时,我们使用 UML 序列图。

    3. 状态图:UML 中的状态意味着一个对象持有的不同种类的信息,而不是它们的行为。当对系统状态的变化建模时,使用状态图。

    4. 活动图:当我们需要可视化系统中的控制流时,这就是要使用的图的类型。基本上,活动图提供了一个系统在执行时如何工作的概念。

  • 结构图:这些类型的图是用来模拟静态系统的本质的。

    1. 类图:这些可能是 UML 中最常用的图。在这一章中,我们将主要关注类图。正如您可能已经猜到的,它们处理的是类方面,这是面向对象编程(OOP)范例的基础。

    2. 对象图:明显与类图相关,对象图描述的是类的实例。在构建系统原型时,经常会用到这些类型的图。

    3. 组件图(Component diagrams):这些图关注的是系统中软件组件的类型以及它们之间的联系。这些组件通常被称为物理资产,尽管从技术上讲,它们倾向于完全驻留在数字层面。

    4. 部署图:这些用于可视化系统的完整布局,显示系统的物理和软件部分。部署图也可以称为系统组件的拓扑。

回到 UML:类图

如前所述,UML 可以用来建模几乎任何东西,一个类图可以帮助我们可视化一个面向对象的软件项目。让我们从简单的事情开始(见图 9-1 )。

img/507458_1_En_9_Fig1_HTML.jpg

图 9-1

一个简单的 UML 类/对象图

图 9-1 展示了 UML 中的类,包括它们的变量和方法以及类间关系;它还具有一个单一的 UML 对象图。

图 9-1 中的主类叫做 Book。它有三个变量(即标题、出版商和出版年份)。如您所见,我们还需要在类中指定变量的类型(例如,String)。UML 中属性或方法前面的加号(+)表示公共访问修饰符。带有破折号(-)字符的类成员表示私有访问修饰符。

你可以像我们在图 9-1 中所做的那样,直接在 UML 图中添加有用的注释;这些将采取右上角折叠的矩形的形式。

现在,简单的线条标记了 UML 中类和其他实体之间的关联。图 9-1 中这些行旁边的数字和星号是 UML 中多样性的演示。这个概念用于指示一个类可以提供或被允许与之交互的实例(即对象)的数量。

转到图 9-1 的对象部分,我们有一个主类的实例,叫做 exampleBook1 。在 UML 中,对象可以被表示为尖边或圆边的盒子;为了多样化,我们选择了后者。

UML 中基于树的继承

继承可以在 UML 中以简洁的方式表示。为此,我们可以使用基于树的方法(参见图 9-2 )。

img/507458_1_En_9_Fig2_HTML.jpg

图 9-2

使用基于树的方法演示 UML 中的继承的图表

图 9-2 中的图表展示了三个专业化水平。首先,您有通用的业务类。接下来是三个专业类别,即出版商、面包店和香水店。最后,我们有两个不同类型的出版商类最专业的水平,一类是婚礼面包店和奢侈品香水店。

在 OOP 中,术语基类也被称为父类。子类通常被称为子类。

图 9-2 中的业务类定义为摘要;在 UML 中,斜体的类名表示这一点。这些类为子类提供了特定的继承方法。抽象类不能直接实例化。相反,您使用它们的非抽象(即具体)子类之一来创建对象。

Java 中的图 9-2

如果将图 9-2 翻译成 Java 会是什么样子?看看清单 9-1 中可能的解决方案。

在清单 9-1 中,业务是基类。所有其他的类都被定义在它的文件 Business.java 中,以避免拥有多个源文件(例如,等等)。).**

// Define abstract base class
abstract class Business {
// Define class attributes/variables
public double turnover = 20000;
public String address = "none";
// Define method for displaying address attribute/variable
void DisplayAddress() {
 System.out.println(address);
}
   // Create main method
   public static void main(String[] args)
   {
   // Uncommenting the next line will throw an error
   // Business business1 = new Business();
   // Create new Bakery object, happybakery, and display its address
   Bakery happybakery = new Bakery();
   System.out.println("A new bakery is opened at " + happybakery.address);
   // Create new FictionPublisher object, jolly_books, and display its turnover
   FictionPublisher jolly_books = new FictionPublisher();
   System.out.println("Fiction publisher Jolly Books had an unfortunate turnover of £" + jolly_books.turnover + " in 2020");
   // Create new NonFictionPublisher object, silly_books, set and display its turnover
   NonFictionPublisher silly_books = new NonFictionPublisher();
   System.out.println(("Non-fiction publisher Silly Books had a great turnover of £" + (silly_books.turnover + " in 2020")));
   // Create new LuxuryPerfumerie object, exquisite_odors, set and display its address
   LuxuryPerfumerie exquisite_odors = new LuxuryPerfumerie();
   exquisite_odors.address = "10 Wacky Avenue";
   System.out.print("A wonderful luxury perfumerie is located at ");
   exquisite_odors.DisplayAddress(); // Summon method inherited from Business class
   }
}
// Define the rest of the classes
class Bakery extends Business { String address = "2 Happy Street"; }
class WeddingBakery extends Bakery { }
class Perfumerie extends Business { }
class LuxuryPerfumerie extends Perfumerie { }
class Publisher extends Business { }
class FictionPublisher extends Publisher { double turnover = 4.55; }
class NonFictionPublisher extends Publisher { /* turnover is inherited from Business class */ }

Listing 9-1A Java implementation

of Figure 9-2 demonstrating inheritance (filename Business.java)

C# 中的图 9-2

接下来让我们观察图 9-2 在 C# 上的实现。您可能还记得本书前面的章节,Java 和 C# 是非常相似的语言。

using System;
abstract class Business {
    //  Define class attributes/variables
    public double turnover = 20000;
    public string address = "none";
    // Define method for displaying address attribute/variable
            void DisplayAddress() {
            Console.WriteLine(address);
            }
    // Create main method
    public static void Main() {
                // Uncommenting the next line will throw an error
                // Business business1 = new Business();
        //  Create new Bakery, happybakery, and display its address
        Bakery happybakery = new Bakery();
        Console.WriteLine("A new bakery is opened at " + happybakery.address);
        //  Create new FictionPublisher, jolly_books, and display its turnover
        FictionPublisher jolly_books = new FictionPublisher();
        Console.WriteLine("Jolly Books had an unfortunate turnover of £"
        + jolly_books.turnover + " in 2020");
               // Create NonFictionPublisher, silly_books, set and display its turnover
               NonFictionPublisher silly_books = new NonFictionPublisher();
               Console.WriteLine("Silly Books had a great turnover of £"
        + silly_books.turnover + " in 2020");
    // Create new LuxuryPerfumerie, exquisite_odors, set and display its address
        LuxuryPerfumerie exquisite_odors = new LuxuryPerfumerie();
        exquisite_odors.address = "10 Wacky Avenue";
        Console.Write("A wonderful luxury perfumerie is located at " );
exquisite_odors.DisplayAddress(); // Summon method inherited from Business class
    }
}
//  Create the rest of the classes
class Bakery : Business { new public string address = "2 Happy Street"; }
class WeddingBakery : Bakery { }
class Perfumerie : Business { }
class LuxuryPerfumerie : Perfumerie { }
class Publisher : Business { }
class FictionPublisher : Publisher { new public double turnover=4.55; }
class NonFictionPublisher : Publisher { /* turnover is inherited from Business class */ }

Listing 9-2A C# implementation

of Figure 9-2 demonstrating inheritance

列表 9-1 和 9-2 几乎相同。首先,在 Java 和 C# 中,类的实现方式非常相似。自然,有一些差异(见表 9-1 )。

表 9-1

清单 9-1 和 9-2 的主要区别

|

元素

|

清单 9-1 (Java)

|

清单 9-2 (C#)

|
| --- | --- | --- |
| 类继承 | 类发布者扩展业务{ } | 类发布者:商业{ } |
| 主要方法 | public static void main(String[]args) | public static void Main( ) |
| 控制台输出 | System.out.println( … ) | 控制台。WriteLine( … ) |
| 成员声明 | 双周转= 4.55; | 新公双成交额= 4.55; |

Python 中的图 9-2

现在来点不同的。让我们看一看 Python 中的图 9-2 可能是什么样子(参见清单 9-3 )。为了在 Python 中使用抽象类,我们需要导入一个名为 ABC 的代码模块。然后,我们让我们的基类 Business 继承这个模块。行 @abstractmethod 是所谓的 Python decorator。你可能已经猜到了,它告诉我们一个方法应该被认为是抽象的。

# import code module for working with abstract classes, ABC
from abc import ABC, abstractmethod
# define classes, starting with an abstract Business class
class Business(ABC):
    def __init__(self): # set class attribute default values
        self.address = "none"
        self.turnover = 20000

    @abstractmethod # define abstract method
    def Display_Address(self):
        pass

class Publisher(Business):
    def Display_Address(self):
        pass

class Bakery(Business):
    def Display_Address(self):
        pass
    def __init__(self):
        self.address = "2 Happy Street"

class Perfumerie(Business):
    def Display_Address(self):
        pass

class FictionPublisher(Publisher):
    def __init__(self):
        self.turnover = 4.55

class NonFictionPublisher(Publisher):
    pass

class WeddingBakery(Bakery):
    pass

class LuxuryPerfumerie(Perfumerie):
    def __init__(self):
        self.address = "10 Wacky Avenue"
    def Display_Address(self): # override abstract method
        print(self.address)

happybakery = Bakery() # Create new Bakery object
print("A new bakery is opened at", happybakery.address)
jolly_books = FictionPublisher() # Create new FictionPublisher object
print("Jolly Books had an unfortunate turnover of £", jolly_books.turnover, "in 2020")
silly_books = NonFictionPublisher() # Create new NonFictionPublisher object
print("Silly Books had a great turnover of £", silly_books.turnover, "in 2020")
exquisite_odors = LuxuryPerfumerie() # Create new LuxuryPerfumerie object
print("A wonderful luxury perfumerie is located at ", end = '')
exquisite_odors.Display_Address() # summon Display_Address-method

Listing 9-3A Python implementation

of Figure 9-2

UML 自行车

img/507458_1_En_9_Fig3_HTML.jpg

图 9-3

用 UML 展示聚合关系的图表

接下来,让我们探索如何用 UML 建模一个基本的踏板驱动的车辆。图 9-3 中引入了新元素。这是由空心菱形符号描绘的集合体

聚合关联意味着一个类可以没有其他类而存在。为了拥有一辆功能齐全的自行车,我们需要所有的部件。但是,即使我们移除了其他组件,它们仍然会存在。

蟒蛇皮自行车

坚持使用 Python,让我们对图 9-3 创建一个可能的编程解释(参见清单 9-4 )。

class Frame:
            # class constructor
            def __init__(self):
             print('Frame ready.')
            weight = 10.5 # define a class variable

class Saddle:
            # class constructor
            def __init__(self):
            print('Saddle installed.')
            material = "rubber" # define a class variable

class Drivetrain:
            # class constructor
            def __init__(self):
            print('Drivetrain installed.')
            type = "one-speed" # define a class variable

            # define class methods
            def pedalForward(self):
            print("Pedaling forward!")

            def brake(self):
            print("Braking!")

class Wheels:
            diameter = 0
            # class constructor
            def __init__(self, diameter):
            print('Wheels installed.')
            self.diameter = diameter

class Handlebars:
            # class constructor
            def __init__(self):
            print("Handlebars installed.")

            # define class methods
            def turnLeft(self):
            print("Turning left..")

            def turnRight(self):
            print("Turning right..")

class Bicycle:
            # define a class variable
            make = "Helkama"
            # set up class constructor & composition
            def __init__(self):
            self.my_Frame = Frame()
            self.my_Saddle = Saddle()
            self.my_Drivetrain = Drivetrain()
            self.my_Wheels = Wheels(571) # pass a new diameter value
            self.my_Handlebars = Handlebars()

def main(): # create main method
            # create Bicycle-object, "your_bike"
            your_bike = Bicycle()
            print("The wheels in your " + your_bike.make + "-bike are " + str(your_bike.my_Wheels.diameter) + " mm in diameter. The frame weighs " + str(your_bike.my_Frame.weight)+" lbs.")
            print("This bike has a " + your_bike.my_Drivetrain.type + " drivetrain and the saddle is made of the finest " + your_bike.my_Saddle.material+".\n")
            # summon class methods
            your_bike.my_Drivetrain.pedalForward()
            your_bike.my_Handlebars.turnLeft()
            your_bike.my_Drivetrain.pedalForward()
            your_bike.my_Handlebars.turnRight()
            your_bike.my_Drivetrain.brake()
if __name__ == "__main__":
            main() # execute main method

Listing 9-4A Python implementation of Figure 9-3 (i.e., a UML diagram for a bicycle). The Component Inspection Unit A is not implemented here for the sake of brevity

问:你自己如何用 Java 和/或 C# 解释图 9-3?

UML 中的个人计算机

现在让我们用复合关联和聚合关联来建模。这是为了让你考虑你需要在 UML 中区分这两种类型的关联的场景。

图 9-4 代表一台典型的个人电脑。它旨在描述一个功能系统所需的所有主要组件。然而,使用复合关联(即,实心菱形符号)仅描绘了这些组件中的一些。这是因为,从理论上讲,一台个人电脑没有它们也能正常运行。这可能不是最有用的系统,但至少它可以打开并显示错误信息。

img/507458_1_En_9_Fig4_HTML.jpg

图 9-4

展示 UML 中组合关系的图表

图 9-4 中用复合关联描述的基本计算机组件:

  • 电源、主板、CPU 和 RAM 芯片

用集合关联描述的不太重要的组件:

  • 存储单元(例如,硬盘驱动器)、输入设备(例如,鼠标和/或键盘)和显示适配器/视频卡(集成视频芯片可以与 CPU 一起提供)

请参见表 9-2 了解聚合和复合关联之间的差异。

表 9-2

UML 中聚合关联和复合关联的主要区别

|   |

总计

|

复合材料

|
| --- | --- | --- |
| 关联强度 | 无力的 | 强烈的 |
| 关系 | "有一个" | “的一部分” |
| 属国 | 无论超类存在与否,子类都可以存在 | 子类需要一个超类才能存在 |
| 例子 | 即使一个班级的学生毕业了,?? 大学依然存在关闭单个部门不会终结整个公司 | 没有了的房子,房间就毫无意义一个功能正常的心脏对于一个 ?? 人来说是必须的 |

UML 中的实现和依赖

实现指的是与两组元素的关系,其中一组代表一个规范,另一组代表它的实现。实现的实际工作取决于上下文,并没有在 UML 中严格定义(见图 9-5 )。

img/507458_1_En_9_Fig5_HTML.jpg

图 9-5

UML 中简单实现关系的三个例子

在图 9-5 的图表中,你会看到 UML 实现关系的三个简单例子。在第一种情况下,搜索算法为在线搜索引擎提供功能。同样,显卡需要显卡驱动软件才能真正显示任何图像。最后,支付事件可以通过三种可用的支付方式实现:现金、卡或支票。

接下来,我们有依赖(见图 9-6 )。这种类型的图表表示从属元素和独立元素之间的连接。用虚线和简单的箭头表示,依赖关系本质上是单向的。

img/507458_1_En_9_Fig6_HTML.jpg

图 9-6

UML 依赖的两个例子

自反联想

一个自反关联用来表示属于同一个类的实例。当一个类可以被分成许多责任时,我们可以使用这种类型的关联。

在图 9-7 中,我们有一个名为雇员的类,它与自己有一个关联,正如现在熟悉的简单线条所描绘的。Employee 类用来代表受人尊敬的主管和地位低下的学员;您会注意到 UML 多样性信息也被插入到这个图中。

img/507458_1_En_9_Fig7_HTML.jpg

图 9-7

UML 中自反关联的一个例子

现在,图 9-7 以两种不同的方式描绘了相同的设置。左图显示了基于角色的方法,因为我们展示了两种不同的角色(即主管和学员)。右图使用了一个命名的关联。

UML 类图:基本元素

在这一点上,回顾一下我们到目前为止遇到的 UML 元素是一个好主意(见图 9-8 )。

img/507458_1_En_9_Fig8_HTML.jpg

图 9-8

显示 UML 类图中使用的大多数基本元素的图表

UML 用例

尽管一些 UML 图类型超出了本书的范围,但是还有一种类型您应该熟悉。这是用例,它以最简单的方式展示了用户与系统的交互。这些通常是包含很少技术细节的高级图表。

我们现在将引入一些新的 UML 元素。这些是角色系统用例。此外,用例图用熟悉的简单线条来描述关系。请看图 9-9 中的简单用例图。

img/507458_1_En_9_Fig9_HTML.jpg

图 9-9

一个描述书籍写作过程中章节回顾的 UML 用例

你会看到图 9-9 中的两个演员,由一些可爱的简笔画代表。保持演员在本质上的明确性通常是一个好主意,就像命名一个作者作者而不是约翰旋律羊毛索克斯。在 UML 中,发起交互的参与者被称为主要参与者。次要角色对主要角色的行为做出反应。

系统的第二个元素在 UML 中用一个简单的矩形表示;在图 9-9 的情况下,称为章审。在系统内部,我们有内部的用例,用椭圆形表示。这些代表了完成特定用例所需的不同阶段。最后,我们用熟悉的简单线条来表示参与者和用例的不同关联。

现在,图 9-9 中的图表描绘了以下事件顺序:

  • 作者,主要演员,写一章。

  • 完成的章节由作者章节审阅者(次要演员)共同阅读。这由两个参与者之间的共享关联来表示。

  • 章节审阅者建议对章节进行修改。

  • 最后,作者将更改提交到一个章节中,以结束该场景。

更多关于用例的信息

是时候看看一个稍微复杂一点的用例来引入两个新元素了:扩展了包含了关系(见图 9-10 )。正如 UML 类图的情况一样,我们也可以将那些有用的注释元素合并到 UML 用例中。

img/507458_1_En_9_Fig10_HTML.jpg

图 9-10

描述电子邮件服务基本功能的 UML 用例

图 9-10 描绘了一个简单的电子邮件用例场景。我们将账户所有者(即人类用户)作为主要参与者,将电子邮件服务器作为次要参与者。图 9-10 包含以下事件序列:

  • 账号拥有者用密码登录。如果输入了错误的代码,则访问名为错误密码的可选用例;这个结果用箭头表示,关键字 < <延伸> >

  • 帐户所有者也可能希望阅读一些帮助文件。这也是一个可选场景,因此分配了关键字<>

  • 用例成功登录的最终结果自动导致另一个用例验证密码,如关键字 < <所示,包括> > 。二级参与者电子邮件服务器参与到密码认证过程中,因此在图 9-9 中,在所述参与者和验证密码的用例之间存在一条关联线。

  • 用例验证密码导致另外三个用例:查看电子邮件删除电子邮件发送电子邮件。自然,次要角色电子邮件服务器也参与了所有这些,尽管只是被动的角色;帐户所有人可以随意启动所有这三个操作。

    当描述用例中的关系时,注意你的箭头的方向。

UML 工具

除了纸和笔,如果你想尝试在你的计算机上绘制 UML,有很多很棒的免费选项。我们现在将回顾这些工具的一些最好的例子。

UMLet 和 UMLetino 由 UMLet 团队制作

一个免费且精彩的开源工具, UMLet 将让你用其直观的拖放式用户界面立刻绘制出时髦的类图。关联线很好地捕捉到类的边缘并保持在那里,使组织你的图变得轻而易举。

UMLet 的特点是为类图、用例、时序图和许多其他的可视化元素提供现成的选择。该计划出口到许多常见的图像文件格式,包括 PNG,GIF 和 SVG。对于那些需要一个漂亮、干净的 UML 设计工具的人来说,UMLet 绝对是必备的。本书中的所有图表都是用 UMLet 创建的。

在 UMLet 中发现的一个有趣的特性是它的半自动图表创建。通过导航到文件从文件或目录生成类元素,您可以设置您的 UML 图的初始阶段。不幸的是,当使用这个特性时,UMLet 在创建关联方面有一些问题。

UMLetino 是同一软件的基于浏览器的版本。它拥有离线版本的大部分功能。此外,UMLetino 还集成了 Dropbox,用于导入和导出图文件。但是,该软件仅支持 PNG 图像文件的输出。

Diagrams.net 团队的 Diagrams.net

由专门的团队积极维护,Diagrams.net是一个通用的开源工具,有浏览器版和桌面版。后者不需要用户注册。Diagrams.net 既面向基本的 UML 工作,也面向高级用户,具有像层和“插件”这样的特性来增加功能。

以前被称为“draw.io ”,该软件在导出 UML 图(包括 Adobe PDF)时提供了强大的文件格式支持。不仅如此,通过选择文件导出为高级,你可以设置诸如 DPI(每英寸点数)和边框宽度等属性。

由 Paul Hensgen 和 Umbrello UML Modeler 作者编写

从 2001 年开始开发, Umbrello 对于任何类型的 UML 工作来说都是一个伟大的工具。该软件有一个直观的用户界面,以及从 UML 生成代码的选项。Umbrello 可以将你的图导出为(通常)函数式 Java、C#、Python 和许多其他编程语言;只需导航到代码代码生成向导,设置您的选项,然后单击下一步。该软件还提供了出色的格式化功能,包括让您正在处理的图表可以使用您的所有操作系统字体。

不要让花哨的默认配色方案欺骗了你——对于许多类型的图表工作来说,Umbrello 是一个非常有用的应用程序。

最后

读完这一章,你已经获得了以下知识:

  • UML 类图和用例图的基本元素

  • 如何将简单的 UML 图翻译成 Java、C# 和 Python

编后记

所以我们到了这本书的结尾。过去你可能会觉得可怕的编程术语已经变得不那么可怕了。到目前为止,变量、类和对象对您来说至少已经有点熟悉了。您知道如何用 Java、C# 和 Python 制作简单的基于文本的应用程序。或许你甚至可以在聚会上随口解释一下 Python 对缩进的严格要求。但是你的程序员之旅还远没有结束。了解了三种语言的编码基础,现在你可以开始在这个领域获得更多的经验。一个解决问题的世界在等着你。

编程可能会令人沮丧。但是,让一个讨厌的、不起作用的清单最终发挥作用有一种独特的满足感。无论你最终是否在专业环境中编码,编程在某些时候可能会成为一种建设性的嗜好。它也是治疗失眠和/或无聊的良方。

显化有意义的编码项目需要大量的意图和时间,无论它们是视频游戏还是由雇主定义的原子化编程任务。但是随着程序员的成长,没有一行是浪费的。即使是错误信息也能教会你最有价值的东西。

阿达·洛芙莱斯是勋爵拜伦女士的私生子,被认为是世界上第一批程序员之一。她与她那个时代的几位杰出科学家一起工作,包括查尔斯·巴贝奇和迈克尔·法拉第,在某个时候,她编写了被认为是有史以来第一个计算机程序。我们让阿达来说最后一句话:

我可以把来自宇宙四分之一的光线投射到一个巨大的焦点上。我的路线是如此清晰明了,以至于想到它有多直就令人愉快。

—阿达·洛芙莱斯(1815–1852)*

posted @ 2024-08-06 16:41  绝不原创的飞龙  阅读(0)  评论(0编辑  收藏  举报