Slice、Struct、Map

1991/6/26 规范

# 1. 参数和返回(slice & map)

slicemap 包含了指向底层数据的指针,因此当在函数中使用他们作为参数和返回值时,需要特别注意。

# 1.1 作为参数

# a. 反面示例

type User struct {
 Likes []string
}

func (u *User) SetLikes(likes []string) {
 u.Likes = likes
}
func TestRun(t *testing.T) {
 like := []string{"篮球", "旅游", "听歌"}
 user := new(User)
  // 切片作为参数传递
 user.SetLikes(like)
 fmt.Println("user Like1: ", user.Likes)
 // 这里修改变量,会影响到user对象中的属性
 like[1] = "学习"
 fmt.Println("user Like2 : ", user.Likes)
}
/**输出
=== RUN   TestRun
user Like1:  [篮球 旅游 听歌]
user Like2 :  [篮球 学习 听歌]
--- PASS: TestRun (0.00s)
PASS
*/
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# b. 推荐写法

// 只需要修改这个方法,其他不变
func (u *User) SetLikes(likes []string) {
 u.Likes = make([]string, len(likes))
 // 这里使用了复制
 copy(u.Likes, likes)
}
/**输出
=== RUN   TestRun
user Like1:  [篮球 旅游 听歌]
user Like2 :  [篮球 旅游 听歌]
--- PASS: TestRun (0.00s)
PASS
*/
1
2
3
4
5
6
7
8
9
10
11
12
13

# 1. 2 作为返回值

# a. 反面示例

type Stats struct {
  mu sync.Mutex
  counters map[string]int
}
// Snapshot 返回当前状态。
func (s *Stats) Snapshot() map[string]int {
  s.mu.Lock()
  defer s.mu.Unlock()
  // 返回的对象中的属性
  return s.counters
}
// snapshot 不再受互斥锁保护
// 因此对 snapshot 的任何访问都将受到数据竞争的影响
// 影响 stats.counters
snapshot := stats.Snapshot()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# b. 推荐写法

type Stats struct {
  mu sync.Mutex
  counters map[string]int
}

func (s *Stats) Snapshot() map[string]int {
  s.mu.Lock()
  defer s.mu.Unlock()
  // 这里重新定义一个变量,复制结果返回
  result := make(map[string]int, len(s.counters))
  for k, v := range s.counters {
    result[k] = v
  }
  return result
}

// snapshot 现在是一个拷贝
snapshot := stats.Snapshot()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 2.结构体嵌入

嵌入的类型,应放在结构体内字段列表的顶部,并且必须有一个空行将嵌入式字段与常规字段分隔开。

# 2.1 反面示例

type Client struct {
  version int
  http.Client
}
1
2
3
4

# 2.2 推荐写法

// 1.嵌入类型在顶部 
// 2.空行将嵌入式字段与常规字段分隔开
type Client struct {
  http.Client

  version int
}
1
2
3
4
5
6
7

# 3. 初始化结构体

# 3.1 使用字段名初始化

// 反面示例
k := User{"John", "Doe", true}

// 推荐写法
k := User{
    FirstName: "John",
    LastName: "Doe",
    Admin: true,
}
1
2
3
4
5
6
7
8
9

# 3.2 对零值结构使用 var

如果在声明中省略了结构的所有字段,请使用 var 声明结构。

// 反面示例
user := User{}

// 推荐
var user User
1
2
3
4
5

# 3.3 初始化Struct 引用

在初始化结构引用时,请使用&T{}代替new(T),以使其与结构体初始化一致。

// 反面示例
sval := T{Name: "foo"}

// inconsistent
sptr := new(T)
sptr.Name = "bar"

// 推荐
sval := T{Name: "foo"}
sptr := &T{Name: "bar"}
1
2
3
4
5
6
7
8
9
10

# 4. nil 是一个有效的slice

# 4.1 当返回长度为零的切片时

// 反面示例
if x == "" {
  return []int{}
}

// 推荐写法
if x == "" {
  return nil
}
1
2
3
4
5
6
7
8
9

# 4.2 检查切片是否为空

要检查切片是否为空,请始终使用len(s) == 0。而非 nil

// 反面示例
func isEmpty(s []string) bool {
  return s == nil
}

// 推荐示例
func isEmpty(s []string) bool {
  return len(s) == 0
}
1
2
3
4
5
6
7
8
9

# 4.3 零值切片

零值切片(用var声明的切片)可立即使用,无需调用make()创建。

# a. 反面示例

nums := []int{}
// or, nums := make([]int)

if add1 {
  nums = append(nums, 1)
}

if add2 {
  nums = append(nums, 2)
}
1
2
3
4
5
6
7
8
9
10

# b. 推荐写法

var nums []int

if add1 {
  nums = append(nums, 1)
}

if add2 {
  nums = append(nums, 2)
}
1
2
3
4
5
6
7
8
9