阅读 Effective Go 的一些摘要笔记

Introduction

Go语言的概念与其它语言如 Java, C# 等不同;直接将其它语言的程式逻辑翻译为 golang 不太可能获得满意结果。因此需要先理解 golang 的概念,以 golang 的思路去撰写 go 程式。

其它三篇相关介绍文章:

Example

golang 有丰富的 API 文档与范例,如 这里 。在线上就可以直接运行,也能在线上修改代码再试。

Formatting

使用 gofmt 指令可以自动编排源代码,变成规范的格式。例如:

type App struct {
  Id string `json:"id"` //编号
  Title string `json:"title"` //名称
}

var z=x >> 1+y << 1
fmt.Printf("x = %d, y = %d, z = %d\n",x,   y,z)

运行 gofmt -w src/github.com/.../xx.go 后,变成:

type App struct {
	Id    string `json:"id"`    //编号
	Title string `json:"title"` //名称
}

var z = x>>1 + y<<1
fmt.Printf("x = %d, y = %d, z = %d\n", x, y, z)

格式

  • Identation: 使用 TAB 缩进
  • Line length: 每行不限制字数;如果觉得某行太长,可以换行再加上一个TAB
  • Parentheses: Go使用较少的括号, if/for/switch的条件不需括号

Commentary

Go 使用 C-style 的注解形式,如:

  • block 区块 : /* ... */
  • line 单行 : // ...

结合 godoc 使用

  • 每个 package 都应有 comment
  • 在 top-level 宣告前的注解会被当作说明使用

Names

package 名称 为 小写的一个词,不需要 _mixedCaps。应该与源文件的最后一层目录名称相同。

Get,Set Getter 使用 首字母大写,不需要 ‘Get’ 前缀。 Setter 使用前缀 ‘Set’,如:

owner := obj.Owner()
if owner != user {
    obj.SetOwner(user)
}

MixedCaps 作为 Go 中的命名方式,不要用 _

Semicolons

Go 使用 分号 作为 statement终止符号。但是多半无需特别写出分号,因为lexer会自动加上。会加上分号的逻辑为: ‘if the newline comes after a token that could end a statement, insert a semicolon.’

因此,只有在 for 以及 单行多statements 的状况下才会使用到 ;

也因为如此,opening brace { 不可以放在下一行,而需要放在 (if, for, switch, or select) 后面。如果不这样,lexer 会自动加上分号,造成错误!

if i < f() {   // okay
	g()
}

if i < f()     // WRONG !!!
{              // WRONG !!!
	g()
}

Control Structures

for 有三种形式:

// Like a C for
for init; condition; post { }

// Like a C while
for condition { }

// Like a C for(;;)
for { }

Type switch 也可以依照型別來 switch

var t interface{}
t = functionOfSomeType()
switch t := t.(type) {
default:
    fmt.Printf("unexpected type %T\n", t)     // %T prints whatever type t has
case bool:
    fmt.Printf("boolean %t\n", t)             // t has type bool
case int:
    fmt.Printf("integer %d\n", t)             // t has type int
case *bool:
    fmt.Printf("pointer to boolean %t\n", *t) // t has type *bool
}

Functions

可以回传多个值

func (file *File) Write(b []byte) (n int, err error)

回传值可以宣告名称,这种状况下,回传的变数会被初始化为 该类型的zero value

func nextInt(b []byte, pos int) (value, nextPos int) {

method : 可以在 Type 上附加方法,方式如下:

type Car struct {
	Brand string
	Speed int
}

func (x Car) Running(hour int) (distance int) {
  fmt.Printf("Car %s, speed=%d, run for %d hours.\n", x.Brand, x.Speed, hour)
  distance = hour * x.Speed
  return
}

defer : 延迟执行。加上 defer 关键字的 statement 会被放到该 function 最后运行,采用 LIFO 的顺序。因此下面的程序会打印出 4 3 2 1 0

for i := 0; i < 5; i++ {
    defer fmt.Printf("%d ", i)
}

Data

new make

Slice 是将 array 包装起来使用的方式;因为array固定长度,而slice提供了弹性的方式使用array,golang中大部分的数组操作使用 slice。

  • slice 的宣告: []T , array 的宣告: [n]T (array必须指定大小)
  • slice 传递入func是使用 value, 而非 reference

下面的程式使用一个 func Append 操作 slice 的内容;在呼叫 function Append(…) 后,function 内的 slice 地址不同。而经过 make 取得的新记忆体,地址也不同 ( &arr[0] )。

package main

import (
	"fmt"
	"unsafe"
)

func Append(slice, data []byte) []byte {
  l := len(slice)
  if l + len(data) > cap(slice) {
    newSlice := make([]byte, (l + len(data)) * 2)
    copy(newSlice, slice)
    showInfo("in func, make", &newSlice)
    slice = newSlice
    showInfo("in func, assigned", &slice)
  }
  
  slice = slice[0:l+len(data)]
  showInfo("in func, cut [0:x]", &slice)
  for i, c := range data {
    slice[l+i] = c
  }
  return slice
}

func showInfo(naming string, data *[]byte) {
  l := len(*data)
  c := cap(*data)
  addr := unsafe.Pointer(data)
  fmt.Printf("[%-20v] slice: len=%d, cap=%d, addr=%v arr[0]=%v, data = %v\n", naming, l, c, addr, &(*data)[0], *data)
}

func main() {
  data := []byte {60, 61, 62}
  data1 := []byte {63}

  showInfo("in main, pre-append", &data)
  data = Append(data, data1)
  showInfo("in main, post-append", &data)
}

打印出来的结果如:

[in main, pre-append ] slice: len=3, cap=3, addr=0xc82000e240 arr[0]=0xc82000a340, data = [60 61 62]
[in func, make       ] slice: len=8, cap=8, addr=0xc82000e2c0 arr[0]=0xc82000a3c0, data = [60 61 62 0 0 0 0 0]
[in func, assigned   ] slice: len=8, cap=8, addr=0xc82000e2a0 arr[0]=0xc82000a3c0, data = [60 61 62 0 0 0 0 0]
[in func, cut [0:x]  ] slice: len=4, cap=8, addr=0xc82000e2a0 arr[0]=0xc82000a3c0, data = [60 61 62 0]
[in main, post-append] slice: len=4, cap=8, addr=0xc82000e240 arr[0]=0xc82000a3c0, data = [60 61 62 63]

Two-dimensional slices 宣告方式如下

type Transform [3][3]float64  // A 3x3 array, really an array of arrays.
type LinesOfText [][]byte     // A slice of byte slices.