这是我参与「第五届青训营 」伴学笔记创作活动的第 1 天
Go语言优点
- 高性能、高并发
- 静态链接
- 垃圾回收
关于Go的一些小tips
- Go语言原生支持Unicode,可以处理全世界任何语言的文本
- go build 编译
- Go语言编译过程没有警告信息
- import 必须在 package之后
- %,取模操作的结果总是与被取模数符号一致
- ^用作一元运算符为按位取反
- &^按位置零
基础语法
基本结构
package main
import (
"fmt"
)
func main{
fmt.Println("hello world")
}
go run xxx.go # 直接运行
go build && ./xxx # 编译为二进制文件 并 运行
输入 & 输出
- fmt包
fmt.Scanln(&x) // 阻塞式输入,只会接受一个回车
fmt.Scanf("%d\n",&x) // 格式化输入,中间需要输入格式中的符号
fmt.Scan(&x) // 从终端获取输入,存储在Scanln中的参数里,空格和换行符作为分隔符
- bufio包,实现了带缓存的I/O操作
// 获取一行的输入
reader := bufio.NewReader(os.Stdin)
input, err := reader.ReadString('\n')
input = strings.Trim(input, "\r\n")
reader := bufio.NewReader(os.Stdin)
s1, _ := reader.ReadString('\n')
变量 & 常量
- 命名大小写敏感
- 类型后置
- 大写字母开头:可以被外部的包访问
- 驼峰命名
- 零值初始化机制
- 简短变量声明对于已经声明的变量只有赋值行为,且至少要声明一个新的变量,不能是_
- *p对应一个变量,可以出现在赋值语句的左边和右边
- *p++改变的是p指针指向的值,不改变指针指向,相当于(*p)++
- 返回函数中局部变量的地址是安全的
- new是一个预定义函数,不是关键字
- 编译器自动选择在栈/堆上分配局部变量的存储空间
- 自增和自减是语句而不是表达式,x=i++是错误的
- 运行时不会发生转换失败的错误(编译处理了)
- 比较运算符==和<也可以用来比较一个命名类型的变量和另一个有相同类型的变量,或有着相同底层类型的未命名类型的值之间做比较。但是如果两个值有着不同的类型,则不能直接进行比较
var + name + (type) // 变量
const + name + (type) // 常量
基础数据类型
整型
- %,取模结果的符号和被取模数的符号相同
- &^,位清空
Go语言的自动垃圾回收变量基本思路
从每个包级的变量和每个当前运行函数的每一个局部变量开始,通过指针或引用的访问路径遍历,是否可以找到该变量。如果不存在这样的访问路径,那么说明该变量是不可达的,也就是说它是否存在不会影响程序后续计算结果。
元组赋值
- 允许同时更新多个变量的值,在赋值前,赋值语句右边的所有表达式将会先进行求值,然后统一更新左边对应变量的值
- 如果表达式太复杂,尽量避免使用元组赋值
- 表达式产生多个返回值并且出现在赋值语句右边时,左边需要有数目对等的变量数目
a[i], a[j] = a[j], a[i]
判断 & 循环
switch不需要在每个case中加上break,switch后可以不加表达式,直接在case中表示
1.if-else
if exp {
xxx
} else {
}
2.switch
switch var {
case 1:
xxx
case 2
xxx
default:
xxx
}
3.for
i := 1
// while
for {
break
}
// for
for j := 7; j < 9; j++ {
}
// while
for i <= 3 {
}
数组
数组只需要在基本类型之前加上数组的大小进行定义
var name [size]type
var a [5]int
a := [5]int{1,2,3,4,5}
切片slice
slice和Python语言中的切片类似,是一个简版的动态数组 数组的子序列[n,m]表示下表n到m-1,不包括m
s := make([]string, 3) // 定义一个大小为3的切片
s.append(s,"g","a") // 切片可以动态添加元素
c := make([]string, len(s))
copy(c, s)
- 判断一个slice是否为空,使用len(s)==0,来判断,不应该用s==nil
- slice之间不能比较
- 由于无法判断采用append后,新的slice使用的是否为旧的底层数组,因此通常是将append返回的结果直接赋值给输入的slice变量:r=append(r,x),data = nonempty(data)
map
map[key_type]val_type 无序
- map中的key必须是支持==比较运算符的数据类型
- map中的元素不能进行取址操作(原因:map随着元素数量的增长可能会重新分配更大的内存空间,导致之前的地址无效)
- map的迭代顺序是不确定的
m := make(map[string]int)
m["key"] = val
delete(m,"key")
m1 := map[string]int{"key":val,...}
range
array:index,val map:key,val 每次循环迭代,range产生一对值,索引值以及对应的元素值,两者都必须处理,即通过变量获取,Go不支持无用的局部变量,可使用空标识符替代'_'
for i, num := range nums
for k, v := range map
函数
func + name + (参数) + (返回值,单个括号可以省略) 原生支持返回多个值
func exists(m map[string]string, k string) (v string, ok bool) {
v, ok = m[k]
return v, ok
}
指针
较为局限
func add2ptr(n *int) {
*n += 2
}
结构体
初始化,没有初始化的字段为空值 带指针可以实现对于结构体内字段进行修改
type user struct {
name string
password string
}
func (u user) checkPassword(password string) bool {
return u.password == password
}
func (u *user) resetPassword(password string) {
u.password = password
}
func main() {
a := user{name: "wang", password: "1024"}
a.resetPassword("2048")
fmt.Println(a.checkPassword("2048")) // true
}
error
有error返回时,需要有错误判断
error.New("xxx")
字符串
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 ++详细
s := "hello"
n := 123
p := point{1, 2}
fmt.Printf("s=%v\n", s) // s=hello
fmt.Printf("n=%v\n", n) // n=123
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}
f := 3.141592653
fmt.Println(f) // 3.141592653
fmt.Printf("%.2f\n", f) // 3.14
json库
结构体序列化条件:每个字段的首字母为大写
package main
import (
"encoding/json"
"fmt"
)
type userInfo struct {
Name string
Age int `json:"age"`
Hobby []string
}
func main() {
a := userInfo{Name: "wang", Age: 18, Hobby: []string{"Golang", "TypeScript"}}
buf, err := json.Marshal(a)
if err != nil {
panic(err)
}
fmt.Println(buf)
fmt.Println(string(buf))
buf, err = json.MarshalIndent(a, "", "\t")
fmt.Println(string(buf))
var b userInfo
err = json.Unmarshal(buf, &b)
}
time
time.Now()
time.Format("2006-01-02 15:04:05")
// 时间戳
time.Unix()
字符串库
strconv.ParseFloat("1.234", 64)
strconv.ParseInt("111", 10, 64)
strconv.ParseInt("0x1000", 0, 64)
strconv.Atoi("123")
进程相关
fmt.Println(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()// 启动子进程,获取输入输出
fmt.Println(string(buf)) // 127.0.0.1 localhost
注释
在每个包声明前添加注释,从整体角度对程序进行描述
其他
rand.Seed(time.Now).UnixNano() // 随机种子
rand.Intn(num)
// bufio读取输入string
reader := bufio.NewReader(os.Stdin)
input, err := reader.ReadString('\n')
input = strings.Trim(input, "\r\n")
-
++和--只能放在变量名后面
-
字符串连接数目较多时,使用strings包的Join函数
命令行参数
os包,跨平台,与操作系统加护的函数和变量 os.Args的第一个元素os.Args[0]:命令本身的名字
变量声明规则
实践中一般使用前两种
s := "" // 只能用在函数内部,不能用于包变量
var s string // 默认初始化
var s = "" // 少用,同时声明多个变量
var s string = ""// 当变量类型与初始类型相同时,类型冗余,一般用于两者不同的情况
包
- 包的初始化首先解决包级变量的依赖顺序,然后按照包级变量声明出现的顺序依次初始化
- 如果包中有多个.go源文件,它们按照发给编译器的顺序进行初始化,go语言构建工具将文件按照文件名排序,然后依次调用编译器编译
- 对于在包级别声明的变量,如果有初始化表达式则用表达式初始化,没有的用init初始化
- 每个文件中的init初始化函数,在程序开始执行时按照它们声明的顺序被自动调用,不能被调用或引用外,每个文件可以包含多个初始化函数
- 每个包在解决依赖的前提下,以导入声明的顺序初始化,每个包只会被初始化一次
- 对于复杂的初始化,可以采用匿名函数包装替代
- 当编译器遇到一个名字引用时,它会对其定义进行查找,查找过程从最内层的词法域向全局的作用域进行,因此,不同词法域可以有重名变量,局部变量可以和全局变量重名
总而言之,go语言的初始化首先解决依赖关系,然后按照声明的顺序进行初始化
socket5
sequenceDiagram
Client->>Socks5 Server: 1. 协商阶段
Socks5 Server-->>Client: 1.1. 通过协商
Client->>Socks5 Server: 2. 发送请求
Socks5 Server->>Host: 2.1. 建立TCP连接
Host-->>Socks5 Server: 2.2. 返回响应
Socks5 Server-->>Client: 2.3. 返回状态
Client->>Socks5 Server: 3. 发送数据
Socks5 Server->>Host: 3.1. relay数据
Host-->>Socks5 Server: 3.2. 响应结果
Socks5 Server-->>Client: 3.3. 响应结果
nc 127.0.0.1 1080
# -v 打印请求的所有细节
curl --socks5 127.0.0.1:1080 -v http://www.qq.com