Go语法笔记

Go小技巧

  1. struct判断是否为空:
    nil只能赋值给指针、channel、func、interface、map或slice类型的变量,将nil赋值给其他类型的变量会引发panic。因此无法直接通过nil判断是否为空。可以通过reflect.DeepEqual()来判断
  2. 多态时,当interface向上转型成父interface后,需要通过断言来强制类型转换才能换回原来的类型。
  3. 循环引用时要分包,可能有点丑,但快速好用
  4. 结构体中有map是不能直接用的。map需要初始化,即make。结构体中的需要初始化结构体的时候初始化一下。

Go基础语法

命名规范:

常量:
const (
systemName = “What”
sysVal = “dasdsada”
)

函数:

  1. 驼峰命名
  2. 包外不需访问 则小写开头
  3. 包外需要访问 则大写开头

接口:
遵循驼峰方式命名, 可以用type alias来定义大写开头的type给包外访问

包机制记录:

  1. 文件名与包名没有直接关系,不一定要将文件名与包名定成同一个。
  2. 文件夹名与包名没有直接关系,并非需要一致。
  3. 同一个文件夹下的文件只能有一个包名,否则编译报错。
  4. main包里定义的全局变量别的包是无法引用的。通常选择放在global包或其他自定义的包中,然后让其他包来引用
  5. 可以放到github上,然后import github这样
  6. go mod的原因,包名不能随便写,比如compiler项目下的src文件夹下的init文件夹,则应import “Compiler/src/init”

变量声明

1
2
3
4
5
6
7
8
9
var (  // 这种因式分解关键字的写法一般用于声明全局变量
a int
b bool
)

//这种不带声明格式的只能在函数体中出现
g, h := 123, "hello"

//go局部变量声明后必须使用,全局可不使用

函数作为实参

回调demo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package main
import "fmt"

// 声明一个函数类型
type cb func(int) int

func main() {
testCallBack(1, callBack)
testCallBack(2, func(x int) int {
fmt.Printf("我是回调,x:%d\n", x)
return x
})
}

func testCallBack(x int, f cb) {
f(x)
}

func callBack(x int) int {
fmt.Printf("我是回调,x:%d\n", x)
return x
}

数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var balance [10] float32 // 声明
var balance = [5]float32{10,0} // 初始化
names := []string{"stanley", "david", "oscar"} //

func getAverage(arr [5]int, size int) float32 {} // 函数参数

// 指针数组与数组指针
var ptr [3]*[4]int // 声明一个指针数组,里边有3个指向4个元素的数组的数组指针

for i:=0; i<=2; i++ {
//var pf *[3]int = &(a[i])
ptr[i] = &(a[i])
for j:=0; j<=3; j++ {
fmt.Print(ptr[i][j])
fmt.Print(" ")
}
}

切片

1
2
3
4
// map数组切片:
make(map[string]string) // make(map[string]*SkuMeta)
// 其他类型切片
make([]type, (len))

range&map

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//range遍历map键值对
kvs := map[string]string{"a": "apple", "b": "banana"} // map可map[String](*Struct)
for k, v := range kvs {
fmt.Printf("%s -> %s\n", k, v)
}

//range枚举字符串输出ascii
for i, c := range "go" {
fmt.Println(i, c)
}
// a -> apple
// b -> banana
// 0 103
// 1 111

结构体小知识点

1.继承是继承父结构体的所有属性和方法,包括大小写开头的变量和函数。
2.如果子结构体和父结构体有同名的变量或者函数,并不会产生覆盖,可以通过”结构体名.父结构名.变量或函数名”的方式调用父结构体的同名变量或函数。但直接通过”结构体名.变量或函数名”则会访问继承后的方法

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

接口demo

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
type user struct {
name string
email string
}
type notifier interface {
notify()
}

func (u *user) notify() {
fmt.Printf("sending user email to %s<%s>\n",
u.name,
u.email)
}
func sendNotification(n notifier) {
n.notify()
}

func main() {
u := user{
name: "stormzhu",
email: "abc@qq.com",
}
/*
也可
var n notifier
n = &u
n.notify()
*/
sendNotification(&u) // 注意这里需要&u, 因为notify方法绑定的是*user, 因此需要&u
}

感觉接口最重要的作用是实现多态。
https://sanyuesha.com/2017/07/22/how-to-understand-go-interface/

defer

最常见的使用场景:关闭各种io资源。当第二个create出错时,关闭第一个open打开的文件句柄。
注意:

  1. defer的调用顺序是后定义的先调用。
  2. return之前定义的defer才有效
  3. defer在return之后调用

https://www.jianshu.com/p/5b0b36f398a2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
func CopyFile(dstName, srcName string) (written int64, err error) {
src, err := os.Open(srcName)
if err != nil {
return
}
defer src.Close()

dst, err := os.Create(dstName)
if err != nil {
return
}
defer dst.Close()

// other codes
return io.Copy(dst, src)
}

数组类型转换Demo

1
2
3
4
5
6
7
8
func main() {
names := []string{"stanley", "david", "oscar"}
vals := make([]interface{}, len(names)) // make([]int, len(names))
for i, v := range names {
vals[i] = v
}
PrintAll(vals)
}

类型断言

1
2
3
4
断言主要用在类型判断、类型转换中。
golang中的类型转换分强制类型转换和类型断言,而且go并没有隐式类型转换。

场景:编译原理中,各种stmt、expr结构体的父结构体是ASTNode节点。在grammer中通过指定类型把所有返回向上转型成ASTNode类型,在传入参数时需要转回去,就可以通过断言实现。

Go常用操作

http

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
//请求Demo
func getReq(req *http.Request) string {
body, _ := ioutil.ReadAll(req.Body)
return string(body)
}

func getResp(resp *http.Response) string {
resbody, err := ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Println(err.Error())
}
return string(resbody)
}

func httpReq(reqUrl string) {
client := &http.Client{}
datas := url.Values{
"a":{"1"},
}
req,err := http.NewRequest("POST",reqUrl,strings.NewReader(datas.Encode()))
if err != nil {
fmt.Println("[-] NewRequest error:", err.Error())
}

req.Header.Set("Cookie","b=2")
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")

//fmt.Println(getReq(req))

resp, err := client.Do(req)
if err != nil {
fmt.Println("[-] Response error:", err.Error())
return
}
defer resp.Body.Close()

fmt.Println(getResp(resp))
}

func main() {
httpReq("https://www.baidu.com")
}


json

简直反人类

编码注意知识点:

  1. 定义Struct首字母必须大写
  2. json:"-" 该字段不会出现在json中
  3. json:"aaa" 别名aaa
  4. json:"aaa,string" 别名aaa,并且直接把”aaa”作为值转为string放到json中
  5. json: "aaa,omitempty" 别名aaa,如果值为空则不输出key

解码注意知识点:

  1. 解析到结构体时直接Unmarshal就可
  2. 解析map注意几层interface,见如下demo
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
//json编码demo
type Test struct {
Key string
}
type jsonTest struct {
Foo string `json:"-"`
Key string `json:"key"`
Test []Test
}

func main() {
test1 := Test{"test1"}
test2 := Test{"test2"}
raw := jsonTest{
Foo: "foo",
Key: "key",
Test: []Test{test1,test2},
}
js,_ := json.Marshal(raw)
fmt.Println(string(js))
}

//json解码demo
//解码到结构体
var jt jsonTest
jsonString := `{"key":"key","Test":[{"Key":"test1"},{"Key":"test2"}]}`
json.Unmarshal([]byte(jsonString), &jt)
fmt.Println(jt.Foo + jt.Key + " " + jt.Test[1].Key)

//解码到map
var m map[string]interface{}
jsonString := `{"key":"key","Test":[{"Key":"test1"},{"Key":"test2"}]}`
json.Unmarshal([]byte(jsonString), &m)
fmt.Println(m["key"],",",m["Test"].([]interface{})[0].(map[string]interface{})["Key"]) //对如上数据结构,要解析最后一层的value这么麻烦..

go mod使用

go mod init name来初始化,主要作用是包管理。

Proudly powered by Hexo and Theme by Hacker
© 2021 LFY