因工作需要,正在了解Go
的相关知识…
goroutine
和channel
go
语言的goroutine
和channel
所谓goroutine
是程序中与其他goroutine
完全独立而并发执行的函数或者方法调用。类似于轻量级的线程 ;不同的goroutine
之间的通讯使用channel
;
go
语言的运行:
- 运行源代码:
go run hello.go
- 编译链接为可执行程序:
go build hello.go
goroutine
goroutine
的创建:
- go function(arguments)
- go func(parameters) { block } (arguments)
channel
goroutine
之间的相互协作:通过channel
来交换数据:
-
make(chan Type)
-
make(chan Type, capacity)
capacity
指定缓冲区容量
通道支持的操作:
语法 | 含义 |
---|---|
channel <- value | 发送value到通道中,有可能阻塞 |
<-channel | 从通道中接收数据 |
x := <-channel | 接收数据并赋值给x |
x, ok := <-channel | 功能同上,同时检查通道是否已关闭或者是否为空 |
匿名函数
示例
分组求和:
package main
import "fmt"
func sum(a []int, result chan int) {
sum := 0
for _, v := range a {
sum += v
}
result <- sum
}
func main() {
a := []int{2, 3, 5, 6, 10, -5, 1, 0}
result := make(chan int)
go sum(a[:len(a)/2], result)
go sum(a[len(a)/2:], result)
x, y := <-result, <-result
fmt.Println(x, y, x+y)
}
格式化go
代码
使用go fmt <文件>.go
,将代码格式化,否则编译不通过
标示符(变量名,函数名)
第一个字符必须是字母
空标示符_
是一个占位符,它用于在赋值操作的时候将某个值赋值给空标示符号,从而达到丢弃该值的目的。
不能单独将’_'放于‘:=’操作符号的左边
count, err = fmt.Println(x) // 获取打印的字节数以及相应的error值
count, _ = fmt.Println(x) // 获取打印的字节数,并且丢弃error值
常量和变量
在Go语言中,声明变量的时候类型名总是在变量名的后面。
变量可以使用关键字var
声明,Go语言可以自动推断出声明变量的类型。
const limit = 512
const top uint16 = 1421
last := 1.5
var a int
var debug = false
当声明多个常量时:
const (
Cyan = 0
Black = 1
White = 2
)
类型转换
type(value)
,只要合法就能转换成功。
整型
类型 | 说明 |
---|---|
byte | 等同于uint8 |
int | 依赖于不同平台下的实现,可以是int32或者int64 |
int8 | [-128, 127] |
int16 | [-32768, 32767] |
int32 | [-2147483648, 2147483647] |
int64 | [-9223372036854775808, 9223372036854775807] |
rune | 等同于uint32 |
uint | 依赖于不同平台下的实现,可以是uint32或者uint64 |
uint8 | [0, 255] |
uint16 | [0, 65535] |
uint32 | [0, 4294967295] |
uint64 | [0, 18446744073709551615] |
uintptr | 一个可以恰好容纳指针值的无符号整型(对32位平台是uint32, 对64位平台是uint64) |
类似于c语言中的sizeof
,在Go语言中可以使用unsafe
包中的unsafe.Sizeof()
函数达到同样的效果。
浮点类型
类型 | 说明 |
---|---|
float32 | ±3.402 823 466 385 288 598 117 041 834 845 169 254 40x1038计算精度大概是小数点后7个十进制数 |
float64 | ±1.797 693 134 862 315 708 145 274 237 317 043 567 981x1038计算精度大概是小数点后15个十进制数 |
complex32 | 复数,实部和虚部都是float32 |
complex64 | 复数,实部和虚部都是float64 |
字符串
语法 | 描述 |
---|---|
s += t | 将字符串t追加到s末尾 |
s + t | 将字符串s和t级联 |
s[n] | 从字符串s中索引位置为n处的原始字节 |
s[n:m] | 从位置n到位置m-1处取得的字符(字节)串 |
s[n:] | 从位置n到位置len(s)-1处取得的字符(字节)串 |
s[:m] | 从位置0到位置m-1处取得的字符(字节)串 |
len(s) | 字符串s中的字节数 |
len([]rune(s)) | 字符串s中字符的个数,可以使用更快的方法utf8.RuneCountInString() |
[ ]rune(s) | 将字符串s转换为一个unicode值组成的串 |
string(chars) | chars类型是[]rune或者[]int32, 将之转换为字符串 |
[ ]byte(s) | 无副本的将字符串s转换为一个原始的字节的切片数组,不保证转换的字节是合法的UTF-8编码字节 |
若要遍历字符串中的每一个字符(非字节),使用range
。
处理字符串官方包
strings
包提供了如查找字符串,分割字符串,判断前后缀,判断字符串包含,字符串替换,统计字符串出现的次数等常用操作;详细的参考官方:https://golang.org/pkg/strings/。
strconv
包提供了许多可以在字符串和其他类型的数据之间进行转换的函数。可以将数字转换为字符串,将数字样式的字符串转换为数值(将转字符串“12345”转换为int类型的整数)。
值、指针和引用类型
package main
import (
"fmt"
)
func swap1(x, y, p *int) {
if *x > *y {
*x, *y = *y, *x // 该句的底层操作流程
}
*p = *x * *y
}
func swap2(x, y int) (int, int, int) {
if x > y {
x, y = y, x
}
return x, y, x * y
}
func main() {
i := 9
j := 5
product := 0
swap1(&i, &j, &product)
fmt.Println(i, j, product)
a := 64
b := 23
a, b, p := swap2(a, b)
fmt.Println(a, b, p)
}
数组和切片
Go中的数组的长度固定且不可修改,创建数组的语法:
[length]Type
[N]Type{value1, value2, ..., valueN}
[...]Type{value1, value2, ..., valueN} // 当使用'...'时,Go语言会自动计算数组的长度
切片
创建切片的语法:
make([ ]Type, length, capacity)
make([ ]Type, length)
[ ]Type{}
[ ]Type{value1, value2, ..., valueN}
长度和容量,是不同的概念,当执行append
之后,切片的capacity的增加问题。
映射(map)
创建map
的语法:
- make(map[KeyType]VauleType, initialCapacity)
- make(map[KeyType]ValueType)
- map[KeyType]ValueType{ }
- map[KeyType]ValueType{key1: value1, key2: value2, …, keyN: valueN}
map
支持的操作:
m[k] = v | 用键k来将值赋值给映射m。如果映射m中的k已存在,则将之前的值舍弃 |
---|---|
delete(m, k) | 将键k及其相关的值从映射m中删除,如果k不存在则不执行任何操作 |
v := m[k] | 从映射m中取得键k相对应的值并赋值给v。如果k不存在,则将映射类型的0值赋值v |
v, found := m[k] | 从映射m中取得键k相对应的值并赋值给v, 并将found的值赋值为true。如果k不存在,则found为false |
len(m) | 返回映射m中的项 |
k := range m | 遍历映射m中的键 |
k, v := range m | 同时遍历映射中的键和值 |
Go语言语句基础
Go语言也支持多重赋值,a, b = b, a
;
快速声明操作符:=
,它的作用是同时在一个语句中声明和赋值一个变量。
但:=
位于一个作用域的起始处时,Go语言会创建一个新的变量。
此外,Go语言必须使用;
的地方:
// 当在一行中放入一条或者多条语句时,需要使用;
a, b, c := 2, 3, 5
for a := 7; a < 8; a++ {
fmt.Println(a)
}
类型转换
resultOfType := Type(expression)
也可以使用type
关键字声明类型:
type StringsSlice []string
类型断言
接口:interface{}
可以用于表示任意类型,类似于object
,因此有时候需要将interface{}
类型转换为我们需要的类型,这个操作称为类型断言。
resultOfType, boolean := expression.(Type) // 安全的类型断言
resultOfType := expression.(Type) // 非安全的类型断言,失败时程序会产生异常
if s, ok := s.([]string); ok { // 创建了影子变量,if的作用域中覆盖了外部的变量s
fmt.Printf("%T -> %q\n", s, s)
}
分支语句
if optionalStatement1; booleanExpression1 {
block1
} else if optionalStatement2; booleanExpression2 {
block2
} else {
block3
}
其中optionalStatement
是可选的表达式,真正决定分支走向的是booleanExpression1
的值。
函数
main
函数是特殊的函数,main
函数必须出现在main
包中,main
函数不可接收任何参数:
func functionName(optionalParameters) optionalReturnType {
block // func 函数名(参数列表) 单个返回值类型
}
func functionName(optionalParameters) (optionalReturnValues) {
block // func 函数名(参数列表) (返回值1 类型,返回值2 类型)
}
函数可以有任意多个参数,也可以有任意多个返回值:
func func1(first int, rest ...int) int {
return first // func1 函数可以接收多个int类型的参数,并且返回一个int类型的值
}
func func2(first int, second string) (int, string) {
return first, second // func2 接收两个参数,并且返回一个int和string类型的值
}
func func3(first int, second string) (a, b int) {
a, b = 1, 2 // func3 接收两个参数,并返回两个int类型的值,因为返回值是命名的,所以这里可以缩写
return
}
defer
,panic
和recover
defer
当存在多条defer
时,以后进先出的顺序执行。
defer
语句用于延迟执行一个函数或者方法或者是当前创建的匿名函数,它会在外部函数或者方法返回之前但是其返回值计算之后执行,类似于python语言中的with
;
panic
和recover
当recover
捕捉到panic
引发的异常的时候,将会停止panic
的传播;
(练习)打印n以内的素数
package main
import (
"fmt"
"math"
)
func exec(n int) (result []int) {
if n == 2 || n == 3 {
return append(result, n)
}
for a := 2; a <= n; a++ {
if ok := isexec(a); ok {
result = append(result, a)
}
}
return result
}
func isexec(n int) (ok bool) {
if n == 2 || n == 3 {
return true
}
for a := 2; a <= int(math.Sqrt(float64(n))); a++ {
if n%a == 0 {
return false
}
}
return true
}
func main() {
result := exec(100)
fmt.Printf("%d", result)
}
GO
语言的面向对象
Go语言以包结构来组织代码,Go语言不支持继承,只支持聚合;当标示符以大写字母开头,那么这些标示符可以导出,可以导入到其他包中使用。
自定义类型
自定义类型中的匿名字段:
type Part struct { // 基于结构体创建自定义类型 Part
stat string
Count // 匿名字段
}
方法
定义方法和定义函数几乎相同,但是需要在func
关键字和方法名之间写上接收者,也就是能够调用该方法的对象的类型指针或者值类型:
type Count int
func (count *Count) Increment() { *count++ } // 接受者是一个`Count`类型的指针
func (count *Count) Decrement() { *count-- }
func (count Count) IsZero() bool { return count == 0 }
- 一个指向自定义类型的值的指针,它的方法集由该类型定义的所有方法组成,无论这些方法接受的是一个值还是一个指针。如果在指针上调用一个接受值的方法,Go语言会聪明的将该指针解引用。
- 一个自定义类型值的方法集合则由该类型定义的接收者为值类型的方法组成,但不包括那些接收者类型为指针的方法。(如果使用
值
去调用接收者为指针类型
的方法,仍然可以成功,这是因为Go语言会自动获取值的地址传递给该方法,前提是该值是可寻址的)
接口
接口的实现和方法的重载:
package main
import "fmt"
type Human struct { // 结构体
name string
age int
phone string
}
//Human实现SayHi方法
func (h Human) SayHi() {
fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone)
}
//Human实现Sing方法
func (h Human) Sing(lyrics string) {
fmt.Println("La la la la...", lyrics)
}
type Student struct {
Human //匿名字段
school string
loan float32
}
type Employee struct {
Human //匿名字段
company string
money float32
}
// Employee重载Human的SayHi方法
func (e Employee) SayHi() {
fmt.Printf("Hi, I am %s, I work at %s. Call me on %s\n", e.name,
e.company, e.phone)
}
//--------------------------------------------------------------
// Interface Men被Human,Student和Employee实现
// 因为这三个类型都实现了这两个方法
type Men interface {
SayHi()
Sing(lyrics string)
}
此实现非彼实现
接口变量值的断言
当需要获得接口变量中的值的具体类型,使用类型断言:
var men Men
men = Human{"Li Ming", 18, "10010"}
switch value := men.(type) {
default:
fmt.Print("default")
case Men:
fmt.Print("Men")
case Human:
fmt.Print("Human name:", value.name)
}
嵌入interface
type Interface1 interface {
Send()
Receive()
}
type Interface2 interface {
Interface1
Close()
}
interface2
中包含了interface1
中的所有方法;