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

352 阅读8分钟

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

由于在之前已经接触了一些go语言相关的知识,所以本次仅记录感觉比较有收获的部分。

这里只记录感觉比较有收获的部分

1. 语法知识点部分

1.1 slice切片

golang中比较少用数组,切片用的比较多,切片相当于是可变长度的数组,可以使用如python一样的切片操作。

s := make([]string, 1) //make方式创建
s[0] = "a"
s = append(s, "d")
s = append(s, "e", "f")

切片可以使用append增加元素,之后 记得把append后的结果赋值回原来的对象,因为golang中slice的原理是存储一个长度+容量+指向数组的指针,如果append过程中长度不够,就会发生扩容并产生一个新的slice,并返回,所以我们需要赋值回原来的s对象。

由此延伸出golang中slice的底层实现,具体详细描述slice和数组实现的可以看一下:juejin.cn/post/686189…

切片底层结构如下所示:

type Slice struct {
       ptr   unsafe.Pointer // 指向数组的指针,表明数组的起始元素 Array pointer
       len   int            // 长度 slice length
       cap   int            // 容量 slice capacity
}

切片本身并不是动态数组或者数组指针。它内部实现的数据结构通过指针引用底层数组,设定相关属性将数据读写操作限定在指定的区域内。切片本身是一个只读对象,其工作机制类似数组指针的一种封装

切片(slice)是对数组一个连续片段的引用,所以切片是一个引用类型(因此更类似于 C/C++ 中的数组类型,或者 Python 中的 list 类型)。这个片段可以是整个数组,或者是由起始和终止索引标识的一些项的子集。需要注意的是,终止索引标识的项不包括在切片内。切片提供了一个与指向数组的动态窗口。

给定项的切片索引可能比相关数组的相同元素的索引小。和数组不同的是,切片的长度可以在运行时修改,最小为 0 最大为相关数组的长度:切片是一个长度可变的数组。 中间说到golang中只有值传递,所以像slice这种很有必要

由于上述切片的性质,即记录的其实是对应的指针、长度、容量,所以可以用在函数调用中,通过函数传递切片时,不会拷贝整个切片,因为切片本身只是个结构体而已。"在golang中只存在值传递,也就是说既然只存在值传递,那么数组在作为形参的时候,将产生副本。为了解决这个问题,就需要引入切片。"即通过切片可以以较少的代价来实现参数传递,同时又能够修改到目标位置的值。

另外文中提到了其他点:

  • 不要混淆nil切片和空切片,空切片只是len为0,但是指针是指向一段真正存在的内存空间的。nil切片你可以认为里面的ptr是nil默认值。

关于copy操作:

  • 对于切片类型来说,copy操作和赋值操作不一样,copy操作会对slice具体指向地址的数值进行修改,而赋值操作应该只是更换了指向的地址以及长度等字段。

  • 在使用内置函数copy()拷贝切片时,会将源切片的数据逐个拷贝到目的切片指向的数组中,拷贝数量取两个切片长度的最小值。故copy过程中不会发生扩容。“这也是最容易让新手犯错和调入陷阱的地方,copy并不是单纯的深拷贝!其他语言中的copy应该是字面意思,完全复制一份相同的切片或者数组拷贝。但是在golang中,copy只能复制两个切片中较小长度数量的元素。至于剩下的元素不会发生拷贝。”

1.2 map

map同样是使用make创建,不赘述

golang中的map是无序的,遍历的时候不会按照字母顺序或插入顺序遍历,而是一个偏随机的顺序

1.3 函数

golang中函数参数声明中 变量类型后置

如果要在函数中修改值:

func add2(n int) {
    n += 2
}
func add2ptr(n *int) {
    *n += 2
}
func main(){
  n := 5
  add2(n)
  add2ptr(&n)
}

第二个函数能够修改main函数中n的值,而第一个函数不行

1.4 json

对于一个结构体,我们只需要保证 每一个字段都是大写,也就是每一个字段都是公开的,那么这个结构体就能够直接调用 json.Marshal 方法来直接进行序列化

1.5 strconv

字符串和数字类型的相互解析

1.6 获取进程信息

os.Args

os.Getenv

os.Setenv

exec.Command

等等

1.7 bufio包

在项目示例中,有:

reader := bufio.NewReader(os.Stdin)

这行代码的作用是:通过缓冲提高效率,io操作本身的效率并不低,低的是频繁的访问本地磁盘的文件。所以bufio就提供了缓冲区(分配一块内存),读和写都先在缓冲区中,最后再读写文件,来降低访问本地磁盘的次数,从而提高效率。

关于bufio可以看一下:可以看一下:zhuanlan.zhihu.com/p/73690883

还有这一篇(感觉更通俗一点):studygolang.com/articles/35…

bufio在要读取的长度和缓存区长度的不同情况下,处理是不同的,中间处理方式值得一看

2 实例中的点

2.1 获取api的方法 以及 相关的代码生成(第三方)

获取api的方法,在浏览器中通过按键点击先发起一次请求,然后在开发者模式中,在network部分找到对应的请求

然后点击 headers perview等查看本次请求的请求信息和响应信息

image-20230115155307827.png

由此我们可以获取到api的使用方法。

然后这里教了一个比较方便的方法,就是 代码生成 使用curl,在右键后选择 copy as cURL:

image-20230115155542364.png

其实这个就是curl的命令,此时我们是可以直接使用terminal来发起这个请求的:

image-20230115155929023.png

而这里为了能够内嵌到代码里面,我们到 curlconverter.com 这个网址,选择go语言,粘贴里面的内容就可以得到对应的go语言代码:

image-20230115155831662.png

灰常好用

使用api (还是使用代码生成)

这里说到了,要能够发送不同的请求,那么就需要构建对应的结构体进行json序列化处理,所以这里要构建每个字段都是大写的结构体

然后针对于响应体,即返回信息的处理:

  • 这里说到了go是一种强类型语言,一般获取返回结果的话方法不太一样
  • 在js和python语言里面,body返回的一般都是字典或map结构,可以使用方括号和点来取值

go一般使用的是创建和返回结果对应结构体,然后把字符串反序列化到结构体中,但是由于api对应的响应体比较多 ,所以这里同样使用了 代码生成的方式

这里使用了 oktools.net 来帮助转化

我们找到前面的网页,然后复制对应请求的响应信息,即 preview部分的信息,然后进行转换:

image-20230115161957551.png

这样就通过第三方完成了这部分结构体的构建,灰常好用

然后由于我们不需要对返回结果进行过多的操作,所以我们这里选择“转换-嵌套”这个选项即可,这样嵌套的结构体会紧凑一些。

2.2 关于socks5协议

2.2.1 背景 和 应用

本次了解了socks5协议,可以看一下 nordvpn.com/zh/blog/soc…

socks5是代理协议,但是没法用来翻墙,因为这是明文传输的。

socks5历史比较久远,诞生于互联网早期。

企业内网为了保证安全性,可能会使用很复杂的防火墙策略。但是这样的副作用就是,就算是管理员自身,访问也非常麻烦,而socks5协议就是在防火墙中开一个口子,让社群用户可以通过单个端口访问内部的所有资源。

实际上很多翻墙软件最终暴露出来的也是一个socks5端口。

爬虫容易遇到ip访问次数发生限制的情况,此时就可以使用一些代理ip池,而中间很多的协议就是socks5协议。

2.2.2 简述简单socks5服务器工作原理

image-20230115164914150.png

正常没有socks5的情况下,client端的访问过程谁:建立TCP三次握手,握手完成之后,正常发起http请求,服务器返回http响应。而如果使用了socks5,则客户端先和socks5服务器先建立TCP链接,然后再和真正的服务器建立连接。

大致有下面四个阶段:

  • 握手:client和socks5服务器之间握手,设置版本、认证方式等;socks5服务器会返回选择的认证方式。就是client给出自己可以用的多种认证方式,然后socks5服务器会在中间选择自己要的一种认证方式

  • 认证:本次实现不进行认证,socks5服务器实现的是不加密的方式(即直接选择不加密的认证方式)

  • 请求:client向socks5发送请求,告诉说要和谁建立连接,然后socks5服务就是对应的真正服务器建立连接

  • relay:client传输要发送的数据,socks5服务器将对应真正服务器的返回信息交给client端,socks5服务器并不关心中间信息的具体细节。