Go语言:为什么要编程做那些只需要按一下计算器就能完成的事情呢?

本章学习目标

学会让计算机执行数学运算学会声明变量和常量了解声明和赋值的区别学会使用标准库生成伪随机数

计算机程序能够完成许多任务。在本章中,你将编写程序去解决数学问题。

请考虑这一点

我们为什么要编写程序来做那些只需要按一下计算器就能完成的事情呢?

首先,人类的记性通常都不太好,可能无法凭借自身的记忆力精确地记下光速或者火星沿着轨道绕太阳一周所需的时间,而程序和计算机没有这个问题。其次,代码可以保存起来以供之后阅读,它既是一个计算器也是一份参考说明。最后,程序是可执行文件,人们可以随时根据自己的需要来共享和修改它。

2.1 执行计算

人们总是希望自己能够变得更年轻和更苗条,如果你也有同样的想法,那么火星应该能满足你的愿望。火星上的一年相当于地球上的687天,而较弱的重力作用则使得同一物体在火星上的重量约占地球上重量的38%。

为了计算本书作者Nathan在火星上的年龄和体重,我们写下代码清单2-1所示的小程序。Go跟其他编程语言一样,提供了+、-、*、/和%等算术操作符,将它们分别用于执行加法、减法、乘法、除法和取模运算。

提示 取模运算符%能够计算出两个整数相除所得的余数。例如,42 % 10的结果为2。

代码清单2-1 你好,火星:mars.go

<code>// 我的减重程序 ←--- 为人类读者提供的注释
package main
import "fmt"
// main是所有程序的起始函数 ←--- 为人类读者提供的注释
func main() {
fmt.Print("My weight on the surface of Mars is ")
fmt.Print(149.0 * 0.3783) ←--- 打印56.3667
fmt.Print(" lbs, and I would be ")
fmt.Print(41 * 365 / 687) ←--- 打印21
fmt.Print(" years old.")
}/<code>

注意 虽然代码清单2-1会以磅为单位显示体重,但计量单位的选择对于体重的计算并无影响。无论你使用的是什么计量单位,在火星上的重量都只相当于在地球上重量的37.83%。

这段代码的第一行为注释。当Go在代码里面发现双斜杠//的时候,它会忽略双斜杠之后直到行尾为止的所有内容。计算机编程的本质就是传递信息,好的代码不仅能够把程序员的指令传递给计算机,还能够把程序员的意图传递给其他阅读代码的人。注释的存在正是为了帮助人们理解代码的意图,它不会对程序的行为产生任何影响。

代码清单2-1会调用Print函数好几次,以便将完整的句子显示在同一行里面。达到这一目的的另一种方法是调用Println函数,并向它传递一组由逗号分隔的参数,这些参数可以是文本、数值或者数学表达式:

<code>fmt.Println("My weight on the surface of Mars is", 149.0*0.3783, "lbs, and I would be", 41*365.2425/687, "years old.") ←--- 打印出“My weight on the surface of Mars is 56.3667 lbs, and I would be 21.79758733624454 years old.”/<code>

速查2-1

请在Go Playground网站中输入并运行代码清单2-1,然后将作者Nathan的年龄(41)以及体重(149.0)替换成你的年龄和重量,看看自己在火星上的年龄和体重是多少?


提示 在修改代码之后,点击Go Playground中的Format(格式化)按钮。这样Go Playground就会在不改变代码行为的前提下,自动重新格式化代码的缩进和空白。

2.2 格式化输出

使用代码清单2-2中展示的Printf函数,用户可以在文本中的任何位置插入给定的值。Printf函数与Println函数同属一族,但前者对输出拥有更大的控制权。

代码清单2-2 Printf:fmt.go

<code>fmt.Printf("My weight on the surface of Mars is %v lbs,", 149.0*0.3783) ←--- 打印出“My weight on the surface of Mars is 56.3667 lbs,”
fmt.Printf(" and I would be %v years old.\\n", 41*365/687) ←--- 打印出“and I would be 21 years old.”/<code>

与Print和Println不一样的是,Printf接受的第一个参数总是文本,第二个参数则是表达式,而文本中包含的格式化变量%v则会在之后被替换成表达式的值。

注意 之后的各章将按需介绍更多除 %v之外的其他格式化变量,你也可以通过Go的在线文档查看完整的格式化变量参考列表。

虽然Println会自动将输出的内容推进至下一行,但是Printf和Print却不会那么做。对于后面这两个函数,用户可以通过在文本里面放置换行符\\n来将输出内容推进至下一行。

如果用户指定了多个格式化变量,那么Printf函数将按顺序把它们替换成相应的值:

<code>fmt.Printf("My weight on the surface of %v is %v lbs.\\n", "Earth", 149.0) ←--- 打印出“My weight on the surface of Earth is 149 lbs.”/<code>

Printf除可以在句子的任意位置将格式化变量替换成指定的值之外,还能够调整文本的对齐位置。例如,用户可以通过指定带有宽度的格式化变量%4v,将文本的宽度填充至4个字符。当宽度为正数时,空格将被填充至文本左边,而当宽度为负数时,空格将被填充至文本右边:

<code>fmt.Printf("%-15v $%4v\\n", "SpaceX", 94)


fmt.Printf("%-15v $%4v\\n", "Virgin Galactic", 100)/<code>

执行上面这两行代码将打印出以下内容:

<code>SpaceX $ 94
Virgin Galactic $ 100/<code>

速查2-2

1.如何才能打印出一个新行?

2.Printf函数在遇到格式化变量%v的时候会产生何种行为?

2.3 常量和变量

代码清单2-1中的计数器在计算时使用了类似0.3783这样的字面数值,但并没有具体说明这些数值所代表的含义,程序员有时候会把这种没有说明具体含义的字面数字称之为魔数。通过使用常量和变量并为字面数值赋予描述性的名称,我们可以有效地减少魔数的存在。

在了解过居住在火星对于年龄和体重有何种好处之后,我们接下来要考虑的就是旅行所需消耗的时长。对我们的旅程来说,以光速旅行是最为理想的。因为光在太空的真空环境中会以固定速度传播,所以相应的计算将会变得较为简单。与此相反的是,根据行星在绕太阳运行的轨道上所处的位置不同,地球和火星之间的距离将会产生相当大的变化。

代码清单2-3引入了两个新的关键字const和var,它们分别用于声明常量和变量。

代码清单2-3 实现光速旅行:lightspeed.go

<code>// 到达火星需要多长时间?
package main
import "fmt"
func main() {
const lightSpeed = 299792 // km/s
var distance = 56000000 // km
fmt.Println(distance/lightSpeed, "seconds") ←--- 打印出“186 seconds”
distance = 401000000
fmt.Println(distance/lightSpeed, "seconds") ←--- 打印出“1337 seconds”
}/<code>

只要将代码清单2-3中的代码录入Go Playground,然后点击Run按钮,我们就可以计算出从地球出发到火星所需的时间了。能够以光速行进是一件非常便捷的事情,不消一会儿工夫你就能到达目的地,你甚至不会听到有人抱怨“我们怎么还没到?”。

这段代码的第一次计算通过声明distance变量并为其赋予初始值56 000 000 km来模拟火星与地球相邻时的情形,而在进行第二次计算的时候,则通过为distance变量赋予新值401 000 000 km来模拟火星和地球分列太阳两侧时的情形(其中401 000 000 km代表的是火星和地球之间的直线距离)。

注意 lightSpeed常量是不能被修改的,尝试为其赋予新值将导致Go编译器报告错误:“无法对lightSpeed进行赋值”。


注意 变量必须先声明后使用。如果尚未使用var关键字对变量进行声明,那么尝试向它赋值将导致Go报告错误,例如在前面的代码中执行speed = 16就会这样。这一限制有助于发现类似于“想要向distance赋值却键入了distence”这样的问题。


速查2-3

1.尽管SpaceX公司的星际运输系统因为缺少曲速引擎而无法以光速行进,但它仍然能够以每小时100 800 km这一可观的速度驶向火星。如果这个雄心勃勃的公司在2025年1月,也就是地球和火星之间相距96 300 000 km的时候发射宇宙飞船,那么它需要用多少天才能够到达火星?请修改代码清单2-3来计算并回答这一问题。

2.在地球上,一天总共有24小时。如果要在程序中为数字24指定一个描述性的名字,你会用什么关键字?

2.4 走捷径

虽然访问火星也许没有捷径可走,但Go却提供了一些能够让我们少敲些字的快捷方式。

用户在声明变量或者常量的时候,既可以在每一行中单独声明一个变量:

<code>var distance = 56000000
var speed = 100800/<code>

<code>var (
distance = 56000000
speed = 100800
)/<code>

<code>var distance, speed = 56000000, 100800/<code>

需要注意的是,为了保证代码的可读性,我们在一次声明一组变量或者在同一行中声明多个变量之前,应该先考虑这些变量是否相关。

速查2-4

请在只使用一行代码的情况下,同时声明每天包含的小时数以及每小时包含的分钟数。

2.4.2 增量并赋值操作符

有几种快捷方式可以让我们在赋值的同时执行一些操作。例如,代码清单2-4中的最后两行就是等效的。

代码清单2-4 赋值操作符:shortcut.go

<code>var weight = 149.0
weight = weight * 0.3783
weight *= 0.3783/<code>

Go为加一操作提供了额外的快捷方式,它们的执行方式如代码清单2-5所示。

代码清单2-5 增量操作符

<code>var age = 41
age = age + 1 ←--- 生日快乐!
age += 1
age++/<code>

用户可以使用count--执行减一操作,或者使用类似于price /= 2这样简短的方式执行其他常见的算术运算。

注意 顺带一提的是,Go并不支持++count这种见诸C和Java等语言中的前置增量操作。


速查2-5

请用最简短的一行代码实现“从名为weight的变量中减去两磅”这一操作。

2.5 数字游戏

让人类随意想出一个介于1至10之间的数字是非常容易的,但如果想要让Go来完成同样的事情,就需要用到rand包来生成伪随机数。这些数字之所以被称为伪随机数,是因为它们并非真正随机,只是看上去或多或少像是随机的而已。

执行代码清单2-6中的代码会显示出两个1 ~ 10的数字。这个程序会先向Intn函数传入数字10以返回一个0 ~ 9的伪随机数,然后把这个数字加一并将其结果赋值给变量num。因为常量无法使用函数调用的结果作为值,所以num被声明成了变量而不是常量。

注意 如果我们在写代码的时候忘记对伪随机数执行加一操作,那么程序将返回一个0 ~ 9的数字而不是我们想要的1 ~ 10的数字。这是典型的“差一错误”(off-by-one error)的例子,这种错误是典型的计算机编程错误之一。

代码清单2-6 随机数字:rand.go

<code>package main

import (
"fmt"
"math/rand"
)

func main() {
var num = rand.Intn(10) + 1
fmt.Println(num)
num = rand.Intn(10) + 1
fmt.Println(num)
}/<code>

虽然rand包的导入路径为math/rand,但是我们在调用Intn函数的时候只需要使用包名rand作为前缀即可,不需要使用整个导入路径。

提示 从原则上讲,我们在使用某个包之前必须先通过import关键字导入该包,但是贴心的Go Playground也可以在需要的时候自动为我们添加所需的导入路径。为此,你需要确保Go Playground中的Imports复选框已经处于选中状态,并点击Format按钮。这样一来,Go Playground就会找出程序正在使用的包,然后更新代码以添加相应的导入路径。


注意 因为Go Playground会把每个程序的执行结果都缓存起来,所以即使我们重复执行代码清单2-6所示的程序,最终也只会得到相同的结果,不过能够做到这一点已经足以验证我们的想法了。


速查2-6

地球和火星相邻时的距离和它们分列太阳两侧时的距离是完全不同的。请编写一个程序,它能够随机地生成一个介于56 000 000 km至401 000 000 km之间的距离。

2.6 小结

Print、Println和Printf函数都可以将文本和数值显示到屏幕上。通过Printf函数和格式化变量%v,用户可以将值放置到被显示文本的任意位置上。const关键字声明的是常量,它们无法被改变。var关键字声明的是变量,它们可以在程序运行的过程中被赋予新值。rand包的导入路径为math/rand。rand包中的Intn函数可以生成伪随机数。到目前为止,我们已经使用了25个Go关键字中的5个,它们分别是:package、import、func、const和var。

为了检验你是否已经掌握了上述知识,请尝试完成以下实验。

实验:malacandra.go

Malacandra并不遥远,我们大约只需要28天就可以到达那里。

——C. S. Lewis,《沉寂的星球》(Out of the Silent Planet)

Malacandra是C. S. Lewis在《太空三部曲》中为火星起的别名。请编写一个程序,计算在距离为56 000 000 km的情况下,宇宙飞船需要以每小时多少千米的速度飞行才能够用28天到达Malacandra。

请将你的解答与“习题答案”中给出的参考答案进行对比。

速查2-1答案 这个问题没有标准答案,程序的具体输出取决于你输入的体重和年龄。

速查2-2答案

1.你可以通过在待打印文本的任意位置添加换行符\\n来插入新行,或者直接调用fmt.Println()。

2.格式化变量%v将被替换成用户在后续参数中指定的值。

速查2-3答案

1.虽然宇宙飞船在实际中不可能只沿着直线行进,但作为一个粗略的估计,它从地球飞行至火星大约需要用39天。以下是进行计算所需修改的代码:

<code>const hoursPerDay = 24
var speed = 100800    // km/h
var distance = 96300000 // km
fmt.Println(distance/speed/hoursPerDay, "days")/<code>

2.因为在地球上一天经过的小时数不会在程序运行的过程中发生变化,所以我们可以使用const关键字来定义它。

速查2-4答案

<code>const hoursPerDay, minutesPerHour = 24, 60/<code>

速查2-5答案

<code>weight -= 2/<code>

速查2-6答案

<code>// 随机地产生一个从地球到火星的距离(以km为单位)
var distance = rand.Intn(345000001) + 56000000
fmt.Println(distance)/<code>


本文摘自《Go语言趣学指南》

内森·扬曼(Nathan Youngman),罗杰·佩珀(Roger Peppé) 著,黄健宏 译

Go语言程序设计教程书籍Go编程语言实战学习笔记入门书学习过程充满乐趣,并能积累丰富的实战经验

《Go语言趣学指南》是一本面向Go语言初学者的书,循序渐进地介绍了使用Go语言所必需的知识,展示了非常多生动有趣的例子,并通过提供大量练习来加深读者对书中所述内容的理解。本书共分8个单元,分别介绍变量、常量、分支和循环等基础语句,整数、浮点数和字符串等常用类型,类型、函数和方法,数组、切片和映射,结构和接口,指针、nil和错误处理方法,并发和状态保护,并且每个单元都包含相应的章节和单元测试。

《Go语言趣学指南》适合对初学Go语言有不同需求的程序员阅读。无论是刚开始学习Go语言的新手,还是想要回顾Go语言基础知识的Go语言使用者,只要是想用Go做开发,无论是开发小型脚本还是大型程序,《Go语言趣学指南》都会非常有帮助。