【轻知识】Go入门学习整理——第一节
准备工作:你要科学上网。不然一些东西下载不下来。Go的官网可能无法访问。
我英语不好。但尽量在官方文档找知识源。然后看看翻译。不看翻译阅读吃力。部分中文文档studygolang.com上有。
声明:知识点非原创,大都是我组合各个书,文章。所以雷同是正常滴。毕竟是整理。
golang 环境搭建
在Downloads 页面,我们选择最新的吧。那就go1.11.4。我想我们机器都是64位。就下载amd64。
安装参考官网windows的msi一路装下去即可。当然mac的pkg也一样。当然yum、apt-get、brew,都中(虽然我没这么装过,我搜了下有这样的例子)。当然我还是建议你编译安装。这样比较funy。
windows
安装完msi文件,去设置系统的环境变量。
linux
第一种,直接使用编译好的。那种 Archive。当你下载的时候列表有一列是kind,选择Archive。
$ wget https://dl.google.com/go/go1.11.4.linux-amd64.tar.gz
$ tar xf go1.11.4.linux-amd64.tar.gz -C /usr/local/
$ /usr/local/go/bin/go version
go version go1.11.4 linux/amd64
第二种,用Go1.4编译 大于1.4的版本的。也就是源码编译安装。
1.下载 1.4.3的源码
$ wget https://dl.google.com/go/go1.5.src.tar.gz
2. 放到/usr/local下面
$ tar xf go1.4.3.src.tar.gz -C /usr/local/
3. 因为要装其他版本,所以改名成go1.4.3
$ mv /usr/local/go /usr/local/go1.4.3
4.进入源码目录,编译(为节省时间,直接用make.bash)
$ cd /usr/local/go1.4.3 && ./make.bash
5.测试
$ /usr/local/go1.4.3/bin/go version
go version go1.4.3 linux/amd64
接着下载1.11.4,用1.4编译
1. 下载 1.11.4
$ wget https://dl.google.com/go/go1.11.4.src.tar.gz
2.解压
$ tar xf go1.11.4.src.tar.gz -C /usr/local
3.进入源码目录,使用1.4编译
$ cd / /usr/local/go/src
$ GOROOT_BOOTSTRAP=/usr/local/go1.4.3 ./make.bash
4.测试
$ /usr/local/go/bin/go version # 直接go version 也可以
go version go1.11.4 linux/amd64
windows也可以编译安装。就不弄了。
设置环境变量
上面不管是windows 跟linux都需要设置三个环境变量
GOPATH 也就是 你写代码的工作区。
GOROOT 也就是 go的安装目录。
GOBIN 也就是GO安装目录下的bin目录。
hello world
在cmd命令行,用notepad调起记事本。我还记得我的第一个html跟php是用的记事本。我们创建一个文件叫做hello.go
。保存的时候一定要选utf-8。否则你打印中文会报错(Go语言的源代码要求是UTF8编码,导致Go源代码中出现的字符串面值常量一般也是UTF8编码的)。
package main
func main() {
println("hello world")
}
我看这段代码。
package main。在官方文档中有一段Package names。源文件开头第一句必须是package 包名称
,可执行命令必须始终使用package main。关于package 之后的文章还会再讲下。
func main
是程序入口函数。 学过其他强类型语言能够想到。
println 是内建的打印函数。下面我们会再次讲到。
vscode编辑器
参看《【轻知识】go开发,装个beego,安装下vscode》
import 、println、fmt.Println
上面我们打印了一下hello world
。有看出什么特别之处么?你是否看过如下形式?
package main
import "fmt"
func main() {
fmt.Println("hello world")
}
那么println跟fmt.Println 有什么区别?import是做什么的?fmt.Printlnt 太长了可以简写吗?
1.println是内建函数(go的内建),fmt.Println 是fmt包中的方法。
println 不保证一直会留在语言中(builtin/#println)。而且println跟print也只能简单的打印(基本类型,可以打印多个,请看文档)。
所以打印,建议使用fmt
包,打印比较丰富,比如fmt.Printf。是不是想起C的printf格式化打印。
2.import 是引入包的,你用啥引啥。你想起了php的 include 、require,于是你想起了面试官问你两者不同之处。ok,扯远了。
import 有两种书写形式(而孔乙己的回字的有四种写法)。如下:
import "fmt"
import (
"fmt"
)
下面那种肯定是推荐的,引入多个的时候直接换行写。
第三个问题,在这里一并回答。可以简写。
import (
m "fmt"
)
m.Println()
还想简单点,可以,请注意那个.
import (
. "fmt"
)
Println()
我把调试打印删了,我不想用了,但是保不齐我还会用。但是我放哪里会报错(not used)不能编译。可以使用_
,这个符号加上之后包中只有init函数(初始化变量之类,之后会说)可以执行,其他方法不能调用。或者你可以用包的副作用(effective_go中Import for side effect)。
import (
_ "fmt"
)
变量
我们单独再创建一个文件var.go
,跟hello.go
在同一个目录下。
package main
import (
"fmt"
)
func main() {
var name string
name = "野原新之助"
fmt.Printf("Hello, %s\n", name)
}
我们看到在var.go这个文件,func main
的f下面有红色的~
,鼠标放上去。
main redeclared in this block previous declaration at .\hello.go:3:6go
当然在你写完程序保存的时候。问题
视窗就会把所有的问题打印出来了。
意思是前面的hello.go人家用了func main
。当你按下F5
调试运行。也跑不起来。hello.go用到了我不能用。难道这个提示是因为hello.go先创建的么?其实,当你创建一个a.go,a.go也用到func main
,在hello.go文件里面也会报红。这么一看,其实跟文件创建时间没关系。跟首字母排序有关。当然这个不重要。重要的是,我们不能在一个文件目录下多个文件用main函数。就好比一个软件不能有多个入口一样。
那我还是想跑起来。三个办法:
1.把其他文件的main改成其他名字比如main01
。当然下次我要运行其他文件又得反复改,当然这是馊主意。
2.我不用调试工具了。我手动在命令行里go run var.go。这个可以。但是
3.建子目录,把文件放进去。
这里思考一个问题。单独 go run var.go是没问题的。单独go build var.go也没问题。那么调试做了啥呢?其实我猜想vscode 调试,当你调试某一个文件的时候是在本文件所属的目录下。执行了go build,而不是单独build 一个文件。比如,把其他文件的main删掉。或者改成init。然后调试,你发下init是跑了的。也就是,当成一个项目来编译。
定义
var name string
var age int
还可以这样写
var (
name string
age int
)
可以一次定义多个变量
var x, y int
上面那种只声明不赋值,系统会默认该类型的零值:int 为 0,float 为 0.0,bool 为 false,string 为空字符串,指针为 nil。所有的内存在 Go 中都是经过初始化的。也是避免出现不可预测的行为。
紧接着就可以给声明的变量赋值了。
name = "野原新之助"
也可以声明跟赋值一起
var name string = "野原新之助"
go支持自动推断类型,Go 是在编译时就已经完成推断过程。
var name = "野原新之助"
var name, age = "野原新之助", 5
简短模式(Short variable declarations)
name := "野原向日葵"
name, age := "野原新之助", 5
注意的是简短模式有些限制
- 定义变量,同时显式初始化
- 不能提供数据类型
- 只能用在函数内部
简短变量声明可能仅出现在函数内部。在某些上下文中,例如“if”, “for”或 “switch”语句的初始化程序 ,它们可用于声明本地临时变量。
关于定义变量:先猜下,下面代码的输出结果
var age int
{
age := 12
print(age)
}
print(age)
答案是:12,0。{}
代码块中的age是重新定义的一个age,作用域限制在代码块中。那么下面的结果就好猜了,打印的是 12 0
。
var age int
{
age = 12
print(age)
}
print(age)
只能用在函数内部,也就意味着不能用于包级变量。也就下面这样是不行的。会报语法错误syntax error: non-declaration statement outside function body
。
illegal := 42
func foo() {
legal := 42
}
因为,在包级别,每个声明都应该以var,const,func,type和import之类的关键字开头。
可以在作用域语句上下文中重用它们,例如if (比如:Comma-ok断言),for,switch:
if foo := someFunc(); foo == 314 {
// foo is scoped to 314 here
// ...
}
还有一个注意点是,下面这两种都会报no new variables on left side of :=
var age = 5
age := 5
或者
age := 5
age := 6
但是,下面这样写就没问题。官网是这么说的(纯谷歌翻译哈):与常规变量声明不同,短变量声明可以重新声明 变量,前提是它们最初在同一个块中被声明(或者如果块是函数体的参数列表)具有相同的类型,并且至少有一个非空变量是新的。因此,重新声明只能出现在多变量简短声明中。重新声明不会引入新变量; 它只是为原始分配一个新值。
age := 5
age, height := 5, 105.9 // 这里用字面量举例子。你可以想象有两个返回值的函数也可以。
什么是非空变量(non-blank),blank就是blank identifier即_
。表示舍弃不用。也就是age, _ := 5,105.9 (代码中,有时候函数返回两个变量,有时候我们可以忽略变量)这样写是不行的。
《go语言学习笔记》说这样的叫做退化赋值。另外我还参考了一篇文章《Short variable declarations rulebook》
总结一下,了解了变量定义的方式。一是了解了一些细节后之后,能看懂别人代码,二是灵活运用。
思考
1.类型推断哪些好处?
Go 语言的类型推断可以明显提升程序的灵活性,使得代码重构变得更加容易(调用函数,返回值类型变了,接收返回值的变量不用改动声明,因为有类型推断),同时又不会给代码的维护带来额外负担(实际上,它恰恰可以避免散弹式的代码修改),更不会损失程序的运行效率。
作用域范围
官方文档Declarations_and_scope。或者自己搜吧。我觉得作用域没什么好说的。
数据类型
int、bool、float、字符类型
上面这几个没什么好说的。看《the way to go》中文版的基本类型和运算符了解下。
当你看到int有那么一堆。我觉得也不用纠结。类型选择我觉得吧,就跟你用数据库的选择数据库的类型那样。就可以了。
我觉得有两个知识点需要有印象。
-
比如数字间的类型提升或者类型降低需要注意下。比如:int、int8、int64。int8的变量转int是没问题。但是int转int8就可能(这个可能的意思是超出了int8的范围)会发生截断(所谓的越界)。这个细节自行了解下。我认为写程序还是尽量保持类型一致。我觉得这样比较规范。
-
字符类型的byte跟rune。rune 对应的是utf-8码点(UTF-8 是一种变长的编码方式,一般用 1~4 个字节序列来表示 Unicode 字符)。byte 是8位那就对应一个字节,byte可以完全表示accii码,但是byte无法表示utf-8。看下下面例子:
fmt.Printf("%#v\n", []byte("世界")) // 打印 []byte{0xe4, 0xb8, 0x96, 0xe7, 0x95, 0x8c}
fmt.Printf("%#v\n", []rune("世界")) // 打印出 []int32{19990, 30028}
先看 []byte前三个0xe4, 0xb8, 0x96
是世
这个字。可以这样验证下——fmt.Println("\xe4\xb8\x96")。[]rune 中的19990也是世
,可以这样验证下——fmt.Println("\u4e16"),以后看到\u开头的字符串首先想到是unicode编码,4e16是19990的十六进制表示,你还可以这样验证fmt.Println(string(19990))
(int to string 不是string(int),请用strconv包,strconv.Itoa(19990)转字符串"19990")。
扩展点:在实际工作中,我们在排查问题时可能会遇到�,字符'�'的 Unicode 代码点是U+FFFD。。它是 Unicode 标准中定义的 Replacement Character,专用于替换那些未知的、不被认可的以及无法展示的字符。比如上面3个byte表示一个世。万一你丢掉了一个byte,比如fmt.Println("\xb8\x96"),那就��。虽然直接把一个整数值转换为一个,类型的值是可行的,但值得关注的是,被转换的整数值应该可以代表一个有效的 Unicode 代码点,否则转换的结果将会是"�"。(这个点,组装于郝林极客时间专栏《Go语言核心36讲》之《06 | 程序实体的那些事儿 (下)》)。
在整理资料的时候,我想到的一些问题
1.package main 中的这个main是什么?
- 同一个目录下多个文件里不允许有多个main吗?
3.vscode 调试究竟干了些什么?
3.什么是远程调试?go如何远程调试?
参考资料:
- 《理解Go import》https://yushuangqi.com/blog/2016/understanding-golang-import-package.html
- 《Install GoLang on Ubuntu and Write Your First Program》https://linuxhint.com/install-golang-ubuntu/
- 《import cycle in Golang》https://wangzhezhe.github.io/2017/02/06/import-cycle/
- 《Debugging Go Code with Visual Studio Code》https://scotch.io/tutorials/debugging-go-code-with-visual-studio-code
- 《The way to go》中文版,https://go.fdos.me/
- 《Go语言学习笔记》雨痕
- 《Short variable declarations rulebook》https://blog.learngoprogramming.com/golang-short-variable-declaration-rules-6df88c881ee
- 《Golang: Rune》http://xahlee.info/golang/golang_rune.html