• Go基础


    Go语言的基础组成-->包声明/引入包/函数/变量/语句&表达式/注释

    package main  
    -->定义了包名,必须在源文件中非注释的第一行指明这个文件属于哪个包。package main表示一个
    可独立执行的程序,每个Go应用程序都包含一个名为main的包
    import
    "fmt" -->告诉Go编译器这个程序需要使用fmt包(的函数,或其他元素),fmt包实现了格式化IO
    (输入/输出)的函数

    -->func main()是程序开始执行的函数。main函数是每一个可执行程序所必须包含的,一般都是在
    启动后第一个执行的函数,如果有init()函数会先执行该函数
    func main() { // --》 { 不能放在单独的行上
    /*注释*/ fmt.Println("Hello, world!") }

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

    运行go代码-->go run hello.go

    生成二进制文件-->go build hello.go --> .exe

    Go语言基础语法

    Go标记:Go程序可以由多个标记组成,可以是关键字、标识符、常量、字符串、符号

    Go语言数据类型

    布尔型-->var b bool = true

    数字类型 --> int  float32 float64

    字符串类型 -->

    派生类型-->指针类型Pointer、数组类型、结构化类型struct、Channel类型、函数类型、切片类型、接口类型interface、Map类型

    与其他主要编程语言的差异:

    • Go语言不允许隐式类型转换
    • 别名和原有类型也不能进行隐式类型转换
    package day0527
    
    import "testing"
    
    func TestImplicit(t *testing.T) {
        var a int = 1
        var b int64
        b = a  //cannot use a (type int) as type int64 in assignment
        t.Log(a, b)
    }
    package day0527
    
    import "testing"
    
    type MyInt int64  //定义别名
    
    func TestImplicit(t *testing.T) {
        var a int = 1
        var b int64
        b = int64(a)  
        var c MyInt
        c = b  //cannot use b (type int64) as type MyInt in assignment
        t.Log(a, b, c)
    }

    类的预定义值:

    math.MaxInt64

    math.MaxFloat64

    math.MaxUint32

    声明变量使用var关键字,var identifier type

    package main
    import "fmt"
    func main() {
        var a string = "Runoob"
        fmt.Println(a)
    
        var b, c int = 1, 2
        fmt.Println(b, c)
    }
    package main
    
    var x, y int
    var (  //这种因式分解关键字的写法一般用于声明全局变量
        a int 
        b bool   
    )
    
    var c, d int = 1, 2
    var e, f = 123, "hello"
    
    
    func main() {
        g, h := 123, "hello"
        println(x, y, a, b, c, d, e, f, g, h)
    }

    值类型和引用类型

    int、float、bool、string这些基本类型都属于值类型,使用这些类型的变量直接指向存在内存中的值

    可以通过&i来获取变量i的内存地址。值类型的变量的值存储在栈中

    一个引用类型的变量r1存储的是r1的值所在的内存地址,或内存地址中第一个字所在的位置

    使用变量的首选形式  :=

    全局变量允许声明但不使用,局部变量声明了就必须使用否则报错

    交换两个变量的值,可以使用a,b = b,a

    空白标识符_也被用于抛弃值,如值5在: _, b = 5, 7中被抛弃

    _实际上是一个只写变量,不能得到它的值。这样做是因为Go语言中必须使用所有被声明的变量,但有时并不需要使用从一个函数得到的所有返回值

    Go语言运算符

    算数运算符

    比较运算符

    位运算符

    &^  按位置清零  1 &^ 0 --  1    1 &^ 1 -- 0    0 &^ 1 -- 0    0 &^ 0 -- 0  右边的二进制位是1,左边对应二进制位清零;右边的二进制位是0,左边对应二进制位不变

    Go语言常量

    常量中的数据类型只可以是布尔型、数字型(整数型、浮点型和复数)和字符串型

    常量定义:const identifier [type] = value

    可以省略类型说明符[type],因为编译器可以根据变量的值来推断其类型

    package main
    
    import "fmt"
    
    func main() {
        const LENGTH int = 10
        const WIDTH int = 5
        var area int
        const a, b, c = 1, false, "str"
    
        area = LENGTH * WIDTH
        fmt.Printf("面积为:%d", area)
        println()
        println(a, b, c)
    }
    //常量还可以用作枚举
    const (
    Unknown = 0
    Female = 1
    Male = 2
    )
    //常量可以用len(),cap(),unsafe.Sizeof()函数计算表达式的值。常量表达式中,函数必须是内置函数,否则编译不过
    
    package main
    
    import "unsafe"
    
    const (
        a = "abc"
        b = len(a)  //3
        c = unsafe.Sizeof(a)  //16
    )
    
    func main() {
        println(a, b, c)
    }

    iota

    iota,特殊常量,可以认为是一个可以被编译器修改的常量

    iota在const关键字出现时将被重置为0(const内部的第一行之前),const中每新增一行常量声明将使iota计数一次(iota可理解为const语句块中的行索引)

    iota可以被用作枚举值

    const (
    Monday = 1 + iota
    Tuesday
    Wednesday
    )

    const (
    Readable = 1 << iota
    Writable
    Executable
    )

    Go语言运算符

    Go语言条件语句

    Go语言循环语句-->仅有for循环

    goto语句,go语言的goto语句可以无条件地转移到过程中指定的行。goto语句通常与条件语句配合使用,可用来实现条件转移,构成循环,跳出循环体等功能

    无限循环while(true)-->for {}

    while条件循环 while(n<5)-->for n < 5 {}

    if条件

    if condition{ } else{ }

    支持变量赋值  if var declaration; condition {}

    package day0527
    
    import "testing"
    
    func TestIfMultiSec(t *testing.T) {
        if a := 1 == 2; a {
            t.Log("1==1")
        }
    
        //方法支持多返回值  v本身返回值  err返回一个错误
        if v, err := someFun(); err==nil {
            t.Log("1==1")
        }else{
            t.Log("1==1")
        }
    }

    switch条件

    条件表达式不限制为常量或者整数

    单个case中,可以出现多个结果选项,使用逗号分隔

    与C语言等规则相反,Go语言不需要用break来明确退出一个case

    可以不设定switch之后的条件表达式,在此种情况下,整个switch结构与多个if else的逻辑作用等同

    package day0527
    
    import "testing"
    
    func TestSwitchMultiCase(t *testing.T) {
        for i:=0;i<5;i++{
            switch i{
            case 0,2:
                t.Log("Even")
            case 1,3:
                t.Log("Odd")
            default:
                t.Log("it is not 0-3")
            }
        }
    }
    
    func TestSwitchCaseCondition(t *testing.T){
        for i:=0;i<5;i++{
            switch {
            case i%2 == 0:
                t.Log("Even")
            case i%2 == 1:
                t.Log("Odd")
            default:
                t.Log("unknow")
            }
        }
    }

    Go语言函数

    与其他编程语言的区别:可以有多个返回值;所有参数都是值传递(slice、map、channel会有传引用的错觉);函数可以作为变量的值;函数可以作为参数和返回值

    package day0529
    
    import (
        "fmt"
        "math/rand"
        "testing"
        "time"
    )
    //go函数可以有多个返回值
    func returnMultiValues()(int, int){
        return rand.Intn(10), rand.Intn(20)
    }
    //函数可以作为参数和返回值,闭包?装饰?
    func timeSpent(inner func (op int) int) func(op int)int{
        return func(n int) int{
            start := time.Now()
            ret:=inner(n)
    
            fmt.Println("time spent:", time.Since(start).Seconds())
            return ret
        }
    }
    
    func slowFn(op int) int{
        time.Sleep(time.Second * 1)
        return op
    }
    
    func TestFn(t *testing.T){
        a, b := returnMultiValues()
        t.Log(a, b)
        tsSF := timeSpent(slowFn)
        t.Log(tsSF(10))
    }
    //可变参数-->本质:转为数组
    func Sum(ops ...int) int{
        ret := 0
        for _, op := range ops{
            ret += op
        }
        return ret
    }
    
    func TestVarParam(t *testing.T){
        t.Log(Sum(1, 2, 3))
        t.Log(Sum(1, 2, 3, 4))
    }
    
    func Clear(){
        fmt.Println("Clear resources.")
    }
    //defer 延迟运行 类似finally
    func TestDefer(t *testing.T){
        defer Clear()
        fmt.Println("start")
        panic("err")//异常中断,后面的代码不会被执行,但会执行前面defer的函数
    }

    函数是基本的代码块,用于执行一个任务

    Go语言最少有个main函数

    函数声明告诉了编译器函数的名称,返回类型,和参数

    函数定义:  func function_name([parameter list]) [return_types] {函数体}

    func-->函数由func开始声明

    function_name-->函数名称,函数名和参数列表一起构成了函数签名

    parameter list-->参数列表

    return_types-->返回类型

    //函数返回两个数的最大值
    func max(num1, num2 int) int {
        //声明局部变量
        var result int
        
        if (num1 > num2) {
            result = num1
        } else {
            result = num2
        }
        return result
    }

    闭包

    package main
    
    import "fmt"
    
    func getSequence() func() int{
        i := 0
        return func() int {
            i += 1
            return i
        }
    }
    
    func main() {
        nextNumber := getSequence()
        /*调用nextNumber函数,i变量自增1并返回*/
        fmt.Println(nextNumber())
        fmt.Println(nextNumber())
        fmt.Println(nextNumber())
    
        /*创建新的函数nextNumber1,并查看结果*/
        nextNumber1 := getSequence()
        fmt.Println(nextNumber1())
        fmt.Println(nextNumber1())
    
    } 

    Go语言函数方法

    Go语言中同时有函数和方法。一个方法就是一个包含了接收者的函数,接受者可以是命名类型或者结构体类型的一个值或者是一个指针

    package main
    
    import (
        "fmt"
    )
    
    /*定义结构体*/
    type Circle struct {
        radius float64
    }
    
    func main() {
        var c1 Circle
        c1.radius = 10.00
        fmt.Println("圆的面积 = ", c1.getArea())
    }
    
    // func (variable_name variable_data_type) function_name() [return_type] {/*函数体*/}
    func (c Circle) getArea() float64 {
        return 3.14 * c.radius * c.radius
    }

    Go语言变量作用域

    Go语言中变量可以在三个地方声明:

    • 函数内定义的变量称为局部变量
    • 函数外定义的变量称为全局变量
    • 函数定义中的变量称为形式参数

    局部变量-->在函数体内声明的变量称之为局部变量,它们的作用域只在函数体内,参数和返回值变量也是局部变量

    全局变量-->在函数体外声明的变量称之为全局变量,全局变量可以在整个包甚至外部包(被导出后)使用

    Go语言程序中全局变量与局部变量名称可以相同,但是函数内的局部变量会被优先考虑

    形式参数-->作为函数的局部变量来使用

    package main
    
    import "fmt"
    
    /*声明全局变量*/
    var a int = 20;
    
    func main() {
        var a int = 10
        var b int = 20
        var c int = 0
    
        fmt.Printf("main()函数中 a = %d
    ", a)
        c = sum(a, b)
        fmt.Printf("main()函数中c = %d
    ", c)
    }
    
    func sum(a, b int) int {
        fmt.Printf("sum()函数中a = %d
    ", a)
        fmt.Printf("sum()函数中b = %d
    ", b)
    
        return a + b
    }

    Go语言数组和切片

    数组是具有相同唯一类型的一组已编号且长度固定的数据项序列,这种类型可以是任意的原始类型例如整形、字符串或者自定义类型

    Go语言数组声明需要指定元素类型及元素个数-->var variable_name [SIZE] variable_type  

    var a [3]int //声明并初始化为默认值

    b := [3]int{1, 2, 3}

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

    Go语言中,数组==比较的是值

    package day0527
    
    import "testing"
    
    func TestCompareArray(t *testing.T) {
        a := [...]int{1, 2, 3, 4}
        b := [...]int{1, 3, 4, 5}
        //c := [...]int{1, 2, 3, 4, 5}
        d := [...]int{1, 2, 3, 4}
        t.Log(a == b) //false
        t.Log(a == d) //true
    
    }

    Go语言字符串

    与其他主要编程语言的差异

    • string是数据类型-->0值是空不是nil,不是引用或指针类型
    • string是只读的byte slice,len函数可以得到它所包含的byte数
    • string的byte数组可以存放任何数据

    Unicode是一种字符集(code point)

    UTF8是unicode的存储实现(转换为字节序列的规则)

    Go语言切片-->动态数组

    Go语言切片是对数组的抽象,与数组相比切片的长度是不固定的,可以追加元素,在追加时可能使切片的容量增大

    定义切片-->可以声明一个未指定大小的数组来定义切片

    var identifier []type

    切片不需要说明长度,或使用make()函数来创建切片

    var slice1 []type = make([]type, len)
    
    或者
    
    slice1 := make([]type, len)
    package day0527
    
    import "testing"
    
    func TestArrayInit(t *testing.T) {
        var arr [3]int
        arr1 := [4]int{1, 2, 3, 4}
        arr3 := [...]int{1, 3, 4, 5}
        arr1[1] = 5
        t.Log(arr[1], arr[2])
        t.Log(arr1, arr3)
    }
    
    func TestArrayTravel(t *testing.T){
        arr3 := [...]int{1, 3, 4, 5}
        for i:=0;i<len(arr3);i++{
            t.Log(arr3[i])
        }
        for idx, e := range arr3 {
            t.Log(idx, e)
        }
    }
    
    func TestArraySection(t *testing.T) {
        arr3 := [...]int{1, 2, 3, 4, 5}
        arr3_sec := arr3[:3]
        t.Log(arr3_sec)
    }

    len()和cap()函数

    切片是可索引的,并且可以由len()方法获取长度

    切片提供了计算容量的方法cap()可以测量切片最长可以达到多少

    切片内部结构

    package day0527
    
    import "testing"
    
    func TestSliceInit(t *testing.T){
        var s0 []int
        t.Log(len(s0), cap(s0)) //0 0
        s0 = append(s0, 1)
        t.Log(len(s0), cap(s0))
        
        s1 := []int{1, 2, 3, 4}
        t.Log(len(s1), cap(s1))
        
        s2 := make([]int, 3, 5) //默认初始化只初始化len个元素
        t.Log(len(s2), cap(s2))
        t.Log(s2[0], s2[1], s2[2], s2[3], s2[4])
    }
    
    //切片是如何实现可变长的
    func TestSliceGrowing(t *testing.T){
        s := []int{}
        for i:=0;i<10;i++{
            s = append(s, i)
            t.Log(len(s), cap(s))
        }
    }

    切片共享存储结构:

    func TestSliceShareMemory(t *testing.T) {
        year := []string{"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep",
            "Oct", "Nov", "Dec"}
        Q2 := year[3:6]
        t.Log(Q2, len(Q2), cap(Q2))
        summer := year[5:8]
        t.Log(summer, len(summer), cap(summer))
        summer[0] = "Unknow"
        t.Log(Q2)
        t.Log(year)
    }

    append()和copy()函数

    如果想增加切片的容量,必须创建一个新的更大的切片并把原分片的内容都拷贝过来

    相同维度相同长度的数组可以比较,切片不可以比较,切片只能和nil比较

    Go语言指针

    变量是一种使用方便的占位符,用于引用计算机内存地址

    Go语言的取地址符是&,放到一个变量前使用就会返回相应变量的内存地址

    指针-->一个指针变量指向了一个值的内存地址

    var ip *int

    使用指针:定义指针变量-->为指针变量赋值-->访问指针变量中指向地址的值

    package main
    
    import "fmt"
    
    func main() {
        var a int = 20
        var ip *int
    
        ip = &a
    
        fmt.Printf("a变量的地址是:%x
    ", &a)
    
        /*指针变量的存储地址*/
        fmt.Printf("ip变量储存的指针地址:%x
    ", ip)
    
        /*使用指针访问值*/
        fmt.Printf("*ip变量的值:%d
    ", *ip)
    
    }

    Go空指针

    当一个指针被定义后没有分配到任何变量时,它的值为nil

    一个指针变量通常缩写为ptr

    指向指针的指针

    如果一个指针变量存放的又是另一个指针变量的地址,则称这个指针变量为指向指针的指针变量

    Go语言指针作为函数参数

    Go语言允许向函数传递指针,只需要在函数定义的参数上设置为指针类型即可

    指针类型 

    与其他主要编程语言的差异:不支持指针运算;string是值类型,其默认的初始化值为空字符串,而不是nil

    Go语言结构体

    Go语言中数组可以存储同一类型的数据,但在结构体中可以为不同项定义不同的数据类型

    定义结构体:使用type和struct语句

    struct语句定义一个新的数据类型,结构体中有一个或多个成员

    type语句设定了结构体的名称

    type struct_variable_type struct {
        member definition
        member definition
        ......
        member definition
    }
    package main
    
    import "fmt"
    
    type Books struct {
        title string
        author string
        subject string
        book_id int
    }
    
    func main() {
        //创建一个新的结构体
        fmt.Println(Books{"Go 语言", "www.runoob.com", "Go", 12})
        //可以使用key=>value格式
        fmt.Println(Books{title: "Go 语言", author:"www.run.com", subject:"Go", book_id: 00})
    
    }

    访问结构体成员-->

    结构体.成员名

    结构体作为函数参数-->

    可以向其他数据类型一样将结构体类型作为参数传递给函数

    结构体指针-->

    可以定义指向结构体的指针  var struct_pointer *Books

    使用结构体指针访问结构体成员,使用“.”操作符

    Go面向对象编程  不支持继承

    //数据和行为封装
    type Employee struct {
        Id string
        Name string
        Age int
    }
    
    //实例创建及初始化
    e := Employee{"0", "Bob", 20}
    e1 := Employee{Name:"Mike", Age:30}
    e2 := new(Employee) 
    //这里返回的是引用/指针,相当于e:=&Employee{}
    e2.Id = "2"
    e2.Age=22
    e2.Name = "Rose"
    //行为(方法)定义
    //与其他编程语言的主要差异
    //第一种定义方式在实例对应方法被调用时,实例的成员会进行值复制
    func (e Employee) String() string{
        return fmt.Println("ID:%S-Name:%s-Age:%d", e.Id, e.Name, e.Age)
    }
    //为了避免内存拷贝,通常使用第二种定义方式
    func (e *Employee) String() string {
        return fmt.Sprintf("ID:%s/Name:%s/Age%d", e.Id, e.Name, e.Age)
    }

    Go语言范围Range

    Go语言中range关键字用于for循环中迭代数组、切片、通道或集合的元素,在数组和切片中它赶回元素的索引和索引对应的值,在集合中返回key-value对

    package main
    
    import "fmt"
    
    func main() {
        nums := []int{2, 3, 4}
        sum := 0
        for _, num := range nums {
            sum += num
        }
        fmt.Println("sum:", sum)
        
        for i, num := range nums {
            if num == 3 {
                fmt.Println("index: ", i)
            }
        }
        
        kvs := map[string] string{"a": "apple", "b": "banana"}
        for k, v := range kvs {
            fmt.Printf("%s -> %s
    ", k, v)
        }
        
        for i, c := range "go" {
            fmt.Println(i, c)
        }
    }

    Go语言Map

    Map是一种无序的键值对的集合。Map最重要的是通过key来快速检索数据,key类似于索引,指向数据的值

    Map是一种集合,可以像迭代数组和切片那样迭代它。Map是无序的,无法决定它的返回顺序,因为Map是使用hash表来实现的

    与其他主要编程语言的差异:

    在访问的Key不存在时,仍会返回零值,不能通过返回nil来判断元素是否存在

    遍历:for range

    Map声明

    m := map[string]int{"one":1, "two":2, "three":3}

    m1 := map[string]int{}

    m2 := make(map[string]int, 10/*Initial Capacity*/)

    package day0527
    
    import "testing"
    
    func TestInitMap(t *testing.T) {
        m1 := map[int]int{1:1,2:4,3:9}
        t.Log(m1[2])
        t.Logf("len m1=%d", len(m1))
        m2 := map[int]int{}
        m2[4]=16
        t.Logf("len m2=%d", len(m2))
        m3 := make(map[int]int, 10)
        t.Logf("len m3=%d", len(m3))
    }
    
    func TestAccessNotExistingKey(t *testing.T) {
        m1 := map[int]int{}
        t.Log(m1[1])
        m1[2] = 0
        t.Log(m1[2])
        if v,ok:=m1[3];ok{
            t.Log("key 3's value is %d", v)
        }else{
            t.Log("key 3 is not existing")
        }
    }

    定义Map

    可以使用内建函数make也可以使用map关键字来定义Map

    //声明变量,默认map是nil
    var map_variable map[key_data_type]value_data_type
    
    //使用make函数
    map_variable := make(map[key_data_type]value_data_type)
    package main
    
    import "fmt"
    
    func main() {
        var countryCapitalMap map[string]string
        countryCapitalMap = make(map[string]string)
        
        //map插入key-value对
        countryCapitalMap["France"] = "巴黎"
        countryCapitalMap["Italy"] = "罗马"
        countryCapitalMap["Japan"] = "东京"
        countryCapitalMap["India"] = "新德里"
        
        for country := range countryCapitalMap {
            fmt.Println(country, "首都是", countryCapitalMap[country])
        }
        
        //查看元素在集合中是否存在
        capital, ok := countryCapitalMap["American"]
        
        if (ok) {
            fmt.Println("American的首都是", capital)
        } else {
            fmt.Println("American的首都不存在")
        }
        
        
    }

    delete()函数:用于删除集合的元素,参数为map和其对应的key

    package main
    
    import "fmt"
    
    func main() {
        countryCapitalMap := map[string]string{"France": "Pairs", "Italy": "Rome", "Japan": "Tokyo", "India": "New delhi"}
    
        fmt.Println("原始地图")
    
        for country := range countryCapitalMap {
            fmt.Println(country, "首都是", countryCapitalMap[country])
        }
    
        delete(countryCapitalMap, "France")
        fmt.Println("法国条目被删除")
        fmt.Println("删除元素后地图")
    
        for country := range countryCapitalMap {
            fmt.Println(country, "首都是", countryCapitalMap[country])
        }
    }

    map遍历

    //遍历
    func TestTravelMap(t *testing.T) {
        m1 := map[int]int{1:1, 2:4, 3:9}
        for k, v:=range m1{
            t.Log(k, v)
        }
    }

    Map与工厂模式

    • Map的value可以是一个方法-->实现工厂模式  
    • 与Go的Dock type接口方式一起,可以方便的实现单一方法对象的工厂模式
    package day0528
    
    import "testing"
    
    func TestMapWithFunValue(t *testing.T) {
        m := map[int]func(op int) int{}
        m[1] = func(op int) int { return op }
        m[2] = func(op int) int { return op*op }
        m[3] = func(op int) int { return op*op*op }
        t.Log(m[1](2), m[2](2), m[3](2))
    }

    实现Set

    Go的内置集合中没有Set实现,可以map[type]bool

    元素的唯一性

    基本操作:添加元素;判断元素是否存在;删除元素;元素个数

    func TestMapForSet(t *testing.T) {
        mySet:=map[int]bool{}
        mySet[1]=true
        n := 1
        if mySet[n]{
            t.Logf("%d is existing", n)
        }else{
            t.Logf("%d is not existing", n)
        }
        mySet[3]=true
        t.Log(len(mySet))
        delete(mySet,1)
    }

    Go语言递归函数

    func recursion() {
        recursion()
    }

    Go语言接口

    接口与依赖

    //Programmer.java
    public interface Programmer {
        String WriteCodes();
    }
    
    //GoProgrammer.java
    public class GoProgrammer implements Programmer {
        @Override
        public String WriteCodes() {
            return "fmt.Println("Hello World")";
        }
    }
    
    //Task.java
    public class Task {
        public static void main(String[] args) {
            Programmer prog = new GoProgrammer();
            String codes = prog.WriteCodes();
            System.out.println(codes);
        }
    }
    package _interface
    
    import "testing"
    
    type Programmer interface {
        WriteHelloWorld() string
    }
    
    type GoProgrammer struct {
    }
    
    func (go *GoProgrammer) WriteHelloWorld() string{
        return "fmt.Println("Hello World")"
    }
    
    func TestClient(t *testing.T) {
        var p Programmer
        p = new(GoProgrammer)
        t.Log(p.WriteHelloWorld())
    }

    Go接口与其他主要编程语言的差异:接口为非入侵性,实现不依赖于接口定义;接口的定义可以包含在接口使用者包内

    接口变量

    自定义类型

    package customer_type
    
    import (
        "fmt"
        "time"
    )
    
    type IntConv func(op int) int
    
    func timeSpent(inner IntConv) IntConv {
        return func(n int) int {
            start := time.Now()
            ret :=inner(n)
            fmt.Println("time spent:", time.Since(start).Seconds())
            return ret
        }
    }

    Go语言提供了另外一种数据类型即接口,它把所有的具有共性的方法定义在一起,任何其他类型只要实现了这些方法就是实现了这个接口

    package main
    
    type interface_name interface {
        method_name1 [return_type]
        method_name2 [return_type]
        method_name3 [return_type]
        method_namen [return_type]
    }
    
    type struct_name struct {
    
    }
    
    func (struct_name_variable struct_name) method_name1[return_type]() {
    
    }
    package main
    
    import (
        "fmt"
    )
    
    type Phone interface {
        call()
    }
    
    type NokiaPhone struct {
    
    }
    
    func (nokiaPhone NokiaPhone) call() {
        fmt.Println("I am Nokia, I can call you!")
    }
    
    type IPhone struct {
    
    }
    
    func (iPhone IPhone) call() {
        fmt.Println("I an iPhone, I can call you!")
    }
    
    func main() {
        var phone Phone
    
        phone = new(NokiaPhone)
        phone.call()
    
        phone = new(IPhone)
        phone.call()
    }

    空接口与断言

    空接口可以表示任何类型  类似于java的object类型 c/c++的void *类型

    通过断言来将空接口转换为指定类型  v, ok := p.(int)  //ok=true时为转换成功

    package empty_interface
    
    func DoSomething(p interface{}) {
        if i, ok := p.(int); ok{
            fmt.Println("Integer", i)
            return
        }
        if i, ok := p.(string); ok{
            fmt.Println("string", s)
            return
        }
        fmt.Println("Unknow Type")
    }
    
    func TestEmptyInterfaceAssertion(t *testing.T) {
        DoSomething(10)
        DoSomething("10")
    }
    package empty_interface
    
    func DoSomething(p interface{}) {
        switch v := p.(type){
        case int:
            fmt.Println("Integer", v)
        case string:
            fmt.Println("String", v)
        default:
            fmt.Println("Unknown Type", v)
        }
    }
    
    func TestEmptyInterfaceAssertion(t *testing.T) {
        DoSomething(10)
        DoSomething("10")
    }

    Go接口最佳实践

    //倾向于使用小的接口定义,很多接口只包含一个方法
    
    type Reader interface {
        Read(p []byte) (n int, err error)
    }
    
    //较大的接口定义,可以由多个小接口定义组合而成
    type ReadWriter interface{
        Reader
        Writer
    }
    
    //只依赖于必要功能的最小接口
    func StoreData(reader Reader) error{
        ...
    }

    Go语言不支持继承

    扩展与复用

    package extension
    
    import (
        "fmt"
        "testing"
    )
    
    type Pet struct {}
    
    func (p *Pet) Speak() {
        fmt.Print("...")
    }
    
    func (p *Pet) SpeakTo(host string) {
        p.Speak()
        fmt.Println(" ", host)
    }
    
    type Dog struct {
        p *Pet
    }
    
    func (d *Dog) Speak() {
        d.p.Speak()
    }
    
    func (d *Dog) SpeakTo(host string) {
        d.p.SpeakTo(host)
    }
    
    func TestDog(t *testing.T) {
        dog := new(Dog) //...
        dog.SpeakTo("a")//a
    }
    
    
    package extension

    import (
    "fmt"
    "testing"
    )

    type Pet struct {}

    func (p *Pet) Speak() {
    fmt.Print("...")
    }

    func (p *Pet) SpeakTo(host string) {
    p.Speak()
    fmt.Println(" ", host)
    }

    type Dog struct {
    Pet //匿名复用-->可以拥有父类的特性
    }


    func TestDog(t *testing.T) {
    dog := new(Dog) //...
    dog.SpeakTo("a")//a
    }
     

    Go错误处理

    与其他主要编程语言的差异

    1、没有异常机制

    2、error类型实现了error接口

    3、可以通过errors.New来快速创建错误实例

    type error interface {
        Error() string
    }
    
    errors.New("n must be in the range [0, 1]")
    //errors是一个package

    Go语言通过内置的错误接口提供了非常简单的错误处理机制

    //error类型是一个接口类型
    
    type error interface {
        Error() string
    }

    可以在编码中通过实现error接口类型来生成错误信息

    函数通常在最后的返回值中返回错误信息

    package main
    
    import (
        "fmt"
    
    )
    
    //定义一个DivideError结构
    type DivideError struct {
        dividee int
        divider int
    }
    
    func (de *DivideError) Error() string {
        strFormat := `
            Cannot proceed, the divider is zero.
            dividee: %d
            divider:0`
        return fmt.Sprintf(strFormat, de.dividee)
    }
    
    func Divide(varDividee int, varDivider int) (result int, errorMsg string) {
        if varDivider == 0 {
            dData := DivideError{
                dividee: varDividee,
                divider: varDivider,
            }
            errorMsg = dData.Error()
            return
        } else {
            return varDividee / varDivider, ""
        }
    }
    
    func main() {
        if result, errorMsg := Divide(100, 10) ; errorMsg == "" {
            fmt.Println("100/10 = ", result)
        }
        if _, errorMsg := Divide(100, 0); errorMsg != "" {
            fmt.Println("errorMsg is:", errorMsg)
        }
    }
    package err_test
    
    import (
        "errors"
        "fmt"
        "testing"
    )
    
    var LessThanTwoError = errors.New("n should be not less than 2")
    var LargerThanHunderError = errors.New("n should be not larger than 100")
    
    func GetFibonacci(n int) ([]int, error) {
        if n < 2 {
            return nil, LessThanTwoError
        }
    
        if n > 100 {
            return nil, LargerThanHunderError
        }
        fibList := []int{1, 1}
    
        for i := 2; i < n; i++ {
            fibList = append(fibList, fibList[i - 2] + fibList[i - 1])
        }
        return fibList, nil
    }
    
    func TestGetFibonacci(t *testing.T){
        if v, err := GetFibonacci(-10); err != nil{
            if err == LessThanTwoError{
                fmt.Println("It is less.")
            }
            t.Error(err)
        } else {
            t.Log(v)
        }
    }

    panic和recover

    panic vs. os.Exit

    os.Exit退出时不会调用defer指定的函数

    os.Exit退出时不输出当前调用栈信息

    recover

    defer func(){
        if err := recover(); err != nil{
            //恢复
        }
    }

    包和依赖管理

    package

    1、基本复用模块单元  以首字母大写来表明可被包外代码访问

    2、代码的package可以和所在的目录不一致(Java中目录名和pachage要保持一致)

    3、同一目录里的Go代码的package要保持一致

    1、通过go get来获取远程依赖  go get -u强制从网络更新远程依赖

    2、注意代码在GitHub上的组织形式,以适应go get

    直接以代码路径开始,不要有src

    init方法

    • 在main被执行前,所有依赖的package的init方法都会被执行
    • 不同包的init函数按照包导入的依赖关系决定执行顺序
    • 每个包可以有多个init函数
    • 包的每个源文件也可以有多个init函数,这点比较特殊

    Thread vs. Groutine

    1、创建时默认的stack的大小

    JDK5以后Java Thread stack默认为1M

    Groutine的Stack初始化大小为2K

    2、和KSE(Kernel Space Entity)的对应关系

    Java Thread是1:1

    Groutine是M:N

    package groutine_test
    
    import (
        "fmt"
        "testing"
        "time"
    )
    
    func TestGroutine(t *testing.T) {
        for i:=0; i<10; i++{
            go func (i int) {
                fmt.Println(i)
            }(i)
        }
        time.Sleep(time.Millisecond * 50)
    }
    
    //----------------------------------------值传递,不会产生竞态条件
    1
    0
    5
    2
    3
    4
    7
    6
    8
    9
    package groutine_test
    
    import (
        "fmt"
        "testing"
        "time"
    )
    
    func TestGroutine(t *testing.T) {
        for i:=0; i<10; i++{
            go func ( ) {
                fmt.Println(i)
            }()
        }
        time.Sleep(time.Millisecond * 50)
    }
    //-----------------------------------------i被共享了,竞态条件
    10
    10
    10
    10
    10
    10
    10
    10
    10
    10

    Go并发

    Go语言支持并发,只需要通过go关键字来开启goroutine即可

    goroutine是轻量级线程,goroutine的调度是由Golang运行时进行管理的

    goroutine语法格式:

    go 函数名(参数列表)

    Go允许使用go语句开启一个新的运行期线程,即goroutine,以一个不同的、新创建的goroutine来执行一个函数。同一个程序中的所有goroutine共享同一个地址空间

    package main
    
    import (
        "fmt"
        "time"
    )
    
    func say(s string) {
        for i := 0; i < 5; i++ {
            time.Sleep(100 * time.Millisecond)
            fmt.Println(s)
        }
    }
    
    func main() {
        //go f(x, y, z)  --> f(x, y, z)
        go say("world")
        say("hello")
    }

    共享内存并发机制 

    Lock
    
    Lock lock = ...;
    lock.lock();
    try {
        //process (thread-safe)
    }catch(Exception ex){
    
    }finally{
        lock.unlock();
    }

    //
    package sync
    Mutex
    RWLock
    package share_mem
    
    import (
        "sync"
        "testing"
        "time"
    )
    
    func TestCounter(t *testing.T) {
        counter := 0
        for i:=0; i<5000; i++{
            go func() {
                counter++
            }()
        }
        time.Sleep(1 * time.Second)
        t.Logf("counter = %d", counter)
    }
    
    func TestCounterThreadSafe(t *testing.T) {
        var mut sync.Mutex
        counter := 0
        for i := 0; i < 5000; i++ {
            go func() {
                 defer func(){
                     mut.Unlock()
                }()
                mut.Lock()
                counter++
            }()
    
        }
        time.Sleep(1 * time.Second)
        t.Logf("counter = %d", counter)
    }
    //WaitGroup
    var wg sync.WaitGroup
    for i := 0; i < 5000; i++ {
        wg.Add(1)
        go func() {
            defer func() {
                wg.Done()
            }()
            ......
        }()
    }
    wg.Wait()
    package share_mem
    
    import (
        "sync"
        "testing"
        "time"
    )
    
    func TestCounter(t *testing.T) {
        counter := 0
        for i:=0; i<5000; i++{
            go func() {
                counter++
            }()
        }
        time.Sleep(1 * time.Second)
        t.Logf("counter = %d", counter)
    }
    
    func TestCounterThreadSafe(t *testing.T) {
        var mut sync.Mutex
        counter := 0
        for i := 0; i < 5000; i++ {
            go func() {
                 defer func(){
                     mut.Unlock()
                }()
                mut.Lock()
                counter++
            }()
    
        }
        time.Sleep(1 * time.Second)
        t.Logf("counter = %d", counter)
    }
    
    func TestCounterWaitGroup(t *testing.T) {
        var mut sync.Mutex
        var wg sync.WaitGroup
        counter := 0
        for i := 0; i < 5000; i++ {
            wg.Add(1)
            go func() {
                defer func(){
                    mut.Unlock()
                }()
                mut.Lock()
                counter++
                wg.Done()
            }()
    
        }
        wg.Wait()
        t.Logf("counter = %d", counter)
    }

    CSP并发机制

    Communicating sequential processes

    CSP vs. Actor

    • 和Actor的直接通讯不同,CSP模式则是通过Channel进行通讯的,更松耦合一些
    • Go中channel是有容量限制并且独立于处理Groutine,而如Erlang、Actor模式中的mailbox容量是无限的,接收进程也总是被动地处理消息

    异步返回

    private static FutureTask<String> service() {
        FutureTask<String> task = new FutureTask<String>(()->"Do something");
        new Thread(task).start();
        return task;
    }
    
    FutureTask<String> ret = service();
    System.out.println("Do something else");
    System.out.println(ret.get()); 

    通道channel

    通道channel是用来传递数据的一个数据结构

    通道可用于两个goroutine之间通过传递一个指定类型的值来同步运行和通讯。操作符<-用于指定通道的方向,发送或接收。如果未指定方向,则为双向通道

    ch <- v  //把v发送到通道ch
    v := <-ch  //从ch接收数据并把值赋给v
    //声明一个通道,使用chan关键字
    ch := make(chan int)
    //默认情况下,通道是不带缓冲区的。发送端发送数据,同时必须有接收端相应的接收数据
    package main
    
    import "fmt"
    
    func sum(s []int, c chan int) {
        sum := 0
        for _, v := range s {
            sum += v
        }
        c <- sum  //把sum发送到通道c
    }
    
    func main() {
        s := []int{7, 2, 8, -9, 4, 0}
    
        c := make(chan int)
        go sum(s[:len(s)/2], c)
        go sum(s[len(s)/2:], c)
        x, y := <-c, <-c //从通道c中接收
    
        fmt.Println(x, y, x+y)
    }
    package main
    
    //通道可以设置缓冲区,通过make的第二个参数指定缓冲区大小
    //ch := make(chan int, 100)
    //带缓冲区的通道允许发送端的数据发送和接收端的数据获取处于异步状态
    //如果通道不带缓冲,发送方会阻塞直到接收方从通道中接收了值
    //如果通道带缓冲,发送方则会阻塞直到发送的值被拷贝到缓冲区内;如果缓冲区已满,则意味着需要等待直到某个接收方获取到一个值。接收方在有值可以接收之前会一直阻塞
    
    import "fmt"
    
    func main() {
        ch := make(chan int, 2)
    
        ch <- 1
        ch <- 2
    
        fmt.Println(<- ch)
        fmt.Println(<- ch)
    
    }
    package main
    
    //Go遍历通道与关闭通道
    //Go通过range关键字来实现遍历读取到的数据,类似于与数组或切片  格式  v, ok := <-ch
    //如果通道接收不到数据后ok就为false,这时通道就可以使用close()函数来关闭
    
    import (
        "fmt"
    )
    
    func fibonacci(n int, c chan int) {
        x, y := 0, 1
        for i := 0; i < n; i++ {
            c <- x
            x, y = y, x+y
        }
        close(c)
        
    }
    
    func main() {
        c := make(chan int, 10)
        go fibonacci(cap(c), c)
        for i := range c {
            fmt.Println(i)
        }
    }

    多路选择和超时

    select

    //多渠道的选择
    select {
    case ret := <-retCh1:
        t.Logf("result %s", ret)
    case ret := <-retCh2:
        t.Logf("result %s", ret)
    default:
        t.Error("No one returned")
    }
    
    //超时控制
    select {
    case ret := <-retCh:
        t.Logf("result %s", ret)
    case <-time.After(time.Second * 1):
        t.Error("time out")
    }

    Go语言开发工具

  • 相关阅读:
    Java常用类库(二):Iterator迭代器和子范围视图
    Java常用类库(一) : Object 和日期类的简单使用
    MyBatis的逆向工程
    AdminLTE介绍和zTree的简单使用
    MyBatis分页组件--PageHelper
    SpringAop--系统日志简例
    Shiro
    Mysql(三):多表查询和存储程序
    MySql(二):常见的那些个约束
    正则表达式一些用法
  • 原文地址:https://www.cnblogs.com/liushoudong/p/12935793.html
Copyright © 2020-2023  润新知