go初学者易错点

1,slice append

go初学者易错点_第1张图片

[0 0 0 0 0 1 2 3]

[1 2 3 4]

append一定是在长度后追加

go初学者易错点_第2张图片

type MyInt1 int  新类型

type MyInt2 = int   int别名

slice  [i:j:k]  长度=j-i  容量=k-i   起始索引 i 到 索引j-1

只有slice和chanel可以用cap函数

go打印 %+d 表示打印十进制的符号  比如fmt.Printf("%+d %+d",-5,+5) 输出为-5 +5

闭包引用的外部变量的地址,外部变量地址变了,闭包内引用的变量肯定改变了

type Person struct {

    age int

}

funcmain() {

    person := &Person{28}

    // 1.

    defer fmt.Println(person.age)  // 参数就是 28

    // 2.

    defer func(p *Person) {    // 参数为 Person指针 指向的是 &Person{28}

        fmt.Println(p.age)

    }(person)

    // 3.

    defer func() {

        fmt.Println(person.age)   //闭包 输出 29 person本身改变了

    }()

    person = &Person{29}

}

函数参数为 interface{} 时可以接收任何类型的参数,包括用户自定义类型等,即使是接收指针类型也用 interface{},而不是使用 *interface{}。

永远不要使用一个指针指向一个接口类型,因为它已经是一个指针。

在go中,在if中else if else

if a:= 1; false{

}else{

    fmt.Println(a)    //输出1

}

3,一个函数秒懂defer

funcmain(){

a :=1

b :=2

defercalc("1", a, calc("10", a, b))

a =0

defercalc("2", a, calc("20", a, b))

b =1

}

funccalc(indexstring, a, bint)int{

ret := a + b

fmt.Println(index, a, b, ret)

returnret

}

答案及解析:

10 1 2 3

20 0 2 2

2 0 2 2

1 1 3 4

给一个结构体Stu绑定方法如 func (stu *Stu) Speak()string{} 或 func (stu Stu)Speak()string{},指针*Stu这两个方法都可以调用,但结构体Stu只能调用Stu的方法。

iota 是 golang 语言的常量计数器,只能在常量的表达式中使用。iota 在 const 关键字出现时将被重置为0,const中每新增一行常量声明将使 iota 计数一次。

4,结构体比较

结构体只能比较是否相等,不能比较大小,相同类型的结构体才能够进行比较,结构体是否相同不但与属性类型有关,还与属性顺序相关。

什么是可比较的呢,常见的有 bool、数值型、字符、指针、数组等,像切片、map、函数等是不能比较的。

5,新类型 type Myint int ,Myint是一个新类型,不能互相赋值;别名 type Myint = int,Myint和int一样,可以互相赋值;

6,nil 只可以用作 interface、function、pointer、map、slice 和 channel 的“空值”。

7,init函数

init() 函数是用于程序执行前做包的初始化的函数,比如初始化包里的变量等;

一个包可以出线多个 init() 函数,一个源文件也可以包含多个 init() 函数;

同一个包中多个 init() 函数的执行顺序没有明确定义,但是不同包的init函数是根据包导入的依赖关系决定的(看下图);

go初学者易错点_第3张图片

init() 函数在代码中不能被显示调用、不能被引用(赋值给函数变量),否则出现编译错误;

一个包被引用多次,如 A import B,C import B,A import C,B 被引用多次,但 B 包只会初始化一次;

引入包,不可出现死循坏。即 A import B,B import A,这种情况编译失败;

8,类型断言

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

9,一个 map 中不存在的值时,返回元素类型的零值

10,闭包引用 函数内部引用外部变量,

11,永远不要使用一个指针指向一个接口类型,因为它已经是一个指针

12,结构体只能调用结构体的方法,指针既可以调用结构体的方法也可以调用指针的方法

13,iota 是 golang 语言的常量计数器,只能在常量的表达式中使用。iota 在 const 关键字出现时将被重置为0,const中每新增一行常量声明将使 iota 计数一次。

14,当且仅当动态值和动态类型都为 nil 时,接口类型值才为 nil。即值为nil类型也为nil。

15,for i,v := range 数组,for i,v := range 切片 不一样 range虽然是值拷贝,但切片是个指针,拷贝的是一个指针,里面的数据指针还是指向同一片内存, 数组就不一样了,数组进行值拷贝,在range的时候就是另一个栈空间了。

16,for range 使用短变量声明(:=)的形式迭代变量时,变量 i、value 在每次循环体中都会被重用,而不是重新声明。

17,多重赋值,多重赋值分为两个步骤,有先后顺序:计算等号左边的索引表达式和取址表达式,接着计算等号右边的表达式;赋值;简单的说就是先确定左边的值(表达式要计算出具体的值),在确定右边的值,然后进行赋值

18,强制类型转换  type Myint int  var a int = 1  var b Myint = Myint(a) 

不可用var b Myint = a.(Myint)  只有接口才使用类型断言

19,自增自减操作。i++ 和 i-- 在 Go 语言中是语句,不是表达式,因此不能赋值给另外的变量。此外没有 ++i 和 --i。

20,select 的用法有点类似 switch 语句,但 select 不会有输入值而且只用于信道操作。select 用于从多个发送或接收信道操作中进行选择,语句会阻塞直到其中有信道可以操作,如果有多个信道可以操作,会随机选择其中一个 case 执行。

21,常量。常量不同于变量的在运行期分配内存,常量通常会被编译器在预处理阶段直接展开,作为指令数据使用,所以常量无法寻址。

22,关于信道

var c chan int // 方式一

c  := make(chan int) // 方式二

使用关键字 chan 创建信道,声明时有类型,表明信道只允许该类型的数据传输。信道的零值为 nil。方式一就声明了 nil 信道。nil 信道没什么作用,既不能发送数据也不能接受数据。方式二使用 make 函数创建了可用的信道 c。

信道在箭头的左边是写数据,在右边是从信道读数据

信道操作默认是阻塞的,往信道里写数据之后当前协程便阻塞,直到其他协程将数据读出。一个协程被信道操作阻塞后,Go 调度器会去调用其他可用的协程,这样程序就不会一直阻塞。信道的这种特性非常有用。

如果缓冲信道是关闭状态但有数据,仍然可以读取数据

rch := make(<-chan int)     只发送信道

sch := make(chan<- int)     只接受信道

使用单向通道主要是可以提高程序的类型安全性,程序不容易出错。

信道为 nil,读写都会阻塞。

23,for range 使用的副本,如果为数组 for range arr arr是原来数组的副本相当于另一个数组,和原来的数组不是一个数组,在循环过程中改变比如arr[0]=1,此时改变的是原数组,和for range arr 这个arr没有任何关系。如果为切片for range slice slice与原来的切片底层数据是一样的,slice包括数据指针,len,cap,数据指针指向的是同一片内存,如果在循环中改变原s切片的len,cap,相当于改变了原切片中的数据指针(cap不够则重新分配内存),len,cap,而for range是拷贝,现有的slice是不会改变的。

24,不能对 nil 的 map 直接赋值,需要使用 make() 初始化。但可以使用 append() 函数对为 nil 的 slice 增加元素。

25,rune 是 int32 的别名一样,byte 是 uint8 的别名,别名类型无序转换,可直接转换。

26,常量是一个简单值的标识符,在程序运行时,不会被修改的量。不像变量,常量未使用是能编译通过的。常量组中如不指定类型和初始化值,则与上一行非空常量右值相同

27,:= 操作符不能用于结构体字段赋值。

28,Go 语言的 switch 语句虽然没有"break",但如果 case 完成程序会默认 break,可以在 case 语句后面加上关键字 fallthrough,这样就会接着走下一个 case 语句(不用匹配后续条件表达式)。

29,函数只能与 nil 比较

30,直接返回的 T{} 不可寻址;不可寻址的结构体不能调用带结构体指针接收者的方法;结构体可以调用指针的方法,但是必须可寻址。

31,从一个基础切片派生出的子切片的长度可能大于基础切片的长度。假设基础切片是 baseSlice,使用操作符 [low,high],有如下规则:0 <= low <= high <= cap(baseSlice),只要上述满足这个关系,下标 low 和 high 都可以大于 len(baseSlice)。

s := make([]int, 3, 9)

fmt.Println(len(s))   // 3

s2 := s[4:8]

fmt.Println(len(s2))     // 4

截取符号 [i:j],如果 j 省略,默认是原切片或者数组的长度,s3 := s[4:]就是错误的 相当于s[4:3]

32,将 Mutex 作为匿名字段时,相关的方法必须使用指针接收者,否则会导致锁机制失效。

33,nil 不是关键字,nil := 123 这样赋值正确,此时nil为变量。

34,defer() 后面的函数如果带参数,会优先计算参数,并将结果存储在栈中,到真正执行 defer() 的时候取出。

35,recover() 必须在 defer() 函数中直接调用才有效

36,goto 不能跳转到其他函数或者内层代码

37,闭包引用(变量指针)简单的说就是一个函数内部可以引用外部变量,

38,在 Go 语言中,双引号用来表示字符串 string,其实质是一个 byte 类型的数组,单引号表示 rune 类型。

39,DeferTest2(1)  执行顺序  首先 r=2 闭包引用 r += 1 即r=3 所以返回3

func DeferTest2(iint) (r int) {

    defer func() {

        r += i

    }()

    return 2

}

40,Go 语言的取反操作是 ^,对数 a 取反,结果为 -(a+1),

41,只能用于函数内部;短变量声明语句中至少要声明一个新的变量;

42,可变函数是指针传递   fun(args...)   var s []int = {1,2.3}  fun(s)

43,类型的 String() 方法。如果类型定义了 String() 方法,使用 Printf()、Print() 、 Println() 、 Sprintf() 等格式化输出时会自动使用 String() 方法。如果这时显式的调用会造成死循环。s.string()

44,关于方法指针接收器用法

一般来说,指针接收器可以使用在:对方法内部的接收器所做的改变应该对调用者可见时。

指针接收器也可以被使用在如下场景:当拷贝一个结构体的代价过于昂贵时。

45,启动一个新的协程时,协程的调用会立即返回。与函数不同,程序控制不会去等待 Go 协程执行完毕。

46,信道接收发送必须成对存在,否则会造成死锁。

47,从一个关闭的信道继续接收值时回返回接受累类型的默认值,如 v,ok : <- ch ch关闭后 v 为0,ok为false

48,select 语句用于在多个发送/接收信道操作中进行选择。select 语句会一直阻塞,直到发送/接收操作准备就绪。如果有多个信道操作准备完毕,select 会随机地选取其中之一执行。该语法与 switch 类似,所不同的是,这里的每个 case 语句都是信道操作。

49,Mutex 用于提供一种加锁机制(Locking Mechanism),可确保在某时刻只有一个协程在临界区运行,以防止出现竞态条件。

50,go通过结构体组合嵌套实现类似面向对象继承。

51,go通过接口来实现面向对象多态。

你可能感兴趣的:(go初学者易错点)