Google Go Primer(一)

Go语言是什么?

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

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

阅读Go

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

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的做者引入了一个新的运算符来 声明和初始化一个新的变量:app

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  查看评论
相关文章
相关标签/搜索