Skip to main content

Go 注意事项

Go 用首字母大小写区分可见性

一个包中,结构体可以以小写字母开头,虽然在其他语言中比较奇怪

函数参数

  1. Go 语言中的函数传参都是值拷贝,当要修改(访问)某个变量的时候,可以创建一个指向该变量地址的指针变量
  2. Go 语言中的指针操作非常简单,只需要记住两个符号&(取地址)*(根据地址取值)

函数内修改非指针变量

你提供的代码示例展示了值传递和指针传递在 Go 语言中的不同效果。以下是代码的详细解释:

package main

import (
"fmt"
)

func modify1(x int) {
x = 100
fmt.Println("这里的值", x)
}

func modify2(x *int) {
*x = 100
}

func main() {
a := 10
modify1(a)
fmt.Println(a) // 10
modify2(&a)
fmt.Println(a) // 100
}

代码解释

  1. modify1 函数

    • 接受一个 int 类型的参数 x
    • 在函数内部,将 x 的值修改为 100
    • 打印函数内部的 x 值。
  2. modify2 函数

    • 接受一个 *int 类型的参数 x,即一个指向 int 的指针。
    • 在函数内部,通过解引用指针 *x 将指针所指向的值修改为 100
  3. main 函数

    • 声明一个 int 类型的变量 a,并赋值为 10
    • 调用 modify1(a),传递 a 的值。
    • 打印 a 的值,结果为 10,因为 modify1 中的修改只在函数内部生效,不会影响函数外部的 a
    • 调用 modify2(&a),传递 a 的指针。
    • 打印 a 的值,结果为 100,因为 modify2 中的修改通过指针影响了函数外部的 a

内存布局和传递方式

  • 值传递modify1 中,参数 xa 的一个副本。修改 x 不会影响到 a
  • 指针传递modify2 中,参数 xa 的地址。通过解引用指针 *x 可以直接修改 a 的值

示例输出

这里的值 100
10
100

解释:

  • modify1 内部打印的值是 100,但这只是 x 的副本,外部的 a 没有改变。
  • modify1 调用后,打印 a 的值仍然是 10
  • modify2 调用后,通过指针修改了 a 的值,打印 a 的值变为 100

总结

  • 在 Go 语言中,函数参数默认是值传递。这意味着传递给函数的是参数的副本,函数内部的修改不会影响到外部的变量。
  • 如果希望函数能修改外部变量,可以使用指针传递。在函数内部通过解引用指针,可以直接修改外部变量的值。

go 语言这么设计的目的:通过理解值传递和指针传递的不同,可以更好地控制函数对变量的修改行为,从而编写出更加高效和正确的代码。

为什么修改值需要解引用

  • 指针:存储变量的内存地址。
  • 解引用:通过指针访问或修改其所指向的变量的值。(这里要理解成:访问,而不要理解成:取值)
  • 为什么需要解引用:因为指针本身只包含地址,要访问或修改地址处的值,必须通过解引用操作。

指针需要分配内存才能够存储数据

在 Go 语言中:

  • 值类型,自动分配内存
  • 引用类型,手动分配内存, new 和 make

new 与 make 的区别

  1. 二者都是用来做内存分配的。
  2. make 只用于 slice、map 以及 channel 的初始化,返回的还是这三个引用类型本身;
  3. 而 new 用于类型的内存分配,并且内存对应的值为类型零值,返回的是指向类型的指针。

字符串数组 与 字符串切片

在 Go 语言中,[]string 表示一个字符串切片(slice)。切片是 Go 语言中的一种数据结构,它提供了对数组的动态大小的灵活支持。切片和数组之间的关键区别在于,数组的大小是固定的,而切片是动态的,可以根据需要增长或缩小。

切片和数组的区别

  1. 数组

    • 数组的大小是固定的,在声明时就必须指定。
    • 例如,[3]string 表示一个固定大小为 3 的字符串数组。
  2. 切片

    • 切片是基于数组的一个动态视图,可以动态增长和缩小。
    • 例如,[]string 表示一个字符串切片,其大小是动态的。

示例代码中的解释

在你的代码中,sliceMap 是一个 map,其键是字符串,值是字符串切片。具体代码如下:

package main

import "fmt"

func main() {
var sliceMap = make(map[string][]string, 3)
fmt.Println(sliceMap)
fmt.Println("after init")

key := "中国"
value, ok := sliceMap[key]
if !ok {
value = make([]string, 0, 2)
}
value = append(value, "北京", "上海")
sliceMap[key] = value

fmt.Println(sliceMap)
}

代码解释

  1. 初始化 sliceMap

    var sliceMap = make(map[string][]string, 3)
    • sliceMap 是一个 map,其键是字符串,值是字符串切片。
    • make(map[string][]string, 3) 创建了一个初始容量为 3 的 map。
  2. 检查键是否存在

    key := "中国"
    value, ok := sliceMap[key]
    if !ok {
    value = make([]string, 0, 2)
    }
    • key 是字符串 "中国"
    • value, ok := sliceMap[key] 检查 sliceMap 中是否存在键 "中国",如果不存在,okfalse
    • 如果键不存在,创建一个初始容量为 2 的字符串切片 value
  3. 修改切片并更新 map

    value = append(value, "北京", "上海")
    sliceMap[key] = value
    • 使用 append 函数将 "北京""上海" 添加到 value 切片中。
    • 更新 sliceMap 中键 "中国" 对应的值为新的 value 切片。

输出

  1. 初始化后的 sliceMap 是空的:

    map[]
  2. 添加键值对后的 sliceMap

    map[中国:[北京 上海]]

总结

在你的代码中,sliceMap 的值是字符串切片([]string),而不是数组。这是因为切片在 Go 语言中提供了更灵活和动态的数组操作方式。切片可以根据需要动态增长或缩小,而数组大小是固定的。

因此,虽然你在代码中使用了字符串数组的概念,但实际上你使用的是字符串切片,这是 Go 语言中的一种动态数组结构。