Go 入门
学习官网的 Go 教程。
Packages, Variable and Function
Function
Declaration
Use func to declare a function. Note that all semicolons can be leaved out inside Go.
func add(x int, y int) int {
return x + y
}Leave Out Parameter Type
We can shorten parameters if they have the same type:
function add(x, y int) int {
return x + y
}Multiple Return Values
A function can return any number of results.
function swap(x, y string) (string, string) {
return y, x
}Named Return Values
The return statement can be written without arguments. It’s known as a “naked” return.
// there're return variables before return value type
func split(sum int) (x, y int) {
x = sum * 4 / 9
y = sum - x
return
}Naked return can harm readability in long functions.
Variable
The var statement declares variables.
var a = 15
var b, c int
// var i, j int = 1, 2
fmt.Printf("a = %d, b = %d, c = %d", a, b, c) // a = 15, b = 0, c = 0If an initializer is present, the type can be omitted. The variable will take the type of the initializer.
Short Variable declarations
We can use := in place of a var declaration.
Short assignment statement only can be used inside a function. Outside a function, every statement must begin with a keyword, such as var, func and so on.
func main() {
x := 3
}Basic Types
Go’s basic types are
bool
string
int8 // range is [-2**7, 2**7-1]
int16 // range is [-2**15, 2**15-1]
int32 // range is [-2**31, 2**31-1]
int64 // range is [-2**63, 2**63-1]
uint8 // range is [0, 2**8-1]
uint16 // range is [0, 2**16-1]
uint32 // range is [0, 2**32-1]
uint64 // range is [0, 2**64-1]
byte // alias for uint8
rune // alias for int32
int // int64 on 64-bit systems, or int32 on 32-bit systems
uint // uint64 on 64-bit systems, or uint32 on 32-bit systems
uintptr // the same as uint
float32
float64
complex64
complex128We can use fmt.Print to print values:
package main
import "fmt"
func main() {
var a rune = -1 << 31
fmt.Println("a =", a) // a = -2147483648
fmt.Printf("Type: %T Value: %v\n", a, a) // Type: int32 Value: -2147483648
}Zero Value
Variable declared without a explicit initial value will be given/assigned a zero value.
Zero values is:
0for numeric typesfalsefor the boolean type""for strings
Type Conversion
The expression T(v) converts the value v to the type T.
var i int = 42
var f float32 = float32(i)Type Inference
For variables without specifying an explicit type (untyped variables), their type is inferred from the value on the right hand side.
The default numeric type will be int, float64 or complex128, depending on the precision of the constant.
package main
import "fmt"
func main() {
g := 0.867 + 0.5i
fmt.Printf("type of g is %T", g) // complex128
}Constant
Constants are declared like variables, but with the const keyword.
Constants can be character (byte?), string, boolean, or numeric values.
const PI = 3.14Flow Control statements: for, if else, switch and defer
For
Go has only one looping construct, the for loop.
Unlike other languages like C, Java or JavaScript, there are no parentheses surrounding the 3 components of the for statement.
package main
import "fmt"
func main() {
for i := 0; i < 10; i++ {
fmt.Printf("i = %v\n", i)
}
}”for” is Go’s “while”
If you wanna use while, you can just use for.
package main
import "fmt"
func main() {
i := 0
for i < 10 {
fmt.Printf("i = %d\n", i)
i++
}
}And If you wanna use while (true), just drop/omit the loop condition.
for {}If
if statements are like for. The parentheses are not needed but the braces are required.
package main
import "fmt"
func main() {
i := 1
if i > 0 {
fmt.Print("i is positive")
} else {
fmt.Print("i is negative")
}
}If with a short declaration
Like for, the if statement can start with a variable declaration.
And, variables declared inside an if statement, are also available inside else blocks.
package main
import "fmt"
func main() {
if i := -1; i > 0 {
fmt.Print("i is positive")
} else {
fmt.Print("i is negative")
}
}Switch
A switch statement is a shorter way to write if else statements.
Go’s switch is like the one in C, Java or JavaScript, except break, which is redundant in Go. switch will stop immediately when a case is matched.
package main
import "fmt"
func main() {
switch i := 0; i {
case 0:
fmt.Println("i is 0")
i++
case 1:
fmt.Println("i is 1")
i++
default:
fmt.Println("not matched")
}
}Switch with No Condition
Yes, switch can run without a condition, just like if. That means switch true, which can be a very clean way to write long if-else chains.
package main
import "fmt"
func main() {
// or switch i := 0; {}
i := 0
switch {
case i >= 0:
fmt.Println("i >= 0")
case i < 0:
fmt.Println("i < 0")
}
}Defer
defer is a very new concept. Note that defer only can be used in front of a function call.
defer is to defer the execution of a function. So you can’t defer a declaration like i := 1, or a calculation statement like i += 1.
A deferred funtion call is not executed until the surrounding function returns.
package main
import "fmt"
func main() {
defer fmt.Print("world")
fmt.Print("Hello ")
}Stacking Defer
Deferred function calls are pushed onto a stack.
package main
import "fmt"
func main() {
fmt.Println("counting")
for i := 0; i < 10; i++ {
defer fmt.Print(i)
}
fmt.Println("done")
// counting
// done
// 9876543210
}More Types: Structs, Slices and Maps
Pointers
A pointer holds the memory address of a value. The pointer’s type is *T for a T value. And it’s zero value is nil.
Use var p *T to declare a pointer.
Once the initializer is present at declaration, the type can be omitted just like basic-type variables.
package main
import "fmt"
func main() {
var p *int
fmt.Printf("type is %T, value is %v", p, p) // type is *int, value is <nil>
}Pointers must be initialized before using. Use & to get the address of its operand as a pointer’s initial value.
package main
import "fmt"
func main() {
i := 3
p := &i
fmt.Printf("type is %T, value is %v", p, p) // type is *int, value is 0xc00000a0e8
}The * operator denotes the pointer’s underlying value. This is known as dereferencing.
package main
import "fmt"
func main() {
i := 3
p := &i
*p = 5
fmt.Printf("underlying value is %v", *p) // underlying value is 5
}Unlike C, Go has no poiner arithmetic.
Structs
You can think of
structasobjectin JavaScript. Struct is item-ordered, unlike map, which we’ll learn later.
Defining Struct starts with a type.
package main
import "fmt"
type Vertex struct {
X int
Y int
}
func main() {
v := Vertex{3, 5}
fmt.Printf("type is %T, value is %v", v, v) // type is main.Vertex, value is {3 5}
}By convention, struct name and fields name both are in capicals in Go.
Access Fields
Use a dot to access struct fields.
package main
import "fmt"
type Vertex struct {
X int
Y int
}
func main() {
v := Vertex{3, 5}
v.Y = 10
fmt.Print(v) // {3 10}
}Note that a struct initializer by a struct variable, will be a deep copy (not shallow copy).
package main
import "fmt"
type Vertex struct {
X int
Y int
}
func main() {
v := Vertex{3, 5}
v.Y = 10
v2 := v
v2.X = 9
fmt.Print(v, v2) // {3 10} {9 10}
}Pointers to Structs
If there’s a p pointer which point to a struct, we can use (*p).X to access struct fields.
But, the language permits us instead to write just p.X, without the explicit dereference.
package main
import "fmt"
type Vertex struct {
X int
Y int
}
func main() {
v := &Vertex{3, 4}
fmt.Print(v.X, (*v).Y) // 3 4
}Struct Literals
Use struct literals to initial structs.
Fields value can be filled in order, or by the name: value syntax.
package main
import "fmt"
type Vertex struct {
X int
Y int
}
func main() {
v := &Vertex{3, 4}
v2 := &Vertex{Y: 3}
fmt.Print(v, v2) // &{3 4} &{0 3}
}Arrays
Array’s type is [n]T. n is length of the array, which is cannot be omitted.
package main
import "fmt"
func main() {
var a1 [3]int
a1[0] = 1
// array literal
a2 := [4]int{2, 4, 6}
fmt.Print(a1, a2) // [1 0 0] [2 4 6 0]
}An array’s length is part of its type, so arrays can’t be resized.
Slices
In practice, slices are much common than arrays. A slice is a dynamically-sized, flexible view into the elements of an array. The zero value is nil.
A slice is formed by specifying two indices, separated by a colon.
The low bound is included, whereas the high bound is not.
package main
import "fmt"
func main() {
a := [5]int{1, 3, 5, 7, 9}
s := a[1:4]
s[0] = 100
fmt.Print(s, a) // [100 5 7] [1 100 5 7 9]
}Slice Literals
Slice literals will create the same array, then build a slice that references it.
// left type declaration can be omitted
var a1 []int = []int{1, 3, 5}A slice does not store any data, it just describes a section of an underlying array.
Multiple slices that share the same underlying array, will see the same changes.
package main
import "fmt"
func main() {
var a1 = []int{1, 3, 5}
a2 := a1
a2[2] = 7
fmt.Printf("value is %v, type is %T", a1, a1) // value is [1 3 7], type is []int
}Slice Defaults
Slice indices can be omitted, and the default values will be used. Zero is for the low bound, and the length of the slice (or array) for the high bound.
package main
import "fmt"
func main() {
a := [4]int{1, 3, 5, 7}
s1 := a[:]
s2 := s1[:3]
s3 := s2[1:]
fmt.Printf("value is %v, type is %T", s3, s3) // value is [3 5], type is []int
}Slice Length and Capacity
A slice has both a length and a capacity.
The length of a slice is the number of elements it contains.
The capacity of a slice is the number of elements in the underlying array, counting from the first element in the slice.
use len() and cap() functions.
package main
import "fmt"
func main() {
a := [4]int{1, 3, 5, 7}
s1 := a[:]
s2 := s1[:3]
s3 := s2[1:]
s4 := s3[0:cap(s3)]
fmt.Println(len(s1), cap(s1), s1) // 4 4 [1 3 5 7]
fmt.Println(len(s2), cap(s2), s2) // 3 4 [1 3 5]
fmt.Println(len(s3), cap(s3), s3) // 2 3 [3 5]
fmt.Println(len(s4), cap(s4), s4) // 3 3 [3 5 7]
}Don’t worry about length or capacity these concepts. They matter just a little.
Slices of Slices
Slices can contain any type, including other slices.
package main
import (
"fmt"
"strings"
)
func main() {
board := [][]string{
{"1", "2", "3"},
{"4", "5", "6"},
// note that this statement must end with a comma
{"7", "8", "9"},
}
board[1][1] = "*"
for i := 0; i < len(board); i++ {
// func strings.Join(elems []string, sep string) string
fmt.Printf("line%d: [%s]\n", i+1, strings.Join(board[i], ", "))
// line1: [1, 2, 3]
// line1: [4, *, 6]
// line1: [7, 8, 9]
}
}make
The make function allocates a zeroed array and returns a slice.
func make(sliceType, length, capacity) TypeThe third argument capacity is optional.
package main
import "fmt"
func main() {
s := make([]int, 5)
fmt.Printf("value is %v, type is %T", s, s) // value is [0 0 0 0 0], type is []int
}append
The append function is used for appending new elements to a slice.
func append(slice []Type, elems ...Type) []Typeappend will point to a newly allocated array, if the original underlying array is too small.
package main
import (
"fmt"
)
func main() {
s := make([]int, 3)
fmt.Println(len(s), cap(s)) // 3 3
s2 := append(s, 4, 5)
s[0] = 1
fmt.Print(s, s2) // [1 0 0] [0 0 0 4 5]
}Otherwise, append won’t allocate memory if the capacity is sufficient.
package main
import (
"fmt"
)
func main() {
s := make([]int, 3, 5)
fmt.Println(len(s), cap(s)) // 3 5
s2 := append(s, 4, 5)
s[0] = 1
fmt.Print(s, s2) // [1 0 0] [1 0 0 4 5]
}Map
A map maps keys to values.
map has fixed types of keys and values. We can use make to create a map.
Use [key] to get or set an element.
package main
import "fmt"
type Vertex struct {
X int
Y string
}
func main() {
a := make(map[string]Vertex)
a["k"] = Vertex{0, "3"}
a["key2"] = Vertex{9, "8"}
fmt.Print(a) // map[k:{0 3} key2:{9 8}]
}Map Literals
Map literals just like struct literals.
package main
import "fmt"
type Vertex struct {
X int
Y string
}
func main() {
var a = map[string]Vertex{
"k": {0, "3"},
}
a["key2"] = Vertex{9, "8"}
fmt.Print(a) // map[k:{0 3} key2:{9 8}]
}Note that a map’s zero value is
nil. And anilmap cannot add any key.
Two-value Assignment
Test if a key is present with a two-value assignment.
ele, ok := m[key]ok is a boolean value.
Delete one Element
Use delete function to remove one item.
package main
import "fmt"
type Vertex struct {
X int
Y string
}
func main() {
m := make(map[string]Vertex)
m["a"] = Vertex{2, "3"}
m["b"] = Vertex{10, "15"}
delete(m, "b")
fmt.Print(m)
}Range
range is used in a for loop.
Two values are returned for each iteration when ranging. The key and the value will be returned over a map, and the index and a copy of the element will over a slice.
package main
import "fmt"
type Vertex struct {
X int
Y string
}
func main() {
s := []string{"hello", "world"}
for i, v := range s {
fmt.Println(i, v)
// 0 hello
// 1 world
}
m := make(map[string]Vertex)
m["a"] = Vertex{2, "3"}
m["b"] = Vertex{10, "15"}
for k, v := range m {
fmt.Println(k, v)
// a {2 3}
// b {10 15}
}
}If you wanna skip one item, omit the second variable or use the _ notation.
for i := range s {}
for i, _ := range s {}_ cannot be used as value or type.
Function as Value
Functions are values too. They can be passed around just like other values.
package main
import "fmt"
func main() {
myFn := withLogging(add)
myFn(3, 4)
}
func add(x, y int) int {
return x + y
}
func withLogging(f func(int, int) int) func(int, int) int {
return func(i1, i2 int) (r int) {
fmt.Println("params are", i1, i2) // params are 3 4
r = f(i1, i2)
fmt.Println("result is", r) // result is 7
return
}
}Function Closures
A closure is a function references variables from outside its body.
package main
import "fmt"
func main() {
add := adder()
add(3)
add(5)
fmt.Print(add(2)) // 10
}
func adder() func(int) int {
sum := 0
return func(x int) int {
sum += x
return sum
}
}Methods and Interfaces
Methods
Go does not have classes. But we can still define methods.
A method is a function with a special receiver argument, which is between the func keyword and the function name.
package main
import (
"fmt"
"math"
)
type Vertex struct {
X float64
Y float64
}
func (v Vertex) getDistance() float64 {
return math.Sqrt((v.X*v.X + v.Y*v.Y))
}
func main() {
d := Vertex{3, 4}.getDistance()
fmt.Print(d) // 5
}Receiver Type
The receiver of methods must be a local type. For instance, you can declare a myInt as int.
package main
import (
"fmt"
)
type myInt int
func (v *myInt) add(a myInt) {
*v = *v + a
}
func main() {
a := myInt(5)
a.add(6)
fmt.Print(a) // 11
}Pointer Receivers
Pointer Receivers are more common than value receivers, since methods often need to modify their receiver.
Refer to the example above.
Note that if you use pointer arguments in a function instead of a method, the & is required.
package main
import (
"fmt"
)
type myInt int
func (v *myInt) add(a myInt) {
*v = *v + a
}
func add(v *myInt, a myInt) {
*v = *v + a
}
func main() {
a := myInt(5)
// & cannot be omitted
add(&a, 6)
b := myInt(6)
p := &b
// totally the same as (*p).add
p.add(7)
fmt.Print(a, b, *p) // 11 13 13
}Interfaces
An interface type is defined as a set of method signatures.
Interfaces are implemented implicitly. There is no implements keyword in Go to implement an interface explicitly.
A type implements an interface by implementing its methods.
package main
import "fmt"
type Animal interface{ Speak() }
type Dog struct{ Name string }
type Cat struct{ Name string }
func (d Dog) Speak() { fmt.Println(d.Name, "says: Woof!") }
func (c Cat) Speak() { fmt.Println(c.Name, "says: Meow!") }
func sayHi(a Animal) {
fmt.Print("Hi. ")
a.Speak()
}
func main() {
wangcai := Dog{"Wangcai"}
lele := Cat{"Lele"}
var jack Animal
sayHi(wangcai) // Hi. Wangcai says: Woof!
sayHi(lele) // Hi. Lele says: Meow!
// panic: runtime error
sayHi(jack)
}Null Pointer
Note that in the code above, jack is a null pointer which is not a concrete value. The method will be called with a nil receiver.
In this case, we can determine if the value is nil before it’s been used.
func sayHi(a Animal) {
if a == nil {
return
}
}Empty Interface
One that specifies zero methods is known as the empty interface.
interface{}
func describe(i interface{}) {
fmt.Printf("(%v, %T)\n", i, i)
}Type Assertions
Use v.(T) to assert a variable’s type. v must be an interface value.
It will return two values, an underlying value and a boolean which denotes whether the assertion succeeded.
If the second return value is
falsewhile it’s omitted, the program will be panic. So, do not omitokwhen using type assertions.
package main
import "fmt"
type Animal interface{ Speak() }
type Dog struct{ Name string }
type Cat struct{ Name string }
func (d Dog) Speak() { fmt.Println(d.Name, "says: Woof!") }
func (c Cat) Speak() { fmt.Println(c.Name, "says: Meow!") }
func sayHi(a Animal) {
t, ok := a.(Cat)
if !ok {
fmt.Print("oh, not a cat? just shut up")
return
}
fmt.Print("Hi, I'm a cat. ")
t.Speak()
}
func main() {
wangcai := Dog{"Wangcai"}
sayHi(wangcai) // oh, not a cat? just shut up
}Type Switches
A type switch is like a regular switch statement, but in a type siwtch the cases specify types (not values).
The format is as below.
package main
import "fmt"
type Animal interface{ Speak() }
type Dog struct{ Name string }
type Cat struct{ Name string }
func (d Dog) Speak() { fmt.Println(d.Name, "says: Woof!") }
func (c Cat) Speak() { fmt.Println(c.Name, "says: Meow!") }
func main() {
var wangcai Animal = Dog{"Wangcai"}
switch v := wangcai.(type) {
case Dog:
fmt.Print("Dog ", v.Name)
case Cat:
fmt.Print("Cat ", v.Name)
}
}Stringers
One of the most ubiquitous interfaces is the Stringer, defined by the fmt package.
type Stringer interface {
String() string
}Once a struct implements the Stringer, the printing message can be customized as you want.
package main
import "fmt"
type People struct {
Name string
Age byte
}
func (p People) String() string {
return "My name is " + p.Name + ", and I'm " + fmt.Sprint(p.Age) + " years old"
}
func main() {
fmt.Print(People{"Jack", 18}) // My name is Jack, and I'm 18 years old
}Errors
The error type is a built-in interface similar to fmt.Stringer.
type error interface {
Error() string
}As with fmt.Stringer, the fmt package looks for the error interface when printing values.
We can define a customized error struct by implementing the error interface.
package main
import (
"fmt"
"strconv"
)
type TransformError struct {
original string
}
func (t TransformError) Error() string {
return fmt.Sprintf("[TransformError] cannot transform `%v` to an integer", t.original)
}
func main() {
// func strconv.Atoi(s string) (int, error)
_, err := transform("haha")
if err != nil {
fmt.Print(err) // [TransformError] cannot transform `haha` to an integer
}
}
func transform(s string) (r int, e error) {
r, err := strconv.Atoi(s)
if err != nil {
e = TransformError{s}
}
return
}A nil error denotes success. On the contrary, a non-nil error denotes failure.
Generics
Type parameters of generics appear between brackets.
Generic Functions
Go functions can be written to work on multiple types using *type parameters.
func Index[T comparable](s []T, x T) int
comparableis an interface that is implemented by all comparable types.
Declare a function which returns the index if a match is found.
package main
import (
"fmt"
)
func Index[T comparable](s []T, x T) int {
for i, v := range s {
if v == x {
return i
}
}
return -1
}
func main() {
s := []string{"ab", "cd", "ef"}
// or Index[string](s, "ef")
fmt.Print(Index(s, "ef")) // 2
}Generic Types
A type can be parameterized with a type parameter. It can be useful for implementing generic data structures.
package main
import "fmt"
type ListNode[T any] struct {
val int
next *ListNode[T]
}
func main() {
head := ListNode[int]{5, nil}
node1 := ListNode[int]{7, nil}
head.next = &node1
fmt.Print(head.next.val) // 7
}Concurrency
Goroutines
A goroutine is a lightweight thread (a coroutine, to be precise) managed by the Go runtime.
Use the keyword go to start a new goroutine.
package main
import (
"fmt"
"time"
)
func main() {
fmt.Println("hello")
go fmt.Println("world")
time.Sleep(50 * time.Millisecond)
}Channels
Channels are a typed conduit through which you can send and receive values.
Use make to declare a channel.
ch := make(chan string)<- is called channel operator. The data flows in the direction of the arrow.
package main
import (
"fmt"
)
func main() {
ch := make(chan string)
go func() {
ch <- "Hello"
}()
v := <-ch
fmt.Print(v)
}Buffered Channels
Channels can be buffered. Just provide the buffer length as the second argument when initializing.
package main
import "fmt"
func main() {
ch := make(chan int, 2)
ch <- 1
ch <- 2
fmt.Println(<-ch) // 1
ch <- 3
fmt.Println(<-ch) // 2
fmt.Println(<-ch) // 3
}Close
A sender can close a channel to indicate that no more values will be sent. Receivers can assign a second paramater which is a boolean.
Only the sender should close a channel.
package main
import (
"fmt"
"time"
)
func main() {
ch := make(chan int)
go func() {
ch <- 5
time.Sleep(500 * time.Millisecond)
close(ch)
}()
v, ok := <-ch
if ok {
fmt.Println(v) // 5
}
v, ok = <-ch
if ok {
fmt.Println("if", v)
} else {
fmt.Println("else") // else
}
}If no close in code, there will be a panic fatal error: all goroutines are asleep - deadlock!.
Range
The loop for v := range ch receives values from the channel repeatedly until it’s closed.
package main
import (
"fmt"
"time"
)
func main() {
ch := make(chan int)
go func() {
ch <- 5
time.Sleep(500 * time.Millisecond)
ch <- 6
time.Sleep(500 * time.Millisecond)
close(ch)
}()
for v := range ch {
fmt.Println(v) // 5 6
}
}Select
The select statement lets a goroutine wait. It blocks untils one of its cases can run.
It chooses one at random if multiple are ready.
package main
import (
"fmt"
"time"
)
func main() {
c1, c2 := make(chan int), make(chan int)
go func(c chan int) {
for i := 0; i < 3; i++ {
c <- i + 1
time.Sleep(time.Second / 2)
}
close(c)
}(c1)
go func(c chan int) {
for i := 10; i < 13; i++ {
c <- i + 1
time.Sleep(time.Second / 2)
}
close(c)
}(c2)
for {
select {
case v, ok := <-c1:
if !ok {
return
}
fmt.Println("c1", v)
case v, ok := <-c2:
if !ok {
return
}
fmt.Println("c2", v)
}
}
}The default case will run if no other case is ready.
Mutex
Mutual Exclusion Lock, 互斥锁
Mutex is in sync package, which is used to make sure that only one goroutine can access a variable at a time, to avoid conflicts.
package main
import (
"fmt"
"sync"
"time"
)
var (
mutex sync.Mutex // 创建一个互斥锁
count int // 共享资源
)
func main() {
for i := 0; i < 1000; i++ {
go increment()
}
time.Sleep(time.Second * 2)
}
func increment() {
mutex.Lock() // 锁定互斥锁
count += 1 // 安全地修改共享资源
fmt.Println("Count:", count) // 如果不加锁,最后结果可能不会到达1000
mutex.Unlock() // 解锁互斥锁
}Define a block of code that is surrounded by the Lock and Unlock. This is an atomic operation.
