Google Go Primer(一)

2010/04/27 15:08
阅读数 1K

Go语言是什么?

Google最近发布新型的编程语言,Go。它被设计为将现代编程语言的先进 性带入到目前仍由C语言占统治地位的系统层面。然而,这一语言仍在试验阶段并在不断演变。

Go语言的设计者计划设计一门简单、高效、安全和 并发的语言。这门语言简单到甚至不需要有一个符号表来进行词法分析。它可以快速地编译;整个工程的编译时间在秒以下的情况是常事。它具备垃圾回收功能,因 此从内存的角度是安全的。它进行静态类型检查,并且不允许强制类型转换,因而对于类型而言是安全的。同时语言还内建了强大的并发实现机制。

阅读Go

Go的语法传承了与C一样的风格。程序由函数组成,而函数体是一系列的语句序列。一段代码块用花括号括起来。这门语言保留有限的关键字。表达式使用 同样的中缀运算符。语法上并无 太多出奇之处。

Go语言的作者在设计这一语言时坚持一个单一的指导原则:简单明了至上。一些新的语法构件提供了简明地表达一些约定俗成的概 念的方式,相较之下用C表达显得冗长。而其他方面则是针对几十年的使用所呈现出来的一些不合理的语言选择作出了改进。

变量声明

变量是如下声明的:

var sum int // 简单声明
var total int = 42 // 声明并初始化

最值得注意的是,这些声明里的类型跟在变量名的后面。乍一看有点怪,但这更清晰明了。比如,以下面这个C片段来说:

int* a, b;

它并明了,但这里实际的意思是a是一个指针,但b不是。如果要将两者都声明为指针,必须要重复星号。然后在Go语言里,通过如下方式可以将两者都 声明为指针:

var a, b *int

如果一个变量初始化了,编译器通常能推断它的类型,所以程序员不必显式的敲出来:

var label = "name"

然而,在这种情况下var几乎显得是多余了。因此,Go的作者引入了一个新的运算符来 声明和初始化一个新的变量:

name := "Samuel"

条件语句

Go语言当中的条件句与C当中所熟知的if-else构造一样,但条件不需要被打包在括号内。这样可以减少阅读代码时的视觉上的混乱。

括号并不是唯一被移去的视觉干扰。在条件之间可以包括一个简单的语句,所以如下的代码:

result := someFunc();
if result > 0 {
/* Do something */
} else {
/* Handle error */
}

可以被精简成:

if result := someFunc(); result > 0 { 
/* Do something */
} else {
/* Handle error */
}

然而,在后面这个例子当中,result只在条件块内部有效??而前者 中,它在整个包含它的上下文中都是可存取的。

分支语句

分支语句同样是似曾相识,但也有增强。像条件语句一样,它允许一个简单的语句位于分支的表达式之前。然而,他们相对于在C语言中的分支而言走得更远。

首先,为了让分支跳转更简明,作了两个修改。情况可以是逗号分隔的列表,而fall-throuth也不再是默认的行为。

因此,如下的C代码:

int result;
switch (byte) {
case 'a':
case 'b':
{
result = 1
break
}

default:
result = 0
}

在Go里就变成了这样:

var result int
switch byte {
case 'a', 'b':
result = 1
default:
result = 0
}

第二点,Go的分支跳转可以匹配比整数和字符更多的内容,任何有效的表达式都可以作为跳转语句值。只要它与分支条件的类型是一样的。

因此如下的C代码:

int result = calculate();
if (result < 0) {
/* negative */
} else if (result > 0) {
/* positive */
} else {
/* zero */
}

在Go里可以这样表达:

switch result := calculate(); true {
case result < 0:
/* negative */
case result > 0:
/* positive */
default:
/* zero */
}

这些都是公共的约定俗成,比如如果分支值省略了,就是默认为真,所以上面的代码可以这样写:

switch result := calculate(); {
case result < 0:
/* negative */
case result > 0:
/* positive */
default:
/* zero */
}

循环

Go只有一个关键字用于引入循环。但它提供了除do-while外C语言当中所有可用的循环方式。

条件

for a > b { /* ... */ }

初始,条件和步进

for i := 0; i < 10; i++ { /* ... */ }

范围

range语句右边的表达式必须是arrayslicestring或者map, 或是指向array的指针,也可以是channel

for i := range "hello" { /* ... */ }

无限循环

for { /* ever */ }

函数

声明函数的语法与C不同。就像变量声明一样,类型是在它们所描述的术语之后声明的。在C语言中:

int add(int a, b) { return a + b }

在Go里面是这样描述的:

func add(a, b int) int { return a + b }

多返回值

在C语言当中常见的做法是保留一个返回值来表示错误(比如,read()返回0),或 者保留返回值来通知状态,并将传递存储结果的内存地址的指针。这容易产生了不安全的编程实践,因此在像Go语言这样有良好管理的语言中是不可行的。

认识到这一问题的影响已超出了函数结果与错误通讯的简单需求的范畴,Go的作者们在语言中内建了函数返回多个值的能力。

作为例子,这个函数将返回整数除法的两个部分:

func divide(a, b int) (int, int) {
quotient := a / b
remainder := a % b
return quotient, remainder
}

有了多个返回值,有良好的代码文档会更好??而Go允许你给返回值命名,就像参数一样。你可以对这些返回的变量赋值,就像其它的变量一样。所以我们可以重写divide

func divide(a, b int) (quotient, remainder int) {
quotient = a / b
remainder = a % b
return
}

多返回值的出现促进了"comma-ok"的模式。有可能失败的函数可以返回第二个布尔结果来表示成功。作为替代,也可以返回一个错误对象,因此像下面这样的代码也就不见怪了:

if result, ok := moreMagic(); ok {
/* Do something with result */
}

匿名函数

有了垃圾收集器意味着为许多不同的特性敞开了大门??其中就包括匿名函数。Go为声明匿名函数提供了简单的语法。像许多动态语言一样,这些函数在它们被定义的范围内创建了词法闭包。

考虑如下的程序:

func makeAdder(x int) (func(int) int) {
return func(y int) int { return x + y }
}

func main() {
add5 := makeAdder(5)
add36 := makeAdder(36)
fmt.Println("The answer:", add5(add36(1))) //=> The answer: 42
}

基本类型

像C语言一样,Go提供了一系列的基本类型,常见的布尔,整数和浮点数类型都具备。它有一个Unicode的字符串类型和数组类型。同时该语言还引入了两 种新的类型:slice map

数组和切片

Go语言当中的数组不是像C语言那样动态的。它们的大小是类型的一部分,在编译时就决定了。数组的索引还是使用的熟悉的C语法(如 a[i]),并且与C一样,索引是由0开始的。编译器提供了内建的功能在编译时求得一个数组的长度 (如len(a))。如果试图超过数组界限写入,会产生一个运行时错误。

Go还提供了切片(slices),作为数组的变形。一个切片(slice)表示一个数组内的连续分段,支持程序员指定底层存储的明确部分。构建一个切片 的语法与访问一个数组元素类似:

/* Construct a slice on ary that starts at s and is len elements long */
s1 := ary[s:len]

/* Omit the length to create a slice to the end of ary */
s2 := ary[s:]

/* Slices behave just like arrays */
s[0] == ary[s] //=> true

// Changing the value in a slice changes it in the array
ary[s] = 1
s[0] = 42
ary[s] == 42 //=> true

该切片所引用的数组分段可以通过将新的切片赋值给同一变量来更改:

/* Move the start of the slice forward by one, but do not move the end */
s2 = s2[1:]

/* Slices can only move forward */
s2 = s2[-1:] // this is a compile error

切片的长度可以更改,只要不超出切片的容量。切片s的容量是数组从s[0]到数组尾端的大小,并由内建的cap()函数返回。一个切片的长度永远不能超出它的容量。

这里有一个展示长度和容量交互的例子:

a := [...]int{1,2,3,4,5} // The ... means "whatever length the initializer has"
len(a) //=> 5

/* Slice from the middle */
s := a[2:4] //=> [3 4]
len(s), cap(s) //=> 2, 3

/* Grow the slice */
s = s[0:3] //=> [3 4 5]
len(s), cap(s) //=> 3, 3

/* Cannot grow it past its capacity */
s = s[0:4] // this is a compile error

通常,一个切片就是一个程序所需要的全部了,在这种情况下,程序员根本用不着一个数组,Go有两种方式直接创建切片而不用引用底层存储:

/* literal */
s1 := []int{1,2,3,4,5}

/* empty (all zero values) */
s2 := make([]int, 10) // cap(s2) == len(s2) == 10

Map类型

几乎每个现在流行的动态语言都有的数据类型,但在C中不具备的,就是dictionary。Go提供了一个基本的dictionary类型叫做map。下 面的例子展示了如何创建和使用Go map:

m := make(map[string] int) // A mapping of strings to ints

/* Store some values */
m["foo"] = 42
m["bar"] = 30

/* Read, and exit program with a runtime error if key is not present. */
x := m["foo"]

/* Read, with comma-ok check; ok will be false if key was not present. */
x, ok := m["bar"]

/* Check for presence of key, _ means "I don't care about this value." */
_, ok := m["baz"] // ok == false

/* Assign zero as a valid value */
m["foo"] = 0;
_, ok := m["foo"] // ok == true

/* Delete a key */
m["bar"] = 0, false
_, ok := m["bar"] // ok == false

面向对象

Go语言支持类似于C语言中使用的面向对象风格。数据被组织成structs,然后定义操作这些structs的函数。类似于Python,Go语言提供 了定义函数并调用它们的方式,因此语法并不会笨拙。

Struct类型

定义一个新的struct类型很简单:

type Point struct {
x, y float64
}

现在这一类型的值可以通过内建的函数new来分配,这将返回一个指针,指向一块内存单元,其所占内存槽初始化为零。

var p *Point = new(Point)
p.x = 3
p.y = 4

这显得很冗长,而Go语言的一个目标是尽可能的简明扼要。所以提供了一个同时分配和初始化struct的语法:

var p1 Point = Point{3,4}  // Value
var p2 *Point = &Point{3,4} // Pointer

方法

一旦声明了类型,就可以将该类型显式的作为第一个参数来声明函数:

func (self Point) Length() float {
return math.Sqrt(self.x*self.x + self.y*self.y);
}

这些函数之后可作为struct的方法而被调用:

p := Point{3,4}
d := p.Length() //=> 5

方法实际上既可以声明为值也可以声明为指针类型。Go将会适当的处理引用或解引用对象,所以既可以对类型T,也可以对类型*T声明方式,并合理地使用它们。

让我们为Point扩展一个变换器:

/* Note the receiver is *Point */
func (self *Point) Scale(factor float64) {
self.x = self.x * factor
self.y = self.y * factor
}

然后我们可以像这样调用:

p.Scale(2);
d = p.Length() //=> 10

很重要的一点是理解传递给MoveToXYself和其它的参数一样,并且是传递,而不是引用传递。如果它被声明为Point,那么在方法内修改的struct就不再跟调用方的一样??值在它们传递给方法的时候被 拷贝,并在调用结束后被丢弃。

 

查看英文原文Google Go: A Primer 阅读全文
类别: Golang  查看评论

展开阅读全文
加载中
点击引领话题📣 发布并加入讨论🔥
0 评论
0 收藏
0
分享
返回顶部
顶部