用Go写Android应用(3) - Go语言速成

  1. 云栖社区>
  2. 博客>
  3. 正文

用Go写Android应用(3) - Go语言速成

lusing 2016-07-30 18:42:14 浏览2359
展开阅读全文

用Go写Android应用(3) - Go语言速成

Go快餐

下面我们将Go与C/C++/Java的一些比较不同的地方提炼一下,让大家可以快速上手。然后在实践中继续学习。

Go是支持GC的

好的方面是,不用自己管理内存了。
不好的方面是,GC影响性能的话,要想办法优化啊。

Go的变量定义类型在后面

例:
变量:

var i int = 10

常量

const ClassFile string = FilePath + "Test.class"

struct也是在后面

定义自定义类型的struct,也不像C语言一样在前面,跟系统类型一样,放到后面。前面有个type关键字。

例:

type ELFFile struct {
    eiClass      int
    littleEndian bool
    osABI        string
}

类型推断和函数返回多个值

在函数里面使用时,可以使用定义和赋值合一的办法,就是使用:=运算符。这时候不需要指定类型,因为可以通过后面的语句来推断出类型。

例:

    buf, err := ioutil.ReadFile(elfFileName)

从上边的例子我们还可以看到,Go语言支持函数返回多个参数。如果有的参数并不重要,可以使用特殊变量"_",不理它就是了。

未使用的变量和包将导致编译不过

在Go中,如果引用了包不用,或者是定义了变量不使用,不是产生警告,而是直接导致编译失败!

大写开头是public,小写开头是private

Go语言没有额外定义public和private限定符。如果一个变量或函数以大写字母开头,比如"Println",那么它就是public的,如果小写开头就是private的。

数组赋值会做拷贝

小心,将一个数组的值赋给另一个数组,会引发对数组的复制哟。

流程控制中可以不用小括号

if语句,for循环等控制语句中的小括号是可以省略不写的。

例:if后面的判断不用小括号

    if err != nil {
        fmt.Println("Error reading ELF:", err)
    }

switch默认带break

Go语言的switch不需要写break,break是默认的行为。相反,如果不需要break,需要加一个fallthrough语句取消掉默认的break.

Go语言有指针

默认是传值复制,如果需要传引用的,请用指针吧。

Go语言有goto

保持一个方向,尽量避免跳来跳去吧。
同时,Go语言也是支持break和continue的,而且二者都是可以带标号跳转的。goto可以留到最后再用。

Go语言只有for循环这一种

Go语言没有提供while和do while循环,更没有do until之类的。一切都是for循环。
死循环就是:

    for ;; {
        ...
    }

main函数和init函数

Go应用的入口点是main包的main函数。
每个package可以写一个init函数,会被自动调用。

Go语言没有this指针

需要明确指定对象,没有隐藏的this指针潜规则可以用。

例,必须直接指定对象:

func (elfFile *ELFFile) ParseEIClass_v2(value byte) {
    if value == 1 {
        elfFile.eiClass = 32
        fmt.Println("It is 32-bit")
    } else if value == 2 {
        elfFile.eiClass = 64
        fmt.Println("It is 64-bit")
    } else {
        elfFile.eiClass = 0
        fmt.Println("unknown format, neither 32-bit nor 64-bit")
    }
}

Go的做法是把潜规则变成明文,在普通函数定义的前面,加上接收对象的声明。

非侵入式的接口设计

鸭子原则,只要一个东西,走起来像鸭子,叫起来像鸭子,我们就可以认为它是一只鸭子。

Go语言的interface就是这么设计的。
我们来看一个例子,假设有三种虚拟机,都支持athrow方法:

package main

type Hotspot struct {
}

type Dalvik struct {
}

type AndroidRuntime struct {
}

func (vm Hotspot) athrow() {

}

func (dalvik Dalvik) athrow() {
    dalvik.throw()
}

func (dalvik Dalvik) throw() {

}

func (art AndroidRuntime) athrow() {
    art.pDeliverException()

}

func (art AndroidRuntime) pDeliverException() {

}

但是上面三种虚拟机的实现是不同的,Hotspot是直接支持这条指令,Dalvik是调用自己的throw指令,而ART是调用pDeliverException过程。
但不管怎样,它们都声称支持athorw这个方法调用。
于是我们可以声明一个interface叫SupportException,定义这一个方法。
从此以后,各种JVM的实现,都可以赋给一个SupportException类型的变量。
我们看使用的例子:

type SupportException interface {
    athrow()
}

func throwException() {
    hotspot := Hotspot{}
    dalvik := Dalvik{}
    art := AndroidRuntime{}

    var jvm1 SupportException
    var jvm2 SupportException
    var jvm3 SupportException

    jvm1 = hotspot
    jvm1.athrow()

    jvm2 = dalvik
    jvm2.athrow()

    jvm3 = art
    jvm3.athrow()
}

也是就说,定义类的时候,根本不用管接口的定义,只要实现就好了。最后再从各个类的实现中总结出接口来就好。

defer延迟执行

defer提供了函数级延时执行的机制度。就相当于函数级的finally,一定会被执行到。
比如打开文件成功后,就可以先defer一个关闭文件或者channel的操作。

func dex2oat(ch chan bool, dexFile string) {
    defer close(ch)
    ch <- dex2oatImpl(dexFile)
    fmt.Println("Dex2OAT finished!")
}

函数小的时候可能还得记得,大了之后defer的作用就显现出来了,可以避免忘事儿。

引用类型

切片(slice)

Go语言的数组是只读的。
数组可以用两种方式来定义长度:

  • 一是直接指定长度
  • 二是让Go来计算长度

直接给定长度,我们可以这么写:

    magic := [4]byte{0x7f, 'E', 'L', 'F'}

如果我们懒得算有几个,或者太长不好算,就可以给3个点,请编译器帮我们算:

    magic := [...]byte{0x7f, 'E', 'L', 'F'}

为什么需要写3个点,而不能直接给个空的方括号呢?
因为如果方括号为空,就不是数组了,而变成另外一种类型,叫做切片。

例:定义切片:

magic2 := buf[0 : 3]

buf是个数组,magic2是从第0个元素到第3个元素的切片。

如果是从头开始,冒号前面可以省略,如果是切片到最后一个元素为止,刚冒号后面可以省略。如果做一个完整的切片,头尾都可以省略。

下面的函数展示了如何从切片中消费一个字节,然后返回一个新的切片:

func ReadU1_v2(data []byte) (byte, []byte) {
    if data == nil {
        return 0, nil
    } else if len(data) > 1 {
        return data[0], data[1:]
    } else {
        return data[0], nil
    }
}

切片的属性

切片其实是一个有三个数据组成的数据结构:

  • 指向数组的指针
  • 切片的长度,如上例,可以通过len()函数获取
  • 切片的最大容量,可通过cap()函数来获取

切片的函数

  • append:向切片追加一个或多个元素。相当于实现了动态数组的功能。如果切片所指向的原数组的容量不足,超出了切片的cap,则会为其分配一个新的数组。原数组不变。
  • copy,切片之间做数据复制。

map

Go语言内建对map的支持。

  • map也是一种引用类型,如果两个map指向同一底层数据结构,则一个改变,另一个也改变。
  • map通过键-值对进行赋值
  • 键可以是任意的实现了==与!=操作的类型
  • map是无序的,不能遍历

引用类型的内存分配

map,slice还有最后要讲的channel可以通过make函数进行内存分配。

用户自定义类型

前面讲过了没有this指针的事情,这里再总结一下。

定义用户自定义类型

通过struct关键字来定义:

type ELFFile struct {
    elfFileName  string
    eiClass      int
    littleEndian bool
    osABI        string
}

使用自定义类型

直接当成普通类型使用就好了。最简单的方法就是直接用:=赋给一个变量使用,也省得指定类型了。
可以通过键:值的方式来赋初值。
例:

elfFile := ELFFile{elfFileName: OatFile}

为自定义类型定义方法

前面讲过了,给普通函数前面加一个对象接收者的声明就可以了。

例:

func (elfFile *ELFFile) ParseEiData_v2(value byte) {
    switch value {
    case 1:
        elfFile.littleEndian = true
        fmt.Println("It is Little Endian")
        IsLittleEndian = true
    case 2:
        elfFile.littleEndian = false
        fmt.Println("It is Big Endian")
        IsLittleEndian = false
    default:
        fmt.Println("Unknow Endian, the value is:", value)
    }
}

Go的多作务机制

Go语言从语言层面天生就支持并发。通过go语句,每个函数都可以运行在一个Goroutine中,类似于一个线程。
多个Goroutine之间通过Channel来发送消息来实现通信。

我们举个简单的例子,实现一个Future模式吧。让三个dex2oat作务并发:

func dex2oat(ch chan bool, dexFile string) {
    ch <- dex2oatImpl(dexFile)
    fmt.Println("Dex2OAT finished!")
}

func dex2oatImpl(dexFile string) bool {
    return true
}

上面的dex2oat函数传入一个bool型的Channel,我们通过这个Channel向调用者返回结果。

调用者的代码如下:

    channels := make([]chan bool, 3)
    for i := 0; i < len(channels); i++ {
        channels[i] = make(chan bool)
    }
    go dex2oat(channels[0], "Test1.dex")
    go dex2oat(channels[1], "Test2.dex")
    go dex2oat(channels[2], "Test3.dex")

    for _, ch := range channels {
        value := <-ch
        fmt.Println("The result is ", value)
    }

首先是new一个Channel数组,然后make Channel对象。
接着通过go关键字去开三个goroutine去分别执行dex2oat。
于是主任务就阻塞等待3个子任务分别返回,最后相当于把结果join在一起,再继续往下执行。

小结

总结一下,Go的核心内容就上面这么多。
当然,其中的细节我们都没有展开。希望给大家留个印象就是Go语言还是很容易上手的。

Go语言一共有25个关键字,除了select,上文基本上已经一网打尽了。为了加深印象,我们用一张结构图来说明一下:
golang1

这张图如果看不清的话,我们将其拆成两张图,再注掉分支流程那部分的局部图:
golang1
分支流程部分的放大图:
golang1_2

网友评论

登录后评论
0/500
评论
lusing
+ 关注