编程基础:Java、C# 和 Python 入门(一)
原文:Programming Basics: Getting Started with Java, C#, and Python
一、编程的基础
视频游戏、社交网络和你的活动手环有什么共同点?它们运行在一群(或多或少)程序员在很远很远的地方编写的软件上。在我们这个技术驱动的社会中,小工具和硬件只是硬币更明显的一面。在这一章中,我们将讨论编程的基础知识。我们还将看看数字系统的可见部分:硬件。
到底什么是编程?
基本上,编程是告诉数字设备,比如你的个人电脑,做什么的行为。我们键入由编程语言定义的命令列表,以便发生有用或有趣的事件。正确编程的计算机运行着世界上大部分的通信和在线服务。你可以提到像自动取款机、票阅读器和智能手机这样的小玩意,它们运行在某人用某种编程语言开发的软件上。
基本硬件缩减
作为一名初露头角的程序员,你将从理解你正在使用的普遍存在的电子设备中受益。最好至少对计算机内部最常见的组件有一个基本的了解。
计算机中的这些硬件组件代表了你的劳动力。作为一名程序员,你来主持这个节目。把编程的行为想象成告诉工厂工人要造什么。你制造应用程序,无论它们是大型复杂的软件项目,还是一些令人敬畏的编程书籍中的教程。
出于本书的目的,任何相对现代的台式机或笔记本电脑都可以。在尝试编程时,我们不需要任何昂贵的硬件。
1.中央处理器
自然,一个数字设备不能只靠软件运行;一个中央处理器(CPU) 是硬件“大脑”,它执行代码并使事情实际发生(见图 1-1 )。即使在一个不太复杂的电子产品中,所有指令都流向并通过一个 CPU(或一组 CPU)。由于体积非常小,自 20 世纪 70 年代以来,这些微芯片越来越成为我们生活的一部分。每个数字设备都有一个中央处理器,甚至可能是你的固定自行车/衣架。
图 1-1
早在 2005 年,数百万台电脑中使用的旧英特尔奔腾 4 CPU 的俯视图。图片由 Eric Gaba 提供。抄送-服务协议 3.0
2.硬盘(又称硬盘)
这个组件是用来永久存储数据的。在硬盘中,你会发现成千上万的文件,无论是图片、文本文件还是数据库。您的操作系统(如 Windows 或 macOS)也在硬盘的范围内。这些设备有两种类型:机械硬盘(见图 1-2 )和固态硬盘(SSD)。
图 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 )。计算机显示器通常直接连接到视频卡。
图 1-3
2006 年的 Nvidia 7900GS 显卡
自 21 世纪初以来,视频卡业务基本上一直是两家数十亿美元的科技巨头英伟达和AMD的垄断。然而,英特尔在这一领域也取得了进展。
4.随机存取存储器
随机存取存储器,俗称 RAM,用作计算机的临时存储。在物理上,它通常以棒状附件的形式出现(见图 1-4 )。当运行任何类型的软件时,您的计算机使用 RAM 来执行它。关闭你的设备将清空你的内存。相比之下,当关闭电脑时,写在硬盘上的数据不会被擦除。定期保存您的文档。
图 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 )。
图 1-5
现代的个人电脑主板。图片由埃文-阿莫斯提供。CC BY-SA 3.0。来源: upload.wikimedia.org/wikipedia/commons/0/0c/A790GXH-128M-Motherboard.jpg
成为一名优秀程序员的三个要求
接下来,让我们讨论一下所有程序员为了在他们的职业中进步而应该拥有的一些个人优先事项,无论他们的起点可能是什么:
-
自信:问问自己这个,为什么要学编码?一些完全正确的答案包括“为了职业发展”、“保持我的能力”和“我想成为伟大事业的一部分。”现在,编程有时被外行人认为是一种可怕的活动。坐下来,置身事外,进入比特操作的世界,确实需要一些勇气。请记住,即使你是一个完全的初学者,你也可以在这个领域获得能力。自信来自经验。一行接一行,你将获得更多的良好氛围,并从编程书籍和在线教程中获得独立性。
-
正确的语言:不是所有人都能从流利的世界语或古典拉丁语中受益。当学习一门新语言时,我们倾向于学习一些有用的东西,如西班牙语或法语。同样,选择一种最适合你的意图的编程语言是至关重要的。如果你想最终为移动用户编写食谱应用程序,比如说,精通 1957 年的 FORTRAN 只能让你到此为止。为此,本书介绍了我们这个时代最流行的三种编程语言:Java、C# 和 Python。
-
耐心:在选择了你想专攻的编程语言之后,你只需要坚持下去。精通一门新语言需要六个月到一年的实践经验。这其实是个好消息。编码对失眠和无聊很有帮助。它还可以防止痴呆症,因为它确实在很大程度上激活了那些大脑突触。
初级程序员词汇表
我们现在将深入研究一些与神圣的编码爱好相关的基本术语。有数百个与各种编程技术和语言相关的术语和概念。然而,我们将只关注最相关的关键词,没有特定的顺序。
输入/输出
输入在编程的上下文中,是指我们输入数据,让计算机上运行的一个软件进行处理。这以键入文本、鼠标命令或各种类型的文件的形式出现。例如,文字处理程序(例如,Microsoft Office)通常将其输入主要作为通过击键提供的字母数字数据。输出是指经过软件处理的数据。在字处理器中,这通常是指与程序一起保存的文件。这种输出也可以指向打印机或其他设备。程序员的输出(尽管有二氧化碳和其他东西)通常是一个工作应用程序,无论它是一个完整的教程文件还是一个更大的项目。
算法
一个工作程序列表基本上构成了一个*算法,*指的是为解决问题而创建的一组步骤。大多数软件由许多子算法组成。在视频游戏的情况下,有显示图形、保存和加载游戏状态、播放音频文件等算法。
流程图
编程项目及其算法通常使用流程图来可视化,尤其是在团队环境中。在大多数情况下,这些都是演示基本程序流的好方法。
流程图仅由几个通用元素组成(见图 1-6 )。在它们最基本的形式中,它们使用四种符号。这些是终端(圆角矩形)流程(矩形)决策(菱形/菱形),以及流线(箭头)。结束符号用来表示程序流的开始和结束。任何操作和一般数据操作都由进程矩形表示。
图 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 变量的好时机。试试这些,坚持你最喜欢的:
-
Programiz Python 在线编译器 :
www.programiz.com/python-programming/online-compiler -
在线 GDB Python 编译器 :
www.onlinegdb.com/online_python_compiler -
Repl.it Python 编译器 :
https://repl.it/languages/python3
准备好编译你的第一行 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# 都有一些在线编译器。以下是一个选择,供您选择的乐趣。挑选一个你最喜欢的,这样你也可以用这些语言尝试一些实时编码。
-
Jdoodle Java 编译器 :
www.jdoodle.com/online-java-compiler -
Paiza.io Java 编译器 :
https://paiza.io/projects/fjCEFnDQzDWOoS9hYZtVtg?language=java -
Dotnet 提琴 C# 编译器 :
www.dotnetfiddle.net -
Rextester C# 编译器 :
https://rextester.com
在花括号、变量范围和代码块上
在本章中,你很快会遇到几个带花括号的代码清单。它们确实是许多编程语言中的重要元素。这些卷曲字符的作用是表示由成组声明和/或语句组成的代码块和。编译器或解释器软件将单个代码块作为单个指令读取。
此外,代码块可以限制变量的范围,即清单中特定变量有效的部分。两个不同的代码块可能无法访问彼此的变量空间。
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_object 或 pink_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、 Photoshop 、 Spotify ,以及在 Firefox 中同时打开的 50 个标签。安装了大量千兆字节的 RAM 消除了通常与运行多个程序相关的许多不连贯和变慢的情况。RAM 升级可能是将个人电脑推向更高性能的最常见方式。
识别您的操作系统架构
您可能需要检查操作系统的架构是 32 位还是 64 位。又快又简单。
-
在 Windows 10 中,进入设置 ➤ 系统 ➤ 关于。你会在这一页看到必要的细节。
-
在 Linux 中,打开一个终端窗口,键入 arch 并按回车键显示您的系统架构。显示 x86_64 的输出意味着您拥有 64 位版本的 Linux。对于该操作系统的 32 位版本,显示 i386 或 i686 的输出。
-
至于 macOS,这个操作系统从 2009 年雪豹(10.6) 开始,每个版本都是 64 位(对 32 位软件有不同程度的向后兼容)。
为 Java 开发安装 Eclipse
好的开发环境提供搜索特性、语法突出显示,在某些情况下还支持多种编程语言。我们将首先为您设置一个专门为 Java 开发打造的环境,由 Eclipse Foundation 开发的强大的 Eclipse IDE (见图 3-1 )。
Eclipse 项目最初是由 IBM 在 2001 年 11 月创建的。Eclipse Foundation于 2004 年作为一个独立的非盈利组织出现。它是作为一个围绕 Eclipse 的开放透明的社区而创建的。
Eclipse 适用于 Windows 、 macOS 和 *Linux。*访问下面提供的下载页面,只需点击反映您正在运行的操作系统类型的链接。然而,有一个警告。Eclipse 的最新版本只适用于现代操作系统的 64 位版本。如果您仍然在使用 32 位操作系统,请查看 Eclipse 旧版本的专用链接。它们也很适合我们的目的。
图 3-1
行动中的 Eclipse
-
下载安装 Eclipse for Java 开发者 (64 位版) :
www.eclipse.org/downloads/packages/release/2020-12/r/eclipse-ide-java-developers -
下载 Eclipse for Java 开发者 (32 位版) :
www.eclipse.org/downloads/packages/release/helios/sr1/eclipse-ide-java-developers
安装 Eclipse for Linux
Linux 存储库可能托管一个过时版本的 Eclipse。为了在 Linux 中安装最新版本,我们将使用 Canonical Ltd .提供的 Snapcraft 方法。这种方法应该适用于 Linux 的所有主要发行版。打开终端窗口,输入以下行:
-
首先,我们确保您的操作系统上安装了***【JRE】*****。**在终端窗口中键入以下字符串:
-
接下来,我们使用 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 社区版 :
https://visualstudio.microsoft.com/free-developer-offers/
启动 Visual Studio 安装程序后,您最终会看到一个关于工作负载的屏幕。这些基本上是指 Visual Studio 中提供的各种语言的不同实现场景。您将看到 C# 和其他语言的四类工作负载;分别是 Web &云、桌面&移动、游戏、和其他工具集(见图 3-2 )。对于我们的编码需求,让我们勾选旁边的复选框。NET 桌面开发。这将很好地为我们设置 C# 语言。
最后点击窗口右下角的安装。您也可以选择在下载时安装,或者先下载所有必需的文件,然后再开始安装过程。
图 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# 所需的一切。
- 遵循 MonoDevelop 网站 :
www.monodevelop.com/download上的这些说明
从前面的下载页面(例如 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 社区版 :
www.jetbrains.com/pycharm/download
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 )。
图 4-1
一个简单的类图展示了 UML 的基础知识
图 4-1 (即冰淇淋)中最上面的盒子是一个抽象类。基本上,这些类不能用来创建对象。那为什么有他们?嗯,抽象类可以保存和普通类相同类型的信息。它们也可以有子类。抽象类的目的是为它的子类提供一些共享信息来继承和创建对象。在许多情况下,它们的使用简化了设计过程。
抽象类下面的三个盒子被称为子类。他们展示了继承,这是 OOP 中的一个关键概念。香草、巧克力和鬼椒接收它们的超类冰激凌内置的所有变量和方法。注意这些盒子是如何连接的。不同种类的箭头在 UML 中描述不同的事物。如图 4-1 所示,一个空的箭头意味着简单的继承。在 UML 中,人们还必须注意箭头的方向。
现在,你在 UML 中用斜体表示抽象类的名字;常规课程通常不会以任何方式风格化。接下来,一个类框列出了它的变量和它们的数据类型。变量前的加号(+)表示它是公共的,而减号(-)表示私有变量。最后,我们列出在我们的类中找到的任何方法,就像我们对图 4-1 中的 Display_Flavor( ) 和所有其他方法所做的那样。
除了我们到目前为止介绍的元素之外,UML 还有更多内容。我们将在本书后面的章节中更详细地介绍这种不可思议的建模语言。现在,只要你理解 UML 如何帮助你可视化一个软件项目就足够了。
包装
OOP 环境中的抽象和封装的概念密切相关。前者指的是对程序的用户/编码者隐藏无关信息的技术。后者处理软件的内部工作;在封装中,数据和操作它们的函数都存储在类中。然后,每个类都有控件,可以从其他类访问这些控件的数据。让我们用 Java 来演示封装,好吗?(见清单 4-1 。)
Get 和 Set 一般来说是 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 的类图(左)和对象图。
图 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 )。
图 5-1
演示双核 CPU 内部多线程的简单图表
一般来说,系统拥有的内核越多,处理数字的效率就越高。这取决于您的操作系统以及它是否支持多核计算;几乎所有现代操作系统都具备这样做的条件。然而,并非所有第三方软件都是为了充分利用多核处理而编写的。在这种情况下,软件只使用系统中的一个内核。
现在,并发运行线程的数量通常不等于 CPU 可支配的内核数量。记住,一个软件应用程序可以为不同的任务调用多个线程。这意味着单核系统可能会有几个线程同时运行。多核 CPU 提供的是在众多内核之间共享所有这些线程的工作负载的可能性。在具有六个或更多 CPU 内核的系统上,这可以极大地提高计算速度。
你可能偶尔会遇到术语超线程。这是指英特尔的专有技术,理论上,它可以提供两倍于处理器实际配备的 CPU 内核。超线程技术于 2002 年推出,它与操作系统协同工作,以增加可用内核的数量。英特尔声称,与没有超线程技术的相同 CPU 相比,配备超线程技术的 CPU 可以全面提升性能。然而,在现实生活中,超线程技术的优势在很大程度上取决于应用。这项技术确实在视频编辑和 3D 建模等 CPU 密集型场景下蓬勃发展。
用 Java 实现多线程
线程可以分为不同的状态,这些状态构成了一个线程生命周期。接下来,让我们详细了解这五个阶段:
-
新:一个线程诞生。
-
Runnable :顾名思义,这个阶段的线程正在运行它的任务,无论它们是什么。
-
等待:当一个线程与其他线程一起工作,并且暂时完成了它们的处理,让其他线程接管时,这个线程就进入了这个阶段。此阶段中的线程正在等待其他线程的“同意”以恢复其工作负载。
-
定时等待:当线程暂时完成了自己的任务,但在将来某个特定的时间点又被需要时,线程就进入这个不活动的阶段。
-
终止:当一个线程完成了它所有的任务,它就死了。
你可能听说过计算环境中的多任务处理。这个概念指的是共享同一组资源(如 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 )。
此外,在连续几次运行清单时,您可能会发现不同的线程以不同的顺序显示它们的输出。对于一个不同步的程序来说,这是非常正常的。
图 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 语言的更高级的功能。