导入包
import "fmt"
导入多个包
import (
"fmt"
"math"
)
大写开头的符号被导出包,小写开头的符号是包私有的。
类型在变量名之后,返回值在函数声明的最后
func add(x int, y int) int {
returnx + y
}
同类型函数参数,可忽略其他:
x, y int
函数可以返回任意多个值:
func swap(x, y string) (string, string) {
return y, x
}
返回值可命名,如果没有在函数体内对命名返回值操作,则默认值被返回。
func split(sum int) (x, y int) {
x = sum* 4 / 9
y = sum- x
return
}
声明一个变量(一个或多个同类型变量):
var c, python, java bool
变量初始化,如果省略类型,则类型自动推断:
var c, python, java = true, false, "no!"
短变量声明,只能用于函数体中:
c, python, java := true, false, "no!"
go的基本类型:
bool
string
int int8 int16 int32 int64
uint uint8 uint16 uint32 uint64 uintptr
byte // uint8 的别名
rune // int32 的别名, 表示一个 Unicode 码点
float32 float64
complex64 complex128
int,uint在32,64位系统上分别是32,64位宽
分组声明变量,一次声明多个不同类型变量:
var (
ToBe bool = false
MaxIntuint64 = 1<<64 - 1
z complex128 = cmplx.Sqrt(-5 + 12i)
)
零值
没有明确初始值的变量声明会被赋予它们的零值。
零值是:
数值类型为 0,
布尔类型为 false,
字符串为 ""(空字符串)。
类型转换,go没有隐式类型转换,即使是安全的也不行
表达式T(v)将值v转换为类型T。
一些关于数值的转换:
var i int = 42
var f float64 = float64(i)
var u uint = uint(f)
常量的声明与变量类似,只不过是使用const关键字。常量不能用:=语法声明。与var一样,可以声明多个const常量:
const (
Big = 1<< 100
Small =Big >> 99
)
Go只有一种循环结构:for循环,大括号是必须的,支持continue,break
sum := 0
for i:= 0; i < 10; i++ {
sum+= i
}
初始化语句和后置语句是可选的。
sum := 1
for ;sum < 1000; {
sum+= sum
}
可以去掉分号:
sum := 1
for sum< 1000 {
sum+= sum
}
无限循环:
for {
}
Go的if语句与for循环类似,表达式外无需小括号( ),而大括号{}则是必须的。
if的简短语句, 同for一样,if语句可以在条件表达式前执行一个简单的语句。该语句声明的变量作用域仅在 if 之内。
func pow(x, n, lim float64) float64 {
if v:= math.Pow(x, n); v < lim {
return v
}
return lim
}
if和else
在if的简短语句中声明的变量同样可以在任何对应的else块中使用。Go自动提供了在这些语言中每个case后面所需的break语句。 除非以fallthrough语句结束,否则分支会自动终止。Go的另一点重要的不同在于switch的case无需为常量,且取值不必为整数。
switch os := runtime.GOOS; os {
case "darwin":
fmt.Println("OSX.")
case "linux":
fmt.Println("Linux.")
default:
fmt.Printf("%s.\n",os)
一个case允许有多个值:
case "vanilla", "chocolate":
使用fallthrough:
switch 1{
case 1:
fallthrough
case 2:
fmt.Println()
default:
}
没有条件的switch同switch true一样。这种形式能将一长串if-then-else写得更加清晰。
t := time.Now()
switch{
case t.Hour() < 12:
fmt.Println("Goodmorning!")
case t.Hour() < 17:
fmt.Println("Goodafternoon.")
default:
fmt.Println("Goodevening.")
}
defer语句会将函数推迟到外层函数返回之后执行。推迟的函数调用会被压入一个栈中。当外层函数返回时,被推迟的函数会按照后进先出的顺序调用。
defer fmt.Println("world")
指针,类型*T是指向T类型值的指针。其零值为nil。
var p *int
&操作符会生成一个指向其操作数的指针。
i := 42
p = &i
fmt.Println(*p) // 通过指针 p 读取i
*p = 21 //通过指针 p 设置i
*操作符表示指针指向的底层值。
fmt.Println(*p) // 通过指针 p 读取i
*p = 21 //通过指针 p 设置i
一个结构体(struct)就是一组字段(field)。
type Vertex struct {
X int
Y int
}
结构体字段可以通过结构体指针来访问。如果我们有一个指向结构体的指针p,那么可以通过(*p).X来访问其字段X。不过这么写太啰嗦了,所以语言也允许我们使用隐式间接引用,直接写p.X就可以。
声明结构体并初始化:
var (
v1 =Vertex{1, 2} //创建一个Vertex类型的结构体
v2 =Vertex{X: 1} // Y:0被隐式地赋予
v3 =Vertex{} // X:0 Y:0
p = &Vertex{1, 2} //创建一个*Vertex类型的结构体(指针)
)
内联结构体,不用指定结构体名字:
c := struct {
Name string
Type string
}{
Name:"Sammy",
Type:"Shark",
}
结构体标记,结构体的使用者决定如何定义和使用这些标记。
type User struct {
Namestring `example:"name"`
}
比如json包需要定义如下标记来使用camelcase:
type User struct {
Name string `json:"name"`
Password string `json:"password"`
PreferredFish[]string `json:"preferredFish"`
CreatedAt time.Time `json:"createdAt"`
}
类型[n]T表示拥有n个T类型的值的数组。
var a [10]int
primes := [6]int{2, 3, 5, 7, 11, 13}
声明一个整型数组, 用具体值初始化每个元素, 容量由初始化值的数量决定
array := [...]int{10, 20, 30, 40, 50}
声明一个有5个元素的数组, 用具体值初始化索引为1和2的元素, 其余元素保持零值
array := [5]int{1: 10, 2: 20}
数组是值类型, 数组赋值会拷贝数组. 长度是数组类型的一部分, 所以元素类型相同,但是长度不同的两个数组, 类型是不一样的, 不能相互赋值.
声明第一个包含3个元素的指向字符串的指针数组
var array1 [3]*string
声明第二个包含3个元素的指向字符串的指针数组, 使用字符串指针初始化这个数组
array2 := [3]*string{new(string), new(string), new(string)}
声明一个二维整型数组,两个维度分别存储4个元素和2个元素
var array [4][2]int
使用数组字面量来声明并初始化一个二维整型数组
array := [4][2]int{{10, 11}, {20, 21}, {30, 31}, {40, 41}}
声明并初始化外层数组中索引为1个和3的元素
array := [4][2]int{1: {20, 21}, 3: {40, 41}}
声明并初始化外层数组和内层数组的单个元素
array := [4][2]int{1: {0: 20}, 3: {1: 41}}
类型[]T表示一个元素类型为T的切片。(括号内没有数字)
创建字符串切片, 其长度和容量都是5个元素. 切片字面量
slice := []string{"Red", "Blue", "Green", "Yellow", "Pink"}
创建字符串切片, 使用空字符串初始化第 100 个元素
slice := []string{99: ""}
nil切片
var slice []int
使用make创建空的整型切片
slice := make([]int, 0)
使用切片字面量创建空的整型切片
slice := []int{}
切片通过两个下标来界定,即一个上界和一个下界,二者以冒号分隔。允许一个半开区间,包括第一个元素,但排除最后一个元素。创建了一个切片,它包含a中下标从1到3的元素:
a[1:4]
切片就像数组的引用。切片并不存储任何数据,它只是描述了底层数组中的一段。
声明一个结构体切片:
s := []struct {
i int
b bool
}{
{2,true},
{3,false},
{5,true},
{7,true},
{11,false},
{13,true},
}
切片s的长度和容量可通过表达式len(s)和cap(s)来获取。切片的容量是当前切片在数字的起始位置到数组尾部的长度。切片的零值是nil。nil切片的长度和容量为0且没有底层数组。
切片可以用内建函数make来创建,这也是你创建动态数组的方式。
a := make([]int,5) // len(a)=5
同时指定len和cap
b := make([]int, 0, 5)// len(b)=0, cap(b)=5
切片的切片,(其实就是锯齿数组)
slice := [][]int{{10}, {100, 200}}
board := [][]string{
[]string{"_","_", "_"},
[]string{"_","_", "_"},
[]string{"_","_", "_"},
}
//两个玩家轮流打上X和O
board[0][0]= "X"
board[2][2]= "O"
board[1][2]= "X"
board[1][0]= "O"
board[0][2]= "X"
创建切片时的三个索引, 起始索引, 结束索引, 容量索引
slice = source[1:2:3]
因为切片会自动增长, 取决于增长的时间点, 切片的赋值操作有可能会,也可能不会该表原来的切片
var array = make([]int32, 1, 1)
array1 := append(array, 1)
array1[0] = 9
//如果未超出容量, 则两个切片引用同一块内存. 否则引用不同内存.
向切片追加元素,当s的底层数组太小,不足以容纳所有给定的值时,它就会分配一个更大的数组。返回的切片会指向这个新分配的数组。
func append(s []T, vs...T) []T
切片的传递是基于引用的
for循环的range形式可遍历切片或映射。当使用for循环遍历切片时,每次迭代都会返回两个值。第一个值为当前元素的下标,第二个值为该下标所对应元素的一份副本。
var pow = []int{1, 2, 4, 8, 16, 32, 64, 128}
for i, v := range pow {
fmt.Printf("2**%d =%d\n", i, v)
}
可以将下标或值赋予_来忽略它。
for i, _ := range pow
for _, value := rangepow
若你只需要索引,忽略第二个变量即可。
for i := range pow
映射将键映射到值。映射的零值为nil。
声明一个映射:
var m map[string]int
具有引用语义的类型不能作为字典的key
初始化1:
m := make(map[string]int)
m["1"] = 1
初始化2:
m := map[string]int{
"1": 1,
"2": 2,
}
删除元素:
delete(m, key)
通过双赋值检测某个键是否存在:
elem, ok = m[key]
访问一个不存在的key不会出错,会返回零值
函数值可以用作函数的参数或返回值。
func compute(fn func(float64, float64) float64) float64 {
returnfn(3, 4)
}
hypot := func(x, y float64) float64 {
returnmath.Sqrt(x*x + y*y)
}
Go没有类。不过你可以为结构体类型定义方法。方法就是一类带特殊的 接收者 参数的函数。
type Vertex struct {
X, Yfloat64
}
func (v Vertex) Abs() float64 {
returnmath.Sqrt(v.X*v.X + v.Y*v.Y)
}
func main() {
v :=Vertex{3, 4}
fmt.Println(v.Abs())
}
还可以这么调用方法:
Vertex.Abs(v)
或者 (当然必须要把接收者声明为指针类型)
(*Vertex).Abs(&v)
你也可以为非结构体类型声明方法。就是接收者的类型定义和方法声明必须在同一包内;不能为内建类型声明方法。
type MyFloat float64
func (f MyFloat) Abs() float64 {
returnfloat64(f)
}
你可以为指针接收者声明方法。这意味着对于某类型T,接收者的类型可以用*T的文法。(此外,T不能是像*int这样的指针。)指针接收者的方法可以修改接收者指向的值。指针接收者比值接收者更常用,这样可以避免在每次调用方法时复制该值。
func (v *Vertex) Scale(f float64) {
v.X =v.X * f
v.Y =v.Y * f
}
可以判断接收者是否为空,这跟C#,Java不一样。
func(t *T) M() {
if t == nil{
fmt.Println("
") return
}
fmt.Println(t.S)
}
指定了零个方法的接口值被称为*空接口:*,空接口可保存任何类型的值。(因为每个类型都至少实现了零个方法。)空接口被用来处理未知类型的值。例如,fmt.Print可接受类型为interface{}的任意数量的参数。
interface{}
func describe(i interface{}) {
fmt.Printf("(%v,%T)\n", i, i)
}
嵌入类型提供了继承的能力, 外部类型对象可以直接调用嵌入类型对象的成员(方法和字段). (go将这个叫做内部类型提升), 外部类型可以声明接收者类型为自己的方法,来重写内部类型的方法.
type admin struct {
user // 嵌入类型
level string
}
类型断言提供了访问接口值底层具体值的方式。
t := i.(T)
var i interface{} = "hello"
s := i.(string)
fmt.Println(s)
s, ok := i.(string)
fmt.Println(s,ok)
f, ok := i.(float64)
fmt.Println(f,ok)
f =i.(float64) //报错(panic)
fmt.Println(f)
类型选择 是一种按顺序从几个类型断言中选择分支的结构。
func do(i interface{}) {
switchv := i.(type) {
case int:
fmt.Printf("Twice%v is %v\n", v,v*2)
case string:
fmt.Printf("%qis %v bytes long\n", v, len(v))
default:
fmt.Printf("Idon't know about type %T!\n", v)
}
}
fmt 包中定义的 Stringer 是最普遍的接口之一。Stringer 是一个可以用字符串描述自己的类型。fmt 包(还有很多包)都通过此接口来打印值。
type Stringerinterface {
String() string
}
Go 程序使用 error 值来表示错误状态。与 fmt.Stringer 类似,error 类型是一个内建接口:
type error interface {
Error() string
}
通常函数会返回一个 error 值,调用的它的代码应当判断这个错误是否等于 nil 来进行错误处理。
i, err :=strconv.Atoi("42")
if err != nil {
fmt.Printf("couldn't convert number:%v\n", err)
return
}
io 包指定了 io.Reader 接口,它表示从数据流的末尾进行读取。io.Reader 接口有一个 Read 方法:
func (T) Read(b[]byte) (n int, err error)
Read 用数据填充给定的字节切片并返回填充的字节数和错误值。在遇到数据流的结尾时,它会返回一个 io.EOF 错误。
r := strings.NewReader("Hello, Reader!")
n, err := r.Read(b)
if err == io.EOF {
}
Go 程(goroutine)是由 Go 运行时管理的轻量级线程。
go f(x, y, z)
信道是带有类型的管道,(就是一个队列起个高大上的名字?)和映射与切片一样,信道在使用前必须创建:
ch := make(chan int)
你可以通过它用信道操作符 <- 来发送或者接收值。
ch <- v //将 v 发送至信道 ch。
v := <-ch //从 ch 接收值并赋予 v。
x, y := <-ch, <-ch //接收多个值
默认情况下,发送和接收操作在另一端准备好之前都会阻塞。
信道可以是 带缓冲的。将缓冲长度作为第二个参数提供给 make 来初始化一个带缓冲的信道:(就是给队列长度设个上线)
ch := make(chan int, 100)
仅当信道的缓冲区填满后,向其发送数据时才会阻塞。当缓冲区为空时,接受方会阻塞。
发送者可通过 close 关闭一个信道来表示没有需要发送的值了。接收者可以通过为接收表达式分配第二个参数来测试信道是否被关闭:若没有值可以接收且信道已被关闭,那么在执行完之后 ok 会被设置为 false。
v, ok := <-ch
循环 for i := range c 会不断从信道接收值,直到它被关闭。(像极了BlockingCollection)
*注意:* 只有发送者才能关闭信道,而接收者不能。向一个已经关闭的信道发送数据会引发程序恐慌(panic)。
func fibonacci(nint, c chan int) {
close(c)
}
select 会阻塞到某个分支可以继续执行为止,这时就会执行该分支。当多个分支都准备好时会随机选择一个执行。(就是C#中Task.WhenAny的一种用法)
func fibonacci(c, quit chan int) {
x, y:= 0, 1
for {
select {
case c <- x:
x,y = y, x+y
case <-quit:
fmt.Println("quit")
return
}
}
}
当 select 中的其它分支都没有准备好时,default 分支就会执行。为了在尝试发送或者接收时不发生阻塞,可使用 default 分支:(类似于依次检测所有Task是否完成,如果都没完成就执行default)
select {
case i := <-c:
//使用i
default:
//从 c 中接收会阻塞时执行
}
Go 标准库中提供了 sync.Mutex 互斥锁类型及其两个方法:
var mux sync.Mutex
mux.Lock()
defermux.Unlock()
go install会安装目标文件到$GOPATH/bin,该目录在安装go时被加入环境变量,所以可以在命令行中使用安装的go程序
$GOPATH/pkg目录包含预编译的go文件,如果编译遇到问题,可删除此目录,go会重建此目录
package,每个文件夹下的go文件只能使用同一个package名称。一个package表示一个文件夹下所有的go文件。
Strconv包用来在字符串和数字之间转换
Strings包用来操作字符串
Flag包用来提供定义和解析命令行参数的功能
字符串和字节数组可以相互类型转换,因为字符串就是用字节数组存储的
a:= "my string"
b:= []byte(a)
c:= string(b)
获取字符串包含的字符个数
len([]rune(a))
两种创建error的方式:
errors.New("barnacles")
err := fmt.Errorf("error occurred at: %v", time.Now())
即使发生了panic,也会先执行defer代码
deferfmt.Println(&MyError{})
panic("oh no")
使用recover函数处理panic:
defer func() {
if err := recover(); err != nil{
fmt.Println("recover panic from here")
}
}()
panic("oh no")
package别名:
import f "fmt"
变长参数:
func sayHello(names ...string) {
for _, n := range names {
fmt.Printf("Hello %s\n", n)
}
}
把数组传递给变长参数:
names := []string{"Sammy", "Jessica", "Drew", "Jamie"}
line := join(",", names...)
init方法,在导入包时执行,先于main方法执行。可以有多个init方法(感觉意义不大)
func init() {
name = "mac"
}
可以导入包,只是为了执行包的init方法
import _ "image/png"
条件编译,在文件头部加buildtag:
// +build tag_name
例如:
// +build pro
编译:
go build -tags pro
如果没有在tags参数中指定pro,那么标记了pro的go文件将不会被编译
指定多个标记,只要有一个标记匹配就加入编译
// +build pro enterprise
下面的用法,只有所有标记匹配才加入编译:
// +build pro
// +build enterprise
反转build tag
// +build !windows
为指定平台编译目标执行文件:
https://www.digitalocean.com/community/tutorials/building-go-applications-for-different-operating-systems-and-architectures
因为构建包是很常用的动作,所以也可以直接指定包:
go build github.com/code/chapter3/wordcount
也可以在指定包的时候使用通配符。3个点表示匹配所有的字符串。例如,下面的命令会编译chapter3目录下的所有包:
go build github.com/code/chapter3/...