Go Overview

Go 的源文件以 .go 为后缀名存储在计算机中,这些文件名均由小写字母组成,如 scanner.go 。如果文件名由多个部分组成,则使用下划线 _ 对它们进行分隔,如 scanner_test.go 。文件名不包含空格或其他特殊字符。

_ 本身就是一个特殊的标识符,被称为空白标识符。它可以像其他标识符那样用于变量的声明或赋值(任何类型都可以赋值给它),但任何赋给这个标识符的值都将被抛弃,因此这些值不能在后续的代码中使用,也不可以使用这个标识符作为变量对其它变量进行赋值或运算。

var arr = []int{1, 2, 3, 4, 5}
 
for _, value := range arr {
    fmt.Printf(value)
} 

当标识符(包括常量、变量、类型、函数名、结构字段等等)以一个大写字母开头,如:Printf,那么使用这种形式的标识符的对象就可以被外部包的代码所使用(客户端程序需要先导入这个包),这被称为导出(像面向对象语言中的 public);标识符如果以小写字母开头,则对包外是不可见的,但是它们在整个包的内部是可见并且可用的(像面向对象语言中的 private )。


一个函数可以拥有多返回值,返回类型之间需要使用逗号分割,并使用小括号 () 将它们括起来:

func functionName (a int, b string) (r1 int, r2 bool) {
    // ...
}

这种多返回值一般用于判断某个函数是否执行成功 (true/false) 或与其它返回值一同返回错误消息(详见之后的并行赋值)。


干净、可读的代码和简洁性是 Go 追求的主要目标。通过 gofmt 来强制实现统一的代码风格。Go 语言中对象的命名也应该是简洁且有意义的。像 Java 和 Python 中那样使用混合着大小写和下划线的冗长的名称会严重降低代码的可读性。名称不需要指出自己所属的包,因为在调用的时候会使用包名作为限定符。返回某个对象的函数或方法的名称一般都是使用名词,没有 Get... 之类的字符,如果是用于修改某个对象,则使用 SetName()。有必须要的话可以使用大小写混合的方式,如 MixedCaps() 或 mixedCaps(),而不是使用下划线来分割多个名称。


iota 是一个预定义的标识符,用于枚举常量,初始值是0。每当 iota 在新的一行被使用时,它的值都会自动加 1,并且没有赋值的常量默认会应用上一行的赋值表达式:

const (
	a = iota  // a = 0
	b         // b = 1
	c         // c = 2
	d = 5     // d = 5   
	e         // e = 5
)

init()函数会在包完成初始化后自动执行,执行优先级比main()函数高。


字符串拼接:

  • +
  • strings.Join()
  • bytes.Buffer
var buffer bytes.Buffer
for {
    //method getNextString() not shown here
	if s, ok := getNextString(); ok { 
		buffer.WriteString(s)
	} else {
		break
	}
}
fmt.Println(buffer.String())

时间格式化 1月2号下午3点4分过5秒,2006年,-7时区

cstZone := time.FixedZone("CST", 8*3600)
t := time.Now().In(cstZone)
// 21 Jun 2023 10:45 +08
fmt.Println(t.Format("02 Jan 2006 15:04 -07"))

指针

a := 1
b := &a
// 0xc0000a4010
fmt.Println(&a)
// 0xc0000a4010
fmt.Println(b)
// 1
fmt.Println(*b)

指针的一个高级应用是你可以传递一个变量的引用(如函数的参数),这样不会传递变量的拷贝。指针传递是很廉价的,只占用 4 个或 8 个字节。当程序在工作中需要占用大量的内存,或很多变量,或者两者都有,使用指针会减少内存占用和提高效率。被指向的变量也保存在内存中,直到没有任何指针指向它们,所以从它们被创建开始就具有相互独立的生命周期。


Prefer:

// 代码更清晰,更加容易读懂
func getX2AndX3_2(input int) (x2 int, x3 int) {
    x2 = 2 * input
    x3 = 3 * input
    return
}
 
// or 
func getX2AndX3_3(input int) (int, int) {
    x2 := 2 * input
    x3 := 3 * input
    return x2, x3
}

Rather than:

func getX2AndX3(input int) (int, int) {
    return 2 * input, 3 * input
}

defer

Like finally in Java, run after the return

func main() {
    // Output:
    // aaa
    // ccc
    // bbb
	function1()
}
 
func function1() {
	fmt.Printf("aaa\n")
	defer function2()
	fmt.Printf("ccc\n")
}
 
func function2() {
	fmt.Printf("bbb\n")
}
func func1(s string) (n int, err error) {
	defer func() {
		log.Printf("func1(%q) = %d, %v", s, n, err)
	}()
	return 7, io.EOF
}
 
func main() {
    // Output: 2011/10/04 10:46:11 func1("Go") = 7, EOF
	func1("Go")
}

闭包

 
func main() {
    file := MakeAddSuffix(".bmp")("file")
    // file.bmp
    fmt.Printf(file)
}
 
func MakeAddSuffix(suffix string) func(string) string {
    return func(name string) string {
        if !strings.HasSuffix(name, suffix) {
            return name + suffix
        }
        return name
    }
}

Slice

切片 (slice) 是对数组一个连续片段的引用。 使用切片作为函数的传入参数,减少拷贝,降低内存占用。

切片的初始化格式是:var slice1 []type = arr1[start:end]

func sum(a []int) int {
	s := 0
	for _, v := range a {
    	s += v
	}
	return s
}
 
func main() {
	var arr = [5]int{0, 1, 2, 3, 4}
	// array to slice
	sum(arr[:])
}

Using make to create a slice when the array has not inited:

// 5: slice length; 10: array capacity
var slice := make([]int, 5, 10)

s := "hello"
c := []byte(s)
c[0] = 'c'
s2 := string(c) // s2 == "cello"

map

Map doesn’t have locking mechanism so it is not thread-safe!

map1 := make(map[string]float32, 10)
map1["key1"] = 4.5
map1["key2"] = 3.14
fmt.Printf(map1["key1"])
 
// init map
noteFrequency := map[string]float32 {
	"C0": 16.35, "D0": 18.35, "E0": 20.60, "F0": 21.83,
	"G0": 24.50, "A0": 27.50, "B0": 30.87, "A4": 440}

Check if a key is in the map:

_, ok := map1[key1]
 
// Or with if 
if _, ok := map1[key1]; ok {
    // ...
}

Delete key-value from the map:

// if doesn't contains key1, will not throw an error
delete(map1, key1)

Get a slice with map type:

package main
import "fmt"
 
func main() {
	// Version A:
	items := make([]map[int]int, 5)
	for i := range items {
		items[i] = make(map[int]int, 1)
		items[i][1] = 2
	}
	fmt.Printf("Version A: Value of items: %v\n", items)
 
	// Version B: NOT GOOD!
	items2 := make([]map[int]int, 5)
	for _, item := range items2 {
		item = make(map[int]int, 1) // item is only a copy of the slice element.
		item[1] = 2 // This 'item' will be lost on the next iteration.
	}
	fmt.Printf("Version B: Value of items: %v\n", items2)
}

type

type struct1 struct {
    i1 int
    f1 float42
    str string
}
 
func main() {
    ms := new(struct1)
    ms.i1 = 10
    ms.f1 = 15.5
    ms.str = "Chris"
    // Or
    ms2 := &struct1{10, 15.5, "Chris"}
    // Or
    var ms3 struct1
    ms3 = struct1{10, 15.5, "Chris"}
 
    // &{10 15.5 Chris}
    fmt.Println(ms)
}

Extends in Go:

type A struct {
    ax, ay int
}
 
func (p *A) Sum() int {
    return p.ax + pa.ay
} 
 
type B struct {
    A
    bx, by float32
}
func main() {
    b := B{A{1, 2}, 3.0, 4.0}
    // function Sum() promotes from type A to type B
    b.Sum()
}

Factory

Names of factories start with new or New

type File struct {
    fd int
    name string
}
 
func NewFile(fd int, name string) *File {
    if fd < 0 {
        return nil
    }
    return &File(fd, name)
}

method

method is a kind of function. It has an extra receiver config, like this in Java or other OOP languages.

type TwoInts struct {
    a int
    b int
}
 
func main() {
    two1 := &TwoInts{12, 10}
    fmt.Printf(two1.AddThem(5))
}
 
// method
func (tn *TwoInts) AddThem(param int) int {
    return tn.a + tn.b + param
}

Note

函数将变量作为参数:Function1(recv) 方法在变量上被调用:recv.Method1()

鉴于性能的原因,recv 最常见的是一个指向 receiver_type 的指针. 如果想要方法改变接收者的数据,就在接收者的指针类型上定义该方法;否则,就在普通的值类型上定义方法。

interface

type Namer interface {
    Method1(param_list) return_type
    Method2(param_list) return_type
}

只包含一个方法的接口的名字由方法名加 er 后缀组成,例如 PrinterReaderWriterLoggerConverter 等等。还有一些不常用的方式(当后缀 er 不合适时),比如 Recoverable,此时接口名以 able 结尾,或者以 I 开头。

Go 不需要显式地声明类型是否满足某个接口。

type Shaper interface {
    Area() float32
}
 
type Square struct {
    side float32
}
 
func (sq *Square) Area() float32 {
    return sq.side * sq.side
}
 
func main() {
    sq1 := new(Square)
    sq1.side = 5
 
    var areaIntf Shaper    
    areaIntf = sq1
    // or areaInft := Shaper(sq1)
    // or even areaInft := sq1
    
    fmt.Printf("The square has area: %f\n", areaIntf.Area())
}

类型断言,检测接口变量的具体类型:

if v, ok := varI.(T); ok {
    Process(v)
    return
}

或是使用 type-switch:

switch t := areaIntf.(type) {
case *Square: 
    fmt.Printf("Type Square %T with value %v\n", t, t)
case *Circle: 
    fmt.Printf("Type Circle %T with value %v\n", t, t)
case nil: 
    fmt.Printf("nil value: nothing to check?\n")
default: 
    fmt.Printf("Unexpected type %T\n", t)
}

读取用户输入

// 从控制台读取输入:
package main
import "fmt"
 
var (
   firstName, lastName, s string
   i int
   f float32
   input = "56.12 / 5212 / Go"
   format = "%f / %d / %s"
)
 
func main() {
   fmt.Println("Please enter your full name: ")
   fmt.Scanln(&firstName, &lastName)
   // fmt.Scanf("%s %s", &firstName, &lastName)
   fmt.Printf("Hi %s %s!\n", firstName, lastName) // Hi Chris Naegels
   fmt.Sscanf(input, format, &f, &i, &s)
   fmt.Println("From the string we read: ", f, i, s)
    // 输出结果: From the string we read: 56.12 5212 Go
}

panic & recover

// panic_recover.go
package main
 
import (
	"fmt"
)
 
func badCall() {
	panic("bad end")
}
 
func test() {
	defer func() {
		if e := recover(); e != nil {
			fmt.Printf("Panicing %s\r\n", e)
		}
	}()
	badCall()
	fmt.Printf("After bad call\r\n") // <-- would not reach
}
 
// Calling test
// Panicing bad end
// Test completed
func main() {
	fmt.Printf("Calling test\r\n")
	test()
	fmt.Printf("Test completed\r\n")
}