一、入门

1. 地址引用错误

1.1 代码

func main() {

     slice := []int{0,1,2,3}
     m := make(map[int]*int)

     for key,val := range slice {
         m[key] = &val
     }

    for k,v := range m {
        fmt.Println(k,"->",*v)
    }
}
0 -> 3
1 -> 3
2 -> 3
3 -> 3

1.2 问题

循环时使用的 keyval 都是同一个。

for key, val := range slice {
    fmt.Printf("%p,%p\n", &key, &val)
    m[key] = &val
}
0xc00000a0c8,0xc00000a0e0
0xc00000a0c8,0xc00000a0e0
0xc00000a0c8,0xc00000a0e0
0xc00000a0c8,0xc00000a0e0

1.3 解决

通过 v := val,基本数据类型直接拷贝值,val 的改变不会影响到 v

package main

import "fmt"

func main() {

    slice := []int{0, 1, 2, 3}
    m := make(map[int]*int)

    for key, val := range slice {
        //fmt.Printf("%p,%p\n", &key, &val)
        v := val
        m[key] = &v
    }

    for k, v := range m {
        fmt.Println(k, "->", *v)
    }
}

2. make 初始化时的指定长度会填充零值

2.1 代码

预期想要输出 [1 2 3]

package main

import "fmt"

func main() {
    s := make([]int, 5)
    s = append(s, 1, 2, 3)
    fmt.Println(s)
}
[0 0 0 0 0 1 2 3]

2.2 问题

make 初始化时的指定长度会使用零值进行填充

2.3 解决

package main

import "fmt"

func main() {
    s := make([]int, 0)
    s = append(s, 1, 2, 3)
    fmt.Println(s)
}

3. append 只能接受切片

3.1 代码

package main

import "fmt"

func main() {
    list := new([]int)
    list = append(list, 1)
    fmt.Println(list)
}

image-20231115111355338

3.2 问题

append 函数第一个参数接收的是一个切片,而不是指针

func append(slice []Type, elems ...Type) []Type

3.3 解决

package main

import "fmt"

func main() {
    list := new([]int)
    *list = append(*list, 1)
    fmt.Println(list)
}
package main

import "fmt"

func main() {
    list := make([]int, 0)
    list = append(list, 1)
    fmt.Println(list)
}

4. 结构体什么情况下可以比较

4.1 代码

package main

import "fmt"

func main() {
    sn1 := struct {
        age  int
        name string
    }{age: 11, name: "qq"}
    sn2 := struct {
        age  int
        name string
    }{age: 11, name: "qq"}

    if sn1 == sn2 {
        fmt.Println("sn1 == sn2")
    }

    sm1 := struct {
        age int
        m   map[string]string
    }{age: 11, m: map[string]string{"a": "1"}}
    sm2 := struct {
        age int
        m   map[string]string
    }{age: 11, m: map[string]string{"a": "1"}}

    if sm1 == sm2 {
        fmt.Println("sm1 == sm2")
    }
}

image-20231115112240723

4.2 问题

结构体比较分为两种情况:

  • 相同结构体类型间比较;
  • 不同结构体类型间比较;
相同结构体
  • 结构体内部都为基本数据类型;
type A struct {
    Name string
    Age  int
}

func TestB_1(t *testing.T) {
    a1 := A{}
    a2 := A{}
    a3 := A{Name: "xxcheng"}
    if a1 == a2 {
        fmt.Println("a1==a2")
    } else {
        fmt.Println("a1!=a2")
    }
    if a1 == a3 {
        fmt.Println("a1==a3")
    } else {
        fmt.Println("a1!=a3")
    }
}
a1==a2
a1!=a3
不相同结构体
  • 必须为匿名结构体;
  • 结构体的属性顺序必须一致;
  • 其他要求同相同结构体要求;
func TestB_2(t *testing.T) {
    a1 := struct {
       Name string
       Age  int
    }{}
    a2 := struct {
       Name string
       Age  int
    }{}
    if a1 == a2 {
       fmt.Println("a1==a2")
    } else {
       fmt.Println("a1!=a2")
    }
}
a1==a2
func TestB_2(t *testing.T) {
    a1 := struct {
       Name string
       Age  int
    }{}
    a2 := struct {
       Age  int
       Name string
    }{}
    if a1 == a2 {
       fmt.Println("a1==a2")
    } else {
       fmt.Println("a1!=a2")
    }
}

image-20231115142554053

5. 接口类型才能使用类型断言

5.1 代码

package main

func GetValue() int {
    return 1
}

func main() {
    i := GetValue()
    switch i.(type) {
    case int:
        println("int")
    case string:
        println("string")
    case interface{}:
        println("interface")
    default:
        println("unknown")
    }
}

image-20231115144245949

5.2 问题

接口类型才能使用类型断言。

5.3 解决

package main

func GetValue() interface{} {
    return 1
}

func main() {
    i := GetValue()
    switch i.(type) {
    case int:
        println("int")
    case string:
        println("string")
    case interface{}:
        println("interface")
    default:
        println("unknown")
    }
}
int

6. 变长参数函数传入的切片可被修改

6.1 代码

package main

import "fmt"

func hello(num ...int) {
    num[0] = 18
}

func main() {
    i := []int{5, 6, 7}
    hello(i...)
    fmt.Println(i[0])
}
18

6.2 理解

使用一个切片作为可变参数实参传入,切片是引用类型,函数内的可变参数切片和传入的切片引用的是同一片地址。

7. defer 延迟调用时上下文问题

7.1 代码

func AreOk(i int) {
    fmt.Println("i:", i)
}
func AreOk2(i *S) {
    fmt.Println("i:", i.Value)
}

type S struct {
    Value int
}

func TestOOO(t *testing.T) {
    i := 5
    j := &S{
        Value: 5,
    }
    defer AreOk(i)
    defer AreOk2(j)
    defer func() {
        AreOk(i)
    }()
    i += 10
    j.Value += 10
}
i: 15
i: 15
i: 5

7.2 理解

在执行到 defer 语句时会保存一份当前环境的上下文副本,后续对变量的改动不会影响变量的变化,变量 j 是一个指针,指向的还是原来的值,defer 保存的是地址值,指针指向的还是相同的一块地址 。如果是一个函数,会形成闭包,不执行就不知道会引用哪些外部变量。

8. 字符串是只读属性

8.1 代码

func TestBBBB(t *testing.T) {
    s := "Teacher"
    s[0] = 'w'
}

image-20231116110540885

8.2 理解

GO 中,字符串是只读的。

9. 具名返回值的输出受到 defer 影响

9.1 代码

func increaseA() int {
    var i int
    defer func() {
        i++
    }()
    return i
}

func increaseB() (r int) {
    defer func() {
        r++
    }()
    return r
}

func main() {
    fmt.Println(increaseA())
    fmt.Println(increaseB())
}
0
1

9.2 理解

如果返回值的参数是具名返回值,那么在 return 的时候会将当前这些返回值赋值给具名返回值,如果 defer 内对具名返回值进行了修改,会影响最后的返回值。

具名返回值 deferreturn 执行顺序
  • 执行 return,将 return 中的返回值赋值给对应的具名返回值
  • 按照先进后出顺序执行 defer,对具名返回值进行修改;
  • 返回 defer 中修改后的值;

9.3 加深理解示例

func JustFn() (a, b, c int) {
    d := 10
    a++
    b++
    c++
    d++
    defer func() {
        a++
        b = 99
        c++
        d=111
    }()
    b++
    fmt.Println(a, b, c, d)
    return a, b, d
}
func Test_JustFn(t *testing.T) {
    fmt.Println(JustFn())
}
1 2 1 11
2 99 12

image-20231116140411781

10. map 无法直接寻址

10.1 代码

type Math struct {
    x, y int
}

var m = map[string]Math{
    "foo": Math{2, 3},
}

func Test9(t *testing.T) {
    m["foo"].x = 4
    fmt.Println(m["foo"].x)
}

image-20231204142759825

10.2 理解

map 类型无法直接寻址访问,只能通过中间变量或者指针的方式访问。

10.3 解决

var m = map[string]Math{
    "foo": Math{2, 3},
}
var m2 = map[string]*Math{
    "foo": &Math{2, 3},
}

func Test9(t *testing.T) {
    a := m["foo"]
    a.x = 111
    m["foo"] = a
    fmt.Println(m)
    fmt.Println("-----")
    m2["foo"].x = 999
    fmt.Println(m2["foo"])
}
map[foo:{111 3}]
-----
&{999 3}

二、刨析深入

1. == 深入理解

1.1 == 使用的前提必须是数据类型是相同的。

var b1 int
var b2 int8
if b1 == b2 {
    fmt.Println("b1==b2")
} else {
    fmt.Println("b1!=b2")
}

image-20231115114415326

1.2 浮点数运算精度缺失判断错误的情况

var a float64 = 0.1
var b float64 = 0.2
var c float64 = 0.3
if (a + b) == c {
    fmt.Println("b1==b2")
} else {
    fmt.Println("b1!=b2")
}

image-20231115115343984

如何解决?设置一个极限值,浮点数运算的差值的绝对值小于这个极限值就视为相等。

1.3 切片之间无法比较、map 之间无法比较

切片之间无法直接比较,但是切片可以和 nil 比较

func TestCCC(t *testing.T) {
    a := []int{1, 2, 3}
    b := []int{1, 2, 3}
    if a == b {

    }
}

image-20231115134028122

func TestCCC(t *testing.T) {
    a := []int{1, 2, 3}
    if a == nil {

    }
}

切片之间无法直接比较,但是切片可以和 nil 比较

func TestCCC(t *testing.T) {
    a := map[string]string{}
    b := map[string]string{}
    if a == b {

    }
}

image-20231115134243730

func TestCCC(t *testing.T) {
    a := map[string]string{}
    if a == nil {

    }
}

1.4 指针可以比较,比较地址值

func TestCCC(t *testing.T) {
    a := 1
    b := &a
    c := &a
    if b == c {
        fmt.Println("equal")
    } else {
        fmt.Println("not equal")
    }
}
equal

1.5 接口可以比较,比较动态类型和动态值

type OKI interface {
    Ok() bool
}
type OBJK struct {
}

func (o *OBJK) Ok() bool {
    return true
}
func TestDDD(t *testing.T) {
    var a OKI
    var b OKI = new(OBJK)
    fmt.Println(a == b)
    fmt.Println(a == nil)
}
false
true

三、参考链接