go lang checklist
注意:
- “_”是特殊标识符,用来忽略结果。
iota
是go
语言的常量计数器,只能在常量的表达式中使用。iota
在const
关键字出现时将被重置为0
。const
中每新增一行常量声明将使iota
计数一次(iota
可理解为const
语句块中的行索引)- slice 并不是数组或数组指针。它通过内部指针和相关属性引用数组片段,也就是slice是一个引用
1. 切片:切片是数组的一个引用,因此切片是引用类型。但自身是结构体,值拷贝传递。
2. 切片的长度可以改变,因此,切片是一个可变的数组。
3. 切片遍历方式和数组一样,可以用len()求长度。表示可用元素数量,读写操作不能超过该限制。
4. cap可以求出slice最大扩张容量,不能超出数组限制。0 <= len(slice) <= len(array),其中array是slice引用的数组。
5. 切片的定义:var 变量名 []类型,比如 var str []string var arr []int。
6. 如果 slice == nil,那么 len、cap 结果都等于 0。
s1 := []int{0, 1, 2, 3, 8: 100} // 通过初始化表达式构造,可使用索引号。 fmt.Println(s1, len(s1), cap(s1)) //结果是: [0 1 2 3 0 0 0 0 100] 9 9
new与make的区别
1.二者都是用来做内存分配的。
2.make只用于slice、map以及channel的初始化,返回的还是这三个引用类型本身;
3.而new用于类型的内存分配,并且内存对应的值为类型零值,返回的是指向类型的指针。
map是一种无序的基于key-value的数据结构,Go语言中的map是引用类型,必须初始化才能使用
1.接收者变量:接收者中的参数变量名在命名时,官方建议使用接收者类型名的第一个小写字母,而不是self、this之类的命名。
例如,Person类型的接收者变量应该命名为 p,Connector类型的接收者变量应该命名为c等
结构体标签(Tag)
Tag是结构体的元信息,可以在运行的时候通过反射的机制读取出来。
Tag在结构体字段的后方定义,由一对反引号包裹起来,具体的格式如下:
`key1:"value1" key2:"value2"`
结构体标签由一个或多个键值对组成。键与值使用冒号分隔,值用双引号括起来。键值对之间使用一个空格分隔。
注意事项: 为结构体编写Tag时,必须严格遵守键值对的规则。结构体标签的解析代码的容错能力很差,一旦格式写错,编译和运行时都不会提示任何错误,通过反射也无法正确取值。例如不要在key和value之间添加空格。
Golang 可变参数本质上就是 slice。只能有一个,且必须是最后一个。
在参数赋值时可以不用用一个一个的赋值,可以直接传递一个数组或者切片,特别注意的是在参数后加上“…”即可。
func myfunc(args ...int) { //0个或多个参数 }
注意:其中args是一个slice,我们可以通过arg[index]依次访问所有参数,通过len(arg)来判断传递参数的个数.
任意类型的不定参数: 就是函数的参数和每个参数的类型都不是固定的。
用interface{}传递任意类型数据是Go语言的惯例用法,而且interface{}是类型安全的。
在Go语言中,...
是一种特殊语法,用于将切片或数组的元素展开作为函数参数传递给可变参数函数。
直接使用值为 nil 的 slice、map
允许对值为 nil 的 slice 添加元素,但对值为 nil 的 map添加元素则会造成运行时 panic
// map 错误示例 func main() { var m map[string]int m["one"] = 1 // error: panic: assignment to entry in nil map // m := make(map[string]int)// map 的正确声明,分配了实际的内存 } // slice 正确示例 func main() { var s []int s = append(s, 1) }
.string 类型的变量值不能为 nil
对那些喜欢用 nil 初始化字符串的人来说,这就是坑:
golang 中字符串是不能赋值 nil
的,也不能跟 nil
比较。
// 错误示例 func main() { var s string = nil // cannot use nil as type string in assignment if s == nil { // invalid operation: s == nil (mismatched types string and nil) s = "default" } } // 正确示例 func main() { var s string // 字符串类型的零值是空串 "" if s == "" { s = "default" } }
访问 map 中不存在的 key
Go 则会返回元素对应数据类型的零值,比如 nil、'' 、false 和 0,取值操作总有值返回,故不能通过取出来的值来判断 key 是不是在 map 中。
检查 key 是否存在可以用 map 直接访问,检查返回的第二个参数即可:
// 错误的 key 检测方式 func main() { x := map[string]string{"one": "2", "two": "", "three": "3"} if v := x["two"]; v == "" { fmt.Println("key two is no entry") // 键 two 存不存在都会返回的空字符串 } } // 正确示例 func main() { x := map[string]string{"one": "2", "two": "", "three": "3"} if _, ok := x["two"]; !ok { fmt.Println("key two is no entry") } }
使用了值为 nil 的 channel
在一个值为 nil 的 channel 上发送和接收数据将永久阻塞:
func main() { var ch chan int // 未初始化,值为 nil for i := 0; i < 3; i++ { go func(i int) { ch <- i }(i) } fmt.Println("Result: ", <-ch) time.Sleep(2 * time.Second) }
runtime 死锁错误:
fatal error: all goroutines are asleep - deadlock! goroutine 1 [chan receive (nil chan)]
利用这个死锁的特性,可以用在 select 中动态的打开和关闭 case 语句块:
func main() { inCh := make(chan int) outCh := make(chan int) go func() { var in <-chan int = inCh var out chan<- int var val int for { select { case out <- val: println("--------") out = nil in = inCh case val = <-in: println("++++++++++") out = outCh in = nil } } }() go func() { for r := range outCh { fmt.Println("Result: ", r) } }() time.Sleep(0) inCh <- 1 inCh <- 2 time.Sleep(3 * time.Second) }
type Person struct { Name string `json:"name"` Age int `json:"age"` Addr string `json:"addr,omitempty"` }
结构体里的 Addr 字段有 omitempty 属性,因此 encoding/json 在将对象转化 json 字符串时,只要发现对象里的 Addr 为 false, 0, 空指针,空接口,空数组,空切片,空映射,空字符串中的一种,就会被忽略。
golang 里的 iota
- 不同 const 定义块互不干扰;
- 所有注释行和空行全部忽略;
- 没有表达式的常量定义复用上一行的表达式;
- 从第一行开始,iota 从 0 逐行加一;
- 替换所有 iota。
协程什么时候会切换
-
I/O,select
-
channel
-
等待锁
-
函数调用
-
runtime.Gosched()
永远不要使用一个指针指向一个接口类型,因为它已经是一个指针。
package main import ( “fmt” ) type nexter interface { next() byte } func nextFew1(n nexter, num int) []byte { var b []byte for i:=0; i < num; i++ { b[i] = n.next() } return b } func nextFew2(n *nexter, num int) []byte { var b []byte for i:=0; i < num; i++ { b[i] = n.next() // 编译错误:n.next未定义(*nexter类型没有next成员或next方法) } return b } func main() { fmt.Println("Hello World!") }
type User struct { Name string Email string } func (u User) Notify() error // User 类型的值可以调用接受者是值的方法 damon := User{"AriesDevil", "ariesdevil@xxoo.com"} damon.Notify() // User 类型的指针同样可以调用接受者是值的方法 alimon := &User{"A-limon", "alimon@ooxx.com"} alimon.Notify()
- 一个结构体的方法的接收者可能是类型值或指针
- 如果接收者是值,无论调用者是类型值还是类型指针,修改都是值的副本
- 如果接收者是指针,则调用者修改的是指针指向的值本身
package main import "fmt" type coder interface { code() debug() } type Gopher struct { language string } func (p Gopher) code() { fmt.Printf("I am coding %s language1\n", p.language) } func (p *Gopher) debug() { fmt.Printf("I am debuging %s language\n", p.language) } func main() { var c coder = Gopher{"Go"} c.code() c.debug() } # command-line-arguments ./code.go:23: cannot use Gopher literal (type Gopher) as type coder in assignment: Gopher does not implement coder (debug method has pointer receiver)
- 类型
*T
的可调用方法集包含接受者为*T
或T
的所有方法集 - 类型
T
的可调用方法集包含接受者为T
的所有方法 - 类型
T
的可调用方法集不包含接受者为*T
的方法
也就是说:
- 接收者是指针
*T
时,接口的实例必须是指针 - 接收者是值
T
时,接口的实例可以是指针也可以是值
Methods Receivers | Values |
---|---|
(t T) | T and *T |
(t *T) | *T |
上面的表格可以解读为:如果是值接收者,实体类型的值和指针都可以实现对应的接口;如果是指针接收者,那么只有实例类型的指针能够实现对应的接口。
在Go语言中,可以使用unsafe.Sizeof
函数来获取uintptr
类型的大小。例如:unsafe.Sizeof(uintptr(0))
Uber Go 语言编码规范
nterface 合理性验证
在编译时验证接口的符合性。这包括:
- 将实现特定接口的导出类型作为接口API 的一部分进行检查
- 实现同一接口的(导出和非导出)类型属于实现类型的集合
- 任何违反接口合理性检查的场景,都会终止编译,并通知给用户
补充:上面3条是编译器对接口的检查机制, 大体意思是错误使用接口会在编译期报错. 所以可以利用这个机制让部分问题在编译期暴露.
Bad | Good |
---|---|
|
|
如果 *Handler
与 http.Handler
的接口不匹配, 那么语句 var _ http.Handler = (*Handler)(nil)
将无法编译通过.
赋值的右边应该是断言类型的零值。 对于指针类型(如 *Handler
)、切片和映射,这是 nil
; 对于结构类型,这是空结构
接收器 (receiver) 与接口
Effective Go 中有一段关于 pointers vs. values 的精彩讲解。
补充:
- 一个类型可以有值接收器方法集和指针接收器方法集
- 值接收器方法集是指针接收器方法集的子集,反之不是
- 规则
- 值对象只可以使用值接收器方法集
- 指针对象可以使用 值接收器方法集 + 指针接收器方法集
- 接口的匹配(或者叫实现)
- 类型实现了接口的所有方法,叫匹配
- 具体的讲,要么是类型的值方法集匹配接口,要么是指针方法集匹配接口
具体的匹配分两种:
- 值方法集和接口匹配
- 给接口变量赋值的不管是值还是指针对象,都ok,因为都包含值方法集
- 指针方法集和接口匹配
- 只能将指针对象赋值给接口变量,因为只有指针方法集和接口匹配
- 如果将值对象赋值给接口变量,会在编译期报错(会触发接口合理性检查机制
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 【杭电多校比赛记录】2025“钉耙编程”中国大学生算法设计春季联赛(1)
2022-05-20 TLS 线上问题
2021-05-20 log&& buffevent&&内存池 2
2021-05-20 gdb 打印每个线程的bt ps 状态等命令