购买
下载掌阅APP,畅读海量书库
立即打开
畅读海量书库
扫码下载掌阅APP

前言
Preface

编程可以提高你解决复杂问题的能力。现在的CPU运算速度可达每秒数十亿次,在其帮助下,我们可以快速、正确地找到难题的解决方案。

本书使用Python来解决工程问题。我们将学习编码几何基元(作为实现其他复杂操作的基础)、读写文件、绘制矢量图并制作动画来展示运算结果,以及求解大型线性方程组。最后,我们将整合所有知识,搭建一个用于求解桁架结构问题的应用程序。

目标读者

本书针对的是工程专业的学生或已毕业的工程师,或者任何有专业背景、想通过学习编写应用程序来解决工程问题的人。

读者必须拥有数学和力学的专业背景。本书会涉及线性代数、平面二维几何和物理学的相关概念,以及材料力学和数值方法的一些原理,这些是工程专业的常规课程。我们不会过多深入讨论这些理论知识,以照顾更多的读者。同时,在本书中学到的技巧可以用于解决涉及更复杂概念的问题。

为了跟上本书的教学,你还需要具备一些编程能力和Python基础。本书不是编程入门教程,优秀的编程入门教程已经有很多,如果你想要这类书,我推荐Eric Matthes撰写的 Python Crash Course (No Starch出版社,2019年)。网络上也有很多好资料,我最喜欢的网站的网址是https://realpython.com。Python的官方网站上也有很好的教程和文档,网址是https://www.python.org/about/gettingstarted/。

我们会编写很多代码,因此我强烈建议你在学习本书时,带上一台计算机,用来编写和测试本书中的所有代码。

读者收获

在本书中,我们将学习优秀程序的开发技巧,以正确、快速地解决工程问题。我们将使用自动化测试来测试代码,以确保其正确。我们开发的每个应用程序都应该运用自动化测试来进行有效测试,关于该测试方法的讨论会贯穿全书。

工程应用程序通常需要一些数据作为输入,因此我们还将学习从文件中读取数据,以及使用正则表达式来解析数据的方法。

工程应用程序通常需要求解大型方程组,因此我们将学习编写数值算法的技巧来实现这些复杂的计算。我们会重点关注线性方程组,但该技巧可以轻易地应用于编写非线性方程组的数值算法。

最后,工程应用程序需要输出计算结果。我们将学习把数据写入文件的方法,以便后续检查。我们将学习绘制优美的矢量图和制作动画,以呈现程序运算的结果。俗话说,一图胜千言:一幅精心绘制的、展示最关键的计算结果的图像可以赋予程序更大的价值。

为了展示以上所有概念,在本书末尾,我们将构建一个求解二维桁架结构的应用程序。这个程序将涵盖构建工程应用程序所需的一切知识。构建该应用程序所获得的知识可以轻易地移植到其他类型的工程应用程序编写中。

关于本书

本部分主要包括三点:本书英文书名的含义、选择Python的原因和本书的章节列表。

“硬核”的含义

本书英文书名中的“Hardcore”(硬核)一词是指我们将只使用Python标准库(与Python一起分发的库)来编写本书所有代码,我们不会使用任何第三方库来求解方程组或绘制矢量图。

你可能会不理解,如果已经有现成的代码可以完成这些操作,为什么不直接使用呢?一定要重复造轮子 [1] 吗?

本书是学习的指南,而学习必须要自己动手。如果不重复造轮子,你将永远无法理解轮子。在你拥有了扎实的编程技能、编写过几千行代码、完成许多项目后,你才能判断哪些外部库可以满足需求,以及该如何利用它们。如果从一开始就使用外部库,你就会形成习惯,将其视作理所当然。切记,在使用外部库的时候,要经常问自己,它是如何解决问题的。

和其他任何事情一样,编程也是熟能生巧的。想成为优秀的程序员,就必须编写大量代码,没有捷径可言。如果你只将编程作为赚钱的手段,或者只是想尽快将某个想法推到市场上,那就使用现有的库吧。但如果你是为了学习并且希望熟练掌握编程的艺术,那就不要使用外部库,要独立编写代码。

为什么选择Python

Python是最受喜爱的编程语言之一。根据Stack Overflow网站的2020年开发者调查报告(https://insights.stackoverflow.com/survey/2020),Python在“最受喜爱的编程语言”中排名第三,有66.7%的用户愿意继续使用它,仅次于TypeScript和Rust(见图1)。

同样是这份调查报告,在“最想学习的语言”排名中,Python排在第一位:在接受调查的、此前没有使用过Python的开发者中,有30%表示有兴趣学习Python(见图2)。

这个结果并不令人惊讶,Python是一门极其通用且高效的编程语言。用Python编程非常轻松,而且它的标准库非常完善:几乎任何你想做的事情,Python都有现成的资源可以使用。

本书之所以选择Python,不仅因为它受欢迎,还因为它易于使用和用途广泛。Python的一个优点在于,即使你在阅读本书前对它完全不了解,入门也不需要花太多时间。它相对易学,互联网上有各式各样的教材和课程。

图1 2020年最受喜爱的编程语言(来源:Stack Overflow网站调查报告)

图2 2020年最想学习的编程语言(来源:Stack Overflow网站调查报告)

通常来说,Python不是一种“快速”的编程语言。的确,执行速度不是Python的长项。图3展示了用Python和Go(谷歌开发的一种快速编程语言)编写的三个相同程序的执行时间(单位为秒)。Python执行每一个程序的时间都比Go长很多。

图3 Python对标测试(来源:https://benchmarksgame-teampages.debian.net/benchmarksgame/fastest/python3-go.html)

那么,难道我们不在乎速度吗?当然在乎,但对于本书的目的而言,开发时间和开发经验更加关键。Python有很多代码结构,可以使编程变得轻松。例如,对集合的过滤或映射等操作,Python的列表推导式可以一步到位,而Go则需要用到“古老”的for循环。我们将要编写的所有程序基本都不用顾虑执行时间,我们会得到超预期的结果。如果你在其他应用程序中遇到速度问题,那么在本书中学习到的技巧也可以应用到其他更快的编程语言中。

在开始学习之前,先快速预览本书的主要内容。

内容概览

本书的内容非常丰富。每一章都建立在之前的章节基础之上,因此请确保按顺序阅读本书,并练习每一章的代码。

本书包括以下内容:

第一部分:基础知识

第1章主要介绍Python的一些中级概念,这些概念会贯穿全书。我会介绍如何将代码拆分成模块和包的形式、如何使用Python的集合,以及如何运行Python脚本和导入模块等。

第2章介绍函数式编程和面向对象编程这两种范式,探讨对应的代码编写技巧。

第3章介绍如何使用命令行来运行程序,以及执行一些简单任务,如创建文件。

第二部分:二维几何

第4章介绍最基础但至关重要的几何基元:点和向量。本书的其余部分都依赖这两个基元的应用。同时,我们还将学习自动化测试方法,以确保代码没有错误。

第5章将直线和线段这两个几何基元加入我们的几何工具箱中。我们将学习如何检查两条线段或直线是否相交,如何计算交点。

第6章将矩形、圆和多边形添加到我们的几何工具箱中。

第7章讲解仿射变换这一有趣的代数构造,我们将用其来制作漂亮的图片和动画。

第三部分:图形和模拟

第8章介绍可缩放矢量图形(SVG)格式。我们将编写自己的库,使用几何基元来生成这类图形。

第9章利用前面章节学到的所有知识,搭建我们的第一个应用程序,该程序会计算出经过三个指定点的圆,并将结果绘制成矢量图。

第10章介绍Tkinter包的基础知识,该包用于在Python中创建用户界面。我们将主要学习如何使用画布小部件,以在屏幕上绘制图像。

第11章学习如何通过在Tkinter的画布中绘图来创建动画。我们将探讨时间循环,这一概念被用于工程仿真和视频游戏引擎中来将场景渲染到屏幕上。

第12章创建一个应用程序,将对一些几何基元应用仿射变换的效果制作成动画。

第四部分:方程组

第13章介绍矩阵和向量的构造,并介绍如何编码这两个基元,这在我们处理方程组时会非常有用。

第14章介绍使用数值方法求解大型线性方程组的方法。我们会对Cholesky因式分解算法进行编码,该算法在第五部分的方程组求解中还会用到。

第五部分:桁架结构

第15章讲解本部分会涉及的材料力学的一些基本概念。我们会编写一些类来代表一个桁架结构模型,然后用这个模型建立一个完整的结构分析应用程序。

第16章使用上一章建立的模型,学习求解结构位移、变形和应力所需的所有计算方法。

第17章介绍读取和解析文件的方法,使得我们的桁架分析应用程序能够读取以文本文档形式存储的数据。

第18章介绍用结构计算结果生成SVG图像的算法。我们将使用自己编写的SVG包来绘制图形,这个图形会包含所有相关的细节,如结构变形后的几何形状和每个杆旁边的应力标签。

第19章学习如何将前面章节中编写的代码组合到一起,构建完整的桁架分析程序。

搭建开发环境

本书使用Python3和PyCharm软件,并会提供PyCharm的操作指导。PyCharm是一个开发环境软件,可以提高编程效率。书中的代码已经经过Python 3.6到3.9版本的测试,对于更新的版本,大概率也可以很好地运行。请下载本书附带的代码,安装最新的Python3解释器,并安装PyCharm软件。

下载本书代码

GitHub网站上有本书所有代码,链接为https://github.com/angelsolaorbaiceta/Mechanics。虽然我强烈建议你手动编写所有代码,但也不妨将其作为参考。

如果你熟悉Git版本控制系统和GitHub网站,那么你可能会克隆该仓库。我建议你定期拉取该仓库,因为我偶尔可能会添加新特性或修复错误代码。

如果你不熟悉Git或GitHub,那么最好的选择是下载一份代码副本。单击Clone(克隆)按钮并选择Download ZIP(下载ZIP)选项即可(见图4)。

解压缩该项目,并将其放在某个目录中。你会发现,我用README文件(README.md)记录了该项目的每个包和子包。这些文件经常出现在软件项目中,它们负责解释和记录项目的特性,也包含编译或运行代码的指导。当打开一个软件项目时,你应该首先阅读README文件,因为它描述了配置项目和运行代码的方法。

图4 从GitHub网站下载代码

注意 README文件使用Markdown格式编写。如果想对该格式有更多了解,可以访问网址https://www.markdownguide.org/。

GitHub上的Mechanics项目所包含的代码比本书中涉及的要多。我不想把这本书写得太长,因此没有涵盖该项目的所有内容。

例如,在第14章中,我们将探讨线性方程组求解的数值方法,并详细解释楚列斯基(Cholesky)分解法。其他数值方法,如共轭梯度法(conjugate gradient),我们在书中无法一一讨论,但是该项目中有对应的代码,可以供你分析和运用。为了简洁起见,我们在书中也省略了许多自动测试代码,当你编写测试代码时,可以参考项目中的代码。

现在是时候安装Python了。

安装Python

从链接https://www.python.org/downloads/可以下载macOS、Linux和Windows操作系统对应的Python版本。Windows和macOS操作系统需要下载安装程序并运行。

Linux操作系统通常预装Python。你可以在shell中使用以下命令查看计算机上安装的Python版本:

在Linux计算机上安装Python的特定版本,需要用到os包管理器(os package manager)。对于使用apt包管理器的Ubuntu系统用户,命令如下:

对于使用dnf包管理器的Fedora系统用户,命令如下:

如果你使用的是Linux的其他发行版本,用谷歌搜索可以找到对应的包管理器的Python安装教程。

关键是下载Python3版本,如3.9版,这是本书编写时的最新版本。3.6及以上的版本都可以。

注意 Python2和Python3不兼容;用Python3编写的代码大概率无法在Python2的解释器上运行。Python的升级是非向后兼容的,因此Python3中的一些特性在Python2中不存在。

安装和配置PyCharm软件

开发代码时,我们通常会使用集成开发环境软件(Integrated Development Environment,IDE),它拥有很多功能,能提高我们的编程效率。IDE通常具有自动补全功能,让你在输入时知道有哪些备选项,构建、调试和测试工具时也一样。花一点时间学习IDE的主要功能是值得的,它将使你在开发阶段更高效。

本书使用PyCharm软件,它是由JetBrains公司创建的功能强大的IDE,该公司不仅拥有市场上最好的IDE软件,还拥有自己的编程语言:Kotlin。如果你已经拥有一些Python编程经验,并且更喜欢使用其他IDE(如Visual Studio Code),倒也无妨,只是在碰到问题时,你需要自己查阅IDE文档来解决问题。如果你没有太多的IDE使用经验,我建议使用PyCharm,这样你就可以紧随本书课程。

要下载PyCharm软件,请打开链接https://www.jetbrains.com/pycharm/,单击DOWNLOAD(下载)按钮(见图5)。

PyCharm有Linux、macOS和Windows版本。每个版本又有两种类型:专业版和社区版。你可以下载免费的社区版,然后在计算机上按照步骤安装PyCharm。

打开Mechanics项目文件

图5 下载PyCharm IDE软件

让我们用PyCharm软件来设置之前下载的Mechanics项目,这样就可以使用它并将其代码作为参考。

打开PyCharm,然后单击欢迎界面的“打开”(Open)选项,找到你在GitHub下载或克隆的Mechanics项目文件夹并选中它。PyCharm会打开该项目,并使用计算机中安装的Python版本为其配置Python解释器。

PyCharm中的每个项目都需要设置对应的Python解释器。因为计算机可能安装了不同版本的Python,安装位置也可能不同,所以你需要告诉PyCharm,你想使用哪一个Python版本来解释项目代码,以及该解释器在什么路径。对于Windows和Linux系统用户,请在菜单栏选择File(文件)→Settings(设置)。对于macOS用户,请选择PyCharm→Preferences(首选项)。在Settings/Preferences(设置/首选项)窗口中,单击左侧的Project:Mechanics来展开项目,并选择相应的Python解释器(见图6)。

图6 设置项目的Python解释器

在窗口的右侧,单击Python解释器一行最右侧的下拉箭头,在下拉窗口中,选择对应的Python版本。如果你遵循前面的教程,Python会安装到默认目录中,PyCharm可以直接找到它,因此对应的解释器也应该出现在列表中。如果你将Python安装到了其他位置,则需要告诉PyCharm具体的文件夹位置。

注意 如果在设置项目解释器时遇到问题,请查看PyCharm的官方文档:https://www.jetbrains.com/help/pycharm/configuring-python-interpreter.html(此链接包含该操作的详细说明)。

再次打开Mechanics项目,它应该已经设置好了。双击README.md文件,在PyCharm中打开它。在PyCharm中打开Markdown文件,会默认显示一个分割视图:左侧是Markdown的源文件,右侧是该文件的渲染版本,见图7。

图7 README文件在PyCharm中的分割视图

这份README.md文件阐述了项目的基本结构。请随意浏览预览中链接的内容,花一些时间阅读每个软件包的README文件。这将让你更好地了解我们将要完成的工作量。

创建单独的Mechanics项目

下载的Mechanics项目已经设置完成,让我们创建一个新项目,用于编写自己的代码。如果项目已打开,请先将其关闭[选择File(文件)→Close Project(关闭项目)],然后你会看到欢迎界面,见图8。

单击欢迎界面的Create New Project(新建项目),在位置一栏中输入项目名称:Mechanics。然后,选择解释器部分,忽略默认的New environment using(使用此工具新建环境),选择Existing interpreter(先前配置的解释器),见图9。找到之前下载的Python版本,然后单击CREATE(创建)按钮。

图8 PyCharm的欢迎界面

图9 创建一个新项目

至此,一个新项目就创建好,可以编写代码了。让我们先快速学习一下PyCharm的主要功能。

PyCharm介绍

这里并没有给出PyCharm的详细指南。要获得对该IDE的更完整概述,请阅读官方文档https://www.jetbrains.com/help/pycharm。该官方文档不仅全面,而且会实时更新最新的特性。

PyCharm功能强大,它的社区(免费)版包含许多功能,这也使得Python编程变得令人愉快。它的用户界面(UI)主要分为四个部分(见图10)。

图10 PyCharm的用户界面

导航栏 :窗口的顶部是导航栏。其左侧是当前文件的路径导航(又叫面包屑导航),右侧是程序运行和调试按钮,以及显示当前运行配置的下拉列表(后面会介绍运行配置)。

项目工具窗口 :显示项目的目录结构,包括所有包和文件。

编辑器 :用于编写代码。

终端 :PyCharm拥有两个终端,即计算机系统终端和Python终端。两者在本书中都会经常使用。我们将在第3章中讲解这些内容。

用户界面的右下角还会显示项目对应的Python解释器。你可以在这里更改解释器版本,从系统上安装的版本列表中选择即可。

创建包和文件

我们可以在项目工具窗口中创建新的Python项目包(我们将在第1章中介绍包的知识)。要创建新包,请在项目工具窗口中,右击要创建新包的文件夹或包,在出现的菜单中选择New(新建)→Python Package(Python包)。同样,创建Python文件可以选择New→Python File(Python文件)。图11中显示了这些选项。

还可以选择New→Directory(目录)以创建常规目录,选择New→File(文件),选中对应文件的扩展名,即可创建任意类型的文件。常规目录和Python包的区别在于,后者包含一个名为__init__.py的文件,该文件会指示Python解释器将其所在的目录视作一个具有Python代码的包。你可以在第1章中了解更多相关信息。

图11 PyCharm创建新的包或文件

创建运行配置

运行配置(run configuration)用于告诉PyCharm项目(或它的某部分)应该如何运行。我们可以保存配置,以便根据需要重复使用。有了运行配置,我们只用一个按钮就能执行应用程序,而不需要在shell中编写繁杂的命令,如复制/粘贴参数、输入文件名称等。

另外,运行配置可以包括其他信息,如应用程序的入口点、标准输入的重定向文件、必须设置的环境变量以及传递给程序的参数等。运行配置非常方便,可以节省开发时间;它还可以让我们轻松地调试Python代码,下文会介绍这一点。你可以通过如下路径获取运行配置的官方文档:https://www.jetbrains.com/help/pycharm/run-debug-configuration.html。

让我们动手创建一个运行配置吧。为此,首先需要创建一个新项目。

创建一个测试项目

在菜单栏中新建项目,请选择File→New Project(新建项目)。在Create Project(创建项目)对话框中,输入“RunConfig”作为项目名称,选择Existing interpreter,然后单击CREATE。

在这个新项目中,右击项目工具窗口中的RunConfig目录,然后选择New→Python File以创建一个Python文件,将其命名为“fibonacci”。打开该文件,输入以下代码:

这是一个用递归算法计算第 n 个斐波那契数的函数,我们用它来计算和输出第30个斐波那契数。让我们创建一个新的运行配置来执行此脚本。

创建一个新的运行配置

若要创建新的运行配置,请在菜单栏中选择Run(运行)→Edit Configurations(编辑配置),从而弹出如图12所示的对话框。

图12 “运行/调试配置”对话框

如图12所示,我们可以使用模板来创建一个新的运行配置。每个模板都定义了一些参数,可以帮助我们轻松地创建正确的配置。在这本书中,我们只会使用Python模板。该模板定义了一个用来运行和调试Python文件的运行配置。

在对话框中,单击左上角的“+”按钮,并从弹出的模板列表中选择Python(见图13)。

图13 创建一个新的Python运行配置

选择配置模板后,对话框的右侧会显示我们需要为运行配置代码提供的参数。我们只需填写两个参数:配置名称和脚本路径。

找到对话框顶部的Name(名称)字段,输入“fibonacci”,然后在Configuration(配置)部分找到脚本路径字段,单击其右侧的文件夹图标。单击图标后,会打开一个选择脚本对话框,路径指向项目的根文件夹,这也是我们添加fibonacci.py文件的位置。选择此文件作为脚本路径。最终的配置对话框如图14所示。单击OK(确定)按钮。

图14 运行配置参数设置

你已经成功地创建了一个运行配置,让我们运行它吧。

使用运行配置

在导航栏的右侧,找到运行配置选择器,如图15所示。

图15 运行配置选择器

在下拉列表中,选择刚才创建的运行配置,单击绿色运行按钮来执行它。IDE的shell中应该会显示如下信息:

还可以通过在菜单中选择Run→Run fibonacci(运行fibonacci)来启动运行配置。

我们已经使用运行配置成功地执行了fibonacci.py脚本。现在让我们学习如何使用它调试Python代码。

调试Python代码

当程序运行不正常且原因不明时,需要对其进行调试。要调试程序,我们可以一次一步,逐行执行该程序,检查变量值。

在调试脚本之前,先对fibonacci函数小改一下。假设使用这个函数的人抱怨它计算大数时速度太慢,例如,函数计算第50个斐波那契数需要好几分钟:

经过仔细分析,我们意识到,如果我们对已经计算出的斐波那契数进行存储,避免一次又一次地重复计算,那么我们的fibonacci函数就可以得到改进。为了加快执行速度,我们决定将已经计算出的斐波那契数保存到一个字典中。代码修改如下:

在开始调试练习之前,请再次运行该脚本,以确保其仍能产生预期结果。你可以更进一步,尝试计算第50个斐波那契数。这一次计算时间将是毫秒级。命令如下:

产生的结果如下:

现在让我们在调用函数的那一行停止执行:

要做到这一点,我们需要在Python解释器停止执行的那一行设置断点(breakpoint)。设置断点有两种方式:一是在编辑器区域你想要停止的行的右侧单击(如图16中显示的点处);二是单击该行的任意位置,然后从菜单中选择Run→Toggle Breakpoints(切换断点)→Line Breakpoint(行断点)。

如果添加断点成功,你应该看到类似图16所示的一个圆点。

要想在调试模式下启动Fibonacci运行配置,需要单击红色的调试按钮,而不是绿色的运行按钮(见图15),或在菜单栏选择Run→Debug fibonacci(调试fibonacci)。

PyCharm会执行脚本并检查断点,一旦发现断点,它就会在这一行之前停止执行。你的IDE应该在设置断点的行上停止执行,并在下方显示调试控件,如图17所示。

图16 在代码中设置断点

图17 PyCharm的调试器

在调试器的顶部附近有一栏,用来控制程序的执行(见图18)。该栏上有一些图标,我们主要关注的是前两个:步过(step over)和步入(step into)。使用“步过”选项,我们可以执行当前行并跳转到下一行。“步入”选项则会进入当前行的函数内部。我们稍后会讲解这两个选项。

图18 调试器的执行控件

调试器的右侧有一个变量窗格,我们可以在这里检查程序的当前状态:所有变量的值。例如,在图17中可以看到cache变量,它是一个空字典。

现在,让我们单击调试器执行控件中的“步入”图标,执行进入fibonacci函数内部,并停在第一行指令上(见图19)。

图19 步入fibonacci函数

调试器的变量窗格显示变量n当前值为50。这个值也出现在fibonacci函数定义的旁边,如图19所示(这两个位置都用箭头表示)。

调试器的左侧是“框架”窗格。这个窗格展示了程序的栈帧。每当函数执行时,一个带有函数局部变量和其他信息的新帧就会被压入该栈。你可以来回单击帧来检查程序在调用函数之前的状态。例如,你可以单击<module>,fibonacci.py:15栈帧,查看调用fibonacci函数之前的状态。要返回当前执行点,只需单击最顶部的栈帧,本例中是fibonacci,fibonacci.py:5。

尝试继续使用步过和步入控件来调试程序,观察cache变量和n的值如何改变。实验结束后,为了停止调试,你可以执行程序中的所有指令,直到结束,或者单击调试器中的停止按钮。你可以从菜单中选择Run→Stop fibonacci(停止fibonacci)来执行此操作或单击调试器左侧的红色正方形图标。

让我们最后尝试一次调试练习。在调试模式下再次运行程序,当执行到断点处停止时,单击“步过”图标。在变量窗格中检查变量cache。如你所见,cache现在被从3到50的斐波纳契数填满了。你可以展开字典以检查其内部的所有值,如图20所示。

你还可以使用调试器的控制台与当前的程序进行交互(见图21)。在调试器视图中,单击Debugger选项卡旁边的Console(控制台)选项卡。在控制台中,你可以检查当前程序的状态,执行某些操作,如检查某个斐波那契数是否被缓存:

图20 调试器中的变量

图21 调试器的控制台

小结

在前言中,我们预览了本书的内容,了解了学习本书并充分利用它所需的先决条件。我们还安装了Python,配置了开发环境,使其能够有效地运行。

最后一部分,我们预览了PyCharm及其强大的调试工具,不过你可能已经意识到,我们的了解非常有限。想了解更多关于PyCharm调试的知识,请查看官方文档https://www.jetbrains.com/help/pycharm/debugging-code.html。

现在,让我们开始学习Python吧。


[1] 重复造轮子,是指重复创造已经存在的方法,多用于软件开发领域。——译者注 M50EIaX4s9inVQQozbO178/uCi8BhlhfSVDKgxtkaio26rB5c0t6jEZ5Sf207FEx

点击中间区域
呼出菜单
上一章
目录
下一章
×