页面加载中...

go语言学习笔记

| 默认分类 | 1 条评论 | 250浏览

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循环有两种方式

  1. 替代while,比如:

    for true {
    }
    
  2. fori的方式,类似Java,只是同样不需要括号,比如

    for i := 0; i < 10; i++ {
    }
    
  3. 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提供一种声明结构体的语句,用typestruct来定义。如下:

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语言也有较多的语法糖,因此也就有不少的坑,所以还得需要持续深入。

参考资料

go官网:https://golang.google.cn/doc/tutorial

菜鸟教程:https://www.runoob.com/go/go-tutorial.html

发表评论

最新评论

  1. 热心网友

    好好学习👍