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

用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语言关键字

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


Go语言关键字-除分支之外

分支流程部分的放大图:
Go语言关键字-分支部分
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,029评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,395评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,570评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,535评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,650评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,850评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,006评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,747评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,207评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,536评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,683评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,342评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,964评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,772评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,004评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,401评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,566评论 2 349

推荐阅读更多精彩内容

  • 出处---Go编程语言 欢迎来到 Go 编程语言指南。本指南涵盖了该语言的大部分重要特性 Go 语言的交互式简介,...
    Tuberose阅读 18,409评论 1 46
  • 官方网站:https://golang.org/标准库文档:https://golang.org/pkg/在线编码...
    技术学习阅读 2,324评论 2 39
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,633评论 18 139
  • 天气渐热,又到了开空调躲湿热的时候。不过,在开空调前,你必须知道这些小窍门,就能既省电又健康。 ●开空调时别忘拉上...
    再见贰哥哥阅读 129评论 0 0
  • 这周的书单是《认知盈余》,作者是Clay Shirky,被称为“互联网革命最伟大的思考者”。因为马化腾的推荐,很多...
    StevenFU阅读 1,164评论 0 0