basic
main函数必须有,入口
package mainimport "fmt"func main() {var a string = "Runoob"var b, c int = 1, 2fmt.Println("Hello, World!")fmt.Println(a)}
执行
go run hello.gogo build hello.gosh ./hello
变量
:= 只可以在函数中使用
func() {power := 9000// 多个变量name, power := "Goku", 9000}// 常量const s string = "constant"
类型
值类型:int float bool string
- 布尔 bool
- 数字 init
- 字符串 string
- 派生
- 指针
- 数组
- struct
- Channel
- 函数
- 切片 slice
- 接口
- Map
忽略类型
可以省略类型说明符 [type],因为编译器可以根据变量的值来推断其类型。
字符串
stra := "the spice must flow"byts := []byte(stra)strb := string(byts)
接口
接口是定义了合约但并没有实现的类型
接口有助于将代码与特定的实现进行分离。
// 这里是一个几何体的基本接口。type geometry interface {area() float64perim() float64}type rect struct {width, height float64}// 要在 Go 中实现一个接口,我们就需要实现接口中的所有方法。// 这里我们在 `rect` 上实现了 `geometry` 接口。func (r rect) area() float64 {return r.width * r.height}func (r rect) perim() float64 {return 2*r.width + 2*r.height}r := rect{width: 3, height: 4}
空接口
因为空接口没有方法,可以说所有类型都实现了空接口,并且由于空接口是隐式实现的,因此每种类型都满足空接口契约。
func add(a interface{}, b interface{}) interface{} {return a.(int) + b.(int)}
map
lookup := make(map[string]int)lookup["goku"] = 9001power, exists := lookup["vegeta"]// 键的数量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, } }
使用 go mod 的好处就是不必强制把项目放到 GOPATH 下了**依赖管理**```go// 更新所有包go get -u
数组
常用数据结构
- 数组
- 切片
- 字典 map
// 数组var n [10]intfor i = 0; i < 10; i++ {n[i] = i + 100}fmt.Println(n)// 声明一个数组var scores [10]intscores[0] = 339len(scores)// 我们可以在初始化数组的时候指定值:scores := [4]int{9001, 9333, 212, 33}// 迭代for index, value := range scores {fmt.Println(value)}
结构体
- 因为没有面向对象,它没有对象和继承的概念
- 类似 JS 中的对象,区别很大
- 可以将一些方法和结构体关联
- 通过组合实现继承
type Saiyan struct {Name stringPower int}goku := Saiyan{Name: "Goku",Power: 9000,}goku := Saiyan{}// `&` 前缀生成一个结构体指针。goku := &Saiyan{Name: "Goku"}// 省略字段名goku := Saiyan{"Goku", 9000}
组合和继承
go 只有组合,没有 Java 的继承
type Person struct {Name string}// 方法func (p *Person) Introduce() {fmt.Printf("Hi, I'm %s\n", p.Name)}type Saiyan struct {*PersonPower int}// 使用它goku := &Saiyan{Person: &Person{"Goku"},Power: 9001,}goku.Introduce()
方法
*Saiyan 类型是 Super 方法的接受者
type Saiyan struct {Name stringPower int}// 类似 Saiyan.Superfunc (s *Saiyan) Super() {s.Power += 10000}// 调用goku := new(Saiyan)// same asgoku := &Saiyan{}goku := &Saiyan{"Goku", 9001}goku.Super()fmt.Println(goku.Power) // 将会打印出 19001
函数
func log(message string) {}// 一个返回值func add(a int, b int) int {}// 指定返回值func add(a int, b int) result int {result := a + breturn}// 两个返回值func power(name string) (int, bool) {}// _ 代表不使用_, exists := power("goku")if exists == false {// handle this error case}
省略用法
// 如果参数有相同的类型,您可以用这样一个简洁的用法func add(a, b int) int {}
指针
一个指针变量指向了一个值的内存地址。
package mainimport "fmt"func main() {var a int= 20 /* 声明实际变量 */var ip *int /* 声明指针变量 */ip = &a /* 指针变量的存储地址 */fmt.Printf("a 变量的地址是: %x\n", &a )/* 指针变量的存储地址 */fmt.Printf("ip 变量储存的指针地址: %x\n", ip )/* 使用指针访问值 */fmt.Printf("*ip 变量的值: %d\n", *ip )}
镜像复制
Go 中传递参数到函数的方式:镜像复制
指针和值的传递
func main() {goku := Saiyan{"Power", 9000}Super(goku)fmt.Println(goku.Power)}func Super(s Saiyan) {s.Power += 10000}// 返回9000
为了达到你的期望,我们可以传递一个指针到函数中:
- 使用了 & 操作符以获取值的地址(它就是 取地址 操作符)
- Super 期望一个地址类型 Saiyan,这里 X 意思是 指向类型 X 值的指针
func main() {goku := &Saiyan{"Power", 9000}Super(goku)fmt.Println(goku.Power)}func Super(s *Saiyan) {s.Power += 10000}
我们仍然传递了一个 goku 的值的副本给 Super,但这时 goku 的值其实是一个地址。所以这个副本值也是一个与原值相等的地址
函数类型
package mainimport ("fmt")type Add func(a int, b int) intfunc main() {fmt.Println(process(func(a int, b int) int{return a + b}))}func process(adder Add) int {return adder(1, 2)}
并发

协程 和 通道
在 协程 中运行的代码可以与其他代码同时运行。
同步问题
Go 语言的并发同步模型是 CSP,通过在 goroutine 之前传递消息来同步,而不是通过对数据进行加锁来实现同步访问。
切片
数组非常高效但是很死板。很多时候,我们在事前并不知道数组的长度是多少。
针对这个情况,slices (切片) 出来了。
在 Go 语言中,我们很少直接使用数组。取而代之的是使用切片。切片是轻量的包含并表示数组的一部分的结构。
创建切片
// 1 声明数组scores := []int{1,4,293,4,9}// 2 创建了一个长度是 0 ,容量是 10 的切片。scores := make([]int, 10)
错误的例子
func main() {scores := make([]int, 0, 10)scores[7] = 9033fmt.Println(scores)}// 切片长度为0
扩展切片的方式
// 想去设置索引为 7 的元素值func main() {scores := make([]int, 0, 10)scores = append(scores, 5)fmt.Println(scores) // prints [5]}func main() {scores := make([]int, 0, 10)scores = scores[0:8]scores[7] = 9033fmt.Println(scores)}
Go 使用 2x 算法来增加数组长度
这有四种方式初始化一个切片:
names := []string{"leto", "jessica", "paul"}checks := make([]bool, 10)var names []stringscores := make([]int, 0, 20)
迭代
range 关键字用于 for 循环中迭代数组(array)、切片(slice)、通道(channel)或集合(map)的元素
//range也可以用在map的键值对上。kvs := map[string]string{"a": "apple", "b": "banana"}for k, v := range kvs {fmt.Printf("%s -> %s\n", k, v)}
错误处理
错误处理方式是返回值
n, err := strconv.Atoi(os.Args[1])if err != nil {fmt.Println("not a valid number")} else {fmt.Println(n)}
panic,表示没有处理好的错误
_, err := os.Create("/tmp/file")if err != nil {panic(err)}
range 遍历
nums := []int{2, 3, 4}sum := 0for _, num := range nums {sum += num}// 遍历mapkvs := map[string]string{"a": "apple", "b": "banana"}for k, v := range kvs {fmt.Printf("%s -> %s\n", k, v)}
defer 延迟调用
Defer 被用来确保一个函数调用在程序执行结束前执行,类似finally
f := createFile("/tmp/defer.txt")defer closeFile(f)writeFile(f)
工具函数
// stringsp("Replace: ", s.Replace("foo", "o", "0", -1))p("Index: ", s.Index("test", "e"))// printf// 需要打印值的类型,使用 `%T`。fmt.Printf("%T\n", p)https://learnku.com/docs/gobyexample/2020/string-formatting/6297// copy// append
json 解析
import "encoding/json"mapD := map[string]int{"apple": 5, "lettuce": 7}mapB, _ := json.Marshal(mapD)fmt.Println(string(mapB))
时间
import "time"now := time.Now()secs := now.Unix()then := time.Date(2009, 11, 17, 20, 34, 58, 651387237, time.UTC)p := fmt.Printlnp(then.Year())p(then.Month())p(then.Day())p(then.Before(now))
随机数
import "math/rand"// `0 <= n <= 100`。fmt.Print(rand.Intn(100), ",")
url
import "net/url"s := "postgres://user:pass@host.com:5432/path?k=v#f"// 解析这个 URL 并确保解析没有出错。u, err := url.Parse(s)if err != nil {panic(err)}
文件
"path/filepath""os"// 您应该总是使用 `Join` 代替手动拼接 `/` 和 `\`。fmt.Println(filepath.Join("dir1//", "filename"))fmt.Println(filepath.Join("dir1/../dir1", "filename"))err := os.Mkdir("subdir", 0755)
命令行参数 Arguments
argsWithProg := os.ArgsargsWithoutProg := os.Args[1:]
环境变量
os.Setenv("FOO", "1")fmt.Println("FOO:", os.Getenv("FOO"))fmt.Println("BAR:", os.Getenv("BAR"))
faq
framework

gin
https://github.com/gin-gonic/gin
go mod initgo run gin.go
教程 https://qiita.com/hyo_07/items/59c093dda143325b1859
Golang 1.13: 解决国内 go get 无法下载的问题

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
