文章目录
Go指南
值类型:int、float、bool、string、数组和结构体
引用类型:指针、切片、map、chan、interface
包
程序都由包构成,main包开始运行程序,包名必须与导入路径最后一个元素一致
导出名
一个名字以大写字母开头就是已导出的,导入包时只能引用其中已导出的名字
函数
当连续两个或多个已命名形参类型相同时,可以只写最后一个的参数
func add(a ,b int) int{
return a+b;
}
多个返回值
func swap(x,y string) (string,string) {
return y,x;
}
func main() {
a,b:=swap("1","2");
fmt.Println(a,b);
}
返回值可以命名,相当于函数顶部声明的变量,如果直接return返回时就相当于返回这个变量,在长代码中慎用,影响可读性
func swap(x,y int) (res int) {
res = x + y;
return
}
func main() {
fmt.Println(swap(1,2));
}
除了返回值只有一个声明的情况,其他情况都要用括号括起来
变量
var
声明变量列表,var li,a,b bool
带有初始值的变量
var i, j int = 1, 2
带初始值的变量也可以去掉类型,会从初始值判断类型
在函数中可以使用简洁赋值语句替代var
func main() {
k := 3
fmt.Println(k)
}
基本类型
bool string int int8 int16 int32 int64 uint uint8 uint16 uint32 uint64 uintptr int 、uint、uintptr在32位系统上为32位宽,在64位系统上为64位宽 byte // uint8 的别名 rune // int32 的别名 // 表示一个 Unicode 码点 float32 float64 complex64 complex128
var (
ToBe bool = false
MaxInt uint64 = 1<<64 - 1
z complex128 = cmplx.Sqrt(-5 + 12i)
)
// 同 import,可以分组为一个语法快
没有明确初始值的变量具有默认零值
类型转换同样使用类型加括号的形式
var i int = 42
var f float64 = float64(i)
var u uint = uint(f)
当赋值右边是未声明类型常量时,类型取决于精度
i := 42 // int
f := 3.142 // float64
g := 0.867 + 0.5i // complex128
常量使用const定义,常量不能使用短变量语法
for
Go中只有for循环一种循环结构
for i := 0; i < 10; i++ {
fmt.Println(i)
}
不能使用var声明,去掉初始化和后置语句就是while循环
i := 0
for i < 10 {
i++
fmt.Println(i)
}
因此无限循环就是
for {
i++
fmt.Println(i)
}
if
if语句可以在执行前执行一条简单的语句(块级作用域,不过在else中同样能使用)
if v := math.Pow(x, n); v < lim {
return v
}
switch
go中的switch不需要break
switch i := 1; i {
case 1:
fmt.Println(1)
case 2:
fmt.Println(2)
default:
fmt.Println("default")
}
无条件的switch条件类似与true,可以将一长串的if-then-else变得很清晰
switch {
case t.Hour() < 12:
fmt.Println("Good morning!")
case t.Hour() < 17:
fmt.Println("Good afternoon.")
default:
fmt.Println("Good evening.")
}
defer
defer 语句会将函数推迟到外层函数返回之后执行。
推迟调用的函数其参数会立即求值,但直到外层函数返回前该函数都不会被调用。
defer fmt.Println("world")
fmt.Println("hello")
defer 推迟执行语句会进入一个压栈区,后进先出
指针
与C类似
var p *int
i := 20
p = &i
*p = 40
fmt.Println(i) // 40
结构体
结构体就是一组字段
type Vex struct {
x int
y int
}
func main() {
fmt.Println(Vex{1, 2})
}
结构体字段可以使用.
进行访问,也可以使用结构体指针来访问
p := Vex{1, 2}
p.x = 123
q := &p
(*q).x = 456
q.x = 789 // 上下两种写法都可以,上边种写法太麻烦
fmt.Println(p)
结构体文法,可以利用声明name的形式列出部分字段
v1 = Vertex{1, 2} // 创建一个 Vertex 类型的结构体
v2 = Vertex{X: 1} // Y:0 被隐式地赋予
v3 = Vertex{} // X:0 Y:0
数组
使用[n]T
的形式创建长度为n的类型为T的数组
var arr [2]int
arr[0] = 1
arr[1] = 2
arr1 := [3]string{"a", "b", "c"}
fmt.Println(arr)
fmt.Println(arr1)
切片
切片使用[]T
进行类型声明,左闭右开
arr1 := [3]string{"a", "b", "c"}
var s []string = arr1[0:2]
切片就像数组的引用,不存储数据,描述底层数组中的一段,更改切片中的内容会更改数组
[]bool{true, true, false}
上述代码类似创建了如下数组,并创建了一个引用它的切片
[3]bool{true, true, false}
切片时可以忽略上下限
切片的长度和容量通过len()
和cap()
进行获取,容量是指第一个元素到底层数组元素末尾的个数,可以通过重新切片来扩展切片
s := []int{2, 3, 5, 7, 11, 13}
printSlice(s)
// 截取切片使其长度为 0
s = s[:0]
printSlice(s)
// 拓展其长度
s = s[:4]
printSlice(s)
// 舍弃前两个值
s = s[2:]
printSlice(s)
//len=6 cap=6 [2 3 5 7 11 13]
//len=0 cap=6 []
//len=4 cap=6 [2 3 5 7]
//len=2 cap=4 [5 7]
切片的零值是nil
,长度为0、容量为0且没有底层数组,有些长度为0,容量为0的但是,底层数组不为0,比如[]int{}
、make([]int,3)[3:]
只要容量有就可以扩展
切片可以使用内建函数make
创建,make函数为分配元素为零值且引用了它的切片
b := make([]int, 0, 5) // 长度为0,容量为5
b = b[:cap(b)]
b = b[1:]
使用**append
**方法可以向切片添加元素,当底层数组太小,不足以容纳给定值时会分配更大的数组
b := make([]int, 0, 5) // 长度为0,容量为5
b = append(b, 1)
for循环的range
形式可以遍历切片或映射
arr := []int{2, 3, 4}
for i, v := range arr {
fmt.Printf("下标为%d", i)
fmt.Printf("元素值为%d", v)
fmt.Println("")
}
映射
键到值的映射,映射的零值为nil
type Vex struct {
x, y int
}
var m map[string]Vex //声明
func main() {
m = make(map[string]Vex) // make返回给定类型的映射并将其初始化
m["键值"] = Vex{1, 2}
fmt.Println(m["键值"])
}
映射文法,类似结构体文法
var m = map[string]Vex{
"键1": {0, 2},
"键2": {1, 2},
} //声明
func main() {
fmt.Println(m["键1"])
}
修改映射
m := make(map[string]int)
m["Answer"] = 42
fmt.Println("The value:", m["Answer"])
m["Answer"] = 48
fmt.Println("The value:", m["Answer"])
delete(m, "Answer")
fmt.Println("The value:", m["Answer"])
v, ok := m["Answer"]// answer 在m中ok为true,否则为false,当为false时v为该类型的零值
fmt.Println("The value:", v, "Present?", ok)
函数可以像JavaScript中一样进行传递,也具有闭包
func adder() func(int) int {
sum := 0
return func(x int) int {
sum += x
return sum
}
}
func main() {
pos, neg := adder(), adder()
for i := 0; i < 10; i++ {
fmt.Println(
pos(i),
neg(-2*i),
)
}
}
方法
Go没有类,但是可以为结构体类型定义方法(结构体类型带有该方法,而不是特定的变量)
type Vertex struct {
X, Y float64
}
func (v Vertex) Abs() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
func main() {
c := Vertex{3, 4}
fmt.Println(c.Abs())
}
方法是带有特殊接受者参数的函数,方法只是个带有接收者参数的函数,上述写法与下面的写法是一致的
type Vertex struct {
X, Y float64
}
func Abs(v Vertex) float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
func main() {
c := Vertex{3, 4}
fmt.Println(Abs(c))
}
也可以为非结构体类型定义方法,但是只能对同一包内的类型进行定义,不能对内建类型定义方法
type MyInt int
func (i MyInt) Abs() int {
if i < 0 {
return int(-i)
}
return int(i)
}
func main() {
v := MyInt(-12)
fmt.Println(v.Abs())
}
还可以为指针接收者声明方法,在进行引用传递修改时非常方便
指针为接受者时接收者既可以为值也可以为指针,但是指针为函数参数时,必须接收指针
func (v *Vex) Scale(f float64) {
(*v).x = (*v).x * f
(*v).y = (*v).y * f
}
func main() {
v := Vex{3, 4}
v.Scale(10) // ok
p := &v
p.Scale(10)
fmt.Println(v) // {300,400}
fmt.Println(*p) // {300,400}
}
实际上v.Scale(10)会被Go解释为(&v).Scale(10)
在相反方向上同样成立,接收值作为参数的函数必须接收指定值,而不能为指针,但是值作为接收者时,接收者既可以为值也可以为指针
func (v Vex) Scale(f float64) {
v.x = v.x * f
v.y = v.y * f
}
func main() {
v := Vex{3, 4}
v.Scale(10) // ok
p := &v
p.Scale(10)
fmt.Println(v) // {300,400}
fmt.Println(*p) // {300,400}
}
实际上p.Scale(10)会被Go解释为(*p).Scale(10)
接口
接口类型 是由一组方法签名定义的集合。
接口类型的变量可以保存任何实现了这些方法的值。
type Abser interface {
Abs() float64
}
func main() {
var a Abser
v := Vex{3, 4}
m := MyFloat(-math.Sqrt2)
a = &v
a = m
fmt.Println(a)
a = v // 报错,值类型没有实现这个方法
}
type MyFloat float64
func (f MyFloat) Abs() float64 {
if f < 0 {
return float64(-f)
}
return float64(f)
}
type Vex struct {
X, Y float64
}
func (v *Vex) Abs() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
在Go中可以使用更优雅的形式处理空指针异常
var a Abser
var v *Vex // 空指针
a = v
a.Abs()
// 处理异常方法
func (v *Vex) Abs() float64 {
if v == nil {
fmt.Println("空指针调用")
return 0.0
}
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
指定零个方法的接口称为空接口,空接口可以保存任意类型的值(每个类型都至少实现了零个方法)
func main() {
var I interface{} // 空接口
I = 2
describe(I)
}
func describe(i interface{}) {
fmt.Printf("type is %T,value is %v", i, i)
}
类型断言
类型断言提供了访问接口值具体值的方式
i.(T)
断言i为T类型,如果断言失败会报错,断言可以返回两个值,可以通过这个返回值进行处理
var i interface{} = "hello"
s := i.(string) //ok
s1, ok := i.(float64)
s2 := i.(float64) // 报错v
类型选择
类型选择与类型断言类似,不过将类型断言的T变为type关键字,只能在switch中使用
func main() {
var i interface{} = "hello"
// s := i.(type)
switch t := i.(type) {
case int:
fmt.Printf("数字类型%v", t)
case string:
fmt.Printf("字符串类型%v", t)
default:
fmt.Printf("不知道")
}
}
错误
Go中使用error接口表示错误
type error interface {
Error() string
}
通常函数会返回一个error值,通过判断是否等于nil进行错误处理
package main
import (
"fmt"
"time"
)
type MyError struct {
When time.Time
What string
}
func (e *MyError) Error() string {
return fmt.Sprintf("at %v, %s",
e.When, e.What)
}
func run() error {
return &MyError{
time.Now(),
"it didn't work",
}
}
func main() {
if err := run(); err != nil {
fmt.Println(err)
}
}
Reader
io包定义了io.Reader
接口,表示从数据流的末尾开始读取,io.Reader接口具有一个Read
方法
Read填充字节切片并返回填充成功的字节数和错误值,到达结尾返回一个io.EOF
错误
func main() {
r := strings.NewReader("Hello , Reader!")
arr := make([]byte, 8) // 定义一个8字节的切片
for {
len, err := r.Read(arr)
fmt.Printf("填充的字节数为%v,错误为%v", len, err)
fmt.Println("")
fmt.Printf("内容为%q", arr[:len])
// 到达结尾返回一个io.EOF错误
if err == io.EOF {
break
}
}
}
图像
image包定义了Image接口
package image
type Image interface {
ColorModel() color.Model
Bounds() Rectangle
At(x, y int) color.Color
}
并发
Go程
Go程(goroutine)是Go运行时管理的轻量级线程
语法:go f(x,y,z)
启动一个线程并执行,f、x、y、z求值在当前Go程,f执行在新的Go程
Go 程在相同的地址空间中运行,因此在访问共享的内存时必须进行同步。
sync
包提供了这种能力,不过在 Go 中并不经常用到
信道
信道是带有类型的管道,通过信道操作符**<-**发送或者接收值。信道在默认情况下发送和接收在另一端准备好之前都会处于阻塞状态。使得Go程没有显示的锁和竞态变量的情况下保持同步
比如现在有send操作,那么则会阻塞在该处,直到另一个goroutine接收数据
demo:
func sum(s []int, c chan int) {
res := 0
for _, value := range s {
res += value
}
c <- res // 把和送入信道
}
func main() {
c := make(chan int) // 需要通过make创建信道
arr := []int{2, 3, 4, 5, -10}
go sum(arr[:len(arr)/2], c)
go sum(arr[len(arr)/2:], c)
x, y := <-c, <-c // 从信道接收值
fmt.Printf("x:%v,y:%v,x+y:%v", x, y, x+y)
}
带缓冲的信道
信道可以带缓冲,缓冲长度作为make第二个参数传入进行初始化,当缓冲区填满后向其发送数据会阻塞。当缓冲区为空时,接收方会阻塞
func main() {
ch := make(chan int, 2)
ch <- 1
ch <- 2
fmt.Println(<-ch)
fmt.Println(<-ch)
}
range和close
发送者可以通过close
关闭一个信道来表示没有需要发送的值了,接收者可以为接收表达式分配第二个参数来测试信道是否被关闭v, ok := <-ch
,也可以使用循环for i := range c
不断从信道接收值,直到它被关闭
func fibonacci(n int, c chan int) {
x, y := 0, 1
for i := 0; i < n; i++ {
c <- x
x, y = y, x+y
}
close(c)// 关闭信道
}
func main() {
c := make(chan int, 10)
go fibonacci(cap(c), c)
// 循环的形式接收
for i := range c {
fmt.Println(i)
}
v,ok:=<-c
for ;ok;{
v,ok=<-c
fmt.Println(v)
}
}
select语句
行为:
除 default 外,如果只有一个 case 语句评估通过,那么就执行这个case里的语句;
除 default 外,如果有多个 case 语句评估通过,那么通过伪随机的方式随机选一个;
如果 default 外的 case 语句都没有通过评估,那么执行 default 里的语句;
如果没有 default,那么 代码块会被阻塞,指导有一个 case 通过评估;否则一直阻塞
select会阻塞到某个分支可以继续执行为止
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
}
}
}
func main() {
c := make(chan int,1)
quit := make(chan int)
go func() {
for i := 0; i < 10; i++ {
fmt.Println(<-c)
}
quit <- 0
}()
fibonacci(c, quit)
}
sync.Mutex
为了保证每次只有一个Go程能够访问一个共享的变量,涉及的概念是互斥(mutual exclusion),使用互斥锁(Mutex)来提供这种机制,Go标准库提供了sync.Mutex
互斥锁类型及其两个方法:
Lock和UnLock
在代码调用前调用Lock方法,在代码调用后使用UnLock方法来保证一段代码的互斥执行,也可以使用defer来保证互斥锁一定会被解锁
package main
import (
"fmt"
"sync"
"time"
)
// SafeCounter 的并发使用是安全的。
type SafeCounter struct {
v map[string]int
mux sync.Mutex
}
// Inc 增加给定 key 的计数器的值。
func (c *SafeCounter) Inc(key string) {
c.mux.Lock()
// Lock 之后同一时刻只有一个 goroutine 能访问 c.v
c.v[key]++
c.mux.Unlock()
}
// Value 返回给定 key 的计数器的当前值。
func (c *SafeCounter) Value(key string) int {
c.mux.Lock()
// Lock 之后同一时刻只有一个 goroutine 能访问 c.v
defer c.mux.Unlock()
return c.v[key]
}
func main() {
c := SafeCounter{v: make(map[string]int)}
for i := 0; i < 1000; i++ {
go c.Inc("somekey")
}
time.Sleep(time.Second)
fmt.Println(c.Value("somekey"))
}