Go语言 语法详解笔记(上)

Posted Golang语言社区

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Go语言 语法详解笔记(上)相关的知识,希望对你有一定的参考价值。

Google Go语言 golang 语法详解笔记

Author:cxy

Date:2015-06-26

Version:1.0

Source:Fork me on GitHub

Blog:yougg.github.io/static/gonote/gogrammar

Description:学习Go语言过程中记录下来的语法详解笔记,可以帮助新接触的朋友快速熟悉理解Golang,也可以作为查询手册翻阅。其中若有错误的地方还请指正,或者在GitHub直接fork修改。

本文使用LiteIDE的Markdown编辑器编写,博客为LiteIDE导出的单页html,可以直接保存离线使用。


包 Package


包的声明 Declare

包的导入 Import

包内元素的可见性 Accessability

数据类型 Data Type


基础数据类型 Basic data type

变量 Variable

常量 Constant

数组 Array

切片 Slice

字典/映射 Map

结构体 Struct

指针 Pointer

通道 Channel

接口 Interface

自定义类型

语句 Statement


分号/括号 ; {

条件语句 if

分支选择 switch

循环语句 for

通道选择 select

延迟执行 defer

跳转语句 goto

函数 Function


函数声明 Declare

函数闭包 Closure

内建函数 Builtin

初始化函数 init

方法 Method

并发 Concurrency


测试 Testing


单元测试 Unit

基准测试 Benchmark

包 Package

包的声明 Declare

使用package关键字声明当前源文件所在的包

包声明语句是所有源文件的第一行非注释语句

包名称中不能包含空白字符

包名推荐与源文件所在的目录名称保持一致

每个目录中只能定义一个package


package cxy     // 声明一个名为“cxy”的包


package 我的包   // 声明一个名为“我的包”的包


package main    // main包, 程序启动执行的入口包

错误的包声明


package "mypkg" // 错误


package a/b/c   // 错误


pakcage a.b.c   // 错误

包的导入 Import

导入包路径是对应包在$GOROOT/pkg/$GOOS_$GOARCH/、$GOPATH/pkg/$GOOS_$GOARCH/或当前路径中的相对路径


// 导入$GOROOT/$GOOS_$GOARCH/中的相对路径包(官方标准库)

import "fmt"

import "math/rand"


// 导入$GOPATH/$GOOS_$GOARCH/中的相对路径包

import "github.com/user/project/pkg"

import "code.google.com/p/project/pkg"

导入当前包的相对路径包

例如有go目录如下:

$GOPATH/src

 ├─x0

 │ ├─y0

 │ │ └─z0

 │ └─y1

 │  └─z1

 └─x1

  └─y2


import "./y0/z0"    // x0包中导入子包 z0包

import "../y0/z0"   // y1包中导入子包 z0包

import "x0/y1/z1"   // y2包中导入 z1包

错误的导入包路径


import a/b/c        // 错误

import "a.b.c"      // 错误

import a.b.c        // 错误

用圆括号组合导入包路径


import ("fmt"; "math")


import (

    "fmt"

    "math"

)

导入包可以定义别名,防止同名称的包冲突


import (

    "a/b/c"


    c1 "x/y/c"     // 将导入的包c定义别名为 c1


    格式化 "fmt"    // 将导入的包fmt定义别名为 格式化


    m "math"       // 将导入的包math定义别名为 m

)

引用包名是导入包路径的最后一个目录中定义的唯一包的名称

定义的包名与目录同名时,直接引用即可


// 引用普通名称的导入包

c.hello()


// 引用定义别名的包

格式化.Println(m.Pi)

定义的包名与所在目录名称不同时,导入包路径仍为目录所在路径,引用包名为定义的包名称


// 源文件路径: $GOPATH/src/proj/my-util/util.go

// 定义包名: util

package util

// 导入util包路径

import "proj/my-util"


// 引用util包

util.doSomething()

静态导入,在导入的包路径之前增加一个小数点.


// 类似C中的include 或Java中的import static

import . "fmt"


// 然后像使用本包元素一样使用fmt包中可见的元素,不需要通过包名引用

Println("no need package name")

导入包但不直接使用该包,在导入的包路径之前增加一个下划线_


// 如果当前go源文件中未引用过log包,将会导致编译错误

import "log"    // 错误

import . "log"  // 静态导入未使用同样报错


// 在包名前面增加下划线表示导入包但是不直接使用它,被导入的包中的init函数会在导入的时候执行

import _ "github.com/go-sql-driver/mysql"

包内元素的可见性 Accessability

名称首字符为Unicode包含的大写字母的元素是被导出的,对外部包是可见的

首字为非大写字母的元素只对本包可见(同包跨源文件可以访问,子包不能访问)


var In int                                      // In is exported

var in byte                                     // in is unexported

var ȸȹ string                                   // ȸȹ is unexported

const Ȼom bool = false                          // Ȼom is exported

const ѧѩ uint8 = 1                             // ѧѩ is unexported

type Ĩnteger int                                // Ĩnteger is exported

type ブーリアン *bool                             // ブーリアン is unexported

func Ӭxport() {...}                              // Ӭxport is exported

func įnner() {...}                               // įnner is unexported

func (me *Integer) ⱱalueOf(s string) int {...}   // ⱱalueOf is unexported

func (i ブーリアン) Ȿtring() string {...}         // Ȿtring is exported

internal包(内部包)Go 1.4

internal包及其子包中的导出元素只能被与internal同父包的其他包访问


例如有Go目录如下:

$GOPATH/src

 ├─x0

 │ ├─internal

 │ │ └─z0

 │ └─y0

 │  └─z1

 └─x1

  └─y1


x0,y0,z1包中可以访问internal,z0包中的可见元素

x1,y1包中不能导入internal,z0包


规范导入包路径Canonical import pathsGo 1.4

包声明语句后面添加标记注释,用于标识这个包的规范导入路径。


package pdf // import "rsc.io/pdf"

如果使用此包的代码的导入的路径不是规范路径,go命令会拒绝编译。

例如有 rsc.io/pdf 的一个fork路径 github.com/rsc/pdf

如下程序代码导入路径时使用了非规范的路径则会被go拒绝编译


import "github.com/rsc/pdf"

数据类型 Data Type

基础数据类型 Basic data type

基本类型包含:数值类型,布尔类型,字符串


类型        取值范围        默认零值        类型        取值范围        默认零值

int        int32,int64        0        uint        uint32,uint64        0

int8        -27 ~ 27-1        0        uint8,byte        0 ~ 28-1        0

int16        -215 ~ 215-1        0        uint16        0 ~ 216-1        0

int32,rune        -231 ~ 231-1        0        uint32        0 ~ 232-1        0

int64        -263 ~ 263-1        0        uint64        0 ~ 264-1        0

float32        IEEE-754 32-bit        0.0        float64        IEEE-754 64-bit        0.0

complex64        float32+float32i        0 + 0i        complex128        float64+float64i        0 + 0i

bool        true,false        false        string        "" ~ "∞"        "",``

uintptr        uint32,uint64        0        error        -        nil

byte 是 uint8 的别名

rune 是 int32 的别名,代表一个Unicode码点

int与int32或int64是不同的类型,只是根据架构对应32/64位值

uint与uint32或uint64是不同的类型,只是根据架构对应32/64位值


变量 Variable

变量声明, 使用var关键字

Go中只能使用var 声明变量,无需显式初始化值


var i int       // i = 0


var s string    // s = ""    (Go中的string是值类型,默认零值是空串 "" 或 ``,不存在nil(null)值)


var e error     // e = nil, error是Go的内建接口类型。

关键字的顺序错误或缺少都是编译错误的


var int a       // 编译错误

a int           // 编译错误

int a           // 编译错误

var 语句可以声明一个变量列表,类型在变量名之后


var a,b,c int   // a = 0, b = 0, c = 0

var (

    a int       // a = 0

    b string    // b = ""

    c uint      // c = 0

)

var (

    a,b,c int

    d string

)

变量定义时初始化赋值,每个变量对应一个值


var a int = 0

var a, b int = 0, 1

变量定义并初始化时可以省略类型,Go自动根据初始值推导变量的类型


var a = 'A'         // a int32

var a,b = 0, "B"    // a int, b string

使用组合符号:=定义并初始化变量,根据符号右边表达式的值的类型声明变量并初始化它的值

:= 不能在函数外使用,函数外的每个语法块都必须以关键字开始


a := 3                     // a int

a, b, c := 8, '呴', true   // a int, b int32, c bool

c := `formatted

 string`                   // c string

c := 1 + 2i                // c complex128

常量 Constant

常量可以是字符、字符串、布尔或数值类型的值,数值常量是高精度的值


const x int = 3

const y,z int = 1,2

const (

    a byte = 'A'

    b string = "B"

    c bool = true

    d int = 4

    e float32 = 5.1

    f complex64 = 6 + 6i

)

根据常量值自动推导类型


const a = 0        // a int

const (

    b = 2.3        // b float64

    c = true       // c bool

)

常量组内定义时复用表达式

常量组内定义的常量只有名称时,其值会根据上一次最后出现的常量表达式计算相同的类型与值


const (

    a = 3               // a = 3

    b                   // b = 3

    c                   // c = 3

    d = len("asdf")     // d = 4

    e                   // e = 4

    f                   // f = 4

    g,h,i = 7,8,9       // 复用表达式要一一对应

    x,y,z               // x = 7, y = 8, z = 9

)

自动递增枚举常量 iota

iota的枚举值可以赋值给数值兼容类型

每个常量单独声明时,iota不会自动递增


const a int = iota        // a = 0

const b int = iota        // b = 0

const c byte = iota       // c = 0

const d uint64 = iota     // d = 0

常量组合声明时,iota每次引用会逐步自增,初始值为0,步进值为1


const (

    a uint8 = iota        // a = 0

    b int16 = iota        // b = 1

    c rune = iota         // c = 2

    d float64 = iota      // d = 3

    e uintptr = iota      // e = 4

)

即使iota不是在常量组内第一个开始引用,也会按组内常量数量递增


const (

    a = "A"

    b = 'B'

    c = iota    // c = 2

    d = "D"

    e = iota    // e = 4

)

枚举的常量都为同一类型时,可以使用简单序列格式(组内复用表达式).


const (

    a = iota     // a int32 = 0

    b            // b int32 = 1

    c            // c int32 = 2

)

枚举序列中的未指定类型的常量会跟随序列前面最后一次出现类型定义的类型


const (

    a byte = iota    // a uint8 = 0

    b                // b uint8 = 1

    c                // c uint8 = 2

    d rune = iota    // d int32 = 3

    e                // e int32 = 4

    f                // f int32 = 5

)

iota自增值只在一个常量定义组合中有效,跳出常量组合定义后iota初始值归0


const (

    a = iota     // a int32 = 0

    b            // b int32 = 1

    c            // c int32 = 2

)

const (

    e = iota     // e int32 = 0    (iota重新初始化并自增)

    f            // f int32 = 1

)

定制iota序列初始值与步进值 (通过组合内复用表达式实现)


const (

    a = (iota + 2) * 3    // a int32 = 6    (a=(0+2)*3) 初始值为6,步进值为3

    b                     // b int32 = 9    (b=(1+2)*3)

    c                     // c int32 = 12    (c=(2+2)*3)

    d                     // d int32 = 15    (d=(3+2)*3)

)

数组 Array

数组声明带有长度信息且长度固定,数组是值类型默认零值不是nil,传递参数时会进行复制。

声明定义数组时中括号[ ]在类型名称之前,赋值引用元素时中括号[ ]在数组变量名之后。


var a [3]int = [3]int{0, 1, 2}                         // a = [0 1 2]

var b [3]int = [3]int{}                                // b = [0 0 0]

var c [3]int

c = [3]int{}

c = [3]int{0,0,0}                                      // c = [0 0 0]

d := [3]int{}                                          // d = [0 0 0]

fmt.Printf("%T\t%#v\t%d\t%d\n", d, d, len(d), cap(d))  // [3]int    [3]int{0, 0, 0}    3    3

使用...自动计算数组的长度


var a = [...]int{0, 1, 2}


// 多维数组只能自动计算最外围数组长度

x := [...][3]int{{0, 1, 2}, {3, 4, 5}}

y := [...][2][2]int{{{0,1},{2,3}},{{4,5},{6,7}}}


// 通过下标访问数组元素

println(y[1][1][0])                                    // 6

初始化指定索引的数组元素,未指定初始化的元素保持默认零值


var a = [3]int{2:3}

var b = [...]string{2:"c", 3:"d"}

切片 Slice

slice 切片是对一个数组上的连续一段的引用,并且同时包含了长度和容量信息

因为是引用类型,所以未初始化时的默认零值是nil,长度与容量都是0


var a []int

fmt.Printf("%T\t%#v\t%d\t%d\n", a, a, len(a), cap(a))    // []int    []int(nil)    0    0


// 可用类似数组的方式初始化slice

var d []int = []int{0, 1, 2}

fmt.Printf("%T\t%#v\t%d\t%d\n", d, d, len(d), cap(d))    // []int    []int{0, 1, 2}    3    3


var e = []string{2:"c", 3:"d"}

使用内置函数make初始化slice,第一参数是slice类型,第二参数是长度,第三参数是容量(省略时与长度相同)


var b = make([]int, 0)

fmt.Printf("%T\t%#v\t%d\t%d\n", b, b, len(b), cap(b))    // []int    []int{}    0    0


var c = make([]int, 3, 10)

fmt.Printf("%T\t%#v\t%d\t%d\n", c, c, len(c), cap(c))    // []int    []int{}    3    10


var a = new([]int)

fmt.Printf("%T\t%#v\t%d\t%d\n", a, a, len(*a), cap(*a))  // *[]int    &[]int(nil)    0    0

基于slice或数组重新切片,创建一个新的 slice 值指向相同的数组

重新切片支持两种格式:


2个参数 slice[beginIndex:endIndex]

需要满足条件:0 <= beginIndex <= endIndex <= cap(slice)

截取从开始索引到结束索引-1 之间的片段

新slice的长度:length=(endIndex - beginIndex)

新slice的容量:capacity=(cap(slice) - beginIndex)

beginIndex的值可省略,默认为0

endIndex 的值可省略,默认为len(slice)


s := []int{0, 1, 2, 3, 4}

a := s[1:3]            // a: [1 2],  len: 2,  cap:  4

b := s[:4]             // b: [0 1 2 3],  len: 4,  cap:  5

c := s[1:]             // c: [1 2 3 4],  len: 4,  cap:  4

d := s[1:1]            // d: [],  len: 0,  cap:  4

e := s[:]              // e: [0 1 2 3 4],  len: 5,  cap:  5

3个参数 slice[beginIndex:endIndex:capIndex]

需要满足条件:0 <= beginIndex <= endIndex <= capIndex <= cap(slice)

新slice的长度:length=(endIndex - beginIndex)

新slice的容量:capacity=(capIndex - beginIndex)

beginIndex的值可省略,默认为0


s := make([]int, 5, 10)

a := s[9:10:10]        // a: [0],  len: 1,  cap:  1

b := s[:3:5]           // b: [0 0 0],  len: 3,  cap:  5

向slice中增加/修改元素


s := []string{}

s = append(s, "a")              // 添加一个元素

s = append(s, "b", "c", "d")    // 添加一列元素

t = []string{"e", "f", "g"}

s = append(s, t...}             // 添加另一个切片t的所有元素

s = append(s, t[:2]...}         // 添加另一个切片t的部分元素


s[0] = "A"                      // 修改切片s的第一个元素

s[len(s)-1] = "G"               // 修改切片s的最后一个元素

删除slice中指定的元素

因为slice引用指向底层数组,数组的长度不变元素是不能删除的,所以删除的原理就是排除待删除元素后用其他元素重新构造一个数组


func deleteByAppend() {

    i := 3

    s := []int{1, 2, 3, 4, 5, 6, 7}

    //delete the fourth element(index is 3), using append

    s = append(s[:i], s[i+1:]...)

}


func deleteByCopy() {

    i := 3

    s := []int{1, 2, 3, 4, 5, 6, 7}

    //delete the fourth element(index is 3), using copy

    copy(s[i:], s[i+1:])

    s = s[:len(s)-1]

}

字典/映射 Map

map是引用类型,使用内置函数 make进行初始化,未初始化的map零值为 nil长度为0,并且不能赋值元素


var m map[int]int

m[0] = 0                              // × runtime error: assignment to entry in nil map

fmt.Printf("type: %T\n", m)           // map[int]int

fmt.Printf("value: %#v\n", m)         // map[int]int(nil)

fmt.Printf("value: %v\n", m)          // map[]

fmt.Println("is nil: ", nil == m)     // true

fmt.Println("length: ", len(m))       // 0,if m is nil, len(m) is zero.

使用内置函数make初始化map


var m map[int]int = make(map[int]int)

m[0] = 0                              // 插入或修改元素

fmt.Printf("type: %T\n", m)           // map[int]int

fmt.Printf("value: %#v\n", m)         // map[int]int(0:0)

fmt.Printf("value: %v\n", m)          // map[0:0]

fmt.Println("is nil: ", nil == m)     // false

fmt.Println("length: ", len(m))       // 1

直接赋值初始化map


m := map[int]int{

0:0,

1:1,                                  // 最后的逗号是必须的

}

n := map[string]S{

"a":S{0,1},

"b":{2,3},                            // 类型名称可省略

}

map的使用:读取、添加、修改、删除元素


m[0] = 3                              // 修改m中key为0的值为3

m[4] = 8                              // 添加到m中key为4值为8


a := n["a"]                           // 获取n中key为“a“的值

b, ok := n["c"]                       // 取值, 并通过ok(bool)判断key对应的元素是否存在.


delete(n, "a")                        // 使用内置函数delete删除key为”a“对应的元素.

结构体 Struct

结构体类型struct是一个字段的集合


type S struct {

    A int

    B, c string

}

结构体初始化通过结构体字段的值作为列表来新分配一个结构体。


var s S = S{0, "1", "2"}

使用 Name: 语法可以仅列出部分字段(字段名的顺序无关)


var s S = S{B: "1", A: 0}

结构体是值类型,传递时会复制值,其默认零值不是nil


var a S

var b = S{}

fmt.Println(a == b)    // true

结构体组合

将一个命名类型作为匿名字段嵌入一个结构体

嵌入匿名字段支持命名类型、命名类型的指针和接口类型


package main


type (

    A struct {

        v int

    }


    // 定义结构体B,嵌入结构体A作为匿名字段

    B struct {

        A

    }


    // 定义结构体C,嵌入结构体A的指针作为匿名字段

    C struct {

        *A

    }

)


func (a *A) setV(v int) {

    a.v = v

}


func (a A) getV() int {

    return a.v

}


func (b B) getV() string {

    return "B"

}


func (c *C) getV() bool {

    return true

}


func main() {

    a := A{}

    b := B{}    // 初始化结构体B,其内匿名字段A默认零值是A{}

    c := C{&A{}}    // 初始化结构体C,其内匿名指针字段*A默认零值是nil,需要初始化赋值


    println(a.v)


    // 结构体A嵌入B,A内字段自动提升到B

    println(b.v)


    // 结构体指针*A嵌入C,*A对应结构体内字段自动提升到C

    println(c.v)


    a.setV(3)

    b.setV(5)

    c.setV(7)

    println(a.getV(), b.A.getV(), c.A.getV())

    println(a.getV(), b.getV(), c.getV())

}

匿名结构体

匿名结构体声明时省略了type关键字,并且没有名称


package main


import "fmt"


type Integer int


// 声明变量a为空的匿名结构体类型

var a struct{}


// 声明变量b为包含一个字段的匿名结构体类型

var b struct{ x int }


// 声明变量c为包含两个字段的匿名结构体类型

var c struct {

    u int

    v bool

}


func main() {

    printa(a)

    b.x = 1

    fmt.Printf("bx: %#v\n", printb(b))    // bx: struct { y uint8 }{y:0x19}

    printc(c)


    // 声明d为包含3个字段的匿名结构体并初始化部分字段

    d := struct {

        x int

        y complex64

        z string

    }{

        z: "asdf",

        x: 111,

    }

    d.y = 22 + 333i

    fmt.Printf("d: %#v\n", d)    // d: struct { x int; y complex64; z string }{x:111, y:(22+333i), z:"asdf"}


    // 声明变量e为包含两个字段的匿名结构体类型

    // 包含1个匿名结构体类型的命名字段和1个命名类型的匿名字段

    e := struct {

        a struct{ x int }

        // 结构体组合嵌入匿名字段只支持命名类型

        Integer

    }{}

    e.Integer = 444

    fmt.Printf("e: %#v\n", e)    // e: struct { a struct { x int }; main.Integer }{a:struct { x int }{x:0}, Integer:444}

}


// 函数参数为匿名结构体类型时,传入参数类型声明必须保持一致

func printa(s struct{}) {

    fmt.Printf("a: %#v\n", s)    // a: struct {}{}

}


// 函数入参和返回值都支持匿名结构体类型

func printb(s struct{ x int }) (x struct{ y byte }) {

    fmt.Printf("b: %#v\n", s)    // b: struct { x int }{x:1}

    x.y = 25

    return

}


func printc(s struct {u int; v bool }) {

    fmt.Printf("c: %#v\n", s)    // c: struct { u int; v bool }{u:0, v:false}

}

指针 Pointer


var i int = 1

pi := &i    // 指向数值的指针


a := []int{0, 1, 2}

pa := &a    // 指向引用对象的指针


var s *S = &S{0, "1", "2"}    // 指向值对象的指针

内置函数new(T)分配了一个零初始化的 T 值,并返回指向它的指针


var i = new(int)

var s *S = new(S)

使用*读取/修改指针指向的值


func main() {

    i := new(int)

    *i = 3

    println(i, *i)    // 0xc208031f80    3


    i = new(int)

    println(i, *i)    // 0xc208031f78    0

}

指针使用点号来访问结构体字段

结构体字段/方法可以通过结构体指针来访问,通过指针间接的访问是透明的。


fmt.Println(s.A)

fmt.Println((*s).A)

指针的指针


func main() {

    var i int

    var p *int

    var pp **int

    var ppp ***int

    var pppp ****int

    println(i, p, pp, ppp, pppp)    // 0 0x0 0x0 0x0 0x0


    i, p, pp, ppp, pppp = 123, &i, &p, &pp, &ppp

    println(i, p, pp, ppp, pppp)    // 123 0xc208031f68 0xc208031f88 0xc208031f80 0xc208031f78

    println(i, *p, **pp, ***ppp, ****pppp)    // 123 123 123 123 123

}

跨层指针元素的使用

在指针引用多层对象时,指针是针对引用表达式的最后一位元素。


package a


type X struct {

    A Y

}

type Y struct {

    B Z

}

type Z struct {

    C int

}

package main

import (

    "a"

    "fmt"

)


func main() {

    var x = a.X{}

    var p = &x

    fmt.Println("x: ", x)    // x:  {{{0}}}

    println("p: ", p)    // p:  0xc208055f20

    fmt.Println("*p: ", *p)    // *p:  {{{0}}}

    println("x.A.B.C: ", x.A.B.C)    // x.A.B.C:  0

    //  println("*p.A.B.C: ", *p.A.B.C)    // invalid indirect of p.A.B.C (type int)

    println("(*p).A.B.C: ", (*p).A.B.C)    // (*p).A.B.C:  0

}

Go的指针没有指针运算,但是 道高一尺,魔高一丈

Go语言中的指针运算

利用unsafe操作未导出变量


通道 Channel

channel用于两个goroutine之间传递指定类型的值来同步运行和通讯。

操作符<-用于指定channel的方向,发送或接收。

如果未指定方向,则为双向channel。


var c0 chan int      // 可用来发送和接收int类型的值

var c1 chan<- int    // 可用来发送int类型的值

var c2 <-chan int    // 可用来接收int类型的值

channel是引用类型,使用make函数来初始化。

未初始化的channel零值是nil,且不能用于发送和接收值。


c0 := make(chan int)        // 不带缓冲的int类型channel

c1 := make(chan *int, 10)    // 带缓冲的*int类型指针channel

无缓冲的channe中有值时发送方会阻塞,直到接收方从channel中取出值。

带缓冲的channel在缓冲区已满时发送方会阻塞,直到接收方从channel中取出值。

接收方在channel中无值会一直阻塞。


通过channel发送一个值时,<-作为二元操作符使用,


c0 <- 3

通过channel接收一个值时,<-作为一元操作符使用。


i := <-c1

关闭channel,只能用于双向或只发送类型的channel

只能由 发送方调用close函数来关闭channel

接收方取出已关闭的channel中发送的值后,后续再从channel中取值时会以非阻塞的方式立即返回channel传递类型的零值。


ch := make(chan string, 1)


// 发送方,发送值后关闭channel

ch <- "hello"

close(ch)


// 接收方,取出发送的值

fmt.Println(<-ch)    // 输出: “hello”


// 再次从已关闭的channel中取值,返回channel传递类型的零值

fmt.Println(<-ch)    // 输出: 零值,空字符串“”


// 接收方判断接收到的零值是由发送方发送的还是关闭channel返回的默认值

s, ok := <-ch

if ok {

    fmt.Println("Receive value from sender:", s)

} else {

    fmt.Println("Get zero value from closed channel")

}


// 向已关闭的通道发送值会产生运行时恐慌panic

ch <- "hi"

// 再次关闭已经关闭的通道也会产生运行时恐慌panic

close(ch)

使用for range语句依次读取发送到channel的值,直到channel关闭。


package main


import "fmt"


func main() {

    // 无缓冲和有缓冲的channel的range用法相同

    var ch = make(chan int)    // make(chan int, 2) 或 make(chan int , 100)

    go func() {

        for i := 0; i < 5; i++ {

            ch <- i

        }

        close(ch)

    }()


    // channel中无发送值且未关闭时会阻塞

    for x := range ch {

        fmt.Println(x)

    }

}

下面方式与for range用法效果相同


loop:

    for {

        select {

        case x, ok := <-c:

            if !ok {

                break loop

            }

            fmt.Println(x)

        }

    }

接口 Interface

接口类型是由一组方法定义的集合。

接口类型的值可以存放实现这些方法的任何值。


type Abser interface {

    Abs() float64

}

类型通过实现定义的方法来实现接口, 不需要显式声明实现某接口。


type MyFloat float64


func (f MyFloat) Abs() float64 {

    if f < 0 {

        return float64(-f)

    }

    return float64(f)

}

接口组合


type Reader interface {

    Read(b []byte) (n int)

}


type Writer interface {

    Write(b []byte) (n int)

}


// 接口ReadWriter组合了Reader和Writer两个接口

type ReadWriter interface {

    Reader

    Writer

}


type File struct {

    // ...

}


func (f *File) Read(b []byte) (n int) {

    println("Read", len(b),"bytes data.")

    return len(b)

}


func (f *File) Write(b []byte) (n int) {

    println("Write", len(b),"bytes data.")

    return len(b)

}


func main() {

    // *File 实现了Read方法和Write方法,所以实现了Reader接口和Writer接口以及组合接口ReadWriter

    var f *File = &File{}

    var r Reader = f

    var w Writer = f

    var rw ReadWriter = f

    bs := []byte("asdf")

    r.Read(bs)

    rw.Read(bs)

    w.Write(bs)

    rw.Write(bs)

}

内置接口类型error是一个用于表示错误情况的常规接口,其零值nil表示没有错误

所有实现了Error方法的类型都能表示为一个错误


type error interface {

    Error() string

}

自定义类型

Go中支持自定义的类型可基于: 基本类型、数组类型、切片类型、字典类型、函数类型、结构体类型、通道类型、接口类型以及自定义类型的类型


type (

    A int

    B int8

    C int16

    D rune

    E int32

    F int64

    G uint

    H byte

    I uint16

    J uint32

    K uint64

    L float32

    M float64

    N complex64

    O complex128

    P uintptr

    Q bool

    R string

    S [3]uint8

    T []complex128

    U map[string]uintptr

    V func(i int) (b bool)

    W struct {a, b int}

    X chan int

    Y interface {}

    Z A

)

以及支持以上所有支持类型的指针类型


以上是关于Go语言 语法详解笔记(上)的主要内容,如果未能解决你的问题,请参考以下文章

go语言学习笔记 — 基础 — 基本数据类型 — 指针:一文详解go指针

Go语言探险思考笔记

Go语言语法笔记

Go语言语法笔记

Go语言学习笔记十: 结构体

go学习Golang底层学习笔记