go学习笔记

1.for range结合指针
如下写法输出的*v都是m的最后一个value
for k,v := range m {
fmt.Println(k,"->",*v)
}
解法:每次循环重新赋新的变量,取新变量的地址
for key,val := range slice {
value := val
m[key] = &value
}
注意:value变量的生命周期是单次循环结束,因为下次循环就被重新定义了(:=)

2.new() 与 make() 的区别
参考答案:
new(T) 和 make(T,args) 是 Go 语言内建函数,用来分配内存,但适用的类型不同。
new(T) 会为 T 类型的新值分配已置零的内存空间,并返回地址(指针),即类型为 *T的值。换句话说就是,返回一个指针,该指针指向新分配的、类型为 T 的零值。适用于值类型,如数组、结构体等。
make(T,args) 返回初始化之后的 T 类型的值,这个值并不是 T 类型的零值,也不是指针 *T,是经过初始化之后的 T 的引用。make() 只适用于 slice、map 和 channel.

3.append追加slice
append() 的第二个参数不能直接使用 slice,需使用 … 操作符,将一个切片追加到另一个切片上:append(s1,s2…)。或者直接跟上元素,形如:append(s1,1,2,3)

4.结构体比较
(1)结构体只能比较是否相等,但是不能比较大小。
(2)相同类型的结构体才能够进行比较,结构体是否相同不但与属性类型有关,还与属性顺序相关,比如
struct {
name string
age int
}{age:11,name:"qq"}
和struct {
age int
name string
}{age:11,name:"qq"}不相等
(3)如果 struct 的所有成员都可以比较,则该 struct 就可以通过 == 或 != 进行比较是否相等,比较时逐个项进行比较,如果每一项都相等,则两个结构体才相等,否则不相等。那么什么类型可以比较,什么类型不可比较呢?bool、数值型、字符、指针、数组等可以比较,但是像切片、map、函数等是不能比较的。

5.类型别名
type MyInt1 int //定义一个新类型,名为MyInt1
type MyInt2 = int //为int类型定义别名MyInt2,实质还是int
var i1 MyInt1 = i //go是强类型语言,类型不一致,报错
var i2 MyInt2 = i //正常赋值
特殊:
type T []int
func F(t T) {}
func main() {
var q []int
F(q)
}
我们知道不同类型的值是不能相互赋值的,即使底层类型一样,所以上面编译不通过;但是对于底层类型相同的变量可以相互赋值还有一个重要的条件,即至少有一个不是有名类型(named type)。
这是 Go 语言规范手册的原文:
"x's type V and T have identical underlying types and at least one of V or T is not a named type. "
Named Type 有两类:
1.内置类型,比如 int, int64, float, string, bool 等;
2.使用关键字 type 声明的类型;
Unnamed Type 是基于已有的 Named Type 组合一起的类型,例如:struct{}、[]string、interface{}、map[string]bool 等。

6.字符串连接方式
(1)str := “abc” + “123”
(2)fmt.Sprintf(“abc%d”, 123)
(3)strings.Join()

s1 := []string{"111", "222"}
s2 := strings.Join(s1, "www")
fmt.Println(s2) //111www222

(4)buffer.WriteString()

7.init说明
init() 函数是用于程序执行前做包的初始化的函数,比如初始化包里的变量等;
一个包可以出线多个 init() 函数,一个源文件也可以包含多个 init() 函数;
同一个包中多个 init() 函数的执行顺序没有明确定义,但是不同包的init函数是根据包导入的依赖关系决定的(看下图);
init() 函数在代码中不能被显示调用、不能被引用(赋值给函数变量),否则出现编译错误;
一个包被引用多次,如 A import B,C import B,A import C,B 被引用多次,但 B 包只会初始化一次;
引入包,不可出现死循坏。即 A import B,B import A,这种情况编译失败;

8.类型断言
语法形如:i.(type),其中 i 是接口,type 是固定关键字,需要注意的是,只有接口类型才可以使用类型选择

9.切片是指向数组的指针,且不能比较
func hello(num ...int) {
num[0] = 18
}
func main() {
i := []int{5, 6, 7}
hello(i...)
fmt.Println(i[0]) //输出18,因为使用的是切片,传递的是地址,如果换成数组则输出5
}
并且,切片是不能比较的!

10.截取操作符 [ i:j ]
a.截取操作符还可以有第三个参数,形如 [i:j:k],第三个参数 k 用来限制新切片的容量,但不能超过原数组(切片)的底层数组大小。截取获得的切片的长度和容量分别是:j-i、k-i。
b. [ i: ]默认取原slice的len大小,相当于 [ i:len(slice) ]
x := make([]int, 2, 10)
c := x[6:] // panic,因为默认6:默认取6:2,6>2导致panic
c := x[2:] // [],2:2相当于没有取

11.数组比较
Go 中的数组是值类型,可比较,但是,数组的长度也是数组类型的组成部分,所以 [2]int{5,6} 和 [3]int{5,6} 是不同的类型,是不能比较的,会产生编译错误。

12.cap()
cap() 函数适用array,slice,channel,但是不适用 map

13.map
func main() {
s := make(map[string]int)
delete(s, "h") //不报错
fmt.Println(s["h"]) //0
}
删除 map 不存在的键值对时,不会报错,相当于没有任何作用;获取不存在的减值对时,返回值类型对应的零值,所以返回 0。

14.map
%d表示输出十进制数字,%+d表示输出数值的符号。

15.defer 的原理
一文让你彻底明白 defer 的原理 - 古明地盆 - 博客园 (cnblogs.com)

16.接口,切片实质都是指针
当且仅当动态值和动态类型都为 nil 时,接口类型值才为 nil。动态类型是赋给接口的类型,动态值是 赋给接口的类型变量的值。

17.map中的struct不可寻址
var m = map[string]Math{
"foo": Math{2, 3},
}
m["foo"].x = 4 //cannot assign to struct field m[“foo”].x in map
原因:map中的struct不可寻址
解决:使用指针
var m = map[string]*Math{
"foo": &Math{2, 3},
}
m["foo"].x = 4

18.defer panic return执行顺序
func f(n int) (r int) {
defer func() {
r += n
recover()
}()
var f func()
defer f()
f = func() {
r += 2
}
return n + 1
}
func main() {
fmt.Println(f(3))
}
第一步执行r = n +1,接着执行第二个 defer,由于此时 f() 未定义,引发异常,随即执行第一个 defer,异常被 recover(),程序正常执行,最后 return,输出7。

19.切片len超出cap的时候会扩容,但是底层数组本身是不能扩容的,所以指向的底层数组会变成一个新数组
func change(s ...int) {
s = append(s,3)
}
func main() {
slice := make([]int,5,5)
slice[0] = 1
slice[1] = 2
change(slice...)
fmt.Println(slice)
change(slice[0:2]...)
fmt.Println(slice)
}
输出结果为:
[1 2 0 0 0]
[1 2 3 0 0]
知识点:可变函数、append()操作。Go 提供的语法糖…,可以将 slice 传进可变函数,不会创建新的切片。第一次调用 change() 时,append() 操作使切片底层数组发生了扩容,原 slice 的底层数组不会改变;第二次调用change() 函数时,使用了操作符[i,j]获得一个新的切片,假定为 slice1,它的底层数组和原切片底层数组是重合的,不过 slice1 的长度、容量分别是 2、5,所以在 change() 函数中对 slice1 底层数组的修改会影响到原切片。
练习题:
func main() {
a := [3]int{0, 1, 2}
s := a[1:2]
s[0] = 11
s = append(s, 12)
s = append(s, 13)
s[0] = 21
fmt.Println(a)
fmt.Println(s)
}
输出:
[0 11 12]
[21 12 13]

20.break高级用法:指定退出哪层循环
I:
for i := 0; i < 2; i++ {
for j := 0; j < 5; j++ {
if j == 2 {
break I
}
fmt.Println("hello")
}
fmt.Println("hi")
}
其中,I是自定义的名字,可以随意

21.switch单个case中,可以出现多个结果选项
以逗号隔开,如case 1,2: 代表1或2都可以

22.channel知识
关于channel的特性,下面说法正确的是?
A. 给一个 nil channel 发送数据,造成永远阻塞
B. 从一个 nil channel 接收数据,造成永远阻塞
C. 给一个已经关闭的 channel 发送数据,引起 panic
D. 从一个已经关闭的 channel 接收数据,如果缓冲区中为空,则返回一个零值
参考答案及解析:ABCD。
有方向的 channel 不可以被关闭。

23.常量知识
a.常量不同于变量的在运行期分配内存,常量通常会被编译器在预处理阶段直接展开,作为指令数据使用,所以常量无法寻址。
b.不像变量,常量未使用是能编译通过的。
c.常量组中如不指定类型和初始化值,则与上一行常量右值相同
const (
x uint16 = 120
y // 与x一致
s = "abc"
z // 与s一致
)

24.下面这段代码存在什么问题?
type Param map[string]interface{}
type Show struct {
*Param
}
func main() {
s := new(Show)
s.Param["day"] = 2
}
参考答案及解析:存在两个问题:1.map 需要初始化才能使用;2.指针不支持索引。修复代码如下:
func main() {
s := new(Show)
// 修复代码
p := make(Param)
p["day"] = 2
s.Param = &p
tmp := *s.Param
fmt.Println(tmp["day"])
}

25.切片定义时的索引
var x = []int{2: 2, 3, 0: 1}
fmt.Println(x)
输出[1 0 2 3],字面量初始化切片时候,可以指定索引,没有指定索引的元素会在前一个索引基础之上加一,所以输出[1 0 2 3]。

26.作用域{}
func main() {
 x := 1
 fmt.Println(x)
 {
   fmt.Println(x)
   i,x := 2,2
  fmt.Println(i,x)
 }
 fmt.Println(x) // print ?
}
参考答案及解析:输出1。知识点:变量隐藏。使用变量简短声明符号 := 时,如果符号左边有多个变量,只需要保证至少有一个变量是新声明的,并对已定义的变量尽进行赋值操作。但如果出现作用域之后,就会导致变量隐藏的问题,就像这个例子一样。

26.json.Marshal()
结构体生成json文本成员变量必须大写,比如
type People struct {
Name string `json:"name"`
}

27.switch的fallthrough
switch(i) {
 case 1:
  fallthrough //不管case2条件满不满足,都进入执行case2
 case 2:
  return true
}

28.recover()
recover() 必须在 defer 定义的函数内部调用才有效
defer func() {
 recover()
}()

29.for range
下面的代码输出什么?

type T struct {
    n int
}

func main() {
    ts := [2]T{}
    for i, t := range &ts {
        switch i {
        case 0:
            t.n = 3
            ts[1].n = 9
        case 1:
            fmt.Print(t.n, " ")
        }
    }
    fmt.Print(ts)
}

参考答案及解析:9 [{0} {9}]。知识点:for-range 数组指针。for-range 循环中的循环变量 t 是原数组元素的副本。如果数组元素是结构体值,则副本的字段和原数组字段是两个不同的值。

30.map
map在赋值前要make来分配内存
map 反序列化时 json.unmarshal() 的入参必须为map的地址
map 是并发读写不安全的

31.可变函数
可变函数是指针传递
func hello(num ...int) {
num[0] = 18
}
i := []int{5, 6, 7}
hello(i...)

fmt.Println(i[0]) // 18

30.Unnamed Type 不能作为方法的接收者

func (m map[string]string) Set(key string, value string) { // 修复:先type User  map[string]string , func定义接收者为m User即可
    m[key] = value
}

func main() {
    m := make(map[string]string)
    m.Set("A", "One")
}

31.sync.WaitGroup
WaitGroup 在调用 Wait() 之后不能再调用 Add() 方法
go func里面不能定义sync.WaitGroup

32.锁机制
sync.Mutex
RWMutex

申明:本文内容提炼自http://www.topgoer.cn/docs/gomianshiti,并在自己学习过程中加以理解和总结,感谢原作者提供很好的学习渠道,万分感谢。

你可能感兴趣的:(go学习笔记)