一年前为了给同事介绍Go而做的演讲文稿。一年过去,我对Go的理解没有任何进展。
最近决定深入地学习这门语言。
Hello World
package main
import "fmt"
// this is a comment
func main() {
fmt.Println("Hello World")
}
Build & Run
$ cd D:\Go\src\github.com\sif\hello
$ go run main.go
Hello World
$ go build main.go
$ main.exe
Hello World
$ go clean
Executable Size
Language
Executable Size (KB)
Comments
Go
1524
C++
12
Using cout. #include <iostream>
C++ (with runtime)
48
Using cout. #include <iostream>
C
7
Using printf() #include <stdio.h>
Go embeds the “virtual machine” into the executable.
Enviroment Variables
On 64bit Windows:
Name Value Comments
GOARCH amd64
GOOS windows
GOPATH D:\Go Location of your workspaces. Can specify multiple workspaces. GOROOT C:\Go
GOPATH可以指定多个workspaces。
Workspace Hierarchy
bin\
hello.exe
pkg\
windows_amd64\
carestream.com\dental\
csi.a
src\
github.com\sif\
hello\
hello.go
carestream.com\dental\
csi\
patient.go
Doc Server
Browse Go documents on http://localhost:6060/
Basic Types
Integer Numbers
Integer types:uint8, uint16, uint32, uint64, int8, int16, int32, int64
Two alias types:byte (uint8) rune (int32)
Three machine dependent integer types:uint, int, uintptr
They are machine dependent because their size depends on the type of architecture you are using.
Floating Point Numbersfloat32, float64
Generally we should stick with float64 when working with floating point numbers.
Complex Numberscomplex64, complex128
Operators +, -, *, / and % are all the same as C.
Booleans
func main() {
fmt.Println(true && true)
fmt.Println(true && false)
fmt.Println(true || true)
fmt.Println(true || false)
fmt.Println(!true)
}
Strings
Go strings are made up of individual bytes, usually one for each character.
"Hello \nWorld" // Similar to C/C++
`Hello
World` // Similar to Python “””
func main() {
fmt.Println(len("Hello World"))
fmt.Println("Hello World"[1])
fmt.Println("Hello " + "World")
}
Characters from other languages like Chinese are represented by more than one byte.
func main() {
s := "abc汉字"
for i := 0; i < len(s); i++ { // byte
fmt.Printf("%c,", s[i])
}
fmt.Println()
for _, r := range s { // rune
fmt.Printf("%c,", r)
}
}
Output:
a,b,c,æ,±,,å,,,
a,b,c,汉,字,
Enum
// A Month specifies a month of the year (January = 1, ...).
type Month int
const (
January Month = 1 + iota
February
March
April
May
// ...
November
December
)
Variables
func main() {
var x string = "Hello World"
fmt.Println(x)
}
Shorter form:
x := "Hello World"
The type is not necessary because the Go compiler is able to infer the type based on the literal value you assign the variable.
The compiler can also do inference with the var statement:
var x = "Hello World"
Generally you should use this shorter form whenever possible.
Scope
The same as C.
Constants
Constants are basically variables whose values cannot be changed later.
func main() {
const x string = "Hello World"
fmt.Println(x)
}
const x string = "Hello World"
x = "Some other string" // cannot assign to x
Constants must be numbers, strings and booleans which can be determined in compile time.
Defining Multiple Values
var (
a = 5
b = 10
c = 15
)
const (
Sunday Weekday = iota
Monday
Tuesday
Wednesday
Thursday
Friday
Saturday
)
Control Structures
IF
可省略条件表达式括号。
支持初始化语句,可定义代码块局部变量。
代码块左大括号必须在条件表达式尾部。
x := 0
if n := "abc"; x > 0 {
fmt.Println(n[2])
} else if x < 0 {
fmt.Println(n[1])
} else {
fmt.Println(n[0])
}
但是,不支持三元操作符 “a > b ? a : b”。
FOR
s := "abc"
for i, n := 0, len(s); i < n; i++ { // 常见的 for 循环
fmt.Println(s[i])
}
n := len(s)
for n > 0 { // 替代 while (n > 0) {}
fmt.Println(s[n]) // 替代 for (; n > 0;) {}
n--
}
for { // 替代 while (true) {}
fmt.Println(s) // 替代 for (;;) {}
}
Range
类似迭代器操作,返回(索引,值)或(键,值)。
1st value 2nd value
string index s[index] unicode, rune array/slice index s[index]
map key m[key]
channel element
s := "abc"
// 忽略 2nd value,支持 string/array/slice/map。
for i := range s {
fmt.Println(s[i])
}
for _, c := range s { // 忽略 index
fmt.Println(c)
}
m := map[string]int{"a": 1, "b": 2}
for k, v := range m { // 返回 (key, value)
fmt.Println(k, v)
}
Switch
分支表达式可以是任意类型,不限于常量。可省略 break,默认自动终止。
x := []int{1, 2, 3}
i := 2
switch i {
case x[1]: // 不限于常量
fmt.Println("a")
case 1, 3: // 多值匹配
fmt.Println("b")
default:
fmt.Println("c")
}
如需要继续下一分支,可使用 fallthrough,但不再判断条件。
x := 10
switch x {
case 10:
fmt.Println("a")
fallthrough
case 0:
fmt.Println("b")
}
Output:
a
b
省略条件表达式,可当 if…else if…else 使用。
switch {
case x[1] > 0:
fmt.Println("a")
case x[1] < 0:
fmt.Println("b")
default:
fmt.Println("c")
}
switch i := x[2]; { // 带初始化语句
case i > 0:
fmt.Println("a")
case i < 0:
fmt.Println("b")
default:
fmt.Println("c")
}
Data Structures
Array
var a [4]int
fmt.Println(a) // [0 0 0 0]
fmt.Println(len(a)) // 4
fmt.Println(cap(a)) // 4
var a = [2]string{"Penn", "Teller"}
fmt.Println(a) // [Penn Teller]
fmt.Println(len(a)) // 2
var a = [...]string{"Penn", "Teller"}
fmt.Println(a) // [Penn Teller]
fmt.Println(len(a)) // 2
Similar to C but with differencies:
Go’s arrays are values. An array variable denotes the entire array; it is not a pointer to the first array element (as would be the case in C).
Slice
The type specification for a slice:
[]T
Slices build on arrays to provide great power and convenience. Unlike an array type, a slice type has no specified length.
Create with literal:
var s = []int{0, 1, 2, 3, 4}
fmt.Println(s) // [0 1 2 3 4]
fmt.Println(len(s)) // 5
fmt.Println(cap(s)) // 5
Create with built-in function make:
func make([]T, len, cap) []T
var s = make([]int, 5, 10)
fmt.Println(s) // [0 0 0 0 0]
fmt.Println(len(s)) // 5
fmt.Println(cap(s)) // 10
The zero value of a slice is nil.
var s []int
fmt.Println(s) // []
fmt.Println(s == nil) // true
fmt.Println(len(s)) // 0
Create by “slicing” an existing slice or array.
b := []byte{'g', 'o', 'l', 'a', 'n', 'g'}
// b[1:4] == []byte{'o', 'l', 'a'}
// b[:2] == []byte{'g', 'o'}
// b[2:] == []byte{'l', 'a', 'n', 'g'}
// b[:] == b
Create a slice given an array:
b := []byte{'g', 'o', 'l', 'a', 'n', 'g'}
s := b[:] // a slice referencing the storage of b
make([]byte, 5), is structured like this:
Re-slicing a slice doesn’t make a copy of the underlying array.
t := make([]byte, len(s), (cap(s)+1)*2)
copy(t, s)
s = t
Grow a slice by built-in function append:
func append(s []T, x ...T) []T
a := make([]int, 1)
// a == []int{0}
a = append(a, 1, 2, 3)
// a == []int{0, 1, 2, 3}
Append one slice to another:
a := []string{"John", "Paul"}
b := []string{"George", "Ringo", "Pete"}
a = append(a, b...) // equivalent to "append(a, b[0], b[1], b[2])"
// a == []string{"John", "Paul", "George", "Ringo", "Pete"}
Map
A map is an unordered collection of key-value pairs.
var x map[string]int
x is a map of strings to ints.
Maps have to be initialized before they can be used.\
x := make(map[string]int)
x["10"] = 10
fmt.Println(x["10"])
Delete items from a map using the built-in delete function:
delete(x, “10”)
Looking up a key which doesn’t exist returns the zero value.
fmt.Println(x["unexisted"]) // 0
Similar to C++ std::map.
Accessing an element of a map can return two values instead of just one.
The first value is the result of the lookup,
The second tells us whether or not the lookup was successful.
In Go we often see code like this:
if value, ok := x["0"]; ok {
fmt.Println(value, ok)
}
Shorter way to create maps:
x := map[string]int{
"1": 1,
"2": 2,
"3": 3,
}
Set
No build-in support.
map[X]struct{}
Functions
func main() {
xs := []float64{98, 93, 77, 82, 83}
fmt.Println(average(xs))
}
func average(xs []float64) float64 {
total := 0.0
for _, v := range xs {
total += v
}
return total / float64(len(xs))
}
We can also name the return type:
func average(xs []float64) (avg float64) {
total := 0.0
for _, v := range xs {
total += v
}
avg = total / float64(len(xs))
return
}
Returning Multiple Values
func f() (int, int) {
return 5, 6
}
func main() {
x, y := f()
}
Multiple values are often used to return an error value along with the result, or a boolean to indicate success.
Here is a simple example, opening a file and reading some of it.
file, err := os.Open("file.go") // For read access.
if err != nil {
log.Fatal(err)
}
count, err := file.Read(data)
if err != nil {
log.Fatal(err)
}
Here’s the implementation of method Read:
// Read reads up to len(b) bytes from the File.
// It returns the number of bytes read and an error, if any.
// EOF is signaled by a zero count with err set to io.EOF.
func (f *File) Read(b []byte) (n int, err error) {
if f == nil {
return 0, ErrInvalid
}
n, e := f.read(b)
if n < 0 {
n = 0
}
if n == 0 && len(b) > 0 && e == nil {
return 0, io.EOF
}
if e != nil {
err = &PathError{"read", f.name, e}
}
return n, err
}
Variadic Functions
There is a special form available for the last parameter in a Go function:
func add(args ...int) int { // Zero or more
total := 0
for _, v := range args {
total += v
}
return total
}
func main() {
fmt.Println(add(1,2,3))
}
This is precisely how the fmt.Println function is implemented:
func Println(a ...interface{}) (n int, err error)
We can also pass a slice of ints by following the slice with …:
func main() {
xs := []int{1,2,3}
fmt.Println(add(xs...))
}
Closure
It is possible to create functions inside of functions:
func main() {
add := func(x, y int) int {
return x + y
}
fmt.Println(add(1,1))
}
It also has access to other local variables:
func main() {
x := 0
increment := func() int {
x++
return x
}
fmt.Println(increment()) // 1
fmt.Println(increment()) // 2
}
A function like this together with the non-local variables it references is known as a closure.
Recursion
func factorial(x uint) uint {
if x == 0 {
return 1
}
return x * factorial(x-1)
}
Defer, Panic & Recover
func first() {
fmt.Println("1st")
}
func second() {
fmt.Println("2nd")
}
func main() {
defer second()
first()
}
Defer is often used when resources need to be freed in some way.
f, _ := os.Open(filename)
defer f.Close()
Panic & Recover
func main() {
defer func() {
str := recover()
fmt.Println(str) // PANIC
}()
panic("PANIC")
}
A panic generally indicates a programmer error (for example,
attempting to access an index of an array that’s out of bounds
forgetting to initialize a map
etc.
) or an exceptional condition that there’s no easy way to recover from.
// pkg\bytes\buffer.go
// makeSlice allocates a slice of size n. If the allocation fails,
// it panics with ErrTooLarge.
func makeSlice(n int) []byte {
// If the make fails, give a known error.
defer func() {
if recover() != nil {
panic(ErrTooLarge)
}
}()
return make([]byte, n)
}
Pointers
Go is similar to C in pointers.
func zero(xPtr *int) { // pointer
*xPtr = 0 // dereference
}
func main() {
x := 5
zero(&x) // address
fmt.Println(x) // 0
}
Build-in function “new”
Function “new” takes a type as an argument, allocates enough memory to fit a value of that type and returns a pointer to it.
func one(xPtr *int) {
*xPtr = 1
}
func main() {
xPtr := new(int)
one(xPtr)
fmt.Println(*xPtr) // 1
}
Pointers are rarely used with Go’s built-in types. They are extremely useful when paired with structs.
Structs
A struct is a type which contains named fields.
type Circle struct {
x float64
y float64
r float64
}
The fields with the same type can be collapsed:
type Circle struct {
x, y, r float64
}
Initialization
Create a local variable with zero initialization:
var c Circle // {0 0 0}
Use new function:
c := new(Circle) // Also {0 0 0}
This allocates memory for all the fields, sets each of them to their zero value and returns a pointer (*Circle).
More often we want to give each of the fields a value.
c := Circle{x: 0, y: 0, r: 5}
Or,
c := Circle{0, 0, 5}
Note that variable c is NOT a pointer of Circle.
func zeroCircle(c *Circle) {
c.x = 0 // No -> operator like C! Just use dot.
c.y = 0
c.r = 0
}
func main() {
c := Circle{x: 0, y: 0, r: 5}
zeroCircle(&c) // c is not a pointer, have to address it.
fmt.Println(c) // {0 0 0}
}
Constructor?
没有构造和析构方法,通常用简单工厂模式返回对象实例。
type Queue struct {
elements []interface{}
}
func NewQueue() *Queue {
return &Queue{make([]interface{}, 10)}
}
// src\pkg\container\list\list.go
package list
// New returns an initialized list.
func New() *List { return new(List).Init() }
Methods
Normal function:
func circleArea(c *Circle) float64 {
return math.Pi * c.r*c.r
}
Method:
func (c *Circle) area() float64 { // Receiver
return math.Pi * c.r*c.r
}
Call the method:
fmt.Println(c.area())
Interfaces
Similarily, define struct Rectangle:
type Rectangle struct {
x1, y1, x2, y2 float64
}
func (r *Rectangle) area() float64 {
l := distance(r.x1, r.y1, r.x1, r.y2)
w := distance(r.x1, r.y1, r.x2, r.y1)
return l * w
}
Both Circle and Rectangle have a method named area(). Let’s define an Interface for this similarity:
type Shape interface {
area() float64
}
Instead of defining fields, interface defines a “method set”: a list of methods that a type must have in order to “implement” the interface.
Function taking interface types as arguments:
func totalArea(shapes ...Shape) float64 {
var area float64
for _, s := range shapes {
area += s.area()
}
return area
}
Now call it:
fmt.Println(totalArea(&c, &r))
Interfaces can also be used as fields:
type MultiShape struct {
shapes []Shape
}
We can even turn MultiShape itself into a Shape by giving it an area method:
func (m *MultiShape) area() float64 {
var area float64
for _, s := range m.shapes {
area += s.area()
}
return area
}
Packages
编译工具对源码目录有严格要求,每个工作空间 (workspace) 必须由 bin、pkg、src 三个目录组成。
“D:\proj\Go” is NOT in \$GOPATH.
$ D:\proj\Go>go build hello.go
$ D:\proj\Go>go install
go install: no install location for directory D:\proj\Go outside GOPATH
所有代码都必须组织在 package 中:
源文件头部以 “package <name>” 声明包名称。
包由同一目录下的多个源码文件组成。
包名类似 namespace,与包所在目录名、编译文件名无关。
可执行文件必须包含 package main,入口函数 main。
Access Control
Lower case private, upper case public.
package list
type Element struct {
next, prev *Element // private
list *List // private
Value interface{} // public
}
package csi
type Patient struct {
Id string
Name string
BirthDate time.Time
dose float32 // Won't exported.
}
package main
...
p := &csi.Patient{"1", "Adam", newDate(1984, time.March, 26), 1.0}
Error:
> implicit assignment of unexported field 'dose' in csi.Patient literal
Concurrency
Go has rich support for concurrency using goroutines and channels.
Goroutines
A goroutine is a function that is capable of running concurrently with other functions.
func f(n int) {
for i := 0; i < 10; i++ {
fmt.Println(n, ":", i)
}
}
func main() {
go f(0)
var input string
fmt.Scanln(&input)
}
Goroutines are lightweight, you can create many of them:
func main() {
for i := 0; i < 10; i++ {
go f(i)
}
var input string
fmt.Scanln(&input)
}
Channels
Channels provide a way for two goroutines to communicate with one another and synchronize their execution.
func pinger(c chan string) {
for i := 0; ; i++ {
c <- "ping"
}
}
func printer(c chan string) {
for {
msg := <- c
fmt.Println(msg)
time.Sleep(time.Second * 1)
}
}
func main() {
var c chan string = make(chan string)
go pinger(c)
go printer(c)
var input string
fmt.Scanln(&input)
}
When pinger attempts to send a message on the channel it will wait until printer is ready to receive the message. (this is known as blocking)
Create Library
Choose a package path (carestream.com/dental/csi) and create the package dir:
$GOPATH\src\carestream.com\dental\csi
Next, create a file named patient.go, containing the following code.
package csi
import "time"
type Patient struct {
Id string
Name string
BirthDate time.Time
Dose float32
}
Now, build it:
$ go build carestream.com\dental\csi
No output. Do go install for it.
$ go install carestream.com\dental\csi
Get:
$GOPATH\pkg\windows_amd64\carestream.com\dental\csi.a
THE END