Hello world
go下载地址(windows、linux环境都支持):https://golang.google.cn/doc/install
环境变量配置好了之后,来看看第一个例子hello world,创建一个helloworld.go文件,内容如下:
// helloworld.go
package main
import "fmt"
func main() {
fmt.Println("hello world")
}
使用go run
命令运行:
$ go run helloworld.go
Hello, World!
mod
go的代码使用mod来进行管理。如同我们使用Java创建jar包一样,go的mod也是类似这种原理,在工程里可以引用别的mod。
创建module
在一个空文件夹(greetings),通过如下命令来创建一个简单的mod:
$ go mod init abreaking.com/greetings
go: creating new go.mod: module abreaking.com/greetings
这个时候会自动生成一个go.mod
文件,内容如下:
module abreaking.com/greetings
go 1.16
然后我们再创建一个go文件:greetings.go。内容如下:
package greetings
import "fmt"
func Hello(name string) string {
message := fmt.Sprintf("Hi, %v. Welcome!", name)
return message
}
使用module
然后呢,我们再用同样的方式,创建另一个mod:
$ cd ..
$ mkdir hello
$ cd hello
$ go mod init abreaking.com/hello
go: creating new go.mod: module abreking.com/hello
同样会自动生成一个go.mod
文件
然后再创建一个hello.go文件来使用前面创建过的greetings的module。
package main
import (
"fmt"
"abreaking.com/greetings"
)
func main() {
message := greetings.Hello("Abreaking")
fmt.Println(message)
}
如果你是在idea里创建的hello.go
文件,你会发现有报错,因为idea会自动去下载abreaking.com/greetings
的module。
所以,目前是开发学习环境,我们只需要通过如下命令就可以解决这个问题:
$ go mod edit -replace abreaking.com/greetings=../greetings
相当于就是告诉go应该去哪里找abreaking.com/greetings
这个modual。
然后再看hello的go.mod文件内容:
module abreaking.com/hello
go 1.16
replace abreaking.com/greetings => ../greetings
使用go mod tidy
命令同步abreaking.com/hello modual的依赖项,也就说,给当前的hello添加greetings依赖。
$ go mod tidy
go: found abreaking.com/greetings in abreaking.com/greetings v0.0.0-00010101000000-000000000000
然后就可以直接运行了:
$ go run .
Hi, Abreaking. Welcome!
指针还是对象
与Java不同点之一:go语言支持指针。这点跟c预研比较像。
所以,涉及到了一个问题:是值传递还是引用传递。
看个例子:
package main
import "fmt"
type User struct {
name string
age int
}
func main() {
user := User{"zhangsan",10}
changeValue(user) // 传递是user的值,但是并不会改变user对象的值
fmt.Println(user) //输出结果仍然是{"zhangsan",10}
}
func changeValue(user User) {
user.age = 20
user.name = "lisi"
}
该代码很好理解,也非常类似Java的语言结构。但是输出结果发现user
对象的值并没有改变,这点跟Java不一样。
Java里像这种写法是引用传递,changeValue
方法执行后,最终对象的属性值会发生改变。
golang是值传递,也就是说传递是user对象的值,但并不是引用对象本身。
所以,如果需要引用对象本身,只需要改为传递成指针即可:
package main
import "fmt"
type User struct {
name string
age int
}
func main() {
user := User{"zhangsan",10}
changePointer(&user) //传递是user对象的指针,所以会改变user对象本身的值
fmt.Println(user) //输出结果:{"lisi",20}
}
func changePointer(user *User) {
user.age = 20
user.name = "lisi"
}
这下如我们的期望,user对象的值被改变了。
对比Java
因为我自己就是Java出身,所以学习新语言会习惯性的跟Java对比,经过几天的学习,在以下几个方面进行了对照:
条件判断
if
语句,基本上同Java,只不过if里面不要括号了,比如:
if true {
}else if false {
}
一个坑:go语言居然不支持三目运算符,真是匪夷所思!
循环
go不支持while
循环,只支持for
循环,准确说,用for完全替代了while循环。
支持的for
循环有两种方式
-
替代while,比如:
for true { }
-
fori的方式,类似Java,只是同样不需要括号,比如
for i := 0; i < 10; i++ { }
-
forr,有点类似Java的foreach循环,如下:
myArray := [3]int{1,2,3} for index, value := range myArray { fmt.Println(index,value) } // index是下标,value就是集合迭代时的每个元素
如果不需要
index
,可以用 '_'替代,如for _, value := range myArray { fmt.Println(value) }
方法
go定义方法基本上同Java,只不过,写Java时,我们一般会类型声明放在前面;但是go语言却放在后面。比如:
// a 表示方法的入参,
// map[int]string 表示返回类型,这里返回一个map对象
// nil 可看作java的null了
func simpleMethod(a int) map[int]string {
return nil
}
另外,go的方法是可以返回多个值的,并且方法是可以返回异常的。
// a,b 都是入参,并且类型是int
// 该方法返回了三个值:map对象、字符串、异常
func complexMethod(a,b int) (map[int]string , string ,error) {
return nil,"",nil
}
结构体
如同Java的类,go提供一种声明结构体的语句,用type
和struct
来定义。如下:
type User struct {
name string
age int
}
还有彩蛋!关于这个User
,首字母是大写开头还是小写开头还有点门道
- 大写开头,类似Java声明了一个 public的对象,也就是说,除了自己能用这个结构体,其他包的go文件里也能使用这个结构体;
- 小写开头,类似Java声明了一个project对象,也就是说,这个结构体只能自己或同包的go文件里才能使用。
用结构体实例化对象也是比较简单,不需要像java一样声明构造方法,而是类似有点json的方式,如下:
zhangsan := User{"zhangsan",18}
lisi := User{name: "lisi"}
lisi.age = 19
切片
我们知道,在java中,数组是不可变的,需要做一些数组的截取操作,我们一般会使用List
里的subString
方法,如果需要对数组合并,一般会用到Arrays.copyOf
这样的方法
同样的,go的数组也是不可变的,对于数组截取或扩容的操作,go 提供了一种切片机制。
func main() {
myArray := []int{1,2,3,4,5,6,7}
mySlice1 := myArray[2:5] //表示截取从下标 2 到下标5的元素,即[2,5)
fmt.Println(mySlice1, len(mySlice1), cap(mySlice1)) // [3 4 5] 3 5
var mySlice2 = make([]int,10) //可用用make来进行声明切片
mySlice2 = myArray[:5] //表示截取从开头 到5 的元素
fmt.Println(mySlice2, len(mySlice2),cap(mySlice2)) //[1 2 3 4 5] 5 7
mySlice3 := myArray[2:] //表示接却从2 到结尾的元素
fmt.Println(mySlice3, len(mySlice3),cap(mySlice3)) //[3 4 5 6 7] 5 5
}
关于容量(cap)和长度(len)的计算,有个公式:
这里有个公式,对于底层数组容量是 k 的切片 slice[i:j] 来说:
长度len: j-i
容量cap: k-i
如果需要对数组扩容,可以使用append
方法。如果需要对两个数组合并,可以用copy
方法
func main() {
array1 := []int{1,2,3,4,5}
array1 = append(array1,6,7,8) //append可以跟一个或多个值
fmt.Println(array1) // [1 2 3 4 5 6 7 8]
array2 := []int{10,11,12}
copy(array1,array2) // 把array2的元素拷贝到array1里面
fmt.Println(array1) //[10 11 12 4 5 6 7 8]
}
map
我们知道,要使用key=value这种键值对的数据结构,Java里最常使用的基本上是HashMap
了,此外呢,还有很多其他特色的Map对象。
go语法里支持map,基本思想同Java的Map
,只不过在使用上更偏向json
的这种风格。
删除map里的key,需要用到delete
关键字:
func main() {
myMap := make(map[string]int) //使用make的方式来初始化一个空map
myMap["zhangsan"] = 18
myMap["lisi"] = 20
fmt.Println(myMap["zhangsan"]) //18
delete(myMap,"zhangsan")
fmt.Println(myMap["zhangsan"]) //0
}
并发
如果说go有什么优势呢,那么可以说go天生就是面向并发的。
我们只需要通过 go 关键字来开启 goroutine 即可。
goroutine 是轻量级线程,goroutine 的调度是由 Golang 运行时进行管理的。
go开启一个线程的操作非常简单,直接如下语法格式:
// go 函数名( 参数列表 )
go f(x,y)
是不是比Java代码new Thread(Runnale).start()
方式简单多了。
线程之间可能需要传递一些值,go提供了一个通道(chan)机制。
func main() {
ch := make(chan int)
go myChan(5, ch) //开启两个线程
go myChan(6, ch)
x, y := <-ch, <-ch //阻塞的操作,会等两个线程执行完
fmt.Println(x + y) //5+6=11
}
func myChan(s int, ch chan int) {
ch <- s
}
总结
一定程度上来说,go代码是比Java代码简洁的。
作为熟练Java的程序员,刚过来学习go语言,是有很多不习惯的。比如变量声明类型放在变量前面到现在我都没习惯。
此外,go语言也有较多的语法糖,因此也就有不少的坑,所以还得需要持续深入。
好好学习👍