• Java程序员的Golang入门指南(上)


    Java程序员的Golang入门指南

    1.序言

    Golang作为一门出身名门望族的编程语言新星,像豆瓣的Redis平台Codis、类Evernote的云笔记leanote等。

    1.1 为什么要学习

    如果有人说X语言比Y语言好,两方的支持者经常会激烈地争吵。如果你是某种语言老手,你就是那门语言的“传道者”,下意识地会保护它。无论承认与否,你都已被困在一个隧道里,你看到的完全是局限的。《肖申克的救赎》对此有很好的注脚:

    [Red] These walls are funny. First you hate ‘em, then you get used to ‘em. Enough time passes, you get so you depend on them. That’s institutionalized.
    这些墙很有趣。起初你恨它们,之后你习惯了它们。随着时间流逝,你开始以来它们。这就是体制。

    在你还没有被完全“体制化”时,为何不多学些语言,哪怕只是浅尝辄止,潜移默化中也许你的思维壁垒就松动了。不管是Golang还是Ruby还是其他语言,当看到一些语法习惯与之前熟悉的C和Java不同时,的确潜意识里就会产生抵触情绪,觉得这不好,还是自己习惯的那套好。长此以往,如果不能冲破自己的心理,“坐以待毙”,被时间淘汰恐怕只是早晚的事儿。所以这里的关键也 不是非要学习Golang,而是要不断地学!

    1.2 豪华的开发团队

    (略)

    1.3 用什么工具来开发

    Golang也有专门的IDE,但由于最近迷上了Sublime Text神器,所以这里还是用ST来学习Golang。配置步骤与在ST中使用其他语言开发都类似:

    1. 安装智能提示插件GoSublime
    2. 创建编译配置脚本

    点Preferences -> Package Settings -> GoSublime -> User Settings中写入(感觉保存时自动格式化出来的缩进、空格等风格有些“讨厌”,所以就禁掉了):

    {
        "fmt_enabled": false,
        "env": {    
            "path":"D:\Program Files (x86)\Goin"
        }
    }

    点新建Build System产生go.sublime-build中写入:

    {
        "path": "D:\Program Files (x86)\Go\bin",
        "cmd": ["go", "run", "${file}"],
        "selector": "source.go"
    }

    2.你好,世界

    Golang版的HelloWorld来了!一眼望去,package和import的声明方式与Java如出一辙,比较明显的区别是:func关键字、每行末尾没有分号、Println()大写的函数名。这个例子虽小,却“五脏俱全”,后面会逐一分析这个小例子中碰到的Golang语法点。

    package main
    
    import "fmt"
    
    func main() {
        fmt.Println("你好,世界!")
    }

    2.1 运行方式

    Golang提供了go run“解释”执行和go build编译执行两种运行方式,所谓的“解释”执行其实也是编译出了可执行文件后才执行的。

    $ go run helloworld.go
    你好,世界!
    
    $ go build helloworld.go
    $ ls
    helloworld  helloworld.go
    $ ./helloworld
    你好,世界!

    2.2 Package管理

    上面例子中我们使用的就是fmt包下的Println()函数。Golang约定:我们可以用./或../相对路径来引自己的package;如果不是相对路径,那么go会去$GOPATH/src下查找。

    2.3 格式化输出

    类似C、Java等语言,Golang的fmt包提供了格式化输出功能,而且像%d、%s等占位符和 、 、 转义也几乎完全一致。但Golang的Println不支持格式化,只有Printf支持,所以我们经常会在后面加入 换行。此外,Golang加入了%T打印值的类型,%v打印数组等集合的所有元素。

    package main
    
    import "fmt"
    import "math"
    
    /**
     * This is Printer!
     * 布尔值:false
     * 二进制:11111111
     * 八进制:377
     * 十六进制:FF
     * 十进制:255
     * 浮点数:3.141593
     * 字符串:printer
     *
     * 对象类型:int,string,bool,float64
     * 集合:[1 2 3 4 5]
     */
    func main() {
        fmt.Println("This is Printer!")
    
        fmt.Printf("布尔值:%t
    ", 1 == 2)
        fmt.Printf("二进制:%b
    ", 255)
        fmt.Printf("八进制:%o
    ", 255)
        fmt.Printf("十六进制:%X
    ", 255)
        fmt.Printf("十进制:%d
    ", 255)
        fmt.Printf("浮点数:%f
    ", math.Pi)
        fmt.Printf("字符串:%s
    ", "printer")
    
        fmt.Printf("对象类型:%T,%T,%T,%T
    ", 1, "hello", true, math.E)
        fmt.Printf("集合:%v
    ", [5]int{1, 2, 3, 4, 5})
    }

    3.语法基础

    3.1 变量和常量

    虽然Golang是静态类型语言,却用类似JavaScript中的var关键字声明变量。而且像同样是静态语言的Scala一样,支持类型自动推断。有一点很重要的不同是:如果明确指明变量类型的话,类型要放在变量名后面。这有点别扭吧?!后面会看到函数的入参和返回值的类型也要这样声明。

    package main
    
    import "fmt"
    
    /**
     * 单变量声明:num[100], word[hello]
     * 多变量声明:i[1], i[2], k[3]
     * 推导类型:b1[true], b2[false]
     * 常量:age[20], pi[3.141593]
     */
    func main() {
        var num int = 100
        var word string = "hello"
        fmt.Printf("单变量声明:num[%d], word[%s]
    ", num, word)
    
        var i, j, k int = 1, 2, 3
        fmt.Printf("多变量声明:i[%d], i[%d], k[%d]
    ", i, j, k)
    
        var b1 = true
        b2 := false
        fmt.Printf("推导类型:b1[%t], b2[%t]
    ", b1, b2)
    
        const age int = 20
        const pi float32 = 3.1415926
        fmt.Printf("常量:age[%d], pi[%f]
    ", age, pi)
    }

    3.2 控制语句

    作为最基本的语法要素,Golang的各种控制语句也是特点鲜明。在对C继承发扬的同时,也有自己的想法融入其中:

    • if/switch/for的条件部分都没有圆括号,但必须有花括号。
    • switch的case中不需要break。《C专家编程》里也“控诉”了C的fall-through问题。既然90%以上的情况都要break,为何不将break作为case的默认行为?而且编程语言后来者也鲜有纠正这一问题的。
    • switch的case条件可以是多个值
    • Golang中没有while
    package main
    
    import "fmt"
    
    /**
     * testIf: x[2] is even
     * testIf: x[3] is odd
     *
     * testSwitch: One
     * testSwitch: Two
     * testSwitch: Three, Four, Five [3]
     * testSwitch: Three, Four, Five [4]
     * testSwitch: Three, Four, Five [5]
     *
     * 标准模式:[0] [1] [2] [3] [4] [5] [6] 
     * While模式:[0] [1] [2] [3] [4] [5] [6] 
     * 死循环模式:[0] [1] [2] [3] [4] [5] [6] 
     */
    func main() {
        testIf(2)
        testIf(3)
        testSwitch(1)
        testSwitch(2)
        testSwitch(3)
        testSwitch(4)
        testSwitch(5)
        testFor(7)
    }
    
    func testIf(x int) {
        if x % 2 == 0 {
            fmt.Printf("testIf: x[%d] is even
    ", x)
        } else {
            fmt.Printf("testIf: x[%d] is odd
    ", x)
        }
    }
    
    func testSwitch(i int) {
        switch i {
            case 1:
                fmt.Println("testSwitch: One")
            case 2:
                fmt.Println("testSwitch: Two")
            case 3, 4, 5:
                fmt.Printf("testSwitch: Three, Four, Five [%d]
    ", i)
            default:
                fmt.Printf("testSwitch: Invalid value[%d]
    ", i)
        }
    }
    
    func testFor(upper int) {
        fmt.Print("标准模式:")
        for i := 0; i < upper; i++ {
            fmt.Printf("[%d] ", i)
        }
        fmt.Println()
    
        fmt.Print("While模式:")
        j := 0
        for j < upper {
            fmt.Printf("[%d] ", j)
            j++
        }
        fmt.Println()
    
        fmt.Print("死循环模式:")
        k := 0
        for {
            if (k >= upper) {
                break
            }
            fmt.Printf("[%d] ", k)
            k++
        }
        fmt.Println()
    }

    分号和花括号
    分号由词法分析器在扫描源代码过程自动插入的,分析器使用简单的规则:如果在一个新行前方的最后一个标记是一个标识符(包括像int和float64这样的单词)、一个基本的如数值这样的文字、或break continue fallthrough return ++ – ) }中的一个时,它就会自动插入分号。
    分号的自动插入规则产生了“蝴蝶效应”:所有控制结构的左花括号不都能放在下一行。因为按照上面的规则,这样做会导致分析器在左花括号的前方插入一个分号,从而引起难以预料的结果。所以Golang中是不能随便换行的

    3.3 函数

    函数有几点不同:

    • func关键字。
    • 最大的不同就是“倒序”的类型声明
    • 不需要函数原型,引用的函数可以后定义。这一点很好,真不喜欢C语言里要么将“最底层抽象”的函数放在最前面定义,要么写一堆函数原型声明在最前面。

    3.4 集合

    Golang提供了数组和Map作为基本数据结构:

    • 数组中的元素会自动初始化,例如int数组元素初始化为0
    • 切片(借鉴Python)的区间跟主流语言一样,都是 “左闭右开”
    • range()遍历数组和Map
    package main
    
    import "fmt"
    
    /**
     * Array未初始化:  [0 0 0 0 0]
     * Array赋值:  [0 10 0 20 0]
     * Array初始化:  [0 1 2 3 4 5]
     * Array二维:  [[0 1 2] [1 2 3]]
     * Array切片: [2 3] [0 1 2 3] [2 3 4 5]
     *
     * Map哈希表:map[one:1 two:2 three:3],长度[3]
     * Map删除元素后:map[one:1 three:3],长度[2]
     * Map打印:
     *  one => 1
     *  four => 4
     *  three => 3
     *  five => 5
     */
    func main() {
        testArray()
        testMap()
    }
    
    func testArray() {
        var a [5]int
        fmt.Println("Array未初始化: ", a)
    
        a[1] = 10
        a[3] = 20
        fmt.Println("Array赋值: ", a)
    
        b := []int{0, 1, 2, 3, 4, 5}
        fmt.Println("Array初始化: ", b)
    
        var c [2][3]int
        for i := 0; i < 2; i++ {
            for j := 0; j < 3; j++ {
                c[i][j] = i + j
            }
        }
        fmt.Println("Array二维: ", c)
    
        d := b[2:4] // b[3,4]
        e := b[:4]  // b[1,2,3,4]
        f := b[2:]  // b[3,4,5]
        fmt.Println("Array切片:", d, e, f)
    }
    
    func testMap() {
        m := make(map[string]int)
    
        m["one"] = 1
        m["two"] = 2
        m["three"] = 3
        fmt.Printf("Map哈希表:%v,长度[%d]
    ", m, len(m))
    
        delete(m, "two")
        fmt.Printf("Map删除元素后:%v,长度[%d]
    ", m, len(m))
    
        m["four"] = 4
        m["five"] = 5
        fmt.Println("Map打印:")
        for key, val := range m {
            fmt.Printf("	%s => %d
    ", key, val)
        }
        fmt.Println()
    }

    3.5 指针和内存分配

    Golang中可以使用指针,并提供了两种内存分配机制:

    • new:分配长度为0的空白内存,返回类型T*。
    • make:仅用于 切片、map、chan消息管道,返回类型T而不是指针。
    package main
    
    import "fmt"
    
    /**
     * 整数i=[10],指针pInt=[0x184000c0],指针指向*pInt=[10]
     * 整数i=[3],指针pInt=[0x184000c0],指针指向*pInt=[3]
     * 整数i=[5],指针pInt=[0x184000c0],指针指向*pInt=[5]
     * 
     * Wild的数组指针: <nil>
     * Wild的数组指针==nil[true]
     * 
     * New分配的数组指针: &[]
     * New分配的数组指针[0x18443010],长度[0]
     * New分配的数组指针==nil[false]
     * New分配的数组指针Make后: &[0 0 0 0 0 0 0 0 0 0]
     * New分配的数组元素[3]: 23
     * 
     * Make分配的数组引用: [0 0 0 0 0 0 0 0 0 0]
     */
    func main() {
        testPointer()
        testMemAllocate()
    }
    
    func testPointer() {
        var i int = 10;
        var pInt *int = &i;
        fmt.Printf("整数i=[%d],指针pInt=[%p],指针指向*pInt=[%d]
    ",
                        i, pInt, *pInt)
    
        *pInt = 3
        fmt.Printf("整数i=[%d],指针pInt=[%p],指针指向*pInt=[%d]
    ",
                        i, pInt, *pInt)
    
        i = 5
        fmt.Printf("整数i=[%d],指针pInt=[%p],指针指向*pInt=[%d]
    ",
                        i, pInt, *pInt)
    }
    
    func testMemAllocate() {
        var pNil *[]int
        fmt.Println("Wild的数组指针:", pNil)
        fmt.Printf("Wild的数组指针==nil[%t]
    ", pNil == nil)
    
        var p *[]int = new([]int)
        fmt.Println("New分配的数组指针:", p)
        fmt.Printf("New分配的数组指针[%p],长度[%d]
    ", p, len(*p))
        fmt.Printf("New分配的数组指针==nil[%t]
    ", p == nil)
    
        //Error occurred
        //(*p)[3] = 23
    
        *p = make([]int, 10)
        fmt.Println("New分配的数组指针Make后:", p)
        (*p)[3] = 23
        fmt.Println("New分配的数组元素[3]:", (*p)[3])
    
        var v []int = make([]int, 10)
        fmt.Println("Make分配的数组引用:", v)
    }

    3.6 面向对象编程

    Golang的结构体跟C有几点不同:

    • 结构体可以有方法,其实也就相当于OOP中的类了。
    • 支持带名称的初始化。
    • 用指针访问结构中的属性也用”.”而不是”->”,指针就像Java中的引用一样。
    • 没有public,protected,private等访问权限控制。C也没有protected,C中默认是public的,private需要加static关键字限定。Golang中方法名大写就是public的,小写就是private的。

    同时,Golang支持接口和多态,而且接口有别于Java中继承和实现的方式,而是采取了类似Ruby中更为新潮的Duck Type。只要struct与interface有相同的方法,就认为struct实现了这个接口。就好比只要能像鸭子那样叫,我们就认为它是一只鸭子一样。

    package main
    
    import (
        "fmt"
        "math"
    )
    
    // -----------------
    //      Struct
    // -----------------
    
    type Person struct {
        name    string
        age     int
        email   string
    }
    
    func (p *Person) getName() string {
        return p.name
    }
    
    // -------------------
    //      Interface
    // -------------------
    
    type shape interface {
        area() float64
    }
    
    type rect struct {
        width float64
        height float64
    }
    
    func (r *rect) area() float64 {
        return r.width * r.height
    }
    
    type circle struct {
        radius float64
    }
    
    func (c *circle) area() float64 {
        return math.Pi * c.radius * c.radius
    }
    
    // -----------------
    //      Test
    // -----------------
    
    /**
     * 结构Person[{cdai 30 cdai@gmail.com}],姓名[cdai]
     * 结构Person指针[&{cdai 30 cdai@gmail.com}],姓名[cdai]
     * 用指针修改结构Person为[{carter 40 cdai@gmail.com}]
     * 
     * Shape[0]周长为[13.920000]
     * Shape[1]周长为[58.088048]
     */
    func main() {
        testStruct()
        testInterface()
    }
    
    func testStruct() {
        p1 := Person{"cdai", 30, "cdai@gmail.com"}
        p1 = Person{name: "cdai", age: 30, email: "cdai@gmail.com"}
        fmt.Printf("结构Person[%v],姓名[%s]
    ", p1, p1.getName())
    
        ptr1 := &p1
        fmt.Printf("结构Person指针[%v],姓名[%s]
    ", ptr1, ptr1.getName())
    
        ptr1.age = 40
        ptr1.name = "carter"
        fmt.Printf("用指针修改结构Person为[%v]
    ", p1)
    }
    
    func testInterface() {
        r := rect {  2.9, height: 4.8 }
        c := circle { radius: 4.3 }
    
        s := []shape{ &r, &c }
        for i, sh := range s {
            fmt.Printf("Shape[%d]周长为[%f]
    ", i, sh.area())
        }
    }

    3.7 异常处理

    Golang中异常的使用比较简单,可以用errors.New创建,也可以实现Error接口的方法来自定义异常类型,同时利用函数的多返回值特性可以返回异常类。比较复杂的是defer和recover关键字的使用。Golang没有采取try-catch“包住”可能出错代码的这种方式,而是用 延迟处理 的方式。

    用defer调用的函数会以后进先出(LIFO)的方式,在当前函数结束后依次顺行执行。defer的这一特点正好可以用来处理panic。当panic被调用时,它将立即停止当前函数的执行并开始逐级解开函数堆栈,同时运行所有被defer的函数。如果这种解开达到堆栈的顶端,程序就死亡了。但是,也可以使用内建的recover函数来重新获得Go程的控制权并恢复正常的执行。由于仅在解开期间运行的代码处在被defer的函数之内,recover仅在被延期的函数内部才是有用的

    package main
    
    import (
        "fmt"
        "errors"
        "os"
    )
    
    /**
     * 自定义Error类型,实现内建Error接口
     * type Error interface {
     *      Error() string
     * }
     */
    type MyError struct {
        arg int
        msg string
    }
    
    func (e *MyError) Error() string {
        return fmt.Sprintf("%d - %s", e.arg, e.msg)
    }
    
    /**
     * Failed[*errors.errorString]: Bad Arguments - negative!
     * Success:  16
     * Failed[*main.MyError]: 1000 - Bad Arguments - too large!
     * 
     * Recovered! Panic message[Cannot find specific file]
     * 4 3 2 1 0
     */
    func main() {
        // 1.Test error
        args := []int{-1, 4, 1000}
        for _, i := range args {
            if r, e := testError(i); e != nil {
                fmt.Printf("Failed[%T]: %v
    ", e, e)
            } else {
                fmt.Println("Success: ", r)
            }
        }
    
        // 2.Test defer
        src, err := os.Open("control.go")
        if (err != nil) {
            fmt.Printf("打开文件错误[%v]
    ", err)
            return
        }
        defer src.Close()
        // use src...
    
        for i := 0; i < 5; i++ {
            defer fmt.Printf("%d ", i)
        }
    
        // 3.Test panic/recover
        defer func() {
            if r := recover(); r != nil {
                fmt.Printf("Recovered! Panic message[%s]
    ", r)
            }
        }()
    
        _, err2 := os.Open("test.go")
        if (err2 != nil) {
            panic("Cannot find specific file")
        }
    }
    
    func testError(arg int) (int, error) {
        if arg < 0 {
            return -1, errors.New("Bad Arguments - negative!")
        } else if arg > 256 {
            return -1, &MyError{ arg, "Bad Arguments - too large!" }
        } else {
            return arg * arg, nil
        }
    }
  • 相关阅读:
    如何让百度网盘下载速度达60MB/s!
    记一次内存溢出问题的排查、分析过程及解决思路
    使用maven命令打包可执行jar方法
    java实现四则运算
    POI如何合并单元格
    我是如何从功能测试成功转型自动化测试人员的?
    Edgar:Netflix分布式系统的可视化问题诊断平台实践
    Uber的API生命周期管理平台边缘网关(Edge Gateway)的设计实践
    UBer面向领域的微服务体系架构实践
    技术团队:问题被过度的夸大小题大做,你该怎么办?
  • 原文地址:https://www.cnblogs.com/xiaomaohai/p/6157614.html
Copyright © 2020-2023  润新知