简介: Go语言出自Ken Thompson、Rob Pike和Robert Griesemer之手,起源于2007年,并在2009年正式对外公布。Go的次要指标是“兼具Python等动静语言的开发速度和C/C++等编译型语言的性能与安全性”,旨在不损失应用程序性能的状况下升高代码的复杂性,具备“部署简略、并发性好、语言设计良好、执行性能好”等劣势。
作者 | 赋行
起源 | 阿里技术公众号
前言
已经我是一名以Java语言为主的开发者,做过JavaWeb相干的开发,起初转Android,还是离不开Java,直到转去做大前端了,其实也就是始终在用JS写业务。现在因为集体倒退起因,来到阿里云,因为我的项目须要就撸起了Go语言;多年编程教训通知我,语言只是工具罢了,重要的还是其思维与逻辑,所以只需学学语法就好了,于是我便三天入门Go,期间次要用Java和JS来类比,语法变动之大,差点让我从入门到放弃了!其实,还真不是学习语法就好了呢,其中蕴含了很多Go的设计理念。正所谓好忘性不如敲烂键盘,学过的货色,还是要积淀积淀,也能够分享进去一起探讨,更有助于成长,于是我就简略记录了一下我的Go语言入门学习笔记。
一 简介
Go语言出自Ken Thompson、Rob Pike和Robert Griesemer之手,起源于2007年,并在2009年正式对外公布,其实都是Google的,设计Go语言的初衷都是为了满足Google的需要。Go的次要指标是“兼具Python等动静语言的开发速度和C/C++等编译型语言的性能与安全性”,旨在不损失应用程序性能的状况下升高代码的复杂性,具备“部署简略、并发性好、语言设计良好、执行性能好”等劣势。最次要还是为了并发而生,并发是基于goroutine的,goroutine相似于线程,但并非线程,能够将goroutine了解为一种虚构线程。Go语言运行时会参加调度goroutine,并将goroutine正当地调配到每个CPU中,最大限度地应用CPU性能。
二 环境
咱们玩Java的时候须要下载JDK,相似于此,用Go开发也须要下载Go,外面提供各种develop-kit、library以及编译器。在官网下载mac版本pkg后间接装置,最初用 go version 命令验证版本:
而后就是设置这两个环境变量,mac零碎是在 .bash_profile 文件外面:
export GOROOT=/usr/local/goexport GOPATH=$HOME/go
- GOROOT:示意的是Go语言编译、工具、规范库等的装置门路,其实就相当于配置JAVA_HOME那样。
- GOPATH:这个和Java有点不一样,Java里并不需要设置这个变量,这个示意Go的工作目录,是全局的,当执行Go命令的时候会依赖这个目录,相当于一个全局的workspace。个别还会把$GOPATH/bin设置到PATH目录,这样编译过的代码就能够间接执行了。
1 纯文本开发
编写代码,能够保留在任意中央,例如新建一个helloworld目录,创立hello.go文件:
package mainimport "fmt"func main() { fmt.Println("hello, world")}
而后执行 go build hello.go 就能够编译出hello文件,在./hello就能够执行了;或者间接 go run hello.go 合二为一去执行。执行这个命令并不需要设置环境变量就能够了。看起来和c差不多,然而和Java不一样,运行的时候不须要虚拟机。晚期的GO工程也是应用Makefile来编译,起初有了弱小的命令 go build、go run,能够间接辨认目录还是文件。
2 GoLand
主动import,超爽的体验!不必按command + /了!
运行我的项目须要设置build config,和Android、Java的都差不多,例如创立一个hello-goland我的项目:
导入go module我的项目的时候须要勾选这项,否则无奈像maven/gradle那样sync下载依赖:
3 VSCODE
间接搜寻Go插件,第一个最多装置量的就是了,我还没用过所以不太分明如何。
三 工程构造
在设置GOPATH环境变量的时候,这个目录外面又分了三个子目录bin、pkg、src,别离用于寄存可执行文件、包文件和源码文件。当咱们执行Go命令的时候,如果咱们指定的不是当前目录的文件或者绝对路径的目录的话,就会去GOPATH目录的去找。这样在GOPATH目录创立了xxx的目录后,就能够在任意中央执行 go build xx 命令来构建或者运行了。
pkg目录应该是在执行 go install 后生成的包文件,包含.a这样的文件,相当于一个归档。
├── bin│ ├── air│ ├── govendor│ ├── swag│ └── wire├── pkg│ ├── darwin_amd64│ ├── mod│ └── sumdb└── src ├── calc ├── gin-blog ├── github.com ├── golang.org ├── google.golang.org ├── gopkg.in └── simplemath
这样对于咱们具体我的项目来说并不好,没有Workspace的概念来隔离每个我的项目了,所以我感觉这个GOPATH目录放的应该是专用的我的项目,例如开源依赖的。咱们在开发过程中,也会下载很多的依赖,这些依赖都下载到这个目录,和咱们的我的项目文件混在一起了。
另外,通过IDE能够设置project的GOPATH,相当于在执行的时候给GOPATH减少了一个目录变量,也就是说,咱们创立一个我的项目,而后外面也有bin、src、pkg这三个目录,和GOPATH一样的,实质上,IDE在运行的时候其实就是设置了一下GOPATH:
GOPATH=/Users/fuxing/develop/testgo/calc-outside:/Users/fuxing/develop/go #gosetup
Go语言在寻找变量、函数、类属性及办法的时候,会先查看GOPATH这个零碎环境变量,而后依据该变量配置的门路列表顺次去对应门路下的src目录下依据包名查找对应的目录,如果对应目录存在,则再到该目录下查找对应的变量、函数、类属性和办法。
其实官网提供了Go Modules的办法更好解决。
1 Go Modules
从Go 1.11版本开始,官网提供了Go Modules治理我的项目和依赖,从1.13版本开始,更是默认开启了对Go Modules的反对,应用Go Modules的益处是不言而喻的 —— 不须要再依赖GOPATH,你能够在任何地位创立Go我的项目,并且在国内,能够通过 GOPROXY 配置镜像源减速依赖包的下载。也就是说,创立一个我的项目就是一个mod,基本上目前Go开源我的项目都是这样做的。其实就是相似于Maven和Gradle。
// 创立mod我的项目,也是能够用IDE来new一个mod我的项目的:go mod init calc-mod// 个别开源在github下面的我的项目名字是这样的;和maven、gradle不一样的是,开发实现基本不须要公布到仓库!只有提交代码后打tag就能够了go mod init github.com/fuxing-repo/fuxing-module-name// 创立一个模块:执行这个命令次要是多了一个go.mod文件,外面就一行内容:module calc-mod// import当前,执行下载依赖命令,不须要编辑go.mod文件。依赖会下载到GOPATH/pkg/mod目录go list
用GoLand来关上不同的我的项目,显示依赖的内部库是不一样的,如果是用GOPATH创立的我的项目,须要用命令下载依赖包到GOPATH:
go get -u github.com/fuxing-repo/fuxing-module-name
四 语法
1 包:Package 和 Import
Java外面的包名个别是很长的,和文件夹名称对应,作用就是命名空间,引入的时候须要写长长的一串,也能够用通配符:
Go外面个别的包名是以后的文件夹名称,同一个我的项目外面,能够存在同样的包名,如果同时都须要援用同样包名的时候,就能够用alias辨别,相似于JS那样。个别import的是一个包,不像Java那样import具体的类。同一个包内,不同文件,然而外面的货色是能够应用的,不须要import。这有点相似于C的include吧。如果多行的话,用括号换行包起来。
Go语言中,无论是变量、函数还是类属性及办法,它们的可见性都是与包相关联的,而不是相似Java那样,类属性和办法的可见性封装在对应的类中,而后通过 private、protected 和 public 这些关键字来形容其可见性,Go语言没有这些关键字,和变量和函数一样,对应Go语言的自定义类来说,属性和办法的可见性依据其首字母大小写来决定,如果属性名或办法名首字母大写,则能够在其余包中间接拜访这些属性和办法,否则只能在包内拜访,所以Go语言中的可见性都是包一级的,而不是类一级的。
在Java外面,只有动态,或者对象就能够应用点运算符,而且是极其罕用的操作,而在Go外面,还能够用一个包名来点,这就是联合了import来应用,能够点出一个函数调用,也能够点出一个构造体,一个接口。另外区别于C,不论是指针地址,还是对象援用,都是用点运算符,不须要思考用点还是箭头了!
入口的package必须是main,否则能够编译胜利,然而跑不起来:
Compiled binary cannot be executed.
起因就是找不到入口函数,跟C和Java一样吧,也须要main函数。
2 变量
- 用 var 关键字润饰(相似于JS),有多个变量的时候用括号 () 包起来,默认是有初始化值的,和Java一样。
- 如果初始化的时候就赋值了那能够不须要 var 来润饰,和Java不同的是变量类型在变量前面而不是后面,不过须要 := 符号。
- 最大的变动就是类型在变量前面!
- 语句能够省略分号 ;
var v1 int = 10 // 形式一,惯例的初始化操作var v2 = 10 // 形式二,此时变量类型会被编译器主动推导进去v3 := 10 // 形式三,能够省略 var,编译器能够主动推导出v3的类型//javaprivate HashMap<String, UGCUserDetail> mBlockInfo;
多重赋值
i, j = j, i能够实现变量替换,有点像JS的对象析构,然而其实不一样。有了这个能力,函数是能够返回多个值了!
匿名变量
用 _ 来示意,作用就是能够防止创立定义一些无意义的变量,还有就是不会分配内存。
指针变量
和C语言一样的,回忆一下替换值的例子即可,到底传值和传址作为参数的区别是啥。
Go语言之所以引入指针类型,次要基于两点思考,一个是为程序员提供操作变量对应内存数据结构的能力;另一个是为了进步程序的性能(指针能够间接指向某个变量值的内存地址,能够极大节俭内存空间,操作效率也更高),这在零碎编程、操作系统或者网络应用中是不容忽视的因素。
指针在Go语言中有两个应用场景:类型指针和数组切片。
作为类型指针时,容许对这个指针类型的数据进行批改指向其它内存地址,传递数据时如果应用指针则毋庸拷贝数据从而节俭内存空间,此外和C语言中的指针不同,Go语言中的类型指针不能进行偏移和运算,因而更为平安。
变量类型
Go语言内置对以下这些根本数据类型的反对:
- 布尔类型:bool
- 整型:int8、byte、int16、int、uint、uintptr 等
- 浮点类型:float32、float64
- 复数类型:complex64、complex128
- 字符串:string
- 字符类型:rune,实质上是uint32
- 谬误类型:error
此外,Go语言也反对以下这些复合类型:
- 指针(pointer)
- 数组(array)
- 切片(slice)
- 字典(map)
- 通道(chan)
- 构造体(struct)
- 接口(interface)
还有const常量,iota这个预约义常量用来定义枚举。能够被认为是一个可被编译器批改的常量,在每一个const关键字呈现时被重置为0,而后在下一个const呈现之前,每呈现一次iota,其所代表的数字会主动增1。
const ( Sunday = iota Monday Tuesday Wednesday Thursday Friday Saturday numberOfDays)
类型强转
v1 := 99.99v2 := int(v1) // v2 = 99v1 := []byte{'h', 'e', 'l', 'l', 'o'}v2 := string(v1) // v2 = hello//字符相干的转化个别用strconv包v1 := "100"v2, err := strconv.Atoi(v1) // 将字符串转化为整型,v2 = 100v3 := 100v4 := strconv.Itoa(v3) // 将整型转化为字符串, v4 = "100"//构造体类型转换//类型断言 //x.(T) 其实就是判断 T 是否实现了 x 接口,如果实现了,就把 x 接口类型具体化为 T 类型;claims, ok := tokenClaims.Claims.(*jwt.StandardClaims)
数组与切片
//定义数组var a [8]byte // 长度为8的数组,每个元素为一个字节var b [3][3]int // 二维数组(9宫格)var c [3][3][3]float64 // 三维数组(平面的9宫格)var d = [3]int{1, 2, 3} // 申明时初始化var e = new([3]string) // 通过 new 初始化var f = make([]string, 3) // 通过 make初始化//初始化a := [5]int{1,2,3,4,5}b := [...]int{1, 2, 3}//切片b := []int{} //数组切片slice就是一个可变长数组c := a[1:3] // 有点相似于subString,或者js.sliced := make([]int, 5) //make相当于,new、alloc,用来分配内存//数组的长度length := len(a)//增加一个元素b = append(b, 4)
字典
其实就是Java里的map,应用上语法有很多不同。
var testMap map[string]inttestMap = map[string]int{ "one": 1, "two": 2, "three": 3,}//还能够这样初始化:var testMap = make(map[string]int) //map[string]int{}testMap["one"] = 1testMap["two"] = 2testMap["three"] = 3
make和new
// The make built-in function allocates and initializes an object of type// slice, map, or chan (only). Like new, the first argument is a type, not a// value. Unlike new, make's return type is the same as the type of its// argument, not a pointer to it. The specification of the result depends on// the type:// Slice: The size specifies the length. The capacity of the slice is// equal to its length. A second integer argument may be provided to// specify a different capacity; it must be no smaller than the// length. For example, make([]int, 0, 10) allocates an underlying array// of size 10 and returns a slice of length 0 and capacity 10 that is// backed by this underlying array.// Map: An empty map is allocated with enough space to hold the// specified number of elements. The size may be omitted, in which case// a small starting size is allocated.// Channel: The channel's buffer is initialized with the specified// buffer capacity. If zero, or the size is omitted, the channel is// unbuffered.func make(t Type, size ...IntegerType) Type// The new built-in function allocates memory. The first argument is a type,// not a value, and the value returned is a pointer to a newly// allocated zero value of that type.func new(Type) *Type
区别就是返回值和参数不同,一个是值,一个是指针,slice、chan、map只能用make,自身就是指针。其余make、new都行。
神奇的nil
Java外面用null比拟难受,间接就判空了,除了在string类型的时候,还要判断字符为 "",然而Go外面的string要判断为空就简略一点,不能判断nil,只能判断 ""。然而Go外面的nil却和null不一样,其实是和JS外面 ==、=== 很像。
nil也是有类型的。
func Foo() error { var err *os.PathError = nil // … return err //理论返回的是[nil, *os.PathError] //return nil //正确的形式是间接return nil 理论返回的是[nil, nil]}func main() { err := Foo() fmt.Println(err) // <nil> fmt.Println(err == nil) // false fmt.Println(err == (*os.PathError)(nil)) //true}
根对象:Object
在Java外面,如果不必多态,没有接口,父类,超类的话,就用Object作为根对象,在Go外面,如果函数参数不晓得用什么类型,通常会用 interface{},这是个空接口,示意任意类型,因为不是弱类型语言,没有any类型,也不是强面向对象语言,没有Object,所以就有这个空接口的呈现。
3 语句
比拟大的一个特点就是能不必括号的中央都不必了。
管制流程
if语句的判断条件都没有了括号包起来,还能够前置写变量初始化语句,相似于for循环,左花括号 { 必须与 if 或者 else 处于同一行。
switch语句变得更弱小了,有这些变动:
- switch关键字前面能够不跟变量,这样case前面就必须跟条件表达式,其实实质上就是丑化了if-else-if。
- 如果switch前面跟变量,case也变得弱小了,能够呈现多个后果选项,通过逗号分隔。
- swtich前面还能够跟一个函数。
- 不须要用break来明确退出一个case,如果要穿透执行一层,能够用 fallthrough 关键字。
score := 100switch score {case 90, 100: fmt.Println("Grade: A")case 80: fmt.Println("Grade: B")case 70: fmt.Println("Grade: C")case 60:case 65: fmt.Println("Grade: D")default: fmt.Println("Grade: F")}s := "hello"switch {case s == "hello": fmt.Println("hello") fallthroughcase s == "xxxx": fmt.Println("xxxx")case s != "world": fmt.Println("world")}//output:hello xxxx
循环流程
去掉了 while、repeat 这些关键字了,只保留了 for 这个关键字,其实用起来差不多。break , continue 这些关键字还是有的。//通用的用法for i := 1; i <= 5; i++ { fmt.Println(i)}//相似于while的用法a := 1for a <= 5 { fmt.Println(a) a ++}//死循环for { // do something}for ;; { // do something}//相似java for-each的用法listArray := [...]string{"xiaobi", "xiaoda", "xiaoji"}for index, item := range listArray { fmt.Printf("hello, %d, %s\n", index, item)}//javafor (String item : someList) { System.out.println(item);}
跳转流程
Go很神奇的保留了始终被放弃的goto语句,记得是Basic、Pascal那些语言才会有,不晓得为啥。
i := 1flag:for i <= 10 { if i%2 == 1 { i++ goto flag } fmt.Println(i) i++}
defer流程有点像Java外面的finally,保障了肯定能执行,我感觉底层也是goto的实现吧。在前面跟一个函数的调用,就能实现将这个xxx函数的调用提早到以后函数执行完后再执行。
这是压栈的变量快照实现。
func printName(name string) { fmt.Println(name)}func main() { name := "go" defer printName(name) // output: go name = "python" defer printName(name) // output: python name = "java" printName(name) // output: java}//output:javapythongo//defer后于return执行var name string = "go"func myfunc() string { defer func() { name = "python" }() fmt.Printf("myfunc 函数里的name:%s\n", name) return name}func main() { myname := myfunc() fmt.Printf("main 函数里的name: %s\n", name) fmt.Println("main 函数里的myname: ", myname)}//output:myfunc 函数里的name:gomain 函数里的name: pythonmain 函数里的myname: go
4 函数
- 关键字是 func,Java则齐全没有 function 关键字,而是用 public、void 等等这样的关键字,JS也能够用箭头函数来去掉 function 关键字了。
- 函数的花括号强制要求在首行的开端。
- 能够返回多个值!返回值的类型定义在参数前面了,而不是一开始定义函数就须要写上,跟定义变量一样,参数的类型定义也是一样在前面的,如果雷同则保留最左边的类型,其余省略。
- 能够显式申明了返回值就能够了,必须每个返回值都显式,就能够省略 return 变量。
//一个返回值func GetEventHandleMsg(code int) string { msg, ok := EventHandleMsgMaps[code] if ok { return msg } return ""}//多个返回值func GetEventHandleMsg(code int) (string, error) { msg, ok := EventHandleMsgMaps[code] if ok { return msg, nil } return "", nil}//不显式return变量值func GetEventHandleMsg(code int) (msg string, e error) { var ok bool msg, ok = EventHandleMsgMaps[code] if ok { //do something return } return}
匿名函数和闭包
在Java外面的实现个别是外部类、匿名对象,不能通过办法传递函数作为参数,只能传一个对象,实现接口。
Go则和JS一样不便,能够传递函数,定义匿名函数。
//传递匿名函数func main() { i := 10 add := func (a, b int) { fmt.Printf("Variable i from main func: %d\n", i) fmt.Printf("The sum of %d and %d is: %d\n", a, b, a+b) } callback(1, add);}func callback(x int, f func(int, int)) { f(x, 2)}//return 匿名函数func main() { f := addfunc(1) fmt.Println(f(2))}func addfunc(a int) func(b int) int { return func(b int) int { return a + b }}
不定参数
和Java相似,不同的是在调用是也须要用 ... 来标识。
//定义func SkipHandler(c *gin.Context, skippers ...SkipperFunc) bool { for _, skipper := range skippers { if skipper(c) { return true } } return false}//调用middlewares.SkipHandler(c, skippers...)
五 面向对象
在C语言外面常常会有用到别名的用法,能够用 type 类起一个别名,很罕用,特地是在看源码的时候经常出现:
type Integer int
1 类
没有 class 的定义,Go外面的类是用构造体来定义的。
type Student struct { id uint name string male bool score float64}//没有构造函数,然而能够用函数来创立实例对象,并且能够指定字段初始化,相似于Java外面的动态工厂办法func NewStudent(id uint, name string, male bool, score float64) *Student { return &Student{id, name, male, score}}func NewStudent2(id uint, name string, male bool, score float64) Student { return Student{id, name, male, score}}
2 成员办法
定义类的成员函数办法比拟隐式,方向是反的,不是申明这个类有哪些成员办法,而是申明这个函数是属于哪个类的。申明语法就是在 func 关键字之后,函数名之前,留神不要把Java的返回值定义给混同了!
//这种申明形式和C++一样的,这个就是不是一般函数了,而是成员函数。//留神到的是,两个办法一个申明的是地址,一个申明的是构造体,两个都能间接通过点操作。func (s Student) GetName() string { return s.name}func (s *Student) SetName(name string) { s.name = name}//应用func main() { //a是指针类型 a := NewStudent(1, "aa", false, 45) a.SetName("aaa") fmt.Printf("a name:%s\n", a.GetName()) b := NewStudent2(2, "bb", false, 55) b.SetName("bbb") fmt.Printf("b name:%s\n", b.GetName())}//如果SetName办法和GetName办法归属于Student,而不是*Student的话,那么批改名字就会不胜利//实质上,申明成员函数,就是在非函数参数的中央来传递对象、指针、或者说是援用,也就是变相传递this指针//所以才会呈现批改名字不胜利的case
3 继承
没有 extend 关键字,也就没有了继承,只能通过组合的形式来实现。组合就解决了多继承问题,而且多继承的程序不同,内存构造也不同。
type Animal struct { name string}func (a Animal) FavorFood() string { return "FavorFood..."}func (a Animal) Call() string { return "Voice..."}type Dog struct { Animal}func (d Dog) Call() string { return "汪汪汪"}//第二种形式,在初始化就须要指定地址,其余都没变动type Dog2 struct { *Animal}func test() { d1 := Dog{} d1.name = "mydog" d2 := Dog2{} d2.name = "mydog2" //构造体是值类型,如果传入值变量的话,实际上传入的是构造体值的正本,对内存消耗更大, //所以传入指针性能更好 a := Animal{"ddog"} d3 := Dog{a} d4 := Dog2{&a}}
这种语法并不是像Java外面的组合,应用成员变量,而是间接援用Animal并没有定义变量名称(当然也是能够的,不过没必要了),而后就能够拜访Animal中的所有属性和办法(如果两个类不在同一个包中,只能拜访父类中首字母大写的公共属性和办法),还能够实现办法重写。
4 接口
Java的接口是侵入式的,指的是实现类必须明确申明本人实现了某个接口。带来的问题就是,如果接口改了,实现类都必须改,所以以前总是会有一个抽象类在两头。
//定义接口:type Phone interface { call()}//实现接口:type IPhone struct { name string}func (phone IPhone) call() { fmt.Println("Iphone calling.")}
Go的接口是非侵入式的,因为类与接口的实现关系不是通过显式申明,而是零碎依据两者的办法汇合进行判断。一个类必须实现接口所有的办法才算是实现了这个接口。接口之间的继承和类的继承一样,通过组合实现,多态的实现逻辑是一样的,如果接口A的办法列表是接口B的办法列表的子集,那么接口B能够赋值给接口A。
六 并发编程
目前并发编程方面还没学习多少,就简略从网上摘了这一个经典的生产者消费者模型例子来初步感受一下,后续深刻学习过后再进行分享。
// 数据生产者func producer(header string, channel chan<- string) { // 有限循环, 不停地生产数据 for { // 将随机数和字符串格式化为字符串发送给通道 channel <- fmt.Sprintf("%s: %v", header, rand.Int31()) // 期待1秒 time.Sleep(time.Second) }}// 数据消费者func customer(channel <-chan string) { // 不停地获取数据 for { // 从通道中取出数据, 此处会阻塞直到信道中返回数据 message := <-channel // 打印数据 fmt.Println(message) }}func main() { // 创立一个字符串类型的通道 channel := make(chan string) // 创立producer()函数的并发goroutine go producer("cat", channel) go producer("dog", channel) // 数据生产函数 customer(channel)}//output:dog: 1298498081cat: 2019727887cat: 1427131847dog: 939984059dog: 1474941318cat: 911902081cat: 140954425dog: 336122540
七 总结
这只是一个简略入门,其实Go还有很多很多货色我没有去波及的,例如context、try-catch、并发相干(如锁等)、Web开发相干的、数据库相干的。以此贴开始,后续持续学习Go语言分享。
原文链接
本文为阿里云原创内容,未经容许不得转载。