Go!漂亮的语言!

这一周,利用每天晚上下班回来后的一小时,学习了Google开发的Go语言,算是对其有了个基本的了解。确实是门漂亮的语言。

首先,从它的设计目标是设计一种高效的、静态编译的、易于编写的语言。它涉足的是系统级的编程,试图与C/C++抗衡。

详细来说,它的设计目标有如下几点(来自wikipediagolang FAQ):

  • 安全:类型安全与内存安全。没有继承,无需处理类型的依赖关系,弱化类型的使用;变量默认初始化,简化设计负担。
  • 并发和通信的支持。内建的并发机制使得多线程编程变得非常简单;内建的chan(channel)类型简化了线程间通讯。
  • 完全的内存垃圾回收机制。
  • 高速编译。没有头文件、Makefile等复杂的工程依赖关系,使得编译速度更快,工程更容易组织。

而在我看来,通过一周的学习,给我留下最深印象的,是如下几个方面:

  1. 更符合自然语言的语法。类型的声明放在变量后面,实战发现确实比放在前面更易读。
  2. 方便的内建类型。string、map、数组等,这些复杂的类型都内建于语言中。
  3. 内建的并发机制。对于多线程程序的编写支持非常好。
  4. 没有类、只有结构和接口。只是很不习惯,目前还没有发现这样做的好处。
  5. 文档。从基本的语法、包的文档,到教程、设计建议等。对于理解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


注:从今天起,我将以每星期至少一篇的频率写这份博客。

至于每周一篇的频率,是由我常年加班的工作性质决定的。每周一篇,法定节日休息。。。


  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值