关于包的问题
上午本来没想这么快实践go的(之所以说是实践,因为在地铁上的时候是刷的go的文章,只是没有上手),同事问了一个问题
下载这个学习教程以后,运行到02的时候,报错引入 github.com 开头的包的时候出错了,无法解析 github 这个引用,按理说应该是可以的,然后试着改了一下,改成本地路径当时貌似也是不行
这个问题不能逃避,得面对,用于建立对go的包管理的理解
所以我需要保持这个态度。
经过查了一下才醒悟,好像是要下载下来的,那么在 goland 里面就要开启这个下载的功能,选项要打开,参考: blog.csdn.net/qq_38018994…
开启自动下载了之后,就不会报错这个问题了,另外自动下载的话,他把 go.mod 也给你改掉了,新增了下载之后的内容go.mod:
module goland-test
go 1.20
require github.com/GoesToEleven/GolangTraining v0.0.0-20230828175525-f9a4f6cf3939
同时能在goland 里面的外部库,看到下载后的包
按理来说,现在运行就应该没问题了,结果又出现了一个报错
main.go:5:2: package goland-test/02_package/winniepooh is not in GOROOT (/usr/local/go/src/goland-test/02_package/winniepooh)
此时我的目录结构是这样的
经过检查,发现是文件夹的名字和包名不一致导致的
文件夹的名字是 icome... 但是里面包名写的是 winniepooh 把文件夹重命名为包名之后就正常了(因为在main里面写的是包名)
改完之后,再来跑还是不行,go run 02_package/main/main.go 还是报错没有main
后来检查发现,是main.go里面的package name 不是main...
改完了以后就能正常运行了...
写着写着发现需要git记录一下,看着goland 突然愣了,发现idea的界面不对,然后立马反应过来,这个不是新的UI,要切换成新的UI,参考:blog.csdn.net/github_3859…
换成新的UI以后,这样看着就对为了,就舒服了
发现会自动删除import
不知道是不是goland的问题,因为昨天是用的vscode 写的,后面再测试下
字符串格式化,鼠标悬停显示的代码不一样
对于第九行代码,直接显示是上面的形式,但是鼠标悬停是显示正常的格式化的形式,同时双击了以后,显示的也是下面逗号格式化的形式,不会再恢复成上面的形式
一开始我以为是goland的推荐写法,就是说下面的写法可以和上面的写法共用,上面的写法就类似于Python的 f" {var}" 变量的形式,但是实测发现不是,因为这本来是想不同进制的输出的,但是在你格式化的时候,都是写的 #{42} 如果完全一样的,肯定是达不到不同进制输出的目的的,肯定不行
再次实验验证
我如果直接按照仿照的来写,都是写 #{42}的话,输出的就是42,没有任何格式化,说明这是不行的,询问ai
这是ide 为了便于你查看,给你直接显示的字面量,直接展示 人易于阅读的文字给你看的,所以不管你即便是二进制,16进制输出,也是转化成了10进制给你方便查看,其他进制的给你转化成10进制了
对于go来说,字符串格式化只有下面这几种形式
没有像Python f"{var}"的形式
time 时间间隔乘以一个Millisecond
超时的一个写法是:t := time.Duaration( timeout ) * time.Millisecond
为什么时间还要乘以一个Millisecond 是因为time.Duaration 返回的是纳秒的时间,要把他变成毫秒,那么就要乘以一个数字,而 time.Millisecond = 1000000 也就是1e6 纳秒的时间乘上了才变成毫秒,比如1000ms 就是 time.Duaration(1000) * time.Millisecond
time.Nanosecond代表 1 纳秒。time.Microsecond代表 1 微秒(1000 纳秒)。time.Millisecond代表 1 毫秒(1000 微秒,或者说 1000000 纳秒)。time.Second代表 1 秒(1000 毫秒,或者说 1000000000 纳秒)。
要想转换一个超时时间,就是time.Duaration * 对应的单位
关于字符串格式化的一些想法
go 里面没有字符串插值的概念,所谓插值就是类似于
s := "world"
output := "Hello ${world}"
println(output)
的用法,只能说用占位符,或者你手工写一个函数替换类似于
package main
import (
// "fmt",
"strings"
)
func replace(str string, value map[string]string) string {
all := ""
for k, s := range value {
k = "${" + k + "}"
all = strings.Replace(str, k, s, 1)
}
return all
}
func main() {
s := `hello ${name}`
m := make( map[string]string )
m["name"] = "ray"
println(replace(s, m))
}
这样的用法,就是这样看起来比较丑陋
单引号,双引号,反引号的用法
- 单引号只能用于 单个字符, 比如
a := 'a'
b := '\u4e00'
println(a, b)
如果里面定义了多个就会报错
- 双引号和反引号的区别就在于会不会转义或者说格式化,能不能包含特殊字符比如说\t \n 这种,如果使用 反引号的话,是 \n 就会直接输出\n 而不是输出换行, 类似于Python的r"" 的表达式,形如
>>> print('\n')
>>> print(r'\n')
\n
>>>
加上r了以后就不会去解析字符串里面的内容,这在处理正则表达式或者sql语句或者包含大量特殊字符文本的时候非常有用
- 另外一个tips 如果一个文件夹里面不同的go 文件,同时有多个main函数的话,编译器还会报错,但是直接 go run 是可以的,大概直接go run 不会去索引包把
变量的定义
简单的就是 a := "string" 能够自动判断类型,但是如果不是 := 的定义的话,千万要记得后面跟上类型 var s string 然后 s="string"
数组
数组不能直接用{}来定义,类似 num := {1,2,3} 数组定义的时候强制必须声明数组的长度,同时指定数组的类型
不过如果想自动推断数组长度的话,可以使用 ... 让编译器来自动推断数组的长度,但是还是一定要指定数组的才行,因为不会自动推断数组的长度,比如int8和int64 和 uint 是无法通过你给的数据来推短的
需要注意的是,num := int64 {1,2,3} 这个声明的不是数组,而是一个切片,切片是可以动态增长的,如果要增加切片也不是 append(num, 4) 就可以了,而是需要重新赋值,比如 num = append(num, 4) 左侧必须要有接受的值
为什么一定要有新的值,因为append返回的是一个新的切片,所以必定需要一个接受的值 如果原切片底层能够扩展,那么就是返回一个新的切片头,但是继续沿用原切片的地址,如果不够了,那么是直接返回一个新的切片,但是不管怎么样,都会返回一个新的切片,所以必须左侧还是使用一个变量来接受的
另外注意切片如果是添加的切片的话,一定要加上逗号,哪怕只有一个,比如 num = append(num, [4]) 是不行的,必须是num = append(num, [4,])
这里错了,因为没有给定类型,都是错的,应该是
go 真的很奇怪,很喜欢... 比如我对一个切片,再增加一个切片 num := []int {1,2,3} 要写成 num = append(num, []int{4,5,6}...) 这是因为append 本来是要用逗号添加的,之所以必须要带上... 是用...来展开这个切片的元素,作为参数,传递给append 使用...操作符,是告诉编译器,将传递切片中的每个元素视为独立参数,这对于不知道切片长度或者需要追加切片中的所有元素的时候非常有用
数组的初始化只有以下几种方式 var num [3]int64 num[0] = 0 num[1] = 1 num[2] = 2 这里就不是直接给[1,2,3]或者是{}了写法了,或者在初始化的时候就要写等于号= 但是看起来很奇怪 var num = [3]int{1,2,3} 其实也不奇怪,因为num 就是变量,等于号后面才是定义
或者可以直接通过make 定义切片,var num = make([]int, 1,2) 但是这样输出num的话,只有一个0 这是因为前面的1,2 不是添加进去1,2进去,第一个1是初始的长度 make的这个函数签名是这样的 make([]T, lenght, capicaty) legnth 是初始的长度,会初始化为T类型的零值,而 capicaty 是切片的初始容量,初始容量决定了切片底层的数组大小,他必须大于等于长度,如果省略了capicaty那么默认就是等于length
如果初始化为4的话 var num = make([]int, 4) for i:=0; i<4; i++ { num[i] = i } 这样是可以的,如果循环变成了10,那是不行的,不能自动扩容,另外可以测试 for i,j:=range 一个东西的用法,这样写也是可以的
var num = make([]int, 4)
for i:=0; i<4; i++ {
num[i] = i
}
num2 := num[1:3]
fmt.Println(num2)
var num3 = make([]int, 4)
for i, j := range num {
num3[i] = j
}
fmt.Println(num3)
字典
注意字典定义的时候一定后面也要加上逗号才行
m := map[string]int {
"apple":1,
"banana": 2,
}
fmt.Println(m)
如果是"banana": 2 没有逗号是不行的,另外字典不能使用make初始化,比如如下都是错误的,如果要定义的时候就初始化,还是要使用上面的形式
// var m map[string]string
// m = make(map[string]string)
// var m = make(map[string]string {
// "a": "b",
// })
// m := make(map[string][string]{
// "1": "2",
// })
回答一下昨天的问题
-
为什么没有看到在哪里设置 ID和SeqenceNum? 其实是已经设置了的,因为你的struct的初始化,实在循环里面的,在循环里面初始化的时候,就已经 for i:=0; i<count; i++ {
// 构建请求 icmp := &ICMP{ Type: typ, Code: code, CheckSum: uint16(0), ID: uint16(i), SequenceNum: uint16(i), }
这里已经是 uint16(i) 设置了
-
为什么没看到在哪里设置报文体? 报文体其实也是设置了,就是在 data := make([]byte, size) 初始化了一个4个字节的数组,因为icmp 发送就是发送一个4个字节的数据,只不过初始化的东西全部都是0而已,那么感觉上应该报文体加什么都是可以的,同时设置了之后,再去计算报文体的校验和的,比如
buffer.Write(data) data = buffer.Bytes() // 计算ICMP校验和 checkSum := checkSum(data)
data 就是初始化为0了,然后计算报文体的和
正在Ping baidu.com [110.242.68.66] 具有 32 字节的数据:
报文体的内容是, 校验和是 63487来自 110.242.68.66 的回复: 字节=32, 时间=49 TTL=48
报文体的内容是, 校验和是 63485来自 110.242.68.66 的回复: 字节=32, 时间=79 TTL=48
报文体的内容是, 校验和是 63483来自 110.242.68.66 的回复: 字节=32, 时间=59 TTL=48
报文体的内容是, 校验和是 63481来自 110.242.68.66 的回复: 字节=32, 时间=139 TTL=48
那么问题来了,既然报文体都是初始化为0的话,为什么校验和还会算出来不一样呢,不是只计算了data报文体的字段吗 ?
其实不是的,他是计算的整体,因为在之前写入了
data := make([]byte, size)
buffer.Write(data)
报文头,然后再把整体的报文一起取出来了
data = buffer.Bytes()
写入进去之后再取出来就是整体了
- 校验和为什么要>>8
因为校验和是一个16位的,如果直接写入的话,会被自动截断,截断是只保留后面的内容,所以
checkSum >>8 是只保留前面,也就是左边的8位 剩下的直接给checkSum的话,是让他自动截断,也就是自动保留右边的8位,也就是后面的8位