basic

main函数必须有,入口

  1. package main
  2. import "fmt"
  3. func main() {
  4. var a string = "Runoob"
  5. var b, c int = 1, 2
  6. fmt.Println("Hello, World!")
  7. fmt.Println(a)
  8. }

执行

  1. go run hello.go
  2. go build hello.go
  3. sh ./hello

变量

:= 只可以在函数中使用

  1. func() {
  2. power := 9000
  3. // 多个变量
  4. name, power := "Goku", 9000
  5. }
  6. // 常量
  7. const s string = "constant"

类型

值类型:int float bool string

  • 布尔 bool
  • 数字 init
  • 字符串 string
  • 派生
    • 指针
    • 数组
    • struct
    • Channel
    • 函数
    • 切片 slice
    • 接口
    • Map

忽略类型
可以省略类型说明符 [type],因为编译器可以根据变量的值来推断其类型。

字符串

  1. stra := "the spice must flow"
  2. byts := []byte(stra)
  3. strb := string(byts)

接口

接口是定义了合约但并没有实现的类型

接口有助于将代码与特定的实现进行分离。

  1. // 这里是一个几何体的基本接口。
  2. type geometry interface {
  3. area() float64
  4. perim() float64
  5. }
  6. type rect struct {
  7. width, height float64
  8. }
  9. // 要在 Go 中实现一个接口,我们就需要实现接口中的所有方法。
  10. // 这里我们在 `rect` 上实现了 `geometry` 接口。
  11. func (r rect) area() float64 {
  12. return r.width * r.height
  13. }
  14. func (r rect) perim() float64 {
  15. return 2*r.width + 2*r.height
  16. }
  17. r := rect{width: 3, height: 4}

空接口

因为空接口没有方法,可以说所有类型都实现了空接口,并且由于空接口是隐式实现的,因此每种类型都满足空接口契约。

  1. func add(a interface{}, b interface{}) interface{} {
  2. return a.(int) + b.(int)
  3. }


map

  1. lookup := make(map[string]int)
  2. lookup["goku"] = 9001
  3. power, exists := lookup["vegeta"]
  4. // 键的数量
  5. total := len(lookup)

包管理

  • 当你想去导入一个包的时候,你需要指定完整路径
  • 当你想去命名一个包的时候,可以通过 package 关键字,提供一个值,而不是完整的层次结构
  • 需要注意包名和文件夹名是一样的。
  • 可见性:如果类型或者函数名称以一个大写字母开始,它就具有了包外可见性。 ```go package db

import ( “github.com/mattn/go-sqlite3” )

type Item struct { Price float64 }

func LoadItem(id int) *Item { return &Item{ Price: 9.001, } }

  1. 使用 go mod 的好处就是不必强制把项目放到 GOPATH 下了
  2. **依赖管理**
  3. ```go
  4. // 更新所有包
  5. go get -u

数组

常用数据结构

  • 数组
  • 切片
  • 字典 map
  1. // 数组
  2. var n [10]int
  3. for i = 0; i < 10; i++ {
  4. n[i] = i + 100
  5. }
  6. fmt.Println(n)
  7. // 声明一个数组
  8. var scores [10]int
  9. scores[0] = 339
  10. len(scores)
  11. // 我们可以在初始化数组的时候指定值:
  12. scores := [4]int{9001, 9333, 212, 33}
  13. // 迭代
  14. for index, value := range scores {
  15. fmt.Println(value)
  16. }

结构体

  • 因为没有面向对象,它没有对象和继承的概念
  • 类似 JS 中的对象,区别很大
  • 可以将一些方法和结构体关联
  • 通过组合实现继承
  1. type Saiyan struct {
  2. Name string
  3. Power int
  4. }
  5. goku := Saiyan{
  6. Name: "Goku",
  7. Power: 9000,
  8. }
  9. goku := Saiyan{}
  10. // `&` 前缀生成一个结构体指针。
  11. goku := &Saiyan{Name: "Goku"}
  12. // 省略字段名
  13. goku := Saiyan{"Goku", 9000}

组合和继承

go 只有组合,没有 Java 的继承

  1. type Person struct {
  2. Name string
  3. }
  4. // 方法
  5. func (p *Person) Introduce() {
  6. fmt.Printf("Hi, I'm %s\n", p.Name)
  7. }
  8. type Saiyan struct {
  9. *Person
  10. Power int
  11. }
  12. // 使用它
  13. goku := &Saiyan{
  14. Person: &Person{"Goku"},
  15. Power: 9001,
  16. }
  17. goku.Introduce()

方法

*Saiyan 类型是 Super 方法的接受者

  1. type Saiyan struct {
  2. Name string
  3. Power int
  4. }
  5. // 类似 Saiyan.Super
  6. func (s *Saiyan) Super() {
  7. s.Power += 10000
  8. }
  9. // 调用
  10. goku := new(Saiyan)
  11. // same as
  12. goku := &Saiyan{}
  13. goku := &Saiyan{"Goku", 9001}
  14. goku.Super()
  15. fmt.Println(goku.Power) // 将会打印出 19001

函数

  1. func log(message string) {
  2. }
  3. // 一个返回值
  4. func add(a int, b int) int {
  5. }
  6. // 指定返回值
  7. func add(a int, b int) result int {
  8. result := a + b
  9. return
  10. }
  11. // 两个返回值
  12. func power(name string) (int, bool) {
  13. }
  14. // _ 代表不使用
  15. _, exists := power("goku")
  16. if exists == false {
  17. // handle this error case
  18. }

省略用法

  1. // 如果参数有相同的类型,您可以用这样一个简洁的用法
  2. func add(a, b int) int {
  3. }

指针

一个指针变量指向了一个值的内存地址。

  1. package main
  2. import "fmt"
  3. func main() {
  4. var a int= 20 /* 声明实际变量 */
  5. var ip *int /* 声明指针变量 */
  6. ip = &a /* 指针变量的存储地址 */
  7. fmt.Printf("a 变量的地址是: %x\n", &a )
  8. /* 指针变量的存储地址 */
  9. fmt.Printf("ip 变量储存的指针地址: %x\n", ip )
  10. /* 使用指针访问值 */
  11. fmt.Printf("*ip 变量的值: %d\n", *ip )
  12. }

镜像复制
Go 中传递参数到函数的方式:镜像复制

指针和值的传递

  1. func main() {
  2. goku := Saiyan{"Power", 9000}
  3. Super(goku)
  4. fmt.Println(goku.Power)
  5. }
  6. func Super(s Saiyan) {
  7. s.Power += 10000
  8. }
  9. // 返回9000

为了达到你的期望,我们可以传递一个指针到函数中:

  • 使用了 & 操作符以获取值的地址(它就是 取地址 操作符)
  • Super 期望一个地址类型 Saiyan,这里 X 意思是 指向类型 X 值的指针
  1. func main() {
  2. goku := &Saiyan{"Power", 9000}
  3. Super(goku)
  4. fmt.Println(goku.Power)
  5. }
  6. func Super(s *Saiyan) {
  7. s.Power += 10000
  8. }

我们仍然传递了一个 goku 的值的副本给 Super,但这时 goku 的值其实是一个地址。所以这个副本值也是一个与原值相等的地址

函数类型

  1. package main
  2. import (
  3. "fmt"
  4. )
  5. type Add func(a int, b int) int
  6. func main() {
  7. fmt.Println(process(func(a int, b int) int{
  8. return a + b
  9. }))
  10. }
  11. func process(adder Add) int {
  12. return adder(1, 2)
  13. }

并发

image.png

协程 和 通道
在 协程 中运行的代码可以与其他代码同时运行。

同步问题
Go 语言的并发同步模型是 CSP,通过在 goroutine 之前传递消息来同步,而不是通过对数据进行加锁来实现同步访问。

切片

数组非常高效但是很死板。很多时候,我们在事前并不知道数组的长度是多少。

针对这个情况,slices (切片) 出来了。

在 Go 语言中,我们很少直接使用数组。取而代之的是使用切片。切片是轻量的包含并表示数组的一部分的结构。

创建切片

  1. // 1 声明数组
  2. scores := []int{1,4,293,4,9}
  3. // 2 创建了一个长度是 0 ,容量是 10 的切片。
  4. scores := make([]int, 10)

错误的例子

  1. func main() {
  2. scores := make([]int, 0, 10)
  3. scores[7] = 9033
  4. fmt.Println(scores)
  5. }
  6. // 切片长度为0

扩展切片的方式

  1. // 想去设置索引为 7 的元素值
  2. func main() {
  3. scores := make([]int, 0, 10)
  4. scores = append(scores, 5)
  5. fmt.Println(scores) // prints [5]
  6. }
  7. func main() {
  8. scores := make([]int, 0, 10)
  9. scores = scores[0:8]
  10. scores[7] = 9033
  11. fmt.Println(scores)
  12. }

Go 使用 2x 算法来增加数组长度

这有四种方式初始化一个切片:

  1. names := []string{"leto", "jessica", "paul"}
  2. checks := make([]bool, 10)
  3. var names []string
  4. scores := make([]int, 0, 20)

迭代

range 关键字用于 for 循环中迭代数组(array)、切片(slice)、通道(channel)或集合(map)的元素

  1. //range也可以用在map的键值对上。
  2. kvs := map[string]string{"a": "apple", "b": "banana"}
  3. for k, v := range kvs {
  4. fmt.Printf("%s -> %s\n", k, v)
  5. }

错误处理

错误处理方式是返回值

  1. n, err := strconv.Atoi(os.Args[1])
  2. if err != nil {
  3. fmt.Println("not a valid number")
  4. } else {
  5. fmt.Println(n)
  6. }

panic,表示没有处理好的错误

  1. _, err := os.Create("/tmp/file")
  2. if err != nil {
  3. panic(err)
  4. }

range 遍历

  1. nums := []int{2, 3, 4}
  2. sum := 0
  3. for _, num := range nums {
  4. sum += num
  5. }
  6. // 遍历map
  7. kvs := map[string]string{"a": "apple", "b": "banana"}
  8. for k, v := range kvs {
  9. fmt.Printf("%s -> %s\n", k, v)
  10. }

defer 延迟调用

Defer 被用来确保一个函数调用在程序执行结束前执行,类似finally

  1. f := createFile("/tmp/defer.txt")
  2. defer closeFile(f)
  3. writeFile(f)

工具函数

  1. // strings
  2. p("Replace: ", s.Replace("foo", "o", "0", -1))
  3. p("Index: ", s.Index("test", "e"))
  4. // printf
  5. // 需要打印值的类型,使用 `%T`。
  6. fmt.Printf("%T\n", p)
  7. https://learnku.com/docs/gobyexample/2020/string-formatting/6297
  8. // copy
  9. // append

json 解析

  1. import "encoding/json"
  2. mapD := map[string]int{"apple": 5, "lettuce": 7}
  3. mapB, _ := json.Marshal(mapD)
  4. fmt.Println(string(mapB))

时间

  1. import "time"
  2. now := time.Now()
  3. secs := now.Unix()
  4. then := time.Date(
  5. 2009, 11, 17, 20, 34, 58, 651387237, time.UTC)
  6. p := fmt.Println
  7. p(then.Year())
  8. p(then.Month())
  9. p(then.Day())
  10. p(then.Before(now))

随机数

  1. import "math/rand"
  2. // `0 <= n <= 100`。
  3. fmt.Print(rand.Intn(100), ",")

url

  1. import "net/url"
  2. s := "postgres://user:pass@host.com:5432/path?k=v#f"
  3. // 解析这个 URL 并确保解析没有出错。
  4. u, err := url.Parse(s)
  5. if err != nil {
  6. panic(err)
  7. }

文件

  1. "path/filepath"
  2. "os"
  3. // 您应该总是使用 `Join` 代替手动拼接 `/` 和 `\`。
  4. fmt.Println(filepath.Join("dir1//", "filename"))
  5. fmt.Println(filepath.Join("dir1/../dir1", "filename"))
  6. err := os.Mkdir("subdir", 0755)

命令行参数 Arguments

  1. argsWithProg := os.Args
  2. argsWithoutProg := os.Args[1:]

环境变量

  1. os.Setenv("FOO", "1")
  2. fmt.Println("FOO:", os.Getenv("FOO"))
  3. fmt.Println("BAR:", os.Getenv("BAR"))

faq

framework

image.png

gin

https://github.com/gin-gonic/gin

  1. go mod init
  2. go run gin.go

教程 https://qiita.com/hyo_07/items/59c093dda143325b1859

Golang 1.13: 解决国内 go get 无法下载的问题

image.png

protobuf

ProtoBuf 是google团队开发的用于高效存储和读取结构化数据的工具。什么是结构化数据呢,正如字面上表达的,就是带有一定结构的数据。比如电话簿上有很多记录数据,每条记录包含姓名、ID、邮件、电话等,这种结构重复出现。

XML、JSON 也可以用来存储此类结构化数据,但是使用ProtoBuf表示的数据能更加高效,并且将数据压缩得更小。

在go中使用google protobuf,有两个可选用的包goprotobuf(go官方出品)和gogoprotobuf。

用protobuf序列化后的大小是json的10分之一,是xml格式的20分之一,但是性能却是它们的5~100倍

和 node.js 对比

https://zhuanlan.zhihu.com/p/29847628

nouns

  • zero value
  • gogo
  • protobuf
    • proto3 里咩有 optional 的
  • grpc