概述
这一周,利用每天晚上下班回来后的一小时,学习了Google开发的Go语言,算是对其有了个基本的了解。确实是门漂亮的语言。
首先,从它的设计目标是设计一种高效的、静态编译的、易于编写的语言。它涉足的是系统级的编程,试图与C/C++抗衡。
详细来说,它的设计目标有如下几点(来自wikipedia和golang FAQ):
- 安全:类型安全与内存安全。没有继承,无需处理类型的依赖关系,弱化类型的使用;变量默认初始化,简化设计负担。
- 并发和通信的支持。内建的并发机制使得多线程编程变得非常简单;内建的chan(channel)类型简化了线程间通讯。
- 完全的内存垃圾回收机制。
- 高速编译。没有头文件、Makefile等复杂的工程依赖关系,使得编译速度更快,工程更容易组织。
而在我看来,通过一周的学习,给我留下最深印象的,是如下几个方面:
- 更符合自然语言的语法。类型的声明放在变量后面,实战发现确实比放在前面更易读。
- 方便的内建类型。string、map、数组等,这些复杂的类型都内建于语言中。
- 内建的并发机制。对于多线程程序的编写支持非常好。
- 没有类、只有结构和接口。只是很不习惯,目前还没有发现这样做的好处。
- 文档。从基本的语法、包的文档,到教程、设计建议等。对于理解Go,写好Go非常有帮助。
下面我将以我在教程中写的一些代码为例,说说我对上面几点的理解。
自然的语法
go语言的语法,非常符合英语语法的习惯(英语语法较汉语更具有逻辑性,更能清楚的解释问题)。
比如
定义一个变量:x int,用英语来读就是x of type int;
定义一个函数:func add(x, y int) int,读出来就是:a function named add, with parameter x & y of type int, that returns int。非常自然。
当然,要显示出这种定义的自然性,我们可以看一个复杂的例子,函数指针:
首先看看C语言的定义方式:
int (*fp)(int (*ff)(int x, int y), int b)它定义了一个函数指针fp,指向一个以函数指针ff和b为参数,并返回int的函数。其中ff指向一个以x,y为参数,并返回int的函数。x!真复杂
如果用Go呢?
fp func(ff func(x, y int) int, b int) int
是不是刚好与上面的描述符合?
这种符合自然语言语法的定义方式,简化了代码理解的步骤,也不容易出错。
关于此部分更详细的内容,可以参考Go's Declaration Syntax。
下面给出一个hello world程序,一睹为快:
// 类似Java,用包名来组织代码
package main
import "fmt"
// 程序的“入口”,main函数。
func main() {
fmt.Println("Hello, 世界")
// 没有return语句
}
方便的内建类型
这里我以tour.golang.org中的一个练习为例子,介绍go语言中的map和string。
这个练习要求:
实现WordCount函数。此函数输入一句英文语句,并返回一个map类型,存储每个单词对应的重复次数。主函数以及写好,包含一个wc.Test函数,用于测试WordCount函数的正确性。
提示:strings.Fields可能会很有帮助。
下面是我的实现:
package main
import (
"tour/wc"
"strings"
)
func WordCount(s string) map[string]int {
// 创建一个键为string,值为int的map
// make可以用来创建任何类型的变量。
// 比如make([]int, 3)是创建3个元素的int数组
m := make(map[string]int)
// 变量的使用可以不用显示的指明类型
// 这里,words的类型即Fields的返回值类型,是个字符串数组
words := strings.Fields(s)
// Go语言没有while、do-while
// for 条件 { 执行体 } 即相当于while
// for { 执行体 } 即无限循环
// 这里,使用for的range特性,取words的索引和值
// 分别给_和word,下划线_相当于一个占位符,不赋值给具体的变量
// 同样,还可以使用:i, _ := range words,表示只需要其索引
// 甚至可以使用:_, _ := range words,表示只需要循环相应次数即可
for _, word := range words {
// 根据键取map中的值,并修改
// Go是内存安全的语言,如果m中不存在word键
// 将会自动创建一个word,并初始化其值为0
m[word]++;
}
return m
}
func main() {
wc.Test(WordCount)
}
由这个例子,我们可以了解到Go语言的很多特性,比如_, word := range words这样的多个赋值同时进行(也可用于函数返回值),比如内建的string、map类型,比如简化的循环体(没有括号,去掉while,do-while,支持多种循环条件的定义),还有代码的组织方式等。
结构体和接口
Go语言没有类的概念,没有构造、析构函数,更没有继承。只有结构体和接口。
下面以Exercise: Images为例,介绍Go语言的结构体和接口的使用:
package main
import (
"image"
"image/color"
"tour/pic"
)
// 定义Image类型
// 类似的定义还可以这样:type MyInt int
// 相当于typedef
type Image struct{
content [][]uint32 // 二维数组,存储图片内容
// 包含每个像素点的RGBA值。
width, height int // 图片宽度和高度
}
// 自定义的像素点函数,返回给定点的RGBA值
func valueOfPointer(x, y int) uint32 {
return uint32(0xfffff*x^(0xfffff*y + 0xff))
}
// 自定义的图片生成函数,用于使用给定的像素点函数生成一幅图片
func makePic(w, h int, f func(int,int) uint32) *Image {
img := new(Image)
img.width = w
img.height = h
// 此处先申请一个长度为w,类型为[]uint32的数组
img.content = make([][]uint32, w)
for x := 0; x < w; x++ {
// 再为每个数组的元素申请h长度的uint32型数组
// 由此而创建出一块 w x h 的二维数组
img.content[x] = make([]uint32, h)
// 使用f函数为每个像素赋值
for y := 0; y < h; y++ {
img.content[x][y] = f(x, y)
}
}
return img
}
// 接下来的几个函数是接口image.Image的函数实现
// Go语言中,无需显示的申明实现接口
// 只需要实现接口的所有函数,即实现了接口
// Bounds 函数返回图片的可用区域
// 在 func 和函数名之间加上类型,表示此函数是该类型的成员函数
// 注意,此处的类型不仅限于结构体,比如浮点数、整数都可以。
func (img *Image) Bounds() image.Rectangle {
return image.Rect(0, 0, img.width, img.height)
}
// ColorModel 函数指明图片使用的颜色模式
// 这里,我们选用RGBA模式
func (img *Image) ColorModel() color.Model {
return color.RGBAModel
}
// At 函数,返回指定像素点的颜色属性
func (img *Image) At(x, y int) color.Color {
// 根据练习的说明设置超出范围的点的颜色
if x >= img.width || y >= img.height {
return color.RGBA{uint8(x), uint8(y), 0xff, 0xff}
}
// 根据存储的二维数组,生成RGBA模式的颜色并返回
var c = img.content[x][y]
return color.RGBA{
uint8((c >> 24) & 0xff),
uint8((c >> 16) & 0xff),
uint8((c >> 8) & 0xff),
uint8(c & 0xff) }
}
func main() {
m := makePic(200, 100, valueOfPointer)
// 调用pic类的ShowImage来显示生成的图片
pic.ShowImage(m)
}
可能是我还未理解Go语言的精髓,暂时没有发现这种没有类、甚至没有显示继承关系的设计有怎样的优势。如果有知道的朋友一定要告诉我,非常感谢!
内建的并发机制
Go语言内建了并发机制,无需第三方库的支持就可以方便的创建线程。并且,Go语言包含一个chan类型用于线程间的变量传递,降低了使用共享内存传递的风险,有些类似于unix里的管道。
下面先以一个Equivalent Binary Trees的例子,来介绍并发以及chan的使用,并进一步熟悉Go语言:
package main
import (
"fmt"
"tour/tree"
"sort"
)
// Walk 遍历t,将其所有的内容由ch发送出去
// 我使用递归的方式实现了它
// 注意,Go语言的channel是有类型的
func Walk(t *tree.Tree, ch chan int) {
if t.Left != nil {
Walk(t.Left, ch)
}
if t.Right != nil {
Walk(t.Right, ch)
}
// 使用 <- 符号将变量值发送到chan
ch <- t.Value
}
// Same 决定t1、t2是否是具有相同内容的两棵树
func Same(t1, t2 *tree.Tree) bool {
// 创建两个具有缓存的管道
// 管理发送的线程将不停的发送,直至缓存溢出,
// 等到管理接收的线程取出值以后,才能继续发送
// 这相当于一个固定大小的队列。
ch1 := make(chan int, 10)
ch2 := make(chan int, 10)
// 使用两个数组来存储树的内容
a1 := make([]int, 10)
a2 := make([]int, 10)
// 新建两个线程,同时开始遍历
go Walk(t1, ch1)
go Walk(t2, ch2)
// 主线程将不停的接收另外两个线程传来的数据
for i := 0; i < 10; i++ {
a1[i] = <- ch1
a2[i] = <- ch2
}
// 排序以便于检查其内容是否一致
sort.Ints(a1)
sort.Ints(a2)
for i := 0; i < 10; i++ {
if a1[i] != a2[i] {
return false
}
}
return true
}
func main() {
// tree.New(k)可以创建内容包含k, 2k, 3k, ..., nk的树
t1 := tree.New(1)
t2 := tree.New(2)
ch := make(chan int, 10)
// 打印t1树
fmt.Print("t1: ")
go Walk(t1, ch)
for i := 0; i < 10; i++ {
fmt.Printf("%2d, ", <- ch)
}
fmt.Println()
// 打印t2树
fmt.Print("t2: ")
go Walk(t2, ch)
for i := 0; i < 10; i++ {
fmt.Printf("%2d, ", <- ch)
}
fmt.Println()
// 测试Same函数
fmt.Printf("Is %v that t1 equal t1.n", Same(t1, t1))
fmt.Printf("Is %v that t1 equal t2.n", Same(t1, t2))
}
由例子可以看到管道的使用方式: <- 。发送可以用 channel <- value,接收可以用 variable := <- channel。非常形象。
Go语言中的并发是基于函数的。使用 go function() 即可使此函数在新的线程中运行,父线程将继续运行,不会等待函数结束。
并发更复杂更灵活的运用,请看练习:Web Crawler。
OK,关于Go的简单介绍就到这里,更详细的文档请参阅Go语言官方网站:http://golang.org/。
本专题(Go语言学习)所涉及的代码已经同步到GitHub上,便于分享,下面是链接:
https://github.com/tankery/study-go
注:从今天起,我将以每星期至少一篇的频率写这份博客。
至于每周一篇的频率,是由我常年加班的工作性质决定的。每周一篇,法定节日休息。。。
最后
以上就是激动哈密瓜为你收集整理的Go!漂亮的语言!自然的语法方便的内建类型结构体和接口内建的并发机制的全部内容,希望文章能够帮你解决Go!漂亮的语言!自然的语法方便的内建类型结构体和接口内建的并发机制所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复