GO语言基础与配套实例 | 青训营笔记
这是我参与「第五届青训营 」笔记创作活动的第1天
1、GO环境配置
-
在Go官网下载对应安装包
-
(Windows)配置好安装目录下bin的环境变量
-
配置依赖包的代理,cmd下运行
$env:GO111MODULE = "on" $env:GOPROXY = "https://goproxy.cn"
2、配套学习实例
使用Git (git-scm.com)克隆课程示例项目wangkechun/go-by-example (github.com)
克隆后项目目录
3、GO基础语法
- 变量定义
// 变量定义
var name1 string //var + 变量名 + 类型名称
var name2, name3 int = 1, 2 //连续定义
var name4 = "hello" //自动类型推断
name5 := "world" //变量定义方式二
const width = 100 //常量定义使用关键字const
- for循环
// go中循环只有for一种关键字
// 常规for循环
for int i = 0; i < 10; i++ {
do()
}
// 其他语言的while循环在go中的实现
for i <= 10 {
do()
}
// 类似while(true)效果
for {
do()
break
}
- if判断
// 常规判断
if i < 10 {
do()
}
// 在if中定义局部变量,只可在if方法块中访问
if i := 10; i < 20 {
do()
}else {
do()
}
-
switch语句
- go中的switch语句会自动在执行完case后,隐式break退出,不像其他语言会继续执行接下来的case块。
- 想要继续执行接下来的语句块,使用fallthrough关键字可以执行下一条case。 但是不检查下一条case的条件
switch f := 5; { // switch中的条件可以为空,即为switch true{} case f < 3: fmt.Println("3") // 正常情况下,执行完这一条case就直接退出switch case f < 6: fmt.Println("6") // 但是由于fallthrough关键字的存在,会执行下一条case中的语句,但不会检查case的条件。 fallthrough case f < 4: fmt.Println("4") } -
数组
// 数组声明 var a [5]int // 数组赋值 a[3] = 19 // 数组访问 fmt.Println(a[3]) // 数组声明时定义 b := [3]int{1, 2, 3} // 二维数组 var two [2][1]int -
切片 切片的本质是对数组片段的描述,其包含三个主要的内容:指向数组的指针、片段的长度和容量
```
// 创建切片
s := make([]string, 3) // make()中第一个参数为创建的类型,可以是slice, map和channel三者。第二个参数为数据类型所占有内存空间长度,当type是slice时,此项为必选项。第三个参数为预留内存空间长度,为可选参数
s[0] = "a"
s[1] = "b"
s[2] = "c"
// 向切片中添加元素,使用append()函数
s = append(s, "d")
s = append(s, "e", "f")
// 切片中没有之间的删除元素操作,必须手动现实
letters := []string{"A", "B", "C", "D", "E"}
remove := 2
letters = append(letters[:remove], letters[remove + 1:]...)
// 使用copy()函数可以创建切片的副本
slices2 = make([]string, 3)
copy(slices, letters[1:4])
```
-
映射
-
添加:直接使用键值对即可
studentAge["Tom"] = 11注意:添加操作是对于已经初始化的映射变量而言,如果对nil映射进行添加操作将报错。var student map[string]int student["Tom"] = 1 // 这将报错,因为此时student为nil -
访问:直接像访问数组一样访问
student["Tom"]当访问不存在的kv对时,Go不会panic,而是返回默认值。在 Go 中,映射的下标表示法可生成两个值。 第一个是项的值。 第二个是指示键是否存在的布尔型标志age, exist := studentAge["Tom"],可以通过exist来判断返回的value是默认值还是确切的项。 -
删除: 如果要从映射中删除项,需要使用内置函数delete()。
delete(map, key):如delete(studentAge, "Tom"),即可删除"Tom"的映射。 当删除不存在的映射时,Go不会panic
// 创建映射 m := make(map[string]int) // 其中map[]中为key的类型,[]后为value的类型 m["one"] = 1 m["two"] = 2 // 访问映射 fmt.Println(m["one"]) // 输出:1 -
-
range
- 当对数组使用range时,返回两个参数:下标和对应值
nums := []int{2, 3, 4} sum := 0 for i, num := range nums { sum += num if num == 2 { fmt.Println("index:", i, "num:", num) // index: 0 num: 2 } }- 当对map使用range时,返回key和value值
m := map[string]string{"a": "A", "b": "B"} for k, v := range m { fmt.Println(k, v) // b 8; a A } -
func函数
go中函数可以有多个返回值:
func methodName(args ...argsType) (returnValue ...valueType)-
无返回值的func:
func method1(a int, b int){...} -
一个返回值的func:
func method2(a int, b int) int {...} -
多个返回值的func:
func method3(a int, b int) (int, int){...} -
返回值可以指定名称:
func add(a int, b int) int { return a + b; } // 当指定返回值名称后 func add(a int, b int) (res int){ res = a + b return }
-
-
指针
go中的指针类型和使用和c中很类似,但是go中指针没有值运算,其主要作用就在于修改值。因为go中的参数传递是值传递,如果函数间传递的参数为大数组之类的很占空间的变量,值传递会带来很严重的性能下降。而指针可以很好的解决这个问题。
var p *int fmt.Printf("%v\n",p) //← 打印 nil var i int //← 定义一个整形变量 i p = &i //← 使得 p 指向 i, 获取 i 的地址 fmt.Printf("%v\n",p) //打印内存地址 *p = 6 fmt.Printf("%v\n",*p) //打印6 -
结构体
-
结构定义
type Employee struct { ID int FirstName string LastName string Address string } -
结构声明
var john Employee -
带初始化的声明
employee := Employee{1001, "John", "Doe", "Doe's Street"} -
初始化的顺序不重要
employee := Employee{LastName: "Doe", FirstName: "John"} -
访问结构中的属性
employee.ID = 1001 fmt.Println(employee.FirstName) -
结构指针
package main import "fmt" type Employee struct { ID int FirstName string LastName string Address string } func main() { employee := Employee{LastName: "Doe", FirstName: "John"} fmt.Println(employee) employeeCopy := &employee employeeCopy.FirstName = "David" fmt.Println(employee) }注意上面指针变量employeeCopy在获取employee实例的属性时,不用使用*employeeCopy属性,而是直接使用.来获取属性内容。
-
结构嵌入
type Person struct { ID int FirstName string LastName string Address string }可以在另一个结构中直接嵌入Person
type Employee struct { Information Person ManagerID int }但是此时要使用Person中的属性,就得包含Information字段:
employee.Information.FirstName = "John"或者可以嵌入一个和结构同名的字段
type Employee struct { Person ManagerID int }package main import "fmt" type Person struct { ID int FirstName string LastName string Address string } type Employee struct { Person ManagerID int } type Contractor struct { Person CompanyID int } func main() { employee := Employee{ Person: Person{ FirstName: "John", }, } employee.LastName = "Doe" fmt.Println(employee.FirstName) }
-
-
结构方法(类似oop中的对象方法) 在普通方法的定义基础上,加上前缀(structVarName structType),就可以指定为某个结构体的方法
type user struct { name string password string } func (u user) checkPassword(password string) bool { return u.password == password } -
error 在go中,使用error传递错误,可以类比java中的异常。可以使用errors.New()新建error
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") } -
string string是go中基础类型的一种。配合
strings包可以实现许多字符串操作strings.Contains(s string, sub string) // 判断是否包含字符串 strings.Count(s string, sub string) // 统计子字符串出现的次数 strings.HasPrefix(a, "he") // 判断是否以sub为前缀 strings.HasSuffix(a, "llo") // 判断是否以sub为后缀 strings.Index(a, "ll") // 定位子字符串的起始位置 strings.Join([]string{"he", "llo"}, "-") // 字符串拼接 strings.Repeat(a, 2) // 重复字符串 strings.Replace(a, "e", "E", -1) // 字符串替换 strings.Split("a-b-c", "-") // 字符串分割 strings.ToLower(a) // 全小写 fmtstrings.ToUpper(a) // 全大写 -
fmt包 格式化包format的缩写,最常用的是其中的字符串输出函数
fmt.Printf() fmt.Println() fmt.Sprintf() fmt.Sprintln() -
json包
json.Marshal(a) // 对结构体对象进行json序列化操作,返回btye数组 json.Unmarshal(buf, &b) // 对json格式进行反序列化操作,存储在b中 -
time包
u := time.Now() // 返回当前时间 time.Date(year int, month Month, day int, hour int, min int, sec int, nsec int, loc *Location) //返回由参数实例化的Time对象 time.Parse("2006-01-02 15:04:05", "2022-03-27 01:25:36") // 将第二个参数根据第一个参数实例化Time对象 u.Unix() // Time对象调用Unix()方法可以返回时间戳 -
strconv包 stringconvert的缩写,字符串转换包。
strconv.ParseFloat("3.14", 64) // 字符串转float类型,第二个参数为转换后位数 strconv.ParseInt("111", 10, 64) // 字符串转int类型,第二个参数为数字字符串的基数,第三个参数为转换后的位数 strconv.Atoi("123") // 字符串转int类型 strconv.Itoa(123) // int类型转字符串 -
os包
os.Args // 返回main调用时的传入参数 os.Getenv("PATH") // 获取环境变量 os.Setenv("AA", "BB") // 设置环境变量 os.exec.Command("bash", "-c", cmdStr) // 执行commond命令
4、3个实例练习
-
猜谜游戏
-
最终版本代码猜谜游戏
-
效果展示
-
rand.Seed(time.Now().UnixNano())使用时间戳作为随机种子,保证每次输出都是随机 -
reader := bufio.NewReader(os.Stdin)使用os包读入输入内容至缓冲区 -
input, err := reader.ReadString('\n')从缓冲区内读入一行(\n为换行符,接收到\n结尾即为一行) -
input = strings.Trim(input, "\r\n")将读入的一行移除指定字符串(\n\r)的前后缀 -
guess, err := strconv.Atoi(input)将读入的字符串转换为int类型变量 -
将用户输入的值与随机生成的数字进行比较,相同则结束,否则继续循环
if guess > secretNumber { fmt.Println("Your guess is bigger than the secret number. Please try again") } else if guess < secretNumber { fmt.Println("Your guess is smaller than the secret number. Please try again") } else { fmt.Println("Correct, you Legend!") break }
-
-
命令行字典
-
最终版本代码命令行字典
-
效果展示
-
首先定义好请求包DictRequest和相应包DictResponse的格式(使用json定义)
-
client := &http.Client{}定义http客户端对象 -
绑定请求包方式和请求api接口
var data = bytes.NewReader(buf) req, err := http.NewRequest("POST", "https://api.interpreter.caiyunai.com/v1/dict", data) -
resp, err := client.Do(req)发送请求,接受响应 -
defer resp.Body.Close()使用go中的延迟执行语句,延迟关闭流 -
接收响应体文本,json反序列化内容为对象
bodyText, err := ioutil.ReadAll(resp.Body) err = json.Unmarshal(bodyText, &dictResponse)
-
-
SOCKS5代理
-
最终版本代码socks5代理
-
效果展示
-
server, err := net.Listen("tcp", "127.0.0.1:1080")监听1080端口 -
go process(client)使用协程处理连接 -
defer conn.Close()延迟处理关闭连接流 -
启动socks5中的认证和连接函数
err := auth(reader, conn) err = connect(reader, conn) -
connect()函数中最重要的是实现代理服务器和访问客户端的双向通信
ctx, cancel := context.WithCancel(context.Background()) defer cancel() go func() { _, _ = io.Copy(dest, reader) cancel() }() go func() { _, _ = io.Copy(conn, dest) cancel() }() <-ctx.Done() // 为了避免协程的启动即结束,使用channel来等待context的执行完毕
-