1. for range循环遍历slice/map,值是复制的,且每次循环都是用同一个值保存复制后的值
package main
import "fmt"
type student struct {
Name string
Age int
}
func main() {
var stus []student
stus = []student{
{Name: "one", Age: 18},
{Name: "two", Age: 19},
}
data1 := make(map[int]student)
data2 := make(map[int]*student)
for i, v := range stus {
fmt.Printf("%d, %v, %X\n", i, v, &v)
data1[i] = v
data2[i] = &v //应该改为:data[i] = &stus[i]
}
for i := range data1 {
fmt.Printf("key=%d, value=%v \n", i, data1[i])
fmt.Printf("key=%d, value=%v \n", i, data2[i])
}
}
// output
0, {one 18}, &{6F6E65 12} 这里引用的不同的值
1, {two 19}, &{74776F 13}
key=0, value={one 18} 赋值
key=0, value=&{two 19} 赋值地址
key=1, value={two 19}
key=1, value=&{two 19}
第一次循环时用v保存了第一个student的值,然后第二次循环时依然用v保存的第二个值,这就导致&v一直是一样的
2. map[int]*struct可以直接对struct元素赋值,而map[int]struct不可以
type UserAlert struct {
WebhookAlerts []*WebhookAlert
}
alertMap := make(map[uint]*WebhookAlert)
uasMap = make(map[uint]*UserAlert)
......
for _, alert := range alertMap {
uasMap[uid].WebhookAlertsTmp = append(uasMap[uid].WebhookAlertsTmp, alert) // 允许
}
alertMap := make(map[uint]WebhookAlert)
uasMap = make(map[uint]UserAlert)
......
for _, alert := range alertMap {
uasMap[uid].WebhookAlertsTmp = append(uasMap[uid].WebhookAlertsTmp, alert) // 不允许
}
3. defer函数参数是在定义函数的那一行就预先运行了参数计算,与函数实际运行顺序无关
func main() {
a := 1
defer print(function(a)) //预先计算了函数参数
a = 2
}
func function(num int) int {
return num
}
func print(num int) {
fmt.Println(num)
}
// output
1
4. 调用对象的方法,会根据需要自动转换为指针或者对象;调用接口的方法,必须区分是实例方法还是指针方法,指针可以调用实例方法,反之不行
// 调用对象方法
type student struct{
name string
age int
}
func (stu student) speak1(){
fmt.Println("I am a student1, I am ", stu.age)
}
func (stu *student) speak2(){
fmt.Println("I am a student2, I am ", stu.age)
}
func main(){
var s1 student
var s2 = &s1
s1.speak1()
s1.speak2()
s2.speak1()
s2.speak2()
//var p people
//p = student{name:"RyuGou", age:12} //应该改为 p = &student{name:"RyuGou", age:12}
//p.speak()
}
// output
I am a student1, I am 0
I am a student2, I am 0
I am a student1, I am 0
I am a student2, I am 0
// 调用接口方法
type people interface {
speak1()
speak2()
}
type student struct{
name string
age int
}
func (stu student) speak1(){
fmt.Println("I am a student1, I am ", stu.age)
}
func (stu *student) speak2(){
fmt.Println("I am a student2, I am ", stu.age)
}
func main(){
var p people
p = &student{name:"RyuGou", age:12} // 如果不是用指针会报错
p.speak1()
p.speak2()
}
// output
I am a student1, I am 12
I am a student2, I am 12
总结一下就是:非指针接口不能调用指针的方法
5. 函数不可以比较(只能和nil比较),所以不可以做为map的key
6. map中的元素是会变动的,所以不能取地址,且遍历循序也不保证一致
7. 接口赋值,即便赋值为nil,但不等于nil
iseprimport (
"bytes"
"fmt"
"io"
)
const debug = false
func main(){
var buf *bytes.Buffer
if debug{
buf = new(bytes.Buffer)
}
f(buf)
}
func f(out io.Writer){
if out != nil{ // out!=nil
fmt.Println("surprise!")
}
}
// 会输出surprise!
8. json与golnag类型对应关系
bool, for JSON booleans
float64, for JSON numbers
string, for JSON strings
[]interface{}, for JSON arrays
map[string]interface{}, for JSON objects
nil for JSON null
9. golang字符串,for循环每个字符是rune类型,数组方式访问是byte
for _, s := range strings // s is rune
strings[0] // byte alias uint8
10. 函数返回值:命名返回值和非命名返回值的区别
package main
func main() {
println(DeferFunc1(1))
println(DeferFunc2(1))
println(DeferFunc3(1))
}
func DeferFunc1(i int) (t int) {
t = i
defer func() {
t += 3
}()
return t
}
func DeferFunc2(i int) int {
t := i
defer func() {
t += 3
}()
return t
}
func DeferFunc3(i int) (t int) {
defer func() {
t += i
}()
return 2
}
// OUTPUT
4
1
3
普通返回执行顺序:
1. res = xxx
2. defer // 无法修改返回值
3. return res
命名返回
1. 函数在执行最开始时已经初始化了命名返回
2. defer // 可以修改返回值
3. return 命名返回
11. iota是逐行增加,每次const语句iota都从0开始
const (
x = iota
y
z = "zz"
k
p = iota
)
func main() {
fmt.Println(x,y,z,k,p)
}
// output
0, 1, zz, zz, 4
const (
a = 1 << iota // a==1 iota=0
b = 1 << iota // b==2 iota=1
c = 3 // c==3 iota=2, unused
d = 1 << iota // d==8 iota=3
)
const a = iota // a ==0
const b = iota // b ==0
12. 细节注意
const 常量不可以取地址
goto 不可以跳入下一层,只能调到和goto语句同层或者上层
break 只会跳出当前for循环
type alias定义的变量不是新的类型,可以直接用原始类型赋值,且具有原始类型的方法,和原始类型除了名字有区别,其他一样,而type定义的是新的类型
Named Type可以与Unamed Type互相赋值,只要底层一样
panic和recover,发生panic会依次执行defer函数,defer中的recover捕获panic;defer中可以继续panic,多个连续panic,只有最后一个会被捕获;捕获不能跨goroutine。
byte alias uint8 rune alias int32
函数签名:形参列表+返回值列表
time.After(xxx) 只有在firing后才会被gc
goroutine只有自己执行完才over,parent goroutine could not kill child goroutine
http body在读取后需要close,否则会导致goroutine泄露
13. string拼接
- 在已有字符串数组的场合,使用 strings.Join() 能有比较好的性能
- 在一些性能要求较高的场合,使用 buffer.WriteString() 以获得更好的性能
- 性能要求不太高的场合,直接使用运算符,代码更简短清晰,能获得比较好的可读性
- 如果需要拼接的不仅仅是字符串,还有数字之类的其他需求的话,可以考虑 fmt.Sprintf()
14. Compare
bool int float complex string 这都是基本类型,直接比较
array 类型相等且所有值相等才相等
pointer 指向同一个value那么相等,或者都为nil
channel 同一个make返回的相等,或者都为nil
interface dynamic type and dynamic value都相等才相等,或者都为nil
struct 非 _ 字段都可以比较,且都相等才相等
x 实现了interface T,如果x可以比较,那么将x转换为interface比较
Slice, map, and function values are not comparable.
pointer channel interface 初始值都是nil
使用reflect.DeepEqual对slice比较,一定要注意nil!= [],通常从db中获取的slice和http获取的slice空值不一样
15. interface and reflect
- 从interface到reflect对象
- 从reflect对象到interface
- 改变一个interface,但它的值必须是可被改变的