01Go语言基础 - 基础语法 | 青训营笔记

101 阅读3分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第1天

学习资料 juejin.cn/post/718822…

飞书版 bytedance.feishu.cn/docx/doxcnZ…

Go语言圣经 books.studygolang.com/gopl-zh/

标准库文档 studygolang.com/pkgdoc

课程示例项目 github.com/wangkechun/…, 克隆后进入目录运行go run example/01-hello/main.go, 输出hello world, 则说明环境配置正确

Go语言学习路线图⭐ bytedance.feishu.cn/docs/doccn3…

学习

Go模块代理 goproxy.cn/

1 Go语言简介

特点:
1 高性能, 高并发
2 语法简单, 学习曲线平缓
3 丰富的标准库
4 完善的工具链
5 静态链接
6 快速编译
7 跨平台
8 垃圾回收
为什么字节跳动全面拥抱Go语言
1 最初使用Python, 由于性能问题换成了Go
2 C++不太适合在线Web业务
3 早期团队非Java背景
4 性能比较好
5 部署简单, 学习成本低
6 内部RPC和HTTP框架的推广
总结: 上手简单, 性能高, 尝到甜头后, 全面推广

2 Go语言开发入门

开发环境配置

安装Golang (见go.dev)
Go模块代理 (见goproxy.cn)
IDE 我选Goland (Jet brain出品)

基础语法和标准库

1 switch 不写break也有break的效果
switch后的变量可以是任意类型
switch后可以不加变量, 以此代替if嵌套

switch {
case t.Hour() < 12:
	fmt.Println("It's before noon")
default:
    fmt.Println("It's after noon")    	
}

2 map

创建
m := make(map[string]int)
读/增/改
m["one"] = 1
读方法2
r, ok := m["unknow"] // ok是bool, 可知道这个unknow这个key是否存在delete(m, "one")

3 函数
原生支持返回多个值
实际业务逻辑代码中, 几乎所有函数都返回多个值, 第一个值是真正结果, 第二个值是错误信息
4 指针
功能有限, 主要用来修改传入的参数或是避免大结构体拷贝的开销
5 结构体

定义
type user struct {
    name string
    password string
}
声明并初始化
a := user{name: "wang", password: "1024"} // 没初始化的字段默认为零值
结构体方法
// 不带指针
func (u user) checkPassword(password string) bool {
    return u.password == password
}
// 带指针
func (u *user) resetPassword(password string) {
    u.password = password
}
// 调用
a.checkPassword("1024")
a.resetPassword("2233")

6 错误处理 习惯利用返回值处理错误, 不同于其它语言用异常处理 优点: 更清晰知道错误来源, 用简单的if else可处理

func findUser(users []user, name string) (v *user, err error) {
    for _, u := range users {
        if u.name == name {
            return &u, nil
        }
    }
    return nil, errors.New("not found")
}
u, err := findUser([]user{{"wang", "1024"}}, "wang")
if err !- nil {
    fmt.Println(err)
    return
}
fmt.Println(u.name)

7 字符串操作

a := "hello"
fmt.Println(strings.Contains(a, "ll"))                // true
fmt.Println(strings.Count(a, "l"))                    // 2
fmt.Println(strings.HasPrefix(a, "he"))               // true
fmt.Println(strings.HasSuffix(a, "llo"))              // true
fmt.Println(strings.Index(a, "ll"))                   // 2
fmt.Println(strings.Join([]string{"he", "llo"}, "-")) // he-llo
fmt.Println(strings.Repeat(a, 2))                     // hellohello
fmt.Println(strings.Replace(a, "e", "E", -1))         // hEllo
fmt.Println(strings.Split("a-b-c", "-"))              // [a b c]
fmt.Println(strings.ToLower(a))                       // hello
fmt.Println(strings.ToUpper(a))                       // HELLO
fmt.Println(len(a))                                   // 5
b := "你好"
fmt.Println(len(b)) // 6
// 格式化
%v 可用于打印各个类型
%+v 打印详细结果
%#v 进一步详细
fmt.Printf("p=%v\n", p)  // p={1 2}
fmt.Printf("p=%+v\n", p) // p={x:1 y:2}
fmt.Printf("p=%#v\n", p) // p=main.point{x:1, y:2}

8 JSON处理 只有一个结构体的字段首字母都是大写, 就可以用json.Marshal进行序列化

type userInfo struct {
    Name string
    Age int `json:"age"` // 用这个tag可以让输出的字段名是age而不是Age
    Hobby []string
}
// 序列化
a := userInfo{Name: "wang", Age: 18, Hobby: []string{"Golang", "TypeScript"}}
buf, err := json.Marshal(a)
if err != nil {
    panic(err)
}
fmt.Println(buf) // [123 34 97 103 ... ]
fmt.Println(string(buf)) // buf类型是[]byte
// 序列化 - 带缩进
buf, err = json.MarshalIndent(a, "", "\t")
// 反序列化
var b userInfo
err = json.Unmarshal(buf, &b)

9 时间处理

// 当前时间
time.Now()
// 自定义时间 time.UTC标准时间, time.Local当地时间
time.Date(2022, 3, 27, 1, 25, 36, 0, time.UTC)
// 格式化为字符串
fmt.Println(t.Format("2006-01-02 15:04:05"))
// 作差
diff := t2.Sub(t)
fmt.Println(diff)                           // 1h5m0s
fmt.Println(diff.Minutes(), diff.Seconds()) // 65 3900
// 字符串转Time, 第一个参数是格式
t3, err := time.Parse("2006-01-02 15:04:05", "2022-03-27 01:25:36")
// 时间戳
now.Unix() // 1648738080
now.UnixMilli() // 毫秒
now.UnixMicro() // 微妙
now.UnixNano() // 纳秒

10 字符串转数字

f, _ := strconv.ParseFloat("1.234", 64)
fmt.Println(f) // 1.234

// 第二个参数指定进制, 2到36, 若为0则根据字符串前置判断, 0x是16, 0是8, 否则是10
// 第三个参数指定结果必须是无溢出复制的整数类型, 64即int64
n, _ := strconv.ParseInt("111", 10, 64)
fmt.Println(n) // 111

n, _ = strconv.ParseInt("0x1000", 0, 64)
fmt.Println(n) // 4096

// 字符串转整型
n2, _ := strconv.Atoi("123")
fmt.Println(n2) // 123

n2, err := strconv.Atoi("AAA")
fmt.Println(n2, err) // 0 strconv.Atoi: parsing "AAA": invalid syntax

11 进程信息

// 命令行参数, []string
os.Args
// 获取环境变量
fmt.Println(os.Getenv("PATH"))
// 设置环境变量
fmt.Println(os.Setenv("AA", "BB"))
// 执行命令并获取输出
buf, err := exec.Command("grep", "127.0.0.1", "/etc/hosts").CombinedOutput()

3 Go实战 ⭐(含金量很高)

练习到很多知识点, 和实战工具
课程示例项目 github.com/wangkechun/…, 克隆后进入目录运行go run example/01-hello/main.go, 输出hello world, 则说明环境配置正确

项目1 猜谜游戏

知识点:
随机数生成
流程控制
输入输出

项目2 在线词典

知识点:
发送HTTP请求
解析JSON
借助代码生成提高效率(用curl生成go代码, 用json生成go struct)

项目3 SOCKS5

知识点:
SOCKS协议 goroutine 很多细节, 建议反复学习这个项目⭐

v1 - TCP echo sever

目的: 建立一个能返回信息的TCP sever
效果查看方法:
运行程序
然后cmd执行nc nc 127.0.0.1 1080建立起tcp连接
之后每发送字符串就会被echo
// nc安装 eternallybored.org/misc/netcat… // 配置环境变量 blog.csdn.net/Zp_insist/a…

v2 - auth (认证阶段)

目的: 实现协议第一步认证阶段
(
核对VER, 是否为0x05, 即socks5
把NMETHODS, METHODS读掉
把VER, METHOD写入
(即_, err = conn.Write([]byte{socks5Ver, 0x00}))
)
效果查看方法:
运行程序
cmd执行curl --socks5 127.0.0.1:1080 -v www.qq.com
cmd显示Failed (因为还没实现完, 是正常的)
IDE中打印出了
2023/01/15 17:55:16 ver 5 method [0 1]
2023/01/15 17:55:16 auth success

v3 - 请求阶段

目的: (
读并校验VER和CMD字段(VER要为0x05即socks5, CMD要为0x01表示CONNECT请求)
读掉保留字段RSV
读掉ATYP
根据ATYP(0x01表示IPv4, 0x03表示域名)再去读DST.ADDR, IPv4读4个字节即可, 域名的话再读一个字节得到hostSize, 根据hostSize再去读
再读BND.PORT(端口号), 2个字节, 并转为整型
写响应报文
(_, err = conn.Write([]byte{0x05, 0x00, 0x00, 0x01, 0, 0, 0, 0, 0, 0}))
)
效果查看方法:
启动程序
cmd执行 curl --socks5 127.0.0.1:1080 -v www.qq.com
IDE打印
2023/01/15 18:16:45 client 127.0.0.1:14147 auth failed:IPv6: no supported yet

v4 - relay阶段

目的: 和真正的服务器建立tcp连接
效果测试方法:
运行程序
// -v 表示打印详细内容
然后cmd中执行curl --socks5 127.0.0.1:1080 -v www.qq.com
// qq不知道为什么不行, 访问百度就可以
curl --socks5 127.0.0.1:1080 -v www.baidu.com

使用Chrome的SwitchyOmega插件测试代理效果

新建情景模式
代理协议 SOCKS5
代理服务器 127.0.0.1
代理端口 1080
应用选项
然后在右上角切换为这个代理
IDE启动我们的代理服务器
此时流量会经过代理服务器, IDE里会log出来相关信息