编程思维可以具体分为四个方面:
分解
拥有编程思维的人,会把一个复杂的大问题,拆解成更可执行、更好理解的小步骤。
复杂问题很难一下子破解,但如果把它细分成很多个小问题,逐步解决,就容易多了。
模式识别
什么是模式识别呢?所谓识别模式,其实就意味着把新问题变成了老问题,我们在以往经验中搜索类似问题,套用类似的解决办法。识别的模式越多,解决问题的速度就越快。
抽象
把主要的精力聚焦重要的关键信息。
这是一个过滤的过程,通过确认问题的核心本质,可以帮助我们形成解决问题的大致构想。这样,可以加快解决问题的核心难点。
算法
设计一步一步的解决路径,解决整个问题。其实,算法也就是解决问题的方法。
通过这四个步骤,一个复杂问题先被拆解成一系列好解决的小问题;每一个小问题再被单独搜索解决方案;然后,聚焦几个重要节点,形成解决思路;最后,设计步骤,执行问题的解决方案。
所以,编程思维并不是编写程序的技巧,而是一种高效解决问题的思维方式,不当程序员也用得上。
何为编程
程序(program)由一系列指令组成,指定了如何执行计算。这里的计算可能是数学计算,如求解方程组或找出多项式的根,也可能是符号计算,如在文档中搜索并替换文本或编译程序(真够奇怪的,编译程序竟然也是计算)。虽然细节因语言而异,但几乎所有语言都支持一些基本指令。
输入:从键盘、文件、传感器或其他设备获取数据。
输出:在屏幕上显示数据,或者将数据发送给文件或其他设备。
数学运算:执行基本的数学运算,如加法和除法。
决策:检查特定的条件,并根据检查结果执行相应的代码。
重复:反复执行某种操作,但通常每次执行时都略有不同。
这几乎就是程序的全部内容。使用的每个程序都由类似于上面的小指令组成,不管它有多复杂。因此,你可将编程(programming)视为这样的过程,即将复杂而庞大的任务分解为较小的子任务。不断重复这个过程,直到分解得到的子任务足够简单,用计算机提供的基本指令就能完成。
何为计算机科学
对编程而言,最有趣的一个方面是决定如何解决特定的问题,尤其是问题存在多种解决方案时。例如,对数字列表进行排序的方法很多,其中每种方法都有其优点。要确定哪种方法是特定情况下的最佳方法,你必须具备规范地描述和分析解决方案的技能。
计算机科学(computer science)就是算法科学,包括找出算法并对其进行分析。算法(algorithm)由一系列指定如何解决问题的步骤组成。有些算法的速度比其他算法快,有些使用的计算机内存更少。面对以前没有解决过的问题,你在学着找出算法的同时,也将学习如何像计算机科学家那样思考。
设计算法并编写代码很难,也很容易出错。由于历史的原因,编程错误被称为 bug,而找出并消除编程错误的过程被称为调试(debugging)。通过学习调试程序,你将获得解决新问题的技能。面临出乎意料的错误时,需要创造性思维。
虽然调试可能令人沮丧,但它是计算机编程中有趣且挑战智商的部分。从某种程度上来说,调试犹如侦破工作:必须根据掌握的线索猜想出引发结果的过程和事件。在有些情况下,在考虑如何修复程序以及改善其性能的过程中,还能发现新的算法。
编程语言
要想运行用高级语言编写的程序,必须将其转换为低级语言(low-level language),即“机器语言”。这种转换需要一定的时间,这是高级语言的一个小小的缺点,但高级语言有两个优点。
用高级语言编程容易得多:编写程序所需要的时间更短,程序更简洁、更容易理解,同时更容易确保程序正确无误。
高级语言是可移植的(portable),这意味着用高级语言编写的程序只需做少量修改甚至无需修改,就可在不同类型的计算机上运行。
用低级语言编写的程序只能在一种计算机上运行,这种程序必须重写才能在其他计算机上运行。
有两种将高级语言转换为低级语言的程序:解释器和编译器。解释器(interpreter)读取并执行用高级语言编写的程序,这意味着程序怎么说它就怎么做。它每次处理程序的一小部分,即交替地读取代码行并执行计算。
解释型语言是如何执行的
相反,编译器(compiler)读取并转换整个程序,然后才开始运行程序。在这种情况下,用高级语言编写的程序称为源代码(source code),而转换得到的程序称为目标代码(object code)或可执行程序(executable)。程序编译后可反复执行,无需在每次执行前都进行转换。因此,编译型程序的运行速度通常比解释型程序更快。
Java 既是解释型的又是编译型的。Java 编译器不将程序直接转换为机器语言,而是生成字节码(byte code)。字节码类似于机器语言,解释起来既轻松又快捷,同时也是可移植的,即可在一台机器上编译程序,在另一台机器上运行生成的字节码。运行字节码的解释器被称为 Java 虚拟机(Java Virtual Machine,JVM)。
Java 程序的编译和运行过程
上面图片展示了这个过程包含的步骤。这个过程看似复杂,但在大多数程序开发环境中,这些步骤都是自动完成的:通常只需按一下按钮或输入简单的命令,就能编译并运行程序。然而,知道幕后执行了哪些步骤很重要,这样就可以在出现问题时找出问题所在。
Hello World程序
传统上,学习一门新的编程语言时,通常先编写一个名为 Hello World 的程序,它所做的只是在屏幕上显示“Hello, World!”。用 Java 编写时,这个程序与下面的类似:
public class Hello {
public static void main(String[] args) {
// 生成一些简单的输出
System.out.println("Hello, World!");
}
}
这个程序运行时显示如下内容:
Hello, World!
注意,输出中没有引号。
Java 程序由类定义和方法定义组成,而其中的方法由语句(statement)组成。语句是一行执行基本操作的代码。在 Hello World 程序中,这是一条打印语句(print statement),在屏幕上显示一条消息:
System.out.println("Hello, World!");
System.out.println 在屏幕上显示结果,其中的 println 表示“打印一行”。令人迷惑的是,打印既可以表示“在屏幕上显示”,也可以表示“发送到打印机”。在本书中,表示输出到屏幕上时,我们尽可能说“显示”。与大多数语句一样,打印语句也以分号(;)结尾。
Java 是区分大小写的,这意味着大写和小写是不同的。在前面的示例中,System 的首字母必须大写,使用 system 或 SYSTEM 都行不通。
方法(method)是一系列命名的语句。前面的程序定义了一个名为 main 的方法:
public static void main(String[] args)
方法 main 比较特殊:程序运行时,首先执行方法 main 中的第一条语句,并在执行完这个方法的最后一条语句后结束。在本书的后文中,你将看到定义了多个方法的程序。
类(class)是方法的集合。前面的程序定义了一个名为 Hello 的类。你可以随便给类命名,但根据约定,类名的首字母应大写。类必须与其所属的文件同名,因此前面的类必须存储在文件 Hello.java 中。
Java 用大括号({ 和 })编组。在 Hello.java 中,外面的大括号包含类定义,而里面的大括号包含方法定义。
以双斜线(//)开头的行是注释(comment),它用自然语言编写的文本解释代码。编译器遇到 // 时,将忽略随后到行尾的所有内容。注释对程序的执行没有任何影响,但可以让其他程序员(还有未来的你自己)更容易地明白你要做什么。
显示字符串
方法 main 中可包含任意条语句。例如,要显示多行输出,你可以像下面这样做:
public class Hello {
public static void main(String[] args) {
// 生成一些简单的输出
System.out.println("Hello, World!"); // 第一行
System.out.println("How are you?"); // 第二行
}
}
个示例表明,除独占一行的注释外,还可在行尾添加注释。
用引号括起的内容称为字符串(string),因为它们包含一系列串在一起的字符。字符包括字母、数字、标点、符号、空格、制表符,等等。
System.out.println 在指定的字符串末尾添加了一个特殊字符——换行符(newline),其作用是移到下一行开头。如果你不想在末尾添加换行符,可用 print 代替 println:
public class Goodbye {
public static void main(String[] args) {
System.out.print("Goodbye, ");
System.out.println("cruel world");
}
}
在这个示例中,第一条语句没有添加换行符,因此输出只有一行:Goodbye, cruel world。请注意,第一个字符串末尾有一个空格,这也包含在输出中。
转义序列
可用一行代码显示多行输出,只需告诉 Java 在哪里换行就可以了:
public class Hello {
public static void main(String[] args) {
System.out.print("Hello!\nHow are you doing?\n");
}
}
上述代码的输出为两行,每行都以换行符结尾:
Hello!
How are you doing?
\n 是一个转义序列(escape sequence),表示特殊字符的字符序列。反斜线让你能够对字符串的内容进行转义。请注意,\n 和 How 之间没有空格;如果在这里添加一个空格,第二行输出的开头将会是一个空格。
转义序列的另一个常见用途是在字符串中包含引号。由于双引号标识字符串的开头和结尾,因此,要想在字符串中包含双引号,必须用反斜线对其进行转义。
System.out.println("She said \"Hello!\" to me.");
结果如下:
She said "Hello!" to me.
\n 换行符
\t 制表符
" 双引号
\ 反斜线
设置代码格式
在 Java 程序中,有些空格是必不可少的。例如,不同的单词之间至少得有一个空格,因此下面的程序是不合法的:
publicclassGoodbye{
publicstaticvoidmain(String[] args) {
System.out.print("Goodbye, ");
System.out.println("cruel world");
}
}
但其他空格大都是可有可无的。例如,下面的程序完全合法:
public class Goodbye {
public static void main(String[] args) {
System.out.print("Goodbye, ");
System.out.println("cruel world");
}
}
换行也是可选的,因此可将前面的代码编写如下:
public class Goodbye { public static void main(String[] args)
{ System.out.print("Goodbye, "); System.out.println
("cruel world");}}
这也是可行的,但程序阅读起来更难了。要以直观的方式组织程序,换行和空格都很重要,使用它们可让程序更容易理解,发生错误时也更容易查找。
很多编辑器都自动设置源代码的格式:以一致的方式缩进和换行。可以按 Ctrl+A 选择所有的代码,再按 Tab 键来缩进代码。
从事大量软件开发工作的组织通常会制定严格的源代码格式设置指南,例如,Google 就发布了针对开源项目的 Java 编码标准,其网址为
http://google.github.io/styleguide/javaguide.html。
调试代码
每当你使用新功能时,都应该尝试故意犯些错误。例如,在 Hello World 程序中,如果遗漏一个引号,结果将如何呢?如果两个引号都遗漏了,结果将如何呢?如果 println 拼写得不正确,结果又将如何呢?这些尝试不仅有助于牢记学过的知识,还有助于调试程序,因为你将知道各种错误消息意味着什么。现在故意犯错胜过以后无意间犯错。
调试犹如实验科学:一旦对出问题的地方有所感觉,就修改程序并再次运行。如果假设没错,你就能预测修改后的结果,从而离程序正确运行更近一步;如果假设有误,你就必须作出新的假设。
编程和调试必须齐头并进。不能先随便编写大量的代码,再通过反复调试来确保它们能够正确地运行;相反,应先编写少量可正确运行的代码,再逐步修改和调试,最终得到一个提供所需功能的程序。这样的方式可以确保在任何时候都有可运行的程序,从而更容易隔离错误。
Linux 操作系统淋漓尽致地展示了这种原则。这个操作系统现在包含数百万行的代码,但最初只是一个简单的程序,Linus Torvalds 用它来研究 Intel 80386 芯片。正如 Larry Greenfield 在 Linxu User’s Guide 中指出的,Linux 是 Linus Torvalds 早期开发的项目之一,最初只是一个决定打印 AAAA 还是 BBBB 的程序,后来才演变为 Linux。
最后,编程可能引发强烈的情绪。面对棘手的 bug 而束手无策时,你可能会感到愤怒、沮丧或窘迫。别忘了,并非只有你这样,大多数乃至所有程序员都有类似的经历;不要犹豫,赶快向朋友求助吧!
术语表
解决问题:
明确地描述问题、找到并描述解决方案的过程。
程序:
一系列的指令,指定了如何在计算机上执行任务。
编程:
用问题解决技能创建可执行的计算机程序。
计算机科学:
科学而实用的计算方法及其应用。
算法:
解决问题的流程或公式,可以涉及计算机,也可以不涉及。
bug:
程序中的错误。
调试:
找出并消除错误的过程。
高级语言:
人类能够轻松读写的编程语言。
低级语言:
计算机能够轻松运行的编程语言,也叫“机器语言”或“汇编语言”。
可移植:
程序能够在多种计算机上运行。
解释:
指运行用高级语言编写的程序,即每次转换其中的一行并立即执行转换得到的指令。
编译:
将用高级语言编写的程序一次性转换为低级语言,供以后执行。
源代码:
用高级语言编写的、未编译的程序。
目标代码:
编译器转换程序后得到的输出。
可执行代码:
可在特定硬件上执行的目标代码的别名。
字节码:
Java 程序使用的一种特殊目标代码,类似于低级语言,但像高级语言一样是可移植的。
语句:
程序的一部分,指定了算法中的一个步骤。
打印语句:
将输出显示到屏幕上的语句。
方法:
命名的语句序列。
类:
就目前而言,指的是一系列相关的方法。(后面你将看到,类并非只包含方法。)
注释:
程序的一部分,包含有关程序的信息,但对程序的运行没有任何影响。
字符串:
一系列字符,是一种基本的文本数据类型。
换行符:
标识文本行尾的特殊字符。
转义序列:
在字符串中用于表示特殊字母的编码序列。