变量/常量
go语言的变量声明主要有两种形式,一种是通过var
声明(常量也可通过const
声明),如下所示:
var t bool //= true,省略后面的赋值时,变量会赋予默认值
//可单个声明,可分组为语法块
const (
x int = 12
f float32 = 1.2
s string = "hello"
)
注意当声明省略了基本类型的时候,会根据赋值进行类型推导
var i = 64 //int
还有一种是短变量声明,通过:=
在类型明确的地方代替var
,这里也用到了类型推导(const
无法这样声明)
i := 12 //int
f := 12.3 //float64
c := 12 + 3i //complex128
类型转换的方法为T(v)
,例如f := float64(i)
流程控制语句
for
for
有三种形式的循环
var sum int = 1
//形式一,有三个条件,与Java的形式区别不大
for i := 1; i < 10; i++ {
sum += i
}
//形式二,有一个条件,类似于Java中的while
for sum < 100 {
sum += sum
}
//形式三,没有条件,是一个无限循环
for {
sum += 1
}
if
if
有两种形式
var x int = -3
//形式一,类似于Java中的if
if x < 0 {
x = -x
}
//形式二,在条件表达式前执行一个简单的语句,语句的变量作用域只在if/else内
if i := 3; i > 0 {
i = -i
} else {
//...
}
defer
defer
会将后面跟的函数推迟到外层函数返回之后执行
注意事项:
- 推迟调用的函数会立即求值,最后被调用
- 被推迟的函数被压入一个栈中,故被调用的顺序为后进先出
fmt.Println("counting")
for i := 0; i < 3; i++ {
defer fmt.Println(i)
}
fmt.Println("done")
/* 结果为
counting
done
2
1
0
*/
更多类型
指针
指针和C++类似
var x int = 13
var y *int = &x
fmt.Println(*y)
结构体
一组字段
type Vertex struct {
X int
Y int
}
v := Vertex{1, 2}
v := Vertex{X: 1} //结构体文法,未初始化的字段会隐式初始化
结构体指针可以直接通过隐式间接引用(同结构体直接访问字段,如p.X
)
数组/切片
注意数组是固定数量的
var a [10]int
想要提供动态大小,就需要切片,切片类似于数组的引用,不存储任何数据,引用了底层的数组,切片有两种声明方式
primes := [6]int{1, 2, 3, 4, 5, 6}
var s1 []int = primes[:4] //方法一,通过数组切片获得
var s2 []int = {1, 2, 3, 4} //方法二,构建一个数组,同时构建一个引用他得切片
var s3 []int = make([]int, 4[, 5]) //方法三,通过make创建len(s3) = 4, cap(s3) = 5
通过func append(s []T, vs ..T) []T
向切片追加元素
var s []int
s = append(s, 0)
s = append(s, 1, 2, 3, 4)
range
for
循环的range
形式可遍历切片或映射
var pow = []int{1, 2, 3, 4}
for i, v := range pow {
fmt.Printf("index is %v, value is %v\n", i, v)
}
//单独获取索引
for i := range pow {
fmt.Printf("index is %v\n", i)
}
映射
映射就是map,将键映射到值,有两种声明方式
var m map[string]int = make(map[string]int) //方式一,通过make创建
var m map[string]int = map[string]int{
"helllo": 1,
"world": 2
} //方式二,文法方式,可创建空映射
修改映射
//修改或添加元素
m[key] = elem
//获得元素
elem = m[key]
elem, ok = m[key]
//删除元素
delete(m, key)
函数闭包
我个人的理解是,一个函数(如func1
)的返回值也是一个函数(如func2
),同时返回的函数中(func2
)引用了了外边函数定义的变量,例:
//内部返回的函数使用了left和right
func fibonacci() func() int {
left := 0
right := 1
return func() int {
cur := left
left = right
right += cur
return cur
}
}
func main() {
f := fibonacci()
//闭包使用过程中会改变所引用的变量,从而导致结果的不同
for i := 0; i < 10; i++ {
fmt.Println(f())
}
}
方法和接口
方法
方法可以当作带接收者参数的函数
type Vertex struct {
X, Y float64
}
//接收者参数位于 func 关键字和方法名之间
func (v Vertex) Abs() float64 {
return math.Sqrt(v.X * v.X + v.Y * v.Y)
}
接收者参数可以分为值接收者和指针接收者,对于值接收者,方法只会对原始值的副本进行操作
type Vertex struct {
X, Y float64
}
//不会修改原始值
func (v Vertex) Scale1(f float64) {
v.X = v.X * f
v.Y = v.Y * f
}
//会修改原始值
func (v *Vertex) Scale2(f float64) {
v.X = v.X * f
v.Y = v.Y * f
}
注:当对象为nil时,go可以通过方法优雅的处理这种情况
type Vertex struct {
X, Y float64
}
func (v *Vertex) M() {
if v == nil {
fmt.Println("<nil>")
return
}
fmt.Println(v)
}
func main() {
var v *Vertex
v.M() //打印<nil>
}
接口
接口类型是由一组方法签名定义的集合,其变量可以保存任何实现了这些方法的值(故无需使用implements关键字显式声明)
type Abser interface {
Abs() float64
}
type MyFloat float64
func (f MyFloat) Abs() float64 {
if f < 0 {
return float64(-f)
}
return float64(f)
}
func main() {
var a Abser
a = MyFloat(-math.Sqrt2)
fmt.Println(a.Abs())
}
在内部,接口值可以看做包含值和具体类型的元组:(value, type)
接口为nil的值既不保存值也不保存具体类型,在调用方法是会产生运行错误
type I interface {
M()
}
func main() {
var i I
describe(i)
i.M()
}
func describe(i I) {
fmt.Printf("(%v, %T)\n", i, i)
}
/*打印
(<nil>, <nil>)
panic: runtime error: invalid memory address or nil pointer dereference
*/
指定了零个方法的接口值被称为空接口:interface{}
,空接口可保存任何类型的值
类型断言提供了访问接口值底层具体值的方式,有两种断言方式
type Vertex struct {
X, Y float64
}
func main() {
var i interface{} = Vertex{1, 2}
//当接口未保存此类型的值,会触发恐慌
s := i.(Vertex)
fmt.Println(s)
//用这种方式来判断是否保存了特定类型
s, ok := i.(float64)
if ok {
fmt.Println(s)
return
}
}
并发
Go程
Go运行时管理的轻量级线程
//f,x,y和z的求值发生在当前的Go程中,而f的执行发生在新的Go程中
func f(x, y, z int) {
//...
}
func main() {
go f(1, 2, 3)
}
线程通讯/同步
信道
信道是带有类型的管道,你可以通过它用信道操作符<-
来发送或者接收值
//信道的创建
ch := make(chan int)
ch2 := make(chan int, 5) //带缓冲的信道,仅当信道的缓冲区填满后,向其发送数据时才会阻塞。当缓冲区为空时,接受方会阻塞
//信道的发送
sum := 100
ch <- sum
//信道的接收
s1 := <-ch
for i := range ch {
//...
}
发送者可通过close
关闭信道,表示没有需要发送的值
ch := make(chan int, 10)
for i := 0; i < 10; i++ {
ch <- i
}
close(ch)
通过range
可以不断从信道中接收值,直到信道无可接收数据且被关闭
//接上文
//close(ch)
for i := range ch {
fmt.Println(i)
}
注:信道与文件不同,通常情况下无需关闭它们。只有在必须告诉接收者不再有需要发送的值时才有必要关闭
当需要从多个通讯中仅等待一个通讯的执行完才能进行后续操作时,可以用select
语句
func fibonacci(c, quit chan int) {
x, y := 0, 1
for {
select {
//三个case一趟循环只能执行其中一个
case c <- x:
x, y = y, x+y
case <-quit:
fmt.Println("quit")
return
default: //其他分支都没准备好,就执行这个
fmt.Println("default")
return
}
}
}
sync.Mutex
go标准库中实现互斥概念的数据结构为互斥锁(mutex)
var mux sync.Mutex
mux.Lock()
//...
mux.UnLock() //可以用 defer mux.UnLock()来保证一定会被解锁