Go 语言小记

lzx_victory · · 752 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/lzx_victory/article/details/78450030

本文主要是读the way  to go的笔记.
本文稍欠缺些整理.

简介


本节主要是对Go的一些主要内容进行基础的介绍

语法特点

变量类型的声明 类型放到变量名之后
函数可以返回多个变量
defer 可以延迟函数的调用(形成一个先进后出的defer栈)
存在指针类型,但没有指针运算
在定义变量时没有进行赋值,默认赋值为”零值”,数值类型默认为0,布尔类型默认为false字符串默认为空””
支持类型推到
存在结构体
return 可以返回多个值以逗号分割

package main

import "fmt"

type MyStruct struct{
    x int
    y int
}


func main()(int, int){
    var a float64 = 1.0
    var p *int

    s := MyStruct{1,2}
    fmt.Println(s.x)
    i := &a
    j := 1
    return i,j
}

基本类型

bool
string
int  int8  int16  int32  int64
uint uint8 uint16 uint32 uint64 uintptr
byte // uint8 的别名
rune // int32 的别名
     // 代表一个Unicode码
float32 float64
complex64 complex128

string 类型操作

Go当中的字符串类型是双引号包起来,不可以使用单引号

字符串可以进行拼接

s += "string"
strings.Join()

非解释类型的字符串用反引号括起来

\` String \n \` 

上述的\n会进行原样输出

注意:
获取字符串当中的某个字节是非法的操作 如:&str[1]

string包与strconv包

这里只做简述与常用的函数

//Index 返回字符串 str 在字符串 s 中的索引(str 的第一个字符的索引),-1 表示字符串 s 不包含字符串 str:
strings.Index(s, str string) int

//LastIndex 返回字符串 str 在字符串 s 中最后出现位置的索引(str 的第一个字符的索引),-1 表示字符串 s 不包含字符串 str:
strings.LastIndex(s, str string) int

//Replace 用于将字符串 str 中的前 n 个字符串 old 替换为字符串 new,并返回一个新的字符串,如果 n = -1 则替换所有字符串 old 为字符串 new:
strings.Replace(str, old, new, n) string

//HasPrefix 判断字符串 s 是否以 prefix 开头:
strings.HasPrefix(s, prefix string) bool

//HasSuffix 判断字符串 s 是否以 suffix 结尾:
strings.HasSuffix(s, suffix string) bool

//ToLower 将字符串中的 Unicode 字符全部转换为相应的小写字符:
strings.ToLower(s) string

//ToUpper 将字符串中的 Unicode 字符全部转换为相应的大写字符:
strings.ToUpper(s) string

//strings.Split(s, sep) 用于自定义分割符号来对指定字符串进行分割,返回 slice。

//strings.TrimSpace(s) 来剔除字符串开头和结尾的空白符号;如果你想要剔除指定字符,则可以使用 

string.conv包主要负责 字符串的转换

strconv.Itoa(i int) //string 返回数字 i 所表示的字符串类型的十进制数。
strconv.FormatFloat(f float64, fmt byte, prec int, bitSize int) //string 将 64 位浮点型的数字转换为字符串,其中 fmt 表示格式(其值可以是 'b'、'e'、'f' 或 'g'),prec 表示精度,bitSize 则使用 32 表示 float32,用 64 表示 float64。

defer

使用defer会使函数的执行被压入defer栈,不会被立即执行,在上层函数返回之前执行,先进后出的方式执行defer栈里的函数

package main
import "fmt"
func main() {
    defer fmt.Println("world")
    fmt.Println("hello")
}

可以使用defer实现类似面向对象中destroy方法,处理资源的回收

slice处理

slice 的零值为nil
一个nil的slice的长度和容量为0

package main
import "fmt"
func main() {
    var a  [2]string  //声明数组
    b := []int{2,3,45}
    c := make([]int, 0, 5)
    fmt.Println("b[1:4]")
}

map

创建和使用

package main
import "fmt"

type MyStruct struct{
    x,y int
}
var m map[string]MyStruct
/**
var m = map[string]Vertex{
    "Bell Labs": Vertex{
        40.68433, -74.39967,
    },
    "Google": Vertex{
        37.42202, -122.08408,
    },
}
若顶级类型只是一个类型名,你可以在文法的元素中省略,上述代码中的vetex
*/
func main(){
    m = make(map[string]MyStruct)//创建map时需要使用此函数
    m["test"] = MyStruct{
        10,20
    }
    fmt.Println(m['test"]);
}

map的增删改查

m[key] = elem //增改
elem = m[key] //查
delete(m, key) //删

map的遍历

package main
import "fmt"
func main(){
    testMap := make(int[], 10)
    for i := range testMap{
        fmt.Println("%d", testMap)
    }
}

判断map当中是否存在该key1值

val1, isPresent = map1[key1]

数组

数组中的类型可以为int,string,或者自定义类型等
数组长度也是类型的一部分, [5]int 与 [10]int就属于不同的类型
函数传递的数组的值默认为值拷贝,使用&符号可以进行引用

var arr = [5]string{1:"name", 3:"value"}//只是指定了

函数值

函数可以被当做一个值进行传递

package main

import (
    "fmt"
    "math"
)

func compute(fn func(x float64) float64) float64{
    return fn(3)
}

func main(){
    test := func(x float64) float64{
        return x * 3
    }
    res := compute(test)
    fmt.Println(res)
}

闭包
Go函数支持闭包

package main
import "fmt"

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),
        )
    }
}

注意上述例子中 闭包保留了sum的上下文,运行结果 数字会递增

作为函数值时,需要传递函数的名字,作为返回时需要制定函数的变量

Go没有类

但是仍然可以在结构体类型上定义方法,接收者为指针的方法,可以使用接受者的值直接做改变

package main

import (
    "fmt"
    "math"
)

type Vertex struct {
    X, Y float64
}

func (v *Vertex) Abs() float64 {
    return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

func main() {
    v := &Vertex{3, 4}
    fmt.Println(v.Abs())
}

Go支持接口
接口当中可以实现接口

type Reader interface {
    Read(b []byte) (n int, err error)
}

type Writer interface {
    Write(b []byte) (n int, err error)
}

type ReadWriter interface {
    Reader
    Writer
}

示例说明:
fmt包当中有个结构提输出化函数的接口

type Stringer interface {
    String() string
}

当结构体实现对应的接口,调用fmt.Println(s)函数时会打调用该接口输出.

错误接口

如果程序运行出现错误,那么Go内置的错误接口将会起到作用

type error interface {
    Error() string
}
//可以让不同的对象去实现接口
func (myStruct * MyStruct) Error() string{
    return fmt.Sprintf("%d %d", myStruct.x,myStruct.y)
}

func test() error{
    return &MyStruct{
        1, 2
    }
}

func main(){
    err := test()
    if err != nil{
        fmt.Println(err)
        return
    }
}

channel

channel 是有类型的管道 配合操作符 <-,有点类似于linux中的管道,
使用前必须进行创建 make(chan int)//也可以指定为其它类型
channel也可以指定缓冲长度,make的第二个参数
向带缓冲的 channel 发送数据的时候,只有在缓冲区满的时候才会阻塞。 而当缓冲区为空的时候接收操作会阻塞

channel只能被发送者关闭,可以将其理解为一个队列,入队后进行读取,读取后出队.
通常情况下无需关闭它们。只有在需要告诉接收者没有更多的数据的时候才有必要进行关闭,例如中断一个 range.
使用range读取channel当中的值

ch := make(chan int, 100)
ch <- 10
ch <- 100
close(ch)

for i:= range ch {
    fmt.Println(ch)
}

select

select 语法只是监听io操作(如channel),语法风格类似于switch case语句

select {
    case <-ch:
        fmt.Println("I'm reading from channel")
    default:
        fmt.Println("Nothing to read")
}

const 定义常量

const  a = "abc"

const (
    Unknow = 1
    Female = 1 << 1
    Male = 2/3
)

const (
    a = iota //0
    b  //1
    c  //2
)

//iota 是从0 开始计算,每个const中的iota都会从0开始重新计算

变量

Go语言当中不允许出现定义却未使用的变量
编译时支持类型推断

var a =  "abc"//自动推断出a的类型为字符串类型

值交换可以进行简写,不需要再定义一个temp变量

a, b = b, a

_空白标识符,在Go当中是一个只写的变量

每一个源文件可以包含一个或者多个init函数,init函数可以做一些初始化的工作

运算操作符

|| && ! == != 运算操作符返回的值类型为bool类型
注意以下几点:
操作符:++ – 只能作为语句 不能作为表达式 a = i++ 这样是在 Go中不允许出现的
在进行比较运算时,需要使操作类型必须相同,如果值的类型是接口,那么需两边实现相同的接口
go语言当中没有溢出,会简单的将超出位进行抛弃

运算符与优先级

优先级 运算符
7 ^ !
6 * / % << >> & &^
5
    -
4 == != < <= >= >
3 <-
2 &&
1

流程控制

if

先说一下if条件判断,在if判断条件里,允许先执行一个语句,这样会使流程可更加清晰.常使用if捕获错误

if f,err:=os.Open("/notexsit"); err != nil{
    fmt.Println(err)
}

switch

switch case语句默认break(switch里不能使用break),分下述两种形式,第二种形式类似于if else分支,只要有其中一个条件符合就会跳出,如果需要贯穿执行,则需要fallthrough关键字

switch value{
    case var1:

    case var2:

    default:

}
switch{
    case a > b:

    case b > a:
}

for

for可以当做其他语言当中while使用,当然也支持continue,break关键字

for i < = 10{
    i -= 1
    fmt.Println(i)
}

for range 特殊语法,如果 slice 为指针,则会产生指针的拷贝,依旧可以修改集合中的原值,如果不是指针那么就会,是值拷贝

for pos, char := range str{
    fmt.print(pos, char)
}

函数

函数重载是不被允许的,同一个函数名不同的参数会引起编译错误

函数也可以以申明的方式被使用,作为一个函数类型

type binOp func(int, int) int
function test(y int, x int) (z int){
    z = x + y
    return
}

函数也可以支持可变参数传入,注意参数的类型需要指明为 …type

func main(){
    test(1,23,4)//第一种传参方式
    var arr []int 
    arr = []int{1, 2,3,4}
    test(arr...)
}
func test(arr ...int){
    for _,val = range a{
        fmt.Printla
    }
}

函数可以传入slice类型,记住传入参数时,变量后需要加入…,否则会报错

函数作为参数传入

func test(str string,f func(int, int)(int, int))

闭包

闭包使用,这里举一个例子

func test (str string) func(int, int) int{
    retrun func (x int,y int) int{
        fmt.Printn(str)//直接输出,闭包里保存了str变量的值
        return x+y
    }
}

对照php版本

function test ($str){
    return funciton ($x, $y) use($str){//需要使用use去保存变量,否则变量不会保存
        echo $str
        return $x + $y
    }
}

可以将变量赋值于闭包,也可以进行直接调用,直接在闭包函数末尾加入()即可,也可以当做返回值.

内置函数

名称 说明
close 用于管道通信
len、cap len 用于返回某个类型的长度或数量(字符串、数组、切片、map 和管道);cap 是容量的意思,用于返回某个类型的最大容量(只能用于切片和 map)
new、make new 和 make 均是用于分配内存:new 用于值类型和用户定义的类型,如自定义结构,make 用于内置引用类型(切片、map 和管道)。它们的用法就像是函数,但是将类型作为参数:new(type)、make(type)。new(T) 分配类型 T 的零值并返回其地址,也就是指向类型 T 的指针。它也可以被用于基本类型:v := new(int)。make(T) 返回类型 T 的初始化之后的值,因此它比 new 进行更多的工作new() 是一个函数,不要忘记它的括号
copy、append 用于复制和连接切片
panic、recover 两者均用于错误处理机制
print、println 底层打印函数(详见第 4.2 节),在部署环境中建议使用 fmt 包
complex、real imag 用于创建和操作复数

defer

延迟操作,主要实用功能是,在函数执行末尾,进行资源的释放,如:文件,锁等等
使用思想类似于面向对象当中的析构函数,但是使用起来defer更加灵活,能处理更多的情况

func test(){
    for i:= 10; i > 0; i--{
        deferfmt.Println(i)
    }
}

下例当中的res结果为2,注意defer的执行顺序:在函数return之后进行操作.

func test() (res int){
    defer func(){
        res ++
    }()
    return 1
}

切片

切片是对数组连续片段的引用
切片是一个引用类型
切片的长度可以在运行时修改
切片是一个长度可变的数组
数组实际上是切片的构建块
对于切片 有以下不等式恒成立
0<= len(s) <= cap(s)

声明格式如下:

var identifier []type //未初始化之前长度为0,默认为nil

var x = []int{2, 3, 5, 7, 11}//声明一个slice切片  

var slice1 []type = arr[start:end] //从数组中截取star至end-1个元素,前闭后开,slice[0] 与arr[start]指向同一个类型

slice := make([]type , len, cap)//cap为可选参数

new 与 make额度区别
new(T) 为每个新的类型T分配一片内存,初始化为 0 并且返回类型为*T的内存地址:这种方法 返回一个指向类型为 T,值为 0 的地址的指针,它适用于值类型如数组和结构体;它相当于 &T{}。
make(T) 返回一个类型为 T 的初始值,它只适用于3种内建的引用类型:切片、map 和 channel。

slice重新切片时需要注意
重新切片后,其起始位置相对于被切片的切片

slice := []int{1,2,3,4,5,6,7,8}
slice1 := slice[2:4]//{3,4}
sclice1 = slice1[0:3]//{3,4,5}  相对于slice1的位置进行切片

map

声明

var map1 map[keyType]valueType
var map1 map[string]int
var map2 = make(map[string]int)

map 未初始化的值为nil

初始化

map3 := map[string]int{"one":1, "two":2}

key的类型需要注意时可以比较的类型,数组切片结构体不可以作为key
value的类型没有限制,函数类型也可以

使用

var val = map[key]

判断存在与删除

if _,ok := map1[key]{
    ...
}
delete(map1, key)

for-range遍历

下例中,key是map的键,value是map的值

for key,value := range map1{

}

map类型切片

var slice = make([]map[string]int, 10)  //切片里包含了map

自定义安装包

命名以不包含_的单个单词

引入包

import “包的路由地址 或者路径”

import "./pack1/pack1"

import "github.com/org1/pack1"

通过远程安装包

go install codesite.ext/author/goExample/goex

引入

import goex "codesite.ext/author/goExample/goex"

上诉goex是包的别名,当包的别名为 . 的时候 不需要使用pack.Item直接使用Item即可.

可执行文件 goex.a 将被放到 $GOPATH/pkg/linux_amd64/tideland-cgl.googlecode.com/author 目录下,源码文件被放置在 $GOPATH//src/tideland-cgl.googlecode.com/goex.a 目录下,同样有个 goex.a 放置在 _obj 的子目录下。

使用godoc生成自定义包的文档

结构体

内存布局:
Go 语言中,结构体和它所包含的数据在内存中是以连续块的形式存在的,即使结构体中嵌套有其他的结构体,这在性能上带来了很大的优势.

type identifier struct {
    field1 type1
    field2 type2
    ...
}

创建结构体

type T struct{
    a int,
    b float64,
}

//var t T
var t *T = new(T) //面向对象的味道
t.b = 123 // 这是在go中是合法的
t.a = 1
(*t.b) = 12//这是在go中是合法的

混合字面量语法,这种需要注意必须要按照顺序去写


t := &T{1, 3}
var t = T{1,3}

递归结构体

type Node struct{
    data int
    su *Node
}

试图 make() 一个结构体变量,会引发一个编译错误,这还不是太糟糕,但是 new() 一个映射并试图使用数据填充它,将会引发运行时错误! 因为 new(map[string]string) 返回的是一个指向 nil 的指针,它尚未被分配内存。所以在使用 map 时要特别谨慎

匿名字段和内嵌结构体

结构体可以包含一个或多个 匿名(或内嵌)字段,即这些字段没有显式的名字,只有字段的类型是必须的,此时类型就是字段的名字。匿名字段本身可以是一个结构体类型,即 结构体可以包含内嵌结构体


type MyStruct2 struct{
    c int
}

type MyStruct struct{
    a int
    b int
    int //匿名字段
    MyStruct2
}

mySuct.int 获取匿名字段

每一个结构体只允许存在一种匿名类型字段

命名冲突

type A struct {a int}
type B strcut {a int}

type C struct {A; B}

结构体C当中内嵌了A,B但是存在很严重的问题,字段a是该从哪个结构体去获取,这时编译器就会报错误,只能通过程序员去手动解决

还有个问题是存在于下述这种情况

type A struct {a int}
type C struct { a int; A}

这种情况类似于面向对象当中的重载,即 C的结构体读取a字段的时候会读取本身的a字段,多继承的概念在此时,已经有些眉目.

方法

Go当中的方法,的确是一个很新颖的概念.”形散而神不散”,首先方法指定了它要作用的接收者,接收者是某种类型的变量.这个概念和面向当中的对象方法类似.

方法的接收者不可以是以下两种类型:
1.指针类型 但是它可以是任何其他允许类型的指针。
2.接口类型

在 Go 中,类型的代码和绑定在它上面的方法的代码可以不放置在一起,它们可以存在在不同的源文件,唯一的要求是:它们必须是同一个包的还有一点是基于不同的接受者方法的重名是允许的.

this,self都不属于Go的关键字,但是你可以在声明方法时,将下述a的变量名字指定为self这样可以更符合一般的面向对象的语法

func (a * receiverType) funcName (param T)(returnValue T)

recevier.Methord(param)

上述的例子展示了方法的声明与调用

如果要声明一个方法直接定义到非本地包上如(int float)等,稳妥的解决方式可以将int float定义一个别名,然后为其创建方法.

指针方法和值方法都可以在指针或非指针上被调用.

多继承

Go语言当中的多继承可以由一个结构体包含多个接口(请见上文)或者多个指针去实现多继承

可见性规则

等价于面向中的类的访问控制
当标识符(包括常量、变量、类型、函数名、结构字段等等)以一个大写字母开头,如:Group1,那么使用这种形式的标识符的对象就可以被外部包的代码所使用(客户端程序需要先导入这个包),这被称为导出(像面向对象语言中的 public);标识符如果以小写字母开头,则对包外是不可见的,但是他们在整个包的内部是可见并且可用的(像面向对象语言中的 private )。

反射包

反射之所以单独拿出来,是因为在各个语言当中,实现例如依赖注入,等解耦有着很大的作用.

变量的最基本信息就是类型和值:反射包的 Type 用来表示一个 Go 类型,反射包的 Value 为 Go 值提供了反射接口。

两个简单的函数,reflect.TypeOfreflect.ValueOf,返回被检查对象的类型和值。例如,x 被定义为:var x float64 = 3.4,那么reflect.TypeOf(x) 返回 float64reflect.ValueOf(x) 返回 <float64 Value>.如果 v:=reflect.ValueOf(x),那么 v.Kind() 返回 reflect.Float64,这个值是个reflect包的常量.变量 v 的 Interface() 方法可以得到还原(接口)值,fmt.Println(v.Interface())原本被指向的值.

通过反射修改结构体的值时需要注意,需要先调用v.CanSet()方法判断是否可修改,然后通过SetInt()等方法设置其值.

反射结构体

type Person struct{
    name string
    age int
}

func main(){
    person := &Person{"Lzx", 10}
    value := reflect.ValueOf(*person)
    for i := 0; i < value.NumField(); i++{
        fmt.Printf("Field %d: %v\n", i, value.Field(i))
        fmt.Printf("Field %d: %v\n", i, value.Field(i).Type())
        fmt.Printf("Field %d: %v\n", i, value.Field(i).Interface())
    }
}

调用结构体当中的方法value.Method(0).Call(nil)

动态方法调用

interface{} 相当于一个“泛型”,通过类型断言传入的值进行转换,与判断

func testFunc(obj interface{}){
    if v,ok := obj.(Foo); ok{
        //TODO
    }
}

也可使用type-swtich

func testSwtich(obj interface{}){
    switch obj.(type){
        case string:
        //TODO
        case float64:
        //TODO
    }
}

接口

接口的名字约定以大写字母I打头

类型不需要显式声明它实现了某个接口:接口被隐式地实现。多个类型可以实现同一个接口。

实现某个接口的类型(除了实现接口方法外)可以有其他的方法。

一个类型可以实现多个接口。

接口类型可以包含一个实例的引用, 该实例的类型实现了此接口(接口是动态类型)。

我们可以通过接口去实现多态<->何类型的变量都是满足 空接口类型(Interface{})的

接口可以进行嵌套.

type MyInterface2 interface{

    MyMethod2() float64
}
type MyInterface interface{
    MyInterface2
    MyMethod() float64
}

接口类型的声明

type MyInterface interface{

    MyMethod float64
}

Go语言当中需要注意点,可以声明接口类型的变量,这就需要动态的去检测接口类型的变量的类型,这种判断方法叫做类型断言.


var myVar MyIntergace = new(MyStruct)

if v,ok = myVar.(*MyStruct);ok{//*号不可以丢弃!!!
    //v是myVar转到MyStruct变量的值
    //
}

还有一种使用type-switch的方式,但是不允许使用fallthrough


switch myVar.(type) {
case *MyStruct:
    // TODO
case *MyStruct2:
    // TODO
...
default:
    // TODO
}

判断变量是否实现了接口

if v,ok := v.(MyInterface);ok{

    //todo
}

再调用的时候需要注意以下事项

指针方法可以通过指针调用
值方法可以通过值调用
接收者是值的方法可以通过指针调用,因为指针会首先被解引用
接收者是指针的方法不可以通过值调用,因为存储在接口中的值没有地址

接口也可以是动态被调用,但是Go会在编译时进行检查

type MyStruct struct{
    //没有声明接口,
}
func ( this *Mystruct)MyMethod (){
   //
}
func testFunc(MyInterFace s){

}

//这时候将MyStruct类型的变量传入到testFunc函数当中
var test* MyStruct = new(MyStruct)
testFunc(test);//如果test实现了MyInterface的所有的方法那么不会报错,否则将在编译时抛出错误

错误处理

可以通过erros包来定义一个新的错误类型,或者直接使用errors包

var errNotFound error = errors.New("Not found error")

type-swtich 处理不同类型的错误

switch err := err.(type) {
    case ParseError:
        PrintParseError(err)
    case PathError:
        PrintPathError(err)
    ...
    default:
        fmt.Printf("Not a special error, just %s\n", err)
}

panic()可以直接在代码当中进行调用,会使程序直接结束运行,panic后的代码不会被执行,但是defer语句都会保证执行.

一个很重要的概念Go packing

在多层嵌套的函数调用中调用 panic,可以马上中止当前函数的执行,所有的 defer 语句都会保证执行并把控制权交还给接收到 panic 的函数调用者。这样向上冒泡直到最顶层,并执行(每层的) defer,在栈顶处程序崩溃,并在命令行中用传给 panic 的值报告错误情况:这个终止过程就是 panicking。

有了异常的抛出那么接下来的就是如何从异常当中恢复,那就是recover

调用recover时需要注意
recover 只能在 defer 修饰的函数中使用:用于取得 panic 调用中传递过来的错误值,如果是正常执行,调用 recover 会返回 nil,且没有其它效果

关于错误与panic
有一下两点建议
1)在包内部,总是应该从 panic 中 recover:不允许显式的超出包范围的 panic()
2)向包的调用者返回错误值(而不是 panic)。

Go中的单元测试和基准测试

测试程序必须属于被测试的包,并且文件名满足这种形式 *_test.go,所以测试代码和包中的业务代码是分开的.

测试文件中必须导入 “testing” 包,文件命名以*_test.go格式去命名,命名测试函数需要已Test开头,如果是一些基准测试,测试性能Benchmark开头.命令的执行见 go test -h.
用下面这些函数来通知测试失败:

1)func (t *T) Fail()
标记测试函数为失败,然后继续执行(剩下的测试)。
2)func (t *T) FailNow()
标记测试函数为失败并中止执行;文件中别的测试也被略过,继续执行下一个文件。
3)func (t *T) Log(args …interface{})
args 被用默认的格式格式化并打印到错误日志中。
4)func (t *T) Fatal(args …interface{})
结合 先执行 3),然后执行 2)的效果。

这几个函数需要手动去调用

示例:

//文件名叫做mytest_test.go 
package myt//包名尽量保持小写

import "testing"

func TestMyTest(t *testing.T){//t变量包含一些测试信息
    if testError(1){//假定这个函数在myTest包当中

    }
}

goroutine

Go的协程需要注意的是,使用Go语言关键字,提高了并发量的同时也可能会引发”竞态”,加锁不是一个很好的选择,Go提供了channels去避免这个问题.

协程的设计隐藏了许多线程创建和管理方面的复杂工作.

channel

数据通过通道:同一时间只有一个协程可以访问数据.
通道实际上是类型化消息的队列:使数据得以传输.
数据是FIFO的模式,保证元素的顺序.
创建一个channel

var ch1 chan string
ch1 = make(chan string)

通道操作符<-:

ch <- val //将变量val值传给通道ch
val = <- ch //读取ch通道的值赋值给val 
<- ch //可以单独运行,读出的值将被抛弃

通道也可以使用for循环读取

for v := range ch {
    fmt.Println(v)
}

所以发送操作会等待 ch 再次变为可用状态:就是通道值被接收时(可以传入变量).
对于同一个通道,接收操作是阻塞的(协程或函数中的),直到发送者可用:如果通道中没有数据,接收者就阻塞了.

对于同一个通道,发送操作(协程或者函数中的),在接收者准备好之前是阻塞的:如果ch中的数据无人接收,就无法再给通道传入其他数据:新的输入无法在通道非空的情况下传入。所以发送操作会等待 ch 再次变为可用状态:就是通道值被接收时(可以传入变量)。

以上可以简而言之:只要任意接收者和发送者没准备好,都无法从管道当中获取数据,没有发送者发送数据或者接受者没有接收数据那么对应的一方(发送者对应接收者)会被阻塞.

在编写代码时常见deadlock panic

在这里温习一下死锁的条件(以下为进程的概念,与线程类似):
(1) 互斥条件:一个资源每次只能被一个进程使用。
(2) 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
(3) 不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。
(4) 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系

运行时会检查所有的协程(也许只有一个是这种情况)是否在等待(可以读取或者写入某个通道),意味着程序无法处理。这是死锁(deadlock)形式,运行时可以检测到这种情况.在iOS当中存在一种主线程的说法,Go当中没有明确说明主线程的的概念,但是死锁的形式与iOS中基本类似,同步任务会导致主线程卡死,如果单独使用go去修饰异步任务不去卡住主线程都是可以的,需要注意的是协程会随着主线程的结束而结束.

从通道读取数据默认是没有buffer,没有buffer的话,默认为阻塞模式.

ch :=make(chan type, value)

//value == 0 -> synchronous, unbuffered (阻塞)
//value > 0 -> asynchronous, buffered(非阻塞)取决于value元素

理解组阻塞与非阻塞很简单只需要,只是在与是否有buffer的存在

带缓冲的通道实现信号量

通道也是可以关闭的

当继续往关闭的通道输入值或者是输出值,那么会引起异常

如何显式的去关闭通道?
close(ch)

还有一种方式是使用if语句去判断

if v,ok := <-ch;ok{

}

select切换协程

从不同的并发执行的协程中获取值可以通过关键字select来完成,它和switch控制语句非常相似也被称作通信开关;它的行为像是“你准备好了吗”的轮询机制;select监听进入通道的数据,也可以是用通道发送值的时候.
(select语法当中不支持fallthrough)

select {
    case v := <- ch :

    case t := <-ch:

    default:
}

一般配合 for无限循环去监听,管道的输入事件.

通道与锁使用场景

使用锁的情景:
访问共享数据结构中的缓存信息
保存应用程序上下文和状态信息数据
使用通道的情景:
与异步操作的结果进行交互
分发任务
传递数据所有权

总结

Go作为强类型语言,额外需要类型的转换,类型转换需要正确要不会引起编译错误,strconv,[]byte(),string()

短声明导致变量覆盖

var test bool = false
if test{
    test := true //新声明了一个变量
}

何时使用new()何时使用make()

  • 切片、映射和通道,使用make
  • 数组、结构体和所有的值类型,使用new

当切片作为参数传递时,切记不要解引用切片。

永远不要使用一个指针指向一个接口类型,因为它已经是一个指针。

常用文件读取


file, err := os.Open("input.dat")
  if err != nil {
    return
  }
  defer file.Close()
  iReader := bufio.NewReader(file)
  for {
    str, err := iReader.ReadString('\n')
    if err != nil {
      return // error or EOF
    }
    fmt.Println(str)
  }

有疑问加站长微信联系(非本文作者)

本文来自:CSDN博客

感谢作者:lzx_victory

查看原文:Go 语言小记

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

752 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传