Go从入门到精通(持续更新)
1.0 搭建环境
由于我们 Go官方网站 在我大天朝被和谐了,所以我们只能去 Go语言中文网 来下载了。Go的安装很简单,不像Java还要配置一大堆的东西,选择自己系统的对应版本,下载安装,像安装QQ一样无脑。我们就不再介绍了~如果真的有特别小白的朋友,也可以去百度看看图文安装教程~
1.1. 第一个Go程序
每个编程语言的学习,都是从一个"Hello, World."程序开始的,这个例子首次出现于1978年出版的C语言圣经《The C Programming Language》。关于"Hello, World."还有一个很美好的故事,那就是所有的程序员期待着计算机有一天能拥有真正的智能,然后对创造他的人们"发自内心"的说一句,Hello, World.
首先我们新建一个hello.go
文件,输入下面的内容并保存:
hello.go
package main
import "fmt"
func main(){
fmt.Println("Hello, World.")
}
打开命令行,找到hello.go
所在的目录,输入命令:
提示:本系列教程,以 $
符号来表示命令行提示符,看到以$
符号开头的命令,说明是在命令行环境下执行的,并非命令本身的符号。
$ go run hello.go
毫无疑问,我们将会看到命令行输出:
Hello, World.
恭喜你!已经完成了第一个Go语言程序,无论你是否理解,我都要祝贺你,开启了Go语言的大门,接下来让我们来做一些更酷的事情吧。
大家加油~
1.2. 编译可执行文件
Go和Python最大的区别在于,Go是一门编译型语言而Python是一门解释型语言。
编译型编程语言
简单来说,编译型编程语言并非简单的写好代码然后就运行。他还需要经过一个编译的步骤,把写好的代码通过语言提供的编译器,编译成操作系统方便阅读而人类几乎无法阅读的代码。这种语言的好处就是,运行速度快。如:Go, Java, C/C++,都是编译型编程语言。
解释型编程语言
解释型编程语言就是,我们写好的代码,无需经过任何处理,就可以直接运行在操作系统之上。但是计算机并不能理解我们所写的代码,这个时候就需要一个叫做解释器的东西。解释器就相当于一个翻译官一样,把我们写的代码一行一行的翻译给计算机,计算机从解释器那里得到翻译后的代码,才能正常执行我们的程序。由于代码是一句一句解释执行的,这中间有很多等待的过程,而且还取决于解释器的执行速度,所以解释型编程语言的特点就是运行速度慢。如:Python就是解释型编程语言。
还记得我们上一章所写的hello.go
吗,来回顾一下:
hello.go
package main
import "fmt"
func main(){
fmt.Println("Hello, World.")
}
既然我们知道Go是一门编译型语言,我们能不能把hello.go进行编译,然后再来运行呢?
答案当然是可以的:
$ go build hello.go
如果你使用的是Windows系统,你会发现在hello.go
文件所在的目录下,出现了一个hello.exe
的可执行文件,直接运行它或者输入下面的命令:
$ hello.exe
如果你使用的是Mac或者Linux,则会发现目录下多了一个hello
的文件,输入下面的命令来运行:
$ ./hello
最终你都能看到,命令行输出了:
Hello, World.
Go提供了很多命令给我们,其中最常用的两个命令就是run
和build
:
go run xx.go
运行指定的go文件
go build xx.go
编译指定的go文件
1.3. 打开封印之门的钥匙
有些同学看到我们这一节的名称,可能会想,车神这是写教程写的大脑抽筋了吧,并不是那样的。我希望编程可以用一种更加简单,更加方便新手接受和更加有趣的形式展现出来,而不是死板的函数,变量。
我们想要进入一栋房子,需要找到这个房子的大门才能进去,那么计算机想要进入我们做的程序,是不是也需要一个大门呢?答案是肯定的,那么我们Go语言的大门在哪里呢?回顾第一章写的hello.go
:
hello.go
package main
import "fmt"
func main(){
fmt.Println("Hello, World.")
}
很显然,main()
就是我们这个程序的大门。package
说明我们这个文件属于哪个包,可以简单的把包理解为一个文件夹。Go语言规定,程序的入口文件,必须输入main
包,而且这个文件必须包含一个main
方法。
那么究竟什么是方法呢?
方法
方法从字面我们可以理解为行为,比如我们人吃饭,睡觉,走路,谈恋爱,啪啪啪。这都是方法,是人的行为。那么我们的程序也会有一些行为,比如运行的行为,输出一句话的行为,在屏幕上画一个圆圈的行为,这些行为我们形象的称它们为方法或者叫函数。无论是方法还是函数,指的都是我们程序的某一项行为,那么这个行为或许是现实世界存在的,或许是现实世界不存在的。当你开始编程的时候,你就是造物主,你可以给你的程序创建任何行为。由于Go是Google创造的,所以我们想在Go的世界当一回造物主,就要依靠Google给我们提供的各种超能力。接下来我们就来看看,第一个超能力。
func 关键字
Go语言为我们提供了一个创建方法的关键字,这个关键字可以理解为我们的超能力,有了它我们就能给程序创建各种各样的方法,各种我们喜欢的方法。它的一个简单的用法就是:
func 你喜欢的名字(){
具体的行为
}
从今天开始,我们就来体验当创世神的快感吧。
新建一个Go文件,我把他叫做 yadang.go
。我们像上帝一样创造了一个叫做亚当的人:
package main
import "fmt"
func main(){
fmt.Println("你好,我是亚当。")
}
让我们来运行一下:
go run yadang.go
我们的亚当可以说话了,大家注意到我们的亚当,只有一个main
方法,这个方法是Go语言规定的,任何一个程序想要启动,必须包含这个方法。大家可以思考一下,我们的Go在给我们的亚当赋予生命的时候,肯定需要找到main
才能启动,main
就相当于打开封印的钥匙一样,这个写法是固定的,也就是说,我们无论创造什么样的生命或者是绚烂多姿的世界,都要有一个大门,这个大门就是main
方法,main
方法只能存在于main
这个package
中,package
就像一个世界,main
就是这个世界的中心大陆,而我们的一切生命都起源于这里。
1.4. 奔跑吧,亚当
上节课我们一起创造了亚当,但是我们的亚当还很低级,他只会说一句话,连走路都不会,这节课我们就让亚当,奔跑起来。
来看一下我们的亚当,现在是什么样子:
yadang.go
package main
import "fmt"
func main(){
fmt.Println("你好,我是亚当。")
}
大家可能注意到 import "fmt"
,这其实是Go语言提供的包含一系列方法的一个工具。大家可以把它理解为插件,装上这个插件,我们的亚当才能使用这个插件里提供的各种功能,其中一个很重要的功能就是:
fmt.Println()
这个方法可以在屏幕上输出一句话,我们的亚当就是依靠这个方法来跟大家问好的。
就像我们人类一样,当你想说话的时候,总是你的大脑想起一句话,然后把这句话送给我们的嘴巴,由我们的嘴巴把这句话说出来。我们并不知道大脑的神经元之间是如何传递的,我们并不是人体专家也不是神经学的专家,我们不懂人到底是怎么说话的,但是我们只需要知道,想说话就通过嘴巴就能说,我们的身体会自动处理一切需要的东西。
同样的,在我们编程的世界里也是一样的,我们不懂怎么样才能让操作系统在屏幕上显示一句话,我们不了解计算机的硬件之间是怎么通信的,但是Go为我们做好了一切,它把一切复杂的东西做成一个个方便使用的方法,使得我们不需要成为计算机专家,也能在电脑屏幕上面输出一句话。我们不用管背后有多么复杂的逻辑,只需要使用这简简单单的一个方法,就能实现。
亚当会说话了
事实上,我们的亚当目前只有一个主方法,他还没有任何身为人的方法,那今天我们就给亚当创造一些方法,让他看上去更像一个人:
yadang.go
package main
import "fmt"
func main(){
speak()
run()
}
func speak(){
fmt.Println("哇哈哈,我会说话了~")
}
func run(){
fmt.Println("我跑,我跑,我跑跑跑~")
}
可以看到,我们的亚当多了两个方法,一个叫做speak
,一个叫做run
。当然你可以把他们改成任何你喜欢的名字,比如叫 shuohua
, aaa
, hahaha
都可以。但是我们人类说话的方法就叫说话,非常的便于理解,如果我们创造了一个亚当,我们把他说话的方法叫一个毫无意义的名字,那其他人看到亚当的时候,就不知道这是一个什么方法了,比如下面的样子:
yadang.go
package main
import "fmt"
func main(){
aaa()
bbb()
}
func aaa(){
fmt.Println("哇哈哈,我会说话了~")
}
func bbb(){
fmt.Println("我跑,我跑,我跑跑跑~")
}
我们来运行一下:
go run yadang.go
同样我们能够看到屏幕上输出了:
哇哈哈,我会说话了~
我跑,我跑,我跑跑跑~
但是我们的亚当看上去是不是变得难以理解了,aaa
和 bbb
是什么方法?让人捉摸不透,所以说,大家在给我们的程序增加一些方法的时候,最好起一些有意义的名字,能够表达这个方法含义的名字,无论是用英语还是汉语拼音都是可以的,只要其他人和自己在阅读代码的时候能够轻易的认出这个方法是做什么的就可以了。
至此,大家已经掌握了如何让我们的亚当奔跑,但是我们的亚当还缺少一项重要的东西,那就是长相,下一节我们将一起来把我们的亚当变成一个帅气的猛男子吧~
1.5. 帅气的猛男子
今天我们将继续在Go语言大陆造人,上节课我们让亚当拥有了说话和奔跑的方法,但是我们的亚当还没有具体的身高,体重。那么这些东西也能通过方法来实现吗?答案是否定的,一个人的身高和体重,并不属于一个人的行为,而是属于这个人的属性。那么我们的程序也拥有一些属性,接下来我们就一起看一下如何给程序定义属性。
来看一下我们的亚当:
yadang.go
package main
import "fmt"
func main(){
speak()
run()
}
func speak(){
fmt.Println("哇哈哈,我会说话了~")
}
func run(){
fmt.Println("我跑,我跑,我跑跑跑~")
}
我们都知道,自然界中的属性有很多种,比如一个人的身高,可能是180cm,也可能是179.5cm,一个人的体重,可能是60kg,也可能是75.8kg。一个人的胳膊数量,可能是0,也可能是1或者2。一个人的外貌,可能是美丽,可能是一般,可能是丑陋。一个人的是不是有钱,那就只有两种可能,是或者否。那么这些数据都拥有不同的类型,可能是整数类型,也可能是小数类型,还可能是文字,负数,和是否类型。
那么在我们的Go语言世界里,也存在许许多多的数据类型。
布尔类型
bool
字符类型
string
整数类型
int int8 int16 int32 int64
无符号整数类型
uint uint8 uint16 uint32 uint64 uintptr
uint8 的别名
byte
int32 的别名(代表一个Unicode码)
rune
浮点型
float32 float64
实数和虚数
complex64 complex128
我们可以看到,在Go语言的世界里存在这么多的数据类型,但是常用的也就是我们上面说的,小数类型(浮点型),整数类型,是否类型(布尔型),文字类型(字符类型)。
那么比较特殊的是跟数字相关的,因为我们都知道,数字是有范围的,比如大于1000,大于10000。在我们现实生活中,如果一个人的体重是100kg,那地球就要使用100kg对应的那么大的空间来让这个人生活。如果一个人的体重是10000kg,那么地球就需要耗费10000kg对应的空间来给这个人。那么,如果一个体重只有60kg的人,他却被地球分配了一个能容纳10000kg体重的空间,是不是就浪费了我们地球有限的土地资源呢?
所以说,在Go语言的世界里,为了节约我们宝贵的土地资源(内存),我们需要在给程序定义属性的时候,选择合适的大小来分配,这样才不会浪费我们计算机的内存。
有符号整数类型
int8 有符号的8位整数,范围 -128 到127
int16 有符号的16位整数,范围 -32768 到 32767
int32 有符号的32位整数,范围 -2147483648 到 2147483647
int64 有符号的64位整数,范围 -9223372036854775808 到 9223372036854775807
int 有符号的32位或64位整数,范围根据Go程序运行的操作系统来决定,如果是32位的操作系统就相当于int32
,如果是64位的操作系统就相当于int64
。那么今天我们大部分的计算机都是64位的,所以如果直接使用int
,而你的电脑刚好是64位的操作系统,那他就等于int64
。
这里所说的位,并不是有几位数字,而是我们计算机的一个容量单位,8位等于1个字节,1024个字节等于1KB,1024KB就等于1兆。那么16位就是两个字节。
那么假如我们要表示一个人的年龄,很显然使用 int16
就足够了,如果使用 int32
就会造成大量的空间浪费,因为 int32
最大可以表示21亿多的数字,很显然人类的寿命是不可能有这么大的。反之,如果我们制作一个网络游戏,需要来表示人物的攻击力,我们都知道有些游戏为了让玩家体会到快感,把攻击力设置的非常高。什么开局只有一条狗,一刀99亿。那么很显然,如果是这种变态游戏的攻击力,我们用 int32
来表示就不合适了,因为 int32
无法容纳我们的一刀99亿,这时候我们就只能使用最大的 int64
来存储。至于他最高能表示多大的数值,大家有兴趣的可以数一下,反正如果换成钱的话,车神大概可以把整个地球买下来。
我们可以看到int
类型无论多少位,都是包含负号的,这种数据类型我们成为有符号整数类型。那么既然存在有符号整数类型,是不是还有一个无符号整数类型呢?恭喜你答对了,uint
就是无符号整数类型。
无符号整数类型
uint8 无符号8位整数,范围 0 到 255
uint16 无符号16位整数,范围 0 到 65535
uint32 无符号32位整数,范围 0 到 4294967295
uint64 无符号64位整数,范围 0 到 18446744073709551615
uint 无符号的32位或64位整数,和 int
一样,根据操作系统的位数来决定。
uintptr 无符号整数,用于存放一个指针。关于指针,我们将在后面为大家详细讲解,这里仅作为了解即可,不需要理解。
聪明的同学可能已经发现了,所谓的无符号整数类型,就是把原来存储负数的那部分空间,给取消了,此消彼长,这样正数部分就能加上原来负数的那部分空间用来存储更大的数字了。
所以 uint8
的存储范围实际上就是 int8
的 -128 去掉负号然后加127,结果就是255。有兴趣的可以验证一下其他的几个是不是都等于,有符号的整型去掉负号相加就是无符号整型的取值范围。
具体的取值范围大家不需要记忆,只需要记住,100以内用 int8
,3万以内用 int16
,21亿以内用 int32
,大于这个范围用 int64
。如果不存在负数的情况,可以使用无符号整型。无符号整型只需要在有符号整型的范围基础上乘以2,就是我们选择的合适范围了。如200以内用 uint8
,42亿以内用 uint32
。
身高一米五?
让我们来看看如何给亚当一个身高的属性
yadang.go
package main
import "fmt"
var height uint8 = 150
func main(){
hello()
}
func hello(){
fmt.Printf("我是亚当,身高 %v 米", height)
}
func speak(){
fmt.Println("哇哈哈,我会说话了~")
}
func run(){
fmt.Println("我跑,我跑,我跑跑跑~")
}
运行我们的亚当,就会发现输出了:
我是亚当,身高 150 米
首先我们来看如何使用我们的变量的,var 变量名 类型 = 值
。等号和后面的值是可选的,如果不指定值,Go会给这个变量一个默认的值。
另一个新的方法就是在我们 fmt
这个包里面的 Printf
。它可以让我们在字符串里面写一些占位符,什么叫占位符呢?大家可以认为占位符就是替你排队的,比如你买东西有事,你想让一个人替你站在那里排队,等你回来了以后让那个人出去,你站在他的位置上,就叫占位符。 %v
就是其中一个占位符。Printf
会拿着后面的 height
的值去替换 %v
,如果有多个占位符,会按照从前往后的顺序,以此替换,比如我们下面看到的。
yadang.go
package main
import (
"fmt"
"reflect"
)
var height uint8 = 150
var weight = 20
func main(){
hello()
}
func hello(){
fmt.Printf("我是亚当,身高 %v 米,体重 %v 吨。", height, weight)
fmt.Println(reflect.TypeOf(height), reflect.TypeOf(weight))
}
func speak(){
fmt.Println("哇哈哈,我会说话了~")
}
func run(){
fmt.Println("我跑,我跑,我跑跑跑~")
}
运行以后发现结果是:
我是亚当,身高 150 米,体重 20 吨。uint8 int
我们发现正常输出了 我是亚当,身高 150 米,体重 20 吨。
下面的 reflect.TypeOf(height)
是Go给我们提供的另一个方便我们创造一切的工具,这个工具包叫做 reflect
里面有一个方法 TypeOf
他可以接收一个变量,然后告诉我们这个变量的类型是什么。
我们发现上面的体重 var weight = 20
我们并没有指定类型,这是因为Go会自己根据等号后面的类型去判断应该给我们的变量一个什么默认值,这里我们可以看到,如果是整数的话,Go默认是把它声明成一个 int
类型的变量了。我们都知道 int
是有符号的类型,根据操作系统位数的不同,最低可以表示21亿的正负数,最高就是 int64
可以表示很大的正负数值。而我们的亚当,很显然不会有这么重的,所以我们为了节约珍贵的资源,就像节约我们的土地一样。就要给一些明知道一定不会超过这个范围的变量一个合适的类型。
比如我们要做一个日历系统,其中表示年份的我们就可以使用 uint16
类型,最高可以表示 65535
,至少在我们有生之年是看不到那一年了,或许只有我们的亚当才能活这么多年吧~~当然,如果你们公司需要做一个未来日历,要求可以查看几百万年以后,那就要根据需求使用其他的数据类型了。