GO基础 | 青训营笔记

79 阅读8分钟

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

Go语言优点

  1. 高性能、高并发
  2. 静态链接
  3. 垃圾回收

关于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")
  1. ++和--只能放在变量名后面

  2. 字符串连接数目较多时,使用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