0%

golang语法

基础语法

代码规则

1
2
3
4
5
6
7
8
9
10
11
12
package main//文件必须声明包名
import
"fmt"

func main(){
var stockcode = 123//结尾不需要分号
var enddate = "2020-12-31"
var url = "Code = %d&endData = %s"
var target_url = fmt.Sprintf(url, stockcode, enddate)//类似C语言的sprintf
fmt.Println(target_url)
//标识符以大写字母开头意味着该对象对包外可见(public),否则则对包外不可见
}

基本类型

uint,uint8,uint16,uint32,uint64,int,int8,int16,int32,int64,float32,float64,complex64(32位实数和虚数),complex128(64位实数和虚数),uintptr(用于存放指针)

变量声明:

1
2
3
4
5
6
7
8
9
10
11
12
13
var x, y int//如果变量没有初始化,则默认为零值
var (
a int
b bool
)
var c, d int = 1, 2//可以并行赋值
var e, f ,_= 123, "hello", "discarded"//可以是不同类型,还可以用空白标识符_来抛弃值
func main(){
g, h := 123, "hello"//用:=初始化语句的用法只能在函数体中出现
}
var binarynum int = 0b1100//二进制数字
var eightnum int = 0o14//八进制
var hexnum int = 0xc//十六进制

浮点型

float32,即单精度,一位符号位,八位指数位,23位尾数位

float64,即双精度,一位符号位,十一位指数位,52位尾数位

精度主要取决于尾数位数,$$ 2^{-23} \approx 1.19*10^{-7}$$,因此float只能精确到后面6位,有效数字为7位

而$$2^{-52}\approx 2.22*10^{-16}$$,因此精确到小数点后15位,有效位数为16位

  • math.MaxFloat32math.MaxFloat64分别大约是3.4e38和1.8e308
  • float32float64能表示的最小值分别是1.4e-45和4.9e-325

byte与rune

byte占用一个字节,与uint8类型本质上没有区别

1
2
3
var a byte = 65
var b byte = '\101'//\是八进制前缀
var c byte = '\x41'//\x是十六进制前缀

rune,占用四个字节,和int32本质上也没有区别,表示一个Unicode字符

1
var name rune = '中'

string

Go语言的string是用utf-8编码的,英文字母占用一个字节,而中文字母占用3个字节

可以使用双引号或反引号包裹字符串,反引号包裹的字符串会忽略转义

可以用fmt的%q来将原生型字符串还原成解释型字符串

1
2
3
var mystr0 string = `\r\n` \\原生型
var mystr2 string = "\\r\\n"\\解释型
fmt.Printf("%v 的解释型字符串是: %q",mystr01)

匿名变量

又称占位符,或者空白标识符,用下划线表示,其有三个优点

  • 不分配内存,不占用内存空间
  • 不需要你为命名无用的变量名而纠结
  • 多次声明不会有任何问题
1
2
3
4
5
6
7
8
func GetData() (int, int) {
return 100, 200
}
func main(){
a, _ := GetData()
_, b := GetData()
fmt.Println(a,b)
}

new函数

用new创建变量和普通变量声明语句创建变量没有什么区别,除了不需要声明一个临时变量的名字外,我们还可以在表达式中使用new(Type)。

换言之,new函数类似是一种语法糖,而不是一个新的基础概念

1
2
3
4
5
6
7
8
func newInt() *int {
return new(int)
}
//is equivilant to
func newInt() *int {
var dummy int
return &dummy
}

数组与切片

1
2
3
var a [5]int//数组声明
arr01 := [...] int{1, 2, 3}
arr := [4]int{2:3}//在下标为2处初始化为3

切片是数组的抽象,其本身是引用类型。其包含容量、长度和指向底层数组的指针

1
2
3
4
5
6
7
8
9
10
//依赖于数组的声明方式
arr := [...]int{1, 2, 3, 4, 5}
slice1 := arr[1:3]
slice2 := arr[1:3:3]//第三个数影响切片容量,arr[a:b:c]的切片容量为c-a
//空切片声明
var strSlice []string
intSlice := []int{}
//使用make函数构造,make([]Type, size, cap),第三个参数可选
numbers := make([]int, 5,10)//声明初始长度为5,容量为10的切片

1
2
3
4
5
6
7
slice := []int{1}
slice = append(slice, 2)//追加一个元素
slice = append(slice, 3, 4)//追加多个元素
slice = append(slice, []int{7, 8}...)//追加一个切片,其中...表示解包
slice = append([]int{0},myarr...)//在第一个位置插入一个切片
slice = append(myarr[:5],append([]int{5,6}, myarr[5:]...)...)//在中间插入一个切片
fmt.Println(slice)

切片的子切片:

1
2
3
4
5
6
7
number2 := []int{1, 2, 3, 4}
slice1 := number2[2:]
fmt.Println(slice1)// ->[3 4]
slice2 := number2[:3]
fmt.Println(slice2)// ->[1 2 3]
slice3 := number2[1:4]
fmt.Println(slice3)// ->[2 3 4]

const与iota

在定义常量组时,如果不提供初始值,则表示将使用上行的表达式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package main
func main(){
//iota 只是在同一个 const 常量组内递增,每当有新的 const 关键字时,iota 计数会重新开始
const (
a = iota //0
b //1
c //2
d = "ha" //"ha"
e //"ha"
f = 100 //100
g //100
h = iota //7
i //8
)
}

类型别名

使用type可以定义一个类型字面量

1
type arr3 [3]int

Chan类型

go后加一个函数,就可以创建一个线程,函数可以为已定义的,也可以是匿名的

1
2
3
4
5
6
7
8
9
10
11
12
13
package main
import "fmt"
func main(){
fmt.Println("main start")
go func(){
fmt.Println("goroutine")//由于Go语言的线程是并发机制,所以这行实际来不及执行
}()
fmt.Println("main end")
}
/*
main start
main end
*/

让线程执行的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package main
import "fmt"
import "time"
func main(){
fmt.Println("main start")
go func(){
fmt.Println("goroutine")
}()
time.Sleep(1*time.Second)
fmt.Println("main end")
}
/*
main start
goroutine
main end
*/

Chan可以理解为队列,通过Chan,并发核心单元可以发送或者接收数据进行通讯,它的操作符是<-

1
2
3
4
5
6
7
8
9
10
11
ch1 := make(chan string)//声明不带缓冲的通道,进和出都会阻塞
ch2 := make(chan string, 10)//声明缓冲区大小为10的通道,当长度等于10时,再进就会阻塞
ch3 := make(<-chan string)//声明只读通道
ch4 := make(chan<- string)//声明只写通道
ch2 <- "a"//写入chan
val, ok := <- ch2
//or
val := <- ch2//读取chan
close(ch2)//关闭chan
//注意只读的chan不能close
//close以后还可以读取,但不能再写入

从无缓存的 channel 中读取消息会阻塞,直到有 goroutine 向该 channel 中发送消息;

同理,向无缓存的 channel 中发送消息也会阻塞,直到有 goroutine 从 channel 中读取消息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package main
import "fmt"
func main(){
fmt.Println("main start")
ch := make(chan string)
go func(){
ch <- "a"
}()
go func(){
ele <- ch
fmt.Println(ele)
}()
time.Sleep(1* time.Second)
fmt.Println("main end")
}

使用range遍历

1
2
3
4
ch := make(chan int, 10)
for x := range ch{//一直从ch中读取数据直到该ch被close
fmt.Println(x)
}

map类型

map是哈希表的一个实现,这就要求它的每个映射里的key都是唯一的,可以使用==!=来进行判等操作,换句话说key必须是可哈希的

1
2
3
4
5
6
7
8
9
var scores map[string]int//纯声明,此时scores的值为nil,无法赋值
scores1 := map[string]int{"english": 80, "chinese": 85}
scores2 := make(map[string]int)
scores2["english"] = 80//添加元素
scores2["chinese"] = 85
scores2["english"] = 140//更新元素
fmt.Peintln(scores2["english"]) //根据key读取value,如果key不存在则返回这个value的零值
delete(scores2, "english")//根据key删除元素,如果key不存在则静默处理
chinese, ok = scores2["chinese"]//ok表示是否存在

对字典进行循环

1
2
3
4
5
6
7
8
9
10
scores1 := map[string]int{"english": 80, "chinese": 85}
for subject, score := range scores1 {
...
}
for subject := range scores1 {
...
}
for _, score := range scores1 {
...
}

布尔类型

在Go语言中,不同类型间无法比较,因而以下表达式是错误的

1
2
3
4
5
//编译错误的表达式
var male bool = true
if male == 1 {//不能将布尔类型与整数类型进行比较
...
}

控制流语句

if-else

Go要求else ifelse 两边的花括号必须在同一行,且要求条件表达式必须严格返回布尔类型的数据

在if 里可以允许先运行一个表达式,取得变量后,再对其进行判断

1
2
3
if age := 18; age >= 18 {
fmt.Println("已经成年了")
}

switch

switch从第一个判断表达式为true 的case开始执行,不需要break

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package main
import "fmt"
func main(){
var x interface{}
switch i := x.(type) {
case nil:
fmt.Printf("The type of x is %T",i)
case int:
fmt.Printf("The type of x is int")
case func(int) float64:
fmt.Printf("The type of x is func(int)")
case bool, string://多条件匹配
fmt.Printf("The type of x is bool or string")
default:
fmt.Printf("Unknown type")
}
}
//The type of x is <nil>

需要强制执行下一条的时候可以使用fallthrough

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package main 
import "fmt"
func main(){
switch{
case false:
fmt.Println("1")
fallthrough
case true:
fmt.Println("2")
fallthrough
case false:
fmt.Println("3")
}
}
/*
2
3
*/

for

for后面可以接的表达式有:

  • 条件表达式
  • 三个表达式(init; condition; increment)
  • range 表达式
  • 不接表达式
1
2
3
4
5
6
i := 0
sum := 0
for i < 10 {//类似于C中的while
sum += 1
i++
}
1
2
3
4
sum := 0
for i := 0; i < 10; i++ {
sum += i
}
1
2
3
4
arr := [...]string{"xiong", "fang"}
for _, item := range arr {//range返回值为 index,item,在这里不需要index
fmt.Printf("hello, %s \n",item)
}

goto

Go语言的goto语句与标签之间不能有变量声明,否则会编译错误

1
2
3
4
5
//编译错误
goto flag
var say = "This is wrong"
flag:
...

defer延时调用

defer后跟一个表达式,意味着在所处函数执行完之后再调用该表达式

1
2
3
4
5
6
7
func myfunc() {
fmt.Println("B")
}
func main(){
defer myfunc()
fmt.Println("A")
}

defer只是延时调用,变量已经传递过去了,且是反序调用,后进先出

1
2
3
4
5
6
7
8
9
10
11
12
13
func main() {
name := "go"
defer fmt.Println(name)
name = "rust"
defer fmt.Println(name)
name = "c++"
fmt.Println(name)
}
/*
c++
rust
go
*/

defer在return之后调用

1
2
3
4
5
6
7
8
9
10
11
12
13
var name string = "go"
func myfunc() string{
defer func() {
name = "python"
}()
fmt.Println("The name in myfunc is ", name)
return name
}
func main(){
myname := myfunc()
fmt.Println("The name in main is ", name)
fmt.Println("The myname in main is ", myname)
}

select

select 的用法类似于IO多路复用,可以同时监听的多个channel的消息状态

1
2
3
4
5
6
7
8
9
10
select {
case <- ch1:
...
case <- ch2:
...
case ch3 <- 10:
...
default:
...
}
  • select 可以同时监听多个channel的写入或读取
  • 执行select时,若只有一个case通过,则执行这个case块
  • 若有多个case通过,则伪随机挑选一个case执行
  • 若所有case都阻塞,且定义了default模块,则执行default模块。若无default模块则select语句阻塞
  • 使用break会跳出select块

设置超时时间:

1
2
3
4
5
6
7
8
9
10
ch := make(chn struct {})
//finish task while send msg to ch
go doTask(ch)
timeout := time.After(5 * time.Second)//time.After返回一个5s后产生信号的管道
select {
case <- ch:
fmt.Println("task finished.")
case <- timout:
fmt.Println("task timeout.")
}

quite channel:

1
2
3
4
5
6
7
8
9
10
11
msgCh := make(chan struct {})
quitCh := make(chan struct {})
for {
select {
case <- msgCh:
doWork()
case <- quitCh:
finish()
return
}
}

函数

函数可以返回多个值

1
2
3
4
func add (a int, b int) (int,string){
c := a + b
return c, "successfully added"
}

函数的返回值也可以预先定义

1
2
3
4
func ad(a int, b int) (c int) {
c = a + b
return
}

方法

方法能给用户自定义的类型添加新的行为。它和函数的区别在于方法有一个接收者。接收者可以是值,也可以是指针

错误处理

panic函数

1
panic("Some error here")//panic: Some error here

recover函数

recover函数可以捕获异常,恢复程序或做收尾工作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
func set_data(x int) {
defer func(){
//recover()只能在defer域中才能生效
if err := recover(); err != nil {
fmt.Println(err)
}
}()
var arr [10]int
arr[x] = 88//故意制造数组越界
}
func main() {
set_data(20)
fmt.Println("everything is ok")
}
/*output
runtime error: index out of range [20] with length 10
everything is ok
*/

面向对象

struct 结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
type Profile struct {
name, gender string//相同类型的字段可以合并
age int
mother, father *Profile
}
func (person Profile) FmtProfile(){
fmt.Println("name: ",person.name)
fmt.Println("age: ",person.age)
fmt.Println("gender: ",person.gender)
}
func main(){
xf := Profile{name: "熊昉"}//未被赋值的字段会自动赋为零值
xf.gender = "male"
xf.age = 18
xf.FmtProfile()
}

改变对象的方法

1
2
3
func (person *Profile) increase_age() {
person.age += 1
}

用组合的方式实现继承

1
2
3
4
5
6
7
8
9
10
type company struct {
companyName string
companyAddr string
}
type staff struct {
name string
age int
gender string
company//匿名字段
}

接口

接口指定了一个类型应该具有的方法,并由该类型决定如何实现这些方法

1
2
3
4
5
6
7
8
9
type Nokia struct {
name string
}
type Phone interface {
call()
}
func (phone Nokia ) call(){
fmt.Println("I'm a phone called Nokia")
}

空接口

普通的接口都有方法,而空接口没有定义任何方法,因此我们可以说所有类型都至少实现了空接口

对于空接口,其值和类型都是nil

1
2
var i interface{}
fmt.Println("type: %T, value: %v",i,i)//type: <nil>, value: <nil>

不能把空接口类型的对象赋值给一个固定类型,也不能对空接口类型的对象切片

当使用空接口来接收任意类型的参数时,它的静态类型是interface{},但动态类型我们并不知道,因此需要使用类型断言

类型断言

类型断言可以

  • 检查i是否为nil
  • 检查i存储的值是否为某个类型
1
2
3
4
5
var i interface{} = 10
t1, ok := i.(int)
fmt.Println(t1, "-", ok)//10-true
t2, ok := i.(string)
fmt.Println(t2, "-", ok)//-false

Type Switch

在需要区分多种类型的时候,用type switch断言会更简单高效

1
2
3
4
5
6
7
8
9
10
11
12
func findType(i interface{}){
switch x := i.(type) {
case int:
fmt.Println(x, "is int")
case string:
fmt.Println(x, "is string")
case nil:
fmt.Println(x, "is nil")
default:
fmt.Println(x "not type matched")
}
}

类型断言仅能对静态类型为空接口(interface{})的对象进行断言

断言完成后,实际上会返回静态类型为你断言的类型的对象,而原有的静态类型仍是空接口类型

接口实现多态

一个接口在不同对象上的不同表现就是多态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
type Good interface {
settleAccount() int
orderInfo() string
}//定义接口
type Phone struct {
name string
quantity int
price int
}
type FreeGift struct {
name string
quantity int
price int
}
func (phone Phone)settleAccount() int{
return phtone.quantity * phtone.price
}
func (phone Phone) orderInfo() string{
return "你要购买" + strconv.Itoa(phone.quantity) + "个" + phone.name + "计:" +
strconv.Itoa(phone.settleAccount()) + "元"
}
func (gift FreeGift) settleAccount() int{
return 0
}
func (gift FreeGift) orderinfo() string{
return "你要购买" + strconv.Itoa(gift.quantity) + "个" + gift.name + "计:" +
strconv.Itoa(gift.settleAccount()) + "元"
}
//实现了这两个方法后,这两个类型在Go语言看来就都是商品类型了
func calculateAllPrice(goods []Good) int{
var allPrice int
for _,good := range goods{
fmt.Println(good.orderInfo())
allPrice += good.settleAcccount()
}
return allPrice
}

Tag

Tag 可以用来增强结构体的定义,Tag会带上一些元信息,不管转义字符串还是原始字符串都可以用来当Tag

1
2
3
4
5
6
type T struct {
f1 string " f one"
f2 string
f3 string `f three`
f4,f5 int64 `f four and five`
}

Tags可以由键值对组成,通过使用这样的格式可以使用LookUp函数获取键值对的值

1
2
3
type T2 struct {
f string `one:"1" two:"2" blank: ""`
}

Tag获取方式

需引入reflect包,通过TypeOf函数绑定类型,通过FieldByName函数获取对应字段的field

1
2
3
4
5
6
7
t := reflect.TypeOf(T{})
f1, _ := t.FieldByName("f1")
fmt.Println(f1.Tag)//f one
f4, _ := t.FieldByName("f4")
fmt.Println(f4.Tag)//f four and five
f5, _ := t.FieldByName("f5")
fmt.Println(f5.Tag)//f four and five

使用Lookup方法

1
2
3
4
5
t := reflect.TypeOf(T2{})
f, _ := t.FieldByName("f")
fmt.Println(f.Tag)//one:"1" two:"2" blank: ""
v, ok := f.Tag.Lookup("one")
fmt.Println("%s, %t", v, ok)

Get方法只是简单的包装了Lookup,但是丢弃了是否成功的结果

1
2
3
4
func (tag StructTag) Get(key string) string{
v, _ := tag.Lookup(key)
return v
}

进阶语法

反射

先看维基百科上的定义

在计算机科学中,反射是指计算机程序在运行时可以访问、检测和修改它本身状态或行为的一种能力。

Go语言圣经:

Go 语言提供了一种机制在运行时更新变量和检查它们的值、调用它们的方法,但是在编译时并不知道这些变量的具体类型,这称为反射机制

reflect.Value类型和reflect.Type类型

1
2
3
4
var x float64 = 3.4
fmt.Println("type:",reflect.TypeOf(x))//type: float64
//在reflect.TypeOf的函数签名里包含一个空接口
fmt.Println("value:",reflect.ValueOf(x))//value: 3.4

kind方法

reflect.Valuereflect.Type都有一个名为Kind的方法,它会返回一个常量,表示底层数据的类型

Value类型也有一些类似于IntFloat的方法,用来提取底层的数据

1
2
3
4
5
var x float64 = 3.4
v := reflect.ValueOf(x)
fmt.Println("type:", v.Type())
fmt.Println("kind is float64:", v.Kind() == reflect.Float64)
fmt.Println("value:", v.Float())

Slice()

Slice()跟其他类型转换的函数不一样,它返回的还是reflect.Value反射对象

Interface方法

可以根据一个reflect.Value类型的变量恢复其接口类型的值,事实上,这个方法会把type和value信息打包并填充到一个接口变量中,然后返回

其函数声明如下:

1
func (v Value) Interface() interface{}

CanSet方法

1
2
3
var x float64 = 34
v := reflect.ValueOf(x)//传递的是x的拷贝,而非x本身
fmt.Println("settability of v:", v.CanSet())//settability of v: false

传递指针则可写

1
2
3
4
5
6
7
var x float64 = 3.4
p := reflect.ValueOf(&x)
v := p.Elem()
fmt.Println("settability of v:", v.CanSet())//settability of v: true
v.SetFloat(7.1)
fmt.Println(v.Interface())//7.1
fmt.Println(x)//7.1

json包

1
2
3
4
5
6
7
8
9
10
package main
import (
"fmt"
"encoding/json"
)
func main(){
mapA := map[string] int{"apple": 5, "lettuce" :7}
mapB,_:=json.Marshal(mapA)//编码
fmt.Println(string(mapB))
}
1
2
3
4
5
6
7
8
package main
import (
"fmt"
"encoding/json"
)
type response struct{
PageNumber int `json:"page"`