芝兰生于深林，不以无人而不芳；君子修道立德，不谓穷困而改节。
我们知道 Go 中的 Struct 可以像其它语言中的类一样，包含数据，同样的，它也可以通过定义方法来保存属性它自己的行为，将一个方法绑定、关联到一个 Struct 或者任何一个其它的类型中是非常简单的，它就像普通的函数定义一样，唯一的不同是你需要为其指定一个类型（type）。下面这个示例是一个普通的函数定义，它将返回一个类型为 int 的值：func myFunc() int { // code } 上面定义的函数将不接收任何参数，返回一个 int 类型的值，如果我们要将其绑定或者说关联到一个Struct上面的话，只需要做很少的修改即可：type myType struct {} func (m myType) myFunc() int { // code } 让我们现在来扩展一下前面一篇文章《Go 中的 Struct——无类实现面向对象编程》中的那个 Rectangle 示例， 我们为其添加一个名为 Area 的方法，用来计算长方形的面积：package main import "fmt" type Rectangle struct { width, length int } func (r Rectangle) Area() int { return r.width * r.length } func main() { r := Rectangle{4,3} fmt.Println("Rectangle is:", r) fmt.Println("Rectangle area is:", r.Area()) } 输出为：Rectangle is: {4 3} Rectangle area is: 12 许多面向对象的程序开发语言都有一个类似 this 或者 self 的概念，用它来代表并访问当前的实例，Go没有这样的概念，如果要将某一个函数或者方法绑定到一个Struct中，直接将一个Struct的实例作为参数传递给该方法或者函数，在上面的示例中，r 就是这个传递给方法 Area 的 Rectangle实例，它会在每一次调用 r.Area() 方法时，指向 r 自身。同样的，你还可以传递Struct的引用，但是这与直接传递实例没有差别，因为Go会自动为你进行转换：package main import "fmt" type Rectangle struct { length, width int } func (r Rectangle) AreaByValue() int { return r.length * r.width } func (r *Rectangle) AreaByReference() int { return r.length * r.width } func main() { r := Rectangle{4,3} fmt.Println("Rectangle is: ", r) fmt.Println("Rectangle area is: ", r.AreaByValue()) fmt.Println("Rectangle area is: ", r.AreaByReference()) fmt.Println("Rectangle area is: ", (&r).AreaByValue()) fmt.Println("Rectangle area is: ", (&r).AreaByReference()) } 输出为：Rectangle is: {4 3} Rectangle area is: 12 Rectangle area is: 12 Rectangle area is: 12 Rectangle area is: 12 在上面的代码中，我们定义了两人个很相似的方法，一个传递实例，一个传递实例的引用，我们同时又使用了实例分别调用它们，再使用了实例的地址调用它们，可以看到，四种方法的结果都是一样的。下面我们再“绑定”一个名为 Perimeter 的方法来设置长方形的周长：package main import "fmt" type Rectangle struct { width, length int } func (r Rectangle) Area() int { return r.width * r.length } func (r Rectangle) Perimeter() int { return 2 * (r.length + r.width) } func main() { r := Rectangle{4,3} fmt.Println("Rectangle is:", r) fmt.Println("Rectangle area is:", r.Area()) fmt.Println("Rectangle perimeter is:", r.Perimeter()) } 输出结果为：Rectangle is: {4 3} Rectangle area is: 12 Rectangle perimeter is: 14 现在你可能会想到了，既然为自定义的Struct添加方法绑定，扩展它的行为这么简单，那是否我也可以很容易扩展以有的Struct的方法呢？看下面的示例：package main import "fmt" import "time" func (t time.Time) first3Chars() string { return t.Weekday().String()[0:3] } func main() { t := time.Time{} fmt.Println("First 3 Chars is: ", t.first3Chars()) } 输出为：./structsmethods4.go:4: cannot define new methods on non-local type time.Time ./structsmethods4.go:9: t.first3Chars undefined (type time.Time has no field or method first3Chars) 你只能将方法或者函数绑定到相同的包内的Struct中，如果你真的需要扩展其它包，那再想想还有什么办法？其实很简单，那就是使用Go Struct中的匿名字段。package main import ( "fmt" "time" ) type myTime struct { time.Time // 匿名字段 } func (t myTime) first3Chars() string { return t.Weekday().String()[0:3] } func main() { m := myTime{} fmt.Println("Full Weekday:", m.Weekday().String()) fmt.Println("First 3 chars:", m.first3Chars()) } 输出结果：Full Weekday: Monday First 3 chars: Mon 匿名字段中的方法在《Go Struct中的匿名字段》这篇文章里面我讲到过了对匿名字段的方法，当 time.Time 成为了 myTime 的一个匿名字段之后，我们就能通过 myTime 访问 Time 所的方法，我们同样可以使用 myTime.String() 将自己转换成为一个字符串，让我们再来做看一看前面的示例。在下面这个示例中，我们返回到Kitchen与House的问题上面来，我们知道了如何访问匿名字段的数据，那么也应该知道如何访问匿名字段的方法了，他们是一样的：package main import "fmt" type Kitchen struct { numOfForks int numOfKnives int } func (k Kitchen) totalForksAndKnives() int { return k.numOfForks + k.numOfKnives } type House struct { Kitchen } func main() { h := House{Kitchen{4,4}} fmt.Println("Sum of forks and knives in house:", h.totalForksAndKnives()) } 输出结果为：Sum of forks and knives in house: 8
Go 允许你定义含有无变量名的字段的Struct，这些无名称的字段称之为匿名字段，让我们通过一些示例来了解它们到底是什么，以及它们为什么很有用。在下面的示例中，我们定义一个名为 Kitchen 的Struct，它仅仅只包含一个名为 numOfPlates 的 int类型的字段用来保存盘子的个数，我还定义了另一个Struct名为 House ，它包含了一个 Kitchen 的实例，但是我没有为其指定字段名，所以，它是一个匿名字段。package main import "fmt" type Kitchen struct { numOfPlates int } type House struct { Kitchen // 匿名字段 numOfRooms int } func main() { h := House{Kitchen{10}, 3} // 初始化需要使用Struct名 fmt.Println("房屋 h 有", h.numOfRooms, "间房") // numOfRooms 是 House 的一个字段 fmt.Println("房屋 h 有", h.numOfPlates, "个盘子") // numOfPlates 是Kitchen提供的字段，而Kitchen又是House的一个匿名字段，所以这里可以访问到它 fmt.Println("这间房屋的厨房有：", h.Kitchen) // 我们可以通过Struct的名称访问整个匿名Struct的内容 }输出结果为：房屋 h 有 3 间房 房屋 h 有 10 个盘子 这间房屋的厨房有： {10}首先我们需要注意到的事情是，当我们定义了 Kitchen 为House的一个匿名字段之后，我们将可以直接通过Kitchen中的字段名称访问Kitchen中字段的值，这同样也“成了”House的字段，但是如果像在Java这样的语言中，我们做一下对比，它需要像下面这样的去实现同样的功能：public class Kitchen { public int numOfPlates; } public class House { public Kitchen kitchen; } public static void main(String[] args) { House h = new House(); h.kitchen.numOfPlates = 10; // 引用自字段的子字段 }其次我们需要知道，匿名字段的完整的实例数据是可以被访问到的，需要通过它的Struct名称，在本示例中，通过 h.Kitchen 来访问获得，所以，如果你想打印出 Kitchen 中的 Plate 数量的话中，可以这样：fmt.Println(h.Kitchen.numofPlates)最后，要注意到，初始化的时候，匿名字段的Struct名称是不允许省略的，它必须像这样：h := House{Kitchen{10}, 3}*，而下面这样的写法都是错误的：*h := House{{10}, 3} 或者 h := House{10,3}。匿名字段发生命名冲突很有可能两个不同的Struct具有同样名称的字段，而一个Struct又是另一个Struct的匿名字段，这就产生的命名冲突的问题，当这个问题出现的时候，外层的Struct中的字段可以像以前一样直接被访问到，但是其匿名字段就需要通过Struct名称访问了。在下面的示例中，我们的 House 与 Kitchen 同时具有 numOfLamps 字段，但是House是外层的struct，它的 numOfLamps* 字段将隐藏掉Ｋitchen的 *numOfLamps*，如果你还是需要访问Kitchen的numOfLamps值，那么需要通过引用它的Struct名：*h.Kitchen.numOfLamps ：package main import "fmt" type Kitchen struct { numOfLamps int } type House struct { Kitchen numOfLamps int } func main() { h := House{Kitchen{2}, 10} // Kitchen 有2个Lamps，House有10个 fmt.Println("House有", h.numOfLamps, "个Lamps") fmt.Println("Kitchen有", h.Kitchen.numOfLamps, "个Lamps") }输出为：House有 10 个Lamps Kitchen有 2 个Lamps所以从上面可以看到，对于不同层级的相同名称的字段，Go制定了相应的规则，但是对于同一层级的相同名称的字段，则需要我们自己去解决，在下面的示例中，Kitchen与Bedroom都具有 numOfLamps 字段，并且它们又都是House的匿名字段，现在，如果我们引用 House.numOfLamps，Go编译器就会不知道你到底是要使用哪一个 numOfLamps值：package main import "fmt" type Kitchen struct { numOfLamps int; } type Bedroom struct { numOfLamps int; } type House struct { Kitchen Bedroom } func main() { h := House{Kitchen{2}, Bedroom{3}} // Kitchen 2, Bedroom 3 fmt.Println("Ambiguous number of lamps:", h.numOfLamps) // 这会出错 }输出为：./anonymousfields3.go:15: ambiguous selector h.numOfLamps因为Go不知道你到底是要 Bedroom 的还是 Kitchen 的 numOfLamps，所以，它直接就提供值了，结果就是返回 h.numOfLamps 有歧义。解决这个问题的办法是通过匿名字段的Struct名称访问其值：fmt.Println("House中Lamps的数量为：", h.Kitchen.numOfLamps + h.Bedroom.numOfLamps)输出为：House中Lamps的数量为：5
星期五, 12/28/2012 - 16:22 — matrixstackGo是非面向对象的，而是面向过程的，但是在当今程序开发的世界里，使用最为广泛的还是面向对象编程，这或许会让你感觉Go作为一个新生的语言最应该有的一面却没有被支持，但是其实它只是用了自己的方法，在本文里，我将介绍在Go里面，如何实现面向对象（Go提供了面向对象所提供的一切，或许提供得更多）。如果你一直都是一个“类”用户，那么转到Go的“面向对象”上来，可能还需要一些观念上的转变，这或许很难，但是一旦你转变过来了，你就会发现，Go的方式有多么的强大了。在了解Go的面向对象之前，一定要在心里面知道：Go是没有类的，这是你进入Go的面向对象世界的第一步，也是最重要的一步；另一个你知道知道的是：Go不是面向对象的，只是他的实现类似于面向对象，我们来看一个简单的示例，它能说明一切：Java代码：class House { public String getHouseName() { // 方法定义在类里面 // 实现 } } Go 代码：type House struct { } func (h House) GetHouseName() string {} //方法被定义在 Struct 外，但是却作用于 House 现在让我们来学习一下如何创建 Struct 以及如何通过它实现“面向对象”，一个 Struct 通过关键字 type 与 struct 定义：type my_struct_name struct { } type Rectangle struct { } type Vehicle struct { } type VehicleCar struct { } 上面的所有定义都是合法的，但是下面这样是不合法的：type Hash# struct { } // 名称不允许包含特殊字符 type 0Struct struct { } // 名称不允许以数字开始 当然了，Struct 还能存储其它数据，所以，在这方面它类似于 Class，可以用来定义真实世界中存在的实体：type mystruct struct { i int j int s string } type Rectangle struct { length, width int //你可以在一行中通过逗号分割的方式创建多个同类型的项目 area float64 } 下面这个示例展示了一个 Struct 的实际应用：package main import "fmt" // 定义 Rectangle 结构 type Rectangle struct { length, width int } func main() { r := Rectangle{} fmt.Println("默认的长方形是：", r) // 打印出默认的 Rectangle 的归零值 } 输出为：默认的长方形是： {0 0} 这里有一个重要的需要注意的事情是：struct中的变量的值都是归零的，也就是说，int 类型的值将为 0，string 类型的值将为空字符串等等，所以，每一个你自己定义的Struct 也都是有一个属于其自己的归堆值的。下面这个示例中，我将展示另一种方式初始化 Struct——在创建它的时候就指定初始值：package main import "fmt" type Rectangle struct { length, width int name string } func main() { r1 := Rectangle{2, 1, "First Rectangle"} fmt.Println("第一个长方形 r1 为：", r1) r2 := Rectangle{width: 3, name: "Second Rectangle", length: 4} fmt.Println("第二个长方形 r2 为：", r2) pr := new (Rectangle) // 获取一个指向一个新的长方形的指针 (*pr).width = 6 // 设置 pr 的宽为 6 pr.length = 8 // 与上面是同样的效果，看这里是没有 -> 符号的 pr.name = "指针长方形" fmt.Println("指针长方形是：", pr) } 输出为：第一个长方形 r1 为： {2 1 First Rectangle} 第二个长方形 r2 为： {4 3 Second Rectangle} 指针长方形是： &{8 6 指针长方形} 这里需要注意的事情是： 你可以只将值按 struct 定义时的顺序赋给它的成员，但是需要放在大括号里面，并且每一个值需要使用逗号分开； 如果你给定值的时候还提供了名称，那么就不受顺序的限制； 你可以通过 new 获取到一个新创建的 struct 的指针； 这样的一个指针可以不需要 *操作符； Go默认提供根据Struct的值转换成为字符打印的功能 封装以及 Struct 及其变量的访问其它的编程语言使用一些特别的关键字（比如 public、private、package‘protected等）定义类中不同的属性与变量的可访问性，当我在使用Go之前，我认为所有的这些都是言存在的，但是之后，我发现，原来实现变量与属性的访问控制可以如此简单：type notExported struct { // 以小写字母开头命名的struct只能在同一个包内调用，我们称这为不导出 } type Exported { // 大写开头则可导出，也就是任何包都可以访问 notExportedVariable int // 小写字母开头不允许包外访问 ExportedVariable int // 大写字母开头则允许包外访问 s string // 不导出 S string // 导出 } 所以，你应该明白，在Go里面命名都应该是统一规定的，它要求你使用驼峰式的命名，是否使用大小写开头则要看是否需要导出。
for 声明是 Go 中唯一的一个循环声明，普通的用法如下：for "initialization statements"; "bool expression that has to evaluate to true"; "statements run prior to every loop except the first" { // Code to be executed if the boolean expression evaluates to true } 翻译过来为：for "初始化声明"; "条件判断，为真是循环才执行"; "除第一外每一次循环都会执行的声明" { // 循环主体 } 上面的三个部分中，任何一个部分都是可以不写的，但是即使不写，那个属于它的结束符的分号却还是必须要写，除非三个声明都没有的时候，所有的分号都可以一并省略，请看下面的示例：package main import "fmt" func main() { // 初始化i为0；每一次循环之后检查i是否小于5；让i加1 for i := 0; i < 5; i++ { fmt.Println("i现在的值为：", i) } } 执行输出结果为：cox@Cox:~/workspace/go/src/gotutorial$ go run forloop1.go i现在的值为： 0 i现在的值为： 1 i现在的值为： 2 i现在的值为： 3 i现在的值为： 4 从上面的结果可以看到，我们在定义 i 的初始值为0之后，在循环主体第一次执行前，i++是没有运行的，所以我们来能取得它的初始值 0。下面这些循环体你都可以在自己的示例中去运行：// 没有判断声明，循环将永远不停的执行 for i := 0; ; i++ { fmt.Println("i现在的值为：", i) } // 因为 i 的值不会变化，而i的初始值小于3，所以，它将永远小于3，那么循环加永远运行 for i := 0; i < 3; { fmt.Println("i现在的值为", i) } // 虽然我们没有在 *for* 中声明初始值以及变量的变量方式，但是我们把初始化移到for循环外面，然后把初始变量的变化声明移动到循环里面也是可以的，下面代码的输出将为： // s的值为： // s的值为： a // s的值为： aa // s的值为： aaa // s的值为： aaaa s := "" for ; s != "aaaaa"; { fmt.Println("s的值为：", s) s = s + "a" } 在 Go 的 for 循环里面，你可以同时初始化多个变量，多个条件判断或者同时更新多个值，请看下面的示例：package main import "fmt" func main() { for i, j, s := 0,5,"a"; i < 3 && j < 100 && s != "aaaaa"; i, j, s = i+1, j+1, s + "a" { fmt.Printf("i=%d, j=%d, s=%sn", i, j, s) } } 输出：i=0, j=5, s=a i=1, j=6, s=aa i=2, j=7, s=aaa 第三次循环之后，虽然 i < 100 与 s != "aaaaa" 都成立，但是 i < 3 已经不成立，所以循终止。break 关键字break 的作用是让循环在它所出现的地方终止，然后执行该循环后面的下一行代码或指令，如下示例：package main import "fmt" func main() { i := 0 for { if i >= 3 { break } fmt.Println("当前i的值为：", i) i++ } fmt.Println("for后的第一个声明") } 输出为：当前i的值为： 0 当前i的值为： 1 当前i的值为： 2 for后的第一个声明 continue 关键字continue 中止当前循环，然后回到循环的起始进行下一步循环，如下示例：package main import "fmt" func main() { for i := 0; i < 11; i++ { if i%2 == 0 { continue //我不输入偶数 } fmt.Println("当前i的值为：", i) } } 输出为：当前i的值为： 1 当前i的值为： 3 当前i的值为： 5 当前i的值为： 7 当前i的值为： 9 range 关键字range 关键字让你可以遍历数据／Map，为了便于理解，你可以把它理解为“每一个XX的索引”，当你使用它对数组或者片段遍历时，每一次返回它们的索引，对于MAP遍历时，每一次返回它的键，如下示例：package main import "fmt" func main() { // 对于数组，range 返回数据的索引 a := [...]string{"a", "b", "c", "d"} for i := range a { fmt.Println("数组的第", i+1, "个值为：", a[i]) } // 对于 map，返回每一个键 capitals := map[string]string {"France":"Paris", "Italy":"Rome", "China":"Beijing" } for key := range capitals { fmt.Println("Capital of", key, "is", capitals[key]) } } 输出为：数组的第 1 个值为： a 数组的第 2 个值为： b 数组的第 3 个值为： c 数组的第 4 个值为： d Capital of Italy is Rome Capital of France is Paris Capital of China is Beijing 同时，range还可以同时返回两人个值，第一个值为索引或者键，第二个值为数组或者Map的值本身，比如上面的两人个循环我们可以改写成为下面这样也可以得到同样一输出：package main import "fmt" func main() { // 对于数组，range 返回数据的索引 a := [...]string{"a", "b", "c", "d"} for i,v := range a { fmt.Println("数组的第", i+1, "个值为：", v) } // 对于 map，返回每一个键 capitals := map[string]string {"France":"Paris", "Italy":"Rome", "China":"Beijing" } for key,value := range capitals { fmt.Println("Capital of", key, "is", value) } }
程序需要根据不同的条件进行不同的处理， 我们程序不可能只按着某一个顺序一路执行下去，比如我们经常会遇到的这样一种情况：如果某一个文件存在，则将新内容更新至已有文件，如果文件不存在，则先创建文件，这个时候我们就可以使用if - else 控制结构了，在Go中，该控制结构如下：if some_boolean_expression { // 如果 some_boolean_expression 为 true，则会执行这里 } else if alternate_boolean_expression { // 如果 some_boolean_expression 为 false 并且 alternate_boolean_expression为 true,则执行这里 } else { // 如果上面的所有判断都为 false ,才能执行这里 } 注意事项 ： 在上面的结构说明，some_boolean_expression 以及 alternate_boolean_expression 的计算结果只能是 true 或者 false 里面的一个，不能为其它的值； 对于判断声明，括号不是必须的，你可以为其添加一个括号，但是这是没必要的； 与其它语言不同的是，Go要求左花括号必须与 if 或者 else 处于同一行内，就会报错，详情看写 GO 程序中一些常见的语法和其它错误。 int 类型的数据不能作为判断声明的结果被 if-else使用。 下面这些是条件判断的比较操作符： == : 等于 != : 不等于 < : 小于 <= : 小于或等于 > : 大于 >= : 大于或等于 && : 和（并且/与） || : 或 下面是一个最很简单的示例：package main import "fmt" func main() { if true { fmt.Println("这里会执行") } if false { fmt.Println("这里不会执行") } } 输出很很简单，就是打印出“这里会执行”这五个字符，下面这个示例则使用了一些判断操作符：package main import "fmt" func main() { a, b := 4, 5 if a < b { fmt.Println("a < b") } else if a > b { fmt.Println("a > b") } else { fmt.Println("a = b") } } 输入结果为 a < b，对于有其它语言编程经验的人来说，下面这些错误可能会经常出现：// 非布尔值 5 if 5 {} // 非布尔值 s var s string if s {} // 非布尔值 e var e os.Error if e {} // if 声明与左大括号不在一行内 if true {} // else 与 右大括号不在一行 if true { } else { }
本文是 「Golang.org」的原文档翻译，出于本人有限的英语水平，如有不当，还请指正。Go 是一个全新的语言，当然，它也从很多以存在多年的优秀语言中吸取了很多优秀的特性，但是因为其自身的特殊性，使得 Go 程序与其它相关的语言所开发的出来的程序有很大的不同，直接将C++或者Java程序翻译为Go程序并不会让你得到一个满意的结果——Java程序永远都只是Java程序，它成不了Go程序。另一方面，从Go的角度去想一个问题，你会得到一个可以成功解决它但是和其它语言完全不一样的方式。换句话说，想写出漂亮的Go程序，你必需要了解它的特性和风格，甚至是已有的约定，比如命名规范、代码格式化结构等，了解这些，可以让你的程序更简单地其它人看懂。本文档给出了一些能让你写出更加干净的符合Go语言习惯的代码，但是建议你在阅读本文之前先阅读一下Language Specification、Tour of Go以及「如何写Go程序(How to Write Go Code)」.示例Go Package Sources 并不只包含了它的核心库，同时它还包含了很多使用该语言写的示例程序，如果你有关于如何使用Go解决问题或者在Go应该如何实现的相关问题，这些示例代码或话会给你答案，它还能告诉你，你应该将自己的想法以怎样的一种方式转换成Go程序。格式化格式化应该是最具有争议但是也是最重要的一个Go语言的特性，每一个开发者可以有属性自己的书写格式，但是所有人都使用同一种格式看起来肯定是最好的，这使得我们不再需要花费太多时间放在了解别人的书写风格上。在Go中，不同于其它语言，它让计算机程序来管理着代码的格式化，gofmt 命令（也可以使用 go fmt)可以读取Go代码文件，并将它们以标准格式对其进行格式化，包括它们的缩进、空格等，请看下面这个示例：type T struct { name string // name of the object value int // its value } gofmt 会将其格式化为：type T struct { name string // name of the object value int // its value } 所有Go标准库中的代码都已经通过gofmt进行了格式化。比如下面这些Go中最常见的会使用到的格式化约定： 缩进：Go使用制表符（Tabs)进行缩进，而不是很多语言中都推荐的空格，gofmt默认会使用制表符进行缩进，你应该仅仅在需要空格的地使用空格。 行长度：Go没有行代度限制，所以，你完全不用担心这个问题，但是如果你感觉某一个行实在是太长了，那你可以将其截断，然后使用一些制表符让它看起来和它原本应该属于的那一行开始于同一个地方。 括号：Go只有很少的一些地方需要括号，结构控制（比如if, for, switch等的条件判断）都是不需要括号的，操作符的优先级层次更短更清晰，所以，x << 1 + y < 6,就可以很明白的知道这是什么了。 注释Go同时对G风格的块注释 /* ......*/ 和 C++风格的行注释 // .....提供支持，行注释是标准的，而块注释一般只建议使用的对包的注释上或者使用在需要禁用（但不是删除）一大段代码的功能上。godoc程序（同时也是文档服务器）会从Go代码中提供包的说明文档，Comments that appear before top-level declarations, with no intervening newlines, are extracted alog with the declaration to serve as explanatory text for the item. The nature and style of these comments determines the quality of the documentation godoc produces.任何一个包都应该包含一个包注释——一个用来说明包功能以及使用方法等信息的块注释，对于被分割为多个文件的包，包注释只需要出现在某中的任何一个文件中即可，godoc会将它作为所有同一个包的说明文档。The package comment should introduce the package and provide information relevant to the package as a whole. It will appear first on the godocpage and should set up the detailed documentation that follows.／＊ Ｐackage regexp implements a simple library for regular expressions. The syntax of the regular expressions accepted is: regecxp: concatenation { '|' concatenation } concatenation: { closure } closure: term [ '*' | '+' | '?' ] term: '^' '$' '.' character '[' [ '^' ] character-ranges ']' '(' regexp ')' */ package regexp 如果某个包是一个十分简单的包，那么这个包的注释可以很简单：// Package path implements utility routines for // manipulating slash-separated filename paths. 注释不需要进行特殊的格式化，文档创建工作不能确定使用的是否是固定宽度的字体，所以，你不需要使用空格去对注释进行格式化——godoc像gofmt一样，它会帮助你完成这些格式化。注释应该仅仅保是纯文本的字符串，所以，HTML或者其它的标记方式（比如 THIS）都是不应该被使用的，godoc只会照字面的意思去解决，所以，请尽可能让你的注释内容就是你想表达的内容的文本——使用正确的拼写，合理的分段，长句进行合理的截段等。在包内，任何一个仅跟着一个声明的注释都被当作该声明的注释文档，任何一个可导出的（大写开头的命名）的声明都应该具有一个说明注释，而这个注释文档最好也有遵循一定的约定，它的最第一句话应该是一个以该声明名称开始的用来概括该声明作用的句子。// Compile parses a regular expression and returns, if successful, a Regexp // object that can be used to match against text. func Compile(str string) (regexp *Regexp, err error) {...} Go 的声明语法允许分组声明，这使得一个单一的文档注释可以说明一组相关的常量或者变量，整个声明可以像下面这样来做（这是懒人做法啊）：// Error codes returned by failures to parse an expression. var ( ErrInternal = errors.New("regexp: internal error") ErrUnmatchedLpar= errors.New("regexp: unmatched '('") ErrUnmatchedRpar= errors.New("regexp: unmatched ')'") ... ) 即使是私有名称，分组也是可以指明它们之间的关系的，比如下面这一组私有变量由一个互斥锁保护：var ( countLock sync.Mutex inputCount unit32 outputCount unit32 errorCount unit32 ) 名称与其它语言一样，名称在Go中也是十分重要的，在某些环境下，一个名称甚至还需要其特有的语义——对于一个实例，它的方法或变量是否能被外部调用和访问取决于那个方法或变量的名称是否以大写字母开始，PublicItem这样的就是可以被外部访问的，而 privateItem则只能放在家里自己使用，也就是说：大写开始表示可以被外部访问，也就是可以被导出，而小写开头则只能是私有的。包名称当一个包被导入之后，包名称就成为一个访问该包资源的入口，比如，当我们：import "bytes" 之后，我们就可以请求 bytes.Buffer了，一个简洁、明了令人回味的包名称可以帮助他人在使用该包的过程中更加方便快乐。我们约定包名称应该是一个“仅仅小写字母的单个字汇字符串”，我们不必要使用下划线或者驼峰式的写出一个完整的长的名称，你要想想，假如你自己在使用别人的某一个包时，需要像fooBarFooBar.FooBar这样请求它的资源，你会是一种什么样的感觉，我们只需要简单的foo.Bar即可。你不必要担心你所选择包名可能与其它已有的包名相同，因为Go允许在导入一个包时为其指定一个本地使用的别名。另一个约定是“包名是该访问该包所有资源的基础入口”，比如 src/pkg/encoding/base64 包应该以encoding/base64导入，它的名称为 base64，而不是 encoding_base64或者encodingBase64。包导入工具将使用名称来引用它的内容（import . 只在某些特殊用途的时候使用，如非必要，请尽可能的不去使用），所以，包中可导出的内容就可以以最直接方式访问到，对于实例，如bufio包中的Reader，就可以直接使用 bufio.Reader访问到，而不是 BufReader或者其它的名称，因为用户看前者会更直观。由于导入的实体总是以其完整的位置访问，所以 bufio.Reader不会与 io.Reader 混淆。相似的，用来创建新的 ring.Ring实例的构建函数，通常我们可能会将其命名为 NewRing，但是，因为 Ring 是 ring 导出的唯一一个类型，所以，我们也只能创建ring.Ring 这一种类型的数据，所以，直接将该构建函数取名为 New即可。另一个＊简化名称＊的示例是 once.Do，我们不将其命名为 once.DoOrWaitUntilDown，长的名称在绝大多数时候并不会让我们更容易理解其所指代的意思，要让使用者更方便的调用也让他们更好的了解其使用方法或者意思，那好的办法是选择简洁的名称，并为其写上足够说明其意思和使用方法的注释。GettersGo 不会自动的提供 getters 和 setters 函数，但是这种事情你完全可以自己去完成，但是你也应该在适当的时候这么做，但是，你不需要把 Get 写进你所定义的方法名称中去，比如你现在有一个字段 owner（注意这是小写字母开头的，所以不导出，外部也部无法直接调用），它的 getter 名称应该称之为 Owner而不是GetOwner，它成为了获取 owner 字段数据的钩子，而 setter 函数则应该称之为 SetOwner：owner := obj.Owner() if owner != user { obj.SetOwner(user) } 接口名称Go 约定接口名称为方法名称后面加上 er：Reader、Writer、Formatter等，Go中有许多这样名称的标准接口，也有很多方法去实现了这些接口，它们这些方法的名称都是像 Read、Write、Close、Flush、String等这样的，为了避免造成混乱，在你自己的程序中尽可能的不要去使用上面这些标准库中提供了的方法或者接口名称。相反的，如果你的程序实现了一个与某一个已经被另一个大家所熟知的程序实现过的接口，那么你最好是使用该程序所使用的方法名称，一个将你的数据转换为字符的方法，不要叫作ToString，而应该直接叫作 String。多词汇名称最后，Go约定使用“驼峰式”的命名，而不使用下划线的方式，比如MixedCaps或者mixedCaps而不是mixed_caps。分号与 C 一样，Go使用分号“;”来终止一条语句，而与C不一样的是这些分号一般是不会出现在的源代码中的，Go使用一种简单的规则在其扫描代码的过程中合适的插入这些分号，所以，你在写代码的时候完全不需要将其明显的写入进源代码中。Go所约定的规则是这样的：如果一个新行的最后面是一个类型识别码（一般都是 int 或者 float64这样的），一个常量（比如数字或者字符串）或者下面这些关键字或者符号中的任何一个：break continue fallthrough return ++ -- ) } Go 会自动在其后面加了分号，上面所说的这些可以简单的描述为：“如果一个新行前面的Token可以终止一个声明，则在该Token后插入分号”。结束大括号前的分号也是可以省略的，比如：go func() { for { dst <- <-src } }() 就不需要分号，Go程序只需要在诸如循环、分隔初始值、条件或者延续元素的情况，才需要明显地使用分号，当然，你可以将多行地声明写进同一行中，这个时候每一个声明后就需要使用分号来区分。有一点你需要特别注意的，你不应该把控制结构（if、for、switch或者select）的左大括号写在下一行，如果你这么做了，那么Go会在这些声明后面加上一个分号，这肯定不是你想看到的。if i < f() { g() } 而下面这样就是错误的：if i < f() // wrong! { go () // wrong! } 控制结构Go 的控制结构与 C 十分类似，但是有一个重要的不同在于Go没有 do 或者 while 循环，仅仅使用 for 来做这些基本工作，switch 有更多的灵活性以及if和switch可以接受一个初始值声明，它还有一个新的控制结构 select，语法也略有不同：没有括号，主体必须分隔。if在Go中一个简单的if语句看起来像下面这样的：if x > 0 { return y } 强制性的要求 if 声明必须被分隔为多行，由于是在它的主体中包含有如 return或者break等控制语句的情况更为适用。由于 if 和 switch 支持初始化声明，所以，我们可以在其条件判断前为该条 if 语句初始化一个本地变量：if err := file.Chmod(0664); err != nil { log.Print(err) return err } 在Go标准库中，如果一个 if 语句不接会导致程序继续执行其下一条语句时，我们可以不需要使用 else ，这通常的都是在if主体中存在 return、break、continue或者goto等等这些能控制代码结构的关键字。f, err := os.Open(name) if err != nil { return err } d, err := f.Stat() if err != nil { f.close() return err } codeUsing(f) 在上面的代码中，我们首先打开一个文件，如果没有错误存在，程序会继续往下执行，如果再没有错误，则会执行 codeUsing(f)函数，但是如果有错误出现，那么在if执行之后就会跳出整个程序。声明重载（Redeclaration）在上面的示例中，你应该看到，我们的一个操作符 := 将 os.Open 返回的值同时声明给了两人个变量：f和err：f, err := os.Open(name) 接着：d, err := f.Stat() 看到，err 在前面声明之后，我们接着又重新声明了它，这表明，在第一个声明中，创建了err并为其赋了值，而在后面一次声明中，仅仅只是为其赋了新值。在一个为变量 v 赋值的 := 的声明中，那么： 如果在该声明同样的作用域内该声明之前已经存在了对该变量的声明，那么本声明直接为其赋值 没有出现上面的情况，本声明将先创建该变量之后再为其赋值 forGo的for循环有点像C的for循环——但是是不一样的，它有三种不同的格式：// Like a C for for init; condition; post { } // Like a C while for condition { } // Like a C for(;;) for { } 更短的声明让我们更容易给循环一个索引变量：sum := 0 for i := o; i < 10; i+ { sum += 1 } 如果你在遍历一个数组、片(Slice）、字符串或者Map，或者从一个 channel 中读取数据，range 可以被用来控制该循环：for key, value := range oldMap { newMap[key] = value } 如果你仅仅只需要循环过程中 range 产生的第一个值（range 产生两人个值 key, value)，那么直接丢掉第二值即可：for key := range m { if expired(k) { delete(m, key) } } 但是如果你仅仅只想要其产生的第二个值，那么需要使用一个“空定义（blank identifier）”作为占位符，它是一个下划线：“_”：sum := 0 for _, value := range array { sum += value } 这是因为，如果你没有那个占位符，那么 range 还是会首先把它产生的第一个值赋值给 value，而有了那个点位符之后，range将key赋值给，之后会直接把该值丢掉，然后range就赋值其第二值 value给value了。对于字符串，range能做更多的事情，breaking out individual Unicode characters by parsing the UTF-8. Erroneous encodings consume one byte and produce the replacement rune U+FFFD. The loopfunc main() { for pos, char := range "编程开发" { fmt.Printf(" aracter %c starts at byte position %dn", char, pos) } } 将会打印出：aracter 编 starts at byte position 0 aracter 程 starts at byte position 3 aracter 开 starts at byte position 6 aracter 发 starts at byte position 9 最后，Go没有逗号操作符，而 ++ 与 -- 是声明而不是表达式，如果你想在一个for循环中使用多个变量，你需要使用 Parallel Assignment。// Reverse a for i, j := 0, len(a)-1; i < j; i, j = i+1, j-1 { a[i], a[j] = a[j], a[i] } switchGo 中的 switch 比C中的更加通用，它的表达式并不局限在常量或者数字中，它的 case 是从上到下进行匹配，直到找到一个为 true 的 case，如果你的 if-else-if-else-... 有点儿太多了，那么就使用 switch吧。func unhex(c byte) byte { switch { case '0' <= c && c <= '9': return c - '0' case 'a' <= c && c <= 'f': return c - 'a' + 10 case 'A' <= c && c <= 'F': return c - 'A' + 10 } ` return 0 Go 中没有自动向下执行（Fall Through）机制，所以，你不能写成这个样子：case i < 0: case i > 10: doSomethingWithI(i) 但是Go可以将多个条件使用逗号分开，所以上面这样在其它语言里面常见的写法在Go你应该写成：case i < 0, i > 10: doSomethingWithI(i) Switch还可以被用来发现某一个接口变量的动态类型，像下面这样的去实现：switch t := interfaceValue.(type) { default: fmt.Printf("unexpected type %T", t) case bool: fmt.Printf("boolean %tn", t) case int: fmt.Printf("integer %dn", t) case *bool: fmt.Printf("pointer to boolean %tn", *t) case *int: fmt.Printf("pointer to integer %dn", *t) } 函数（Functions）多值返回 （Multiple return values）Go函数和其它语言最不一样的特性就是它可以在一个函数中同时返回多个值，这个特性能改进很多在C里面要通过各种各样的特殊办法实现的功能，而这个特性你在本文档前面也已经见到过了：f, err := os.Open(name) 同样的，在os包中还定义的很多比如 Write方法：func (file *File) Write(b []byte) (n int, err error)( 就像它的文档所说的，该方法返回写入的总字节数，并且当 n != len(b) 的时候返回一个 non-nil 的 error，这种写法在Go很通用，你可以在专门的章节中看到更多的关于 error 处理的示例。类似的这种方法省却了一个指针的使用，而且它看起来更加符合我们的普通常人的理解（需要返回什么东西就直接返回就是），下面这个示例演示了一个能同时返回两个值的函数，它先从一个字节数组（byte array）中找到一个数字，然后返回下一个为数字的元素的位置：func nextInt(b [byte], i int) (int, int) { x := 0 for ; i < len(b) && isDigit(b[i]); i++ { x = x*10 + int(b[i]) - '0' } return x, i } 然后你可以像下面这样的去调用它：for i := 0; i < len(a); { x, i = nextInt(a, i) fmt.Println(x) } 带名称的结果参数Go 函数中的返回结果参数可以被赋予一个名称，它可以像其它的变量一样的在函数中使用，命名之后，当函数执行时，它会被赋予一个其所属的数据类型的0值，而当函数执行一个 return声明之后（不需要带任何参数的声明），该命名了的返回参数当前的值就会被返回。Go 不强制你必须为返回参数命名，但是这样做可以使你的代码更短——它们被文档化了。如果我们将上面的函数进行命名，那么：func nextInt(b []byte, pos int) ( value, nextPos int) { ... } 因为命名了的返回结果会在函数执行时被初始化了，并且和一个未经修饰的 return 绑定，所以它能使唤代码更清晰，下面这是一个 io.ReadFull的一个版本：func ReadFull(r Reader, buf []byte) ( n int, err error) { for len(buf) > 0 && err == nil { var nr int nr, err = r.Read(buf) n += nr buf = buf[nr:] } return } 延迟（Defer）Go的defer声明一个定时运行的函数呼叫（deferred function），这样的函数会在函数终止前立马执行，这种方式在其它方法里面不太常见，但是它却非常有用，比如我们有一个函数要打开一个文件，但是不管这个函数是否执行成功，我们都应该在不再需要这个文件的时候关闭这个文件，这个时候我们就可以在成功打开文件之后，立马定时关闭该文件，当函数再往下执行时，不管是出现什么错误或者是成功执行，文件都会被正确关闭。// Contents returns the file's contents as a string. func Contents(filename string) (string, error) { f, err := os.Open(filename) if err != nil { return "", err } defer f.close() // f.close will run when we're finished. var result []byte buf := make([]byte, 100) for { n, err := f.Read(buf[0:]) result = append(result, buf[0:n]...) // append is discussed later if err != nil { if err == io.EOF { break } return "", err // f will be closed if we return here } } return string(result), nil // f will be closed if we return here } 延迟请求一个函数的执行至少有两人个好处：首先，它能让你永远不会因为某些错误（这实在是太容易发生了）而关闭文件；其次，关闭与打开文件的代码可以被写在一起，这样后面的代码完全可以放开安排自己的代码结构了（肯定啊，不需要在每一个 return 前面关闭一次文件了，我在任何一个地方可以 return ，文件会自动的关闭）。传递给被延迟请求的函数的参数是在 defer 执行是就计算出来的，而不是函数执行时才计算的，所以像下面这段代码运行的结果将是 10 0，而不是10 10：func forDefer() { i := 0 defer fmt.Printf("%d ", i) i = 10 fmt.Printf("%d ", i) } 同时它还遵循LIFO（Last Input First Output：先进后出）所以，当defer执行之后，延迟函数所调用的参数不管再怎么改变，都不会影响延迟函数本身的输出，所以像下面这段你代码，它最终执行后的结果将不是 0 1 2 3 4，而是4 3 2 1 0：for i := 0; i < 5; i++ { defer fmt.Printf("%d ", i) } 延迟函数执行还时常被用在跟踪函数本身的执行上，比如下面的示例：func trace(s string) { fmt.Println("entering: ", s) } func untrace(s string) { fmt.Println("leaving: ", s) } // Use them like this: func a() { trace("a") defer untrace("a") // do something you like as normal... } 对于这个跟踪函数，我们还可以做得更简洁高效，还记得我们在前面了解到的关于延迟函数的参数的值是在defer执行时就被计算的吗？我们在这里来使用这一特性重新完成前面的那个函数运行跟踪函数：func trace(s string) string{ fmt.Println("entering: ", s) return s } func un(s string) { fmt.Println("leaving: ", s) } func a() { defer un(trace("a")) fmt.Println("in a") } func b() { def un(trace("b")) fmt.Println("in b") a() } func main() { b() } 上面代码的输出结果是：entering: b in b entering: a in a leaving: a leaving: b 对于习惯于以块为单位管理代码的其它语言的开发者来说，defer可以看起来很奇怪，但是它功能的功能及实用性是可以肯定的，而且是十分有趣的，它使得代码不在以块为单位而是以功能来进行组织管理的。数据通过 new 分配内存Go内置了两人函数 make 和 new，它们做的事情是不一样的，而且绑定了不同的类型，这很容易让人混淆，但实际上它的规则是很简单的。让我们先来谈谈 new，它是一个用来分配内存的内置函数，但是它不像其它语言中的 new 函数，它不初始化内存， new(T) 只是创建一个没有任何数据的类型为T的实例，并且返回它的地址——*T类型的值，在Go的术语中，它返回一个新建的分配了0空间的类型为T的实例的指针。由于new返回的内存是归零了的，所以它能让我们很方便的设计它的数据结构，因为归零的数据可以不需要更多的初始货即可直接使用，比如 bytes.Buffer的文档这么写着“Buffer的归零数据是一个空的可以随便人到中年来使用的缓冲”。相反的，sync.Mutex没有明确的构建或者初始化方法，转而它的归零值为一个未锁定的mutex。The zero-value-is-useful property works transitively. Consider this type declaration.type SyncedBuffer struct { lock sync.Mutex buffer bytes.Buffer } SyncedBuffer类型的值在其分配了或者仅仅只是声明了之后都是可以仍的，下面这段代码不需要更多的安排即可以正确的工作：p := new(SyncedBuffer) // type *Synceduffer var v SyncedBuffer // type SyncedBuffer 构造函数与复合声明（Constructors and composite literals）很多时候一个归零的值并不足以满足程序的业务需求，这时一个初始化构造函数就成了必须的东西了，比如下面这段从 os 衍生的函数：func NewFile(fd ind, name string) *File{ if fd < 0 { return nil } f := new(File) f.fd = fd f.name = name f.dirinfo = nil f.nepipe = 0 return f } 上面这样的写法有点太过于复杂和麻烦了，使用初始化成员可以更简单的完成这个工作，每次只需要使用一个表达式即可创建一个新实例：func NewFile(fd int, name string) *File { if fd < 0 { return nil } f := File(fd, name, nil, 0} return &f } 注意：不像 C，返回本地变量的地址在Go中是完全可以的，当函数返回之后，本地变量地址所占用的存储空间同样的会生存下来，事实上，我们可以直接可以将上面函数中的最后两人行合并为一行：return &File(fd, name, nil, 0} 上面这些初始化成员必须按其构造函数定义了的顺序赋值，这是很麻烦的一件事情，我们必须要一直记得它们是一个什么样的顺序，即使有100个，我们也必须记住这100个的顺序，好在我们可以使用一种更加合理且方便的方法来使用它，那就是为每一个初始化成员指定一个标签，其格式为field: value：return &File(fd: fd, name: name} 如果初始化成员没有任何元素的话，那么就会创建一个归零的实例，这个时候，&File{}与new(File)是等价的。复合声明同样可以被用来创建array、slice、map，通过指定适当的索引和map键来标识字段，在下面这个例子中，无论是Enone、Eio还是Einval初始化都能很好的工作，只要确保他们不同就好了。a := [...]string {Enone: "no error", Eio: "Eio", Einval: "invalid argument"} s := []string {Enone: "no error", Eio: "Eio", Einval: "invalid argument"} m := map[int]string {Enone: "no error", Eio: "Eio", Einval: "invalid argument"} 用 make 分配内存回到内存分配。内奸函数 make(T, args) 与 new(T) 有着不同的功能。它只能创建 slice、map和channel，并且返回一个有初始值（非零）的T类型的实例，而不是 *T。从本质上讲，导致这三个类型有所不同的原因在于指向数据结构的引用在使用前必须被初始化。例如，一个slice，是一个包含指向数据（内部array）的指针、长度和容量这三项的描述符，在这些项目被初始化之前，slice为nil，对于slice、map和channel，make初始化了内部的数据结构，填充适应的值。make 返回初始化后的（非零）值例如，*make([]int, 10, 100)分配了100个整数的数组，然后用长度10和容易100创建了slice结构指向数组的前10个元素，区别是new([]int)返回指向新分配的内存的指针，而零值填充的slice鸨是指向nil的slice值。下面这个例子展示这两人者之间的不同：var p *[]int = new([]int) // 分配slice结构内存 : *p = nil // 已经可用 var v []int = make([]int, 100) // v 指向一个新分配的有 100 个整数的数组 var p *[]int = new([]int) // 不必要的复杂的例子 *p = make([]int, 10, 100) v := make([]int, 100) // 更常见 务必赢得 make 仅适用于 map、slice和channel，并且返回的不是指针，应当用new来获得特定的指针。
本文是 「Golang.org」的原文档翻译，出于本人有限的英语水平，如有不当，还请指正。说明本文档演示了一个最简单的 Go 包的开发过程以及一些 Go 命令行工具的使用，以及Go是如何架构、安装与搜索包的。代码组织GOPATH 以及工作区Go 语言设计的目标之一是让软件开发更简单，得到的结果是 Go命令不使用任何的 Makefiles或者其它的配置文件，取而代之的是直接使用程序的源代码来管理依赖关系及架构条件，这意味着，你程序的源代码与架构脚本总是同步的，因为它们就是同样的东西（注：如果使用 Makefiles之类的东西，那么，你源代码修改了，还需要再去修改Makefiles，而是在Go中，因为没有这些配置文件，所以，你修改源代码，同时直接就修改了这些配置）。你唯一需要做的是设置一个 GOPATH 环境变量，GOPATH 的作用是告诉 Go 从哪里去搜索Go的执行脚本以及将你的Go安装到哪里.$GOPATH 是一个包括很多路径的列表，它的格式与系统的PATH变量格式一样，一个Unix系统中常见的GOPATH像这样的：GOPATH=/home/user/ext:/home/user/mygo （在Windows系统中，将“:”修改为“;”）任何一个被定义至 $GOPATH$ 环境变量中的路径都指定着一个工作区的的位置（比如上例中的 /home/user/ext或者/home/user/mygo），一个工作区包含着Go源代码以及它们所依赖的包对象以及一些可执行的工具，它被规定为需要有下面这三个子目录： src : Go 源代码文件 pkg : 以编译的 Go 包 bin : 可执行命令 src 目录中的子目录都是独立的包，这些包中的所有源代码文件（.go，.co，.h以及.s等）都是子目录包的元素。它构建一个import "widget"的程序时，Go构建工具会搜索 Go 根目录下的 src/pkg/widget 目录——如果没有找到，那么它会接着尝试去寻找同一工作区中的 src/widget 目录。多个工作区可以给我们带来更我铁灵活性与方便，但是在本文档中，我们只涉及单个工作区，下面让我们先创建一个示例工作区，首先创建一个目录，我们用它来存放项目的源代码：cox@CoxStation:~$ mkdir $HOME/example/src -p 下一步，设置 GOPATH 环境变量，你需要同时将 $GOPATH/bin 添加到 PATH 环境变量中，这样一来，你就不需要每次执行这些命令的时候输入完整的绝对路径了。cox@CoxStation:~$ export GOPATH=$HOME/example cox@CoxStation:~$ export PATH=$PATH:$HOME/example/bin 导入路径Go 标准库或者包都可以使用最短的路径名导入，比如 fmt 或者 net/http 等，这是为了程序开发者的方便而设计的，但是对于你自己的项目，你需要认真的设计一个基本的导入路径，这需要考虑它它的可读性同时还需要考虑到它不应该和以有的或者将来会出现的其它的库相冲突。最好的一个办法是总是以你所开发的项目的版本管理工具名称开始，比如，如果你的项目源代码库位置为 example.com 或者 code.google.com/p/example ，那么你应该让你的包路径从该 URL 地址开始，比如：example.com/foo/bar 或者 code.google.com/p/example/foo/bar ，如果你遵循这样的约定，那么 Go 会自动为你检测URL地址所指定的包，同时下载以及以你的导入路径安装至你的工作区中。如果你没有像上面这样的发布路径，那么你至少应该使用一个唯一的前缀，比如 widget 以及 widget/foo/bar ，一个好的规则是使用你的项目名称或者公司名称作为前缀，这一般是不会被别人使用的，比如：example.com。我们下面使用 example.com 作为基本的导入路径：cox@CoxStation:~$ mkdir -p $GOPATH/src/example.com 包名称任何一个 Go 源代码文件的第一个声明都应该是包名：package NAME NAME 就是你的包名称，它也是你导入该包时默认使用的名称（所有处于同一个包中的文件都必须共享同一个包名）。Go 约定包名称是导入路径中的最后一个元素，一个以 crypto/rot12 导入的包的包名应该是 rot13，Go 不要求所有被导入到某一个程序中的包名都具有唯一性，但是要求其完整的导入路径必须是唯一的。在 example 中创建一个新包，名为 newmath:cox@CoxStation:~$ cd $GOPATH/src/example.com cox@CoxStation:~/example/src/example.com$ mkdir newmath 之后创建一个名为 $GOPATH/src/example.com/newmath/sqrt.go 的文件，它的内容为：// Package newmath is a trivial example package. package newmath // Sqrt returns an approximation to the square root of x. func Sqrt(x float64) float64 { // This is a terrible implementation. // Real code should import "math" and use math.Sqrt. z := 0.0 for i := 0; i < 1000; i++ { z -= (z*z - x) / (2 * x) } return z } 该包将可以使用下面这个路径导入：import "example.com/newmath" 构建以及安装Go 命令包含了很多子命令，最核心的子命令就是install，运行go install importpath 将构建并安装它所依赖的所有包。安装一个包 表示将构建之后的包文件或者可执行的文件写入到当前工作区的pkg或者bin目录中。构建一个包要构建并安装 newmath包，使用下面命令：cox@CoxStation:~/example$ go install example.com/newmath 如果构建成功，那么该命令不会有任何输出。Go约定，如果没有指定导入路径，那么go使用当前目录，下面的这两行命令与上面那个是一样的效果：$ cd $GOPATH/src/example.com/newmath $ go install 构建之后的结果是go为你创建了一个目录树（假定我们现在使用的是Linux 64位系统——我就是这样的系统），它看起来像是这样的：$GOPATH/ pkg/ linux_amd64/ example.com/ newmath.a src/ example.com/ newmath/ sqrt.go 构建一个命令工具Go 命令将包名为 main 的包视为可执行的，从而会将它安装到 $GOPATH/bin 目录中，创建一个名为 hello 的可执行命令，我们首先需要创建它的包，之后再创建包文件：cox@CoxStation:~/example$ mkdir $GOPATH/src/example.com/hello cox@CoxStation:~/example$ vi $GOPATH/src/example.com/hello/hello.go 文件内容为：// Hello is a trivial example of a main package. package main import ( "example.com/newmath" "fmt" ) func main() { fmt.Printf("Hello, Golang. Sqrt(2) = %vn", newmath.Sqrt(2)) } 保存上面文件之后，我们运行下面这个命令安装它：cox@CoxStation:~/example$ go install example.com/hello 之后你就可以像运行其它命令一样的运行我们的 hello 了：cox@CoxStation:~/example$ $GOPATH/bin/hello Hello, Golang. Sqrt(2) = 1.414213562373095 如果你将 $GOPATH/bin 目录添加到了全局环境变量 PATH 中的话，那么你还可以像下面这样的执行：cox@CoxStation:~/example$ export PATH=$PATH:$HOME/example/bin/ cox@CoxStation:~/example$ hello Hello, Golang. Sqrt(2) = 1.414213562373095 现在我们的示例程序的目录数看起来应该像下面这个样子的了：$GOPATH/ bin/ hello pkg/ linux_amd64/ example.com/ newmath.a src/ example.com/ hello/ hello.go newmath/ sqrt.go 测试Go 内置了一个轻量级的测试框架，它由 go test 命令和 testing 包组成。创建一个以 _test.go 结尾的文件，让它包含只有用来标记的参数 t *testing.T的名为TestFUNC* 的函数，那么测试框架就会执行每一个像上面这样的函数，其实函数名称中的 FUNC 修改成为你需要测试的函数名，如果该函数调用了失败函数，比如t.Error或者t.Fail，那么表示整个测试失败。我们现在创建一个名为 $GOPATH/src/example.com/newmath/sqrt_test.go 的测试文件用来测试 Sqrt函数：cox@CoxStation:~/example$ vi $GOPATH/src/example.com/newmath/sqrt_test.go 它的内容为：package newmath import "testing" func TestSqrt(t *testing.T) { const in, out = 4, 2 if x := Sqrt(in); x != out { t.Errorf("Sqrt(%v) = %v, want %v", in, x, out) } } 现在运行测试：cox@CoxStation:~/example$ go test example.com/newmath ok example.com/newmath 0.005s 远程包一个导入路径可以告诉 go 如何从一个版本管理系统（比如Git或者Mercurial）中获取包代码，go命令使用这些属性自动从远程搜索包，比如我们的这个示例程序，它的远程地址是：code.google.com/p/go.example，如果你将该URL址址告诉go，那么go会自动从远程下载该包的源代码：$ go get code.google.com/p/go.example/hello $ $GOPATH/bin/hello Hello, world. Sqrt(2) = 1.414213562373095 如果指定的远程包在本地没有复本，那么 go 会自动的为你下载它，如果已经存在，则直接跳过，运行了上面的命令之后，你的目录看起来应该像下面这样的了。$GOPATH/ bin/ hello pkg/ linux_amd64/ code.google.com/p/go.example/ newmath.a example/ newmath.a src/ code.google.com/p/go.example/ hello/ hello.go newmath/ sqrt.go sqrt_test.go example/ hello/ hello.go newmath/ sqrt.go sqrt_test.go 如果你不直接告诉 go 去下载安装某个包，而是在某一个你所写的包中导入它，比如：import "code.google.com/p/go.example/newmath" 那么，go 一样的也会自动的为你做好这一切。说明 本翻译文档中所涉及的代码和原始文档有些话差入。 你可以从这里下载到本文档中所创建的示例代码：http://dl.antusoft.com/examples/golang/go.example.tar.gz 官方示例项目地址为：http://code.google.com/p/go.example/
什么是Go？来自其网站的介绍：Go 编程语言是一个使得程序员更加有效率的开源项目。Go 是有表 达力、简洁、清晰和有效率的。它的并行机制使其很容易编写多核 和网络应用，而新奇的类型系统允许构建有弹性的模块化程序。Go 编译到机器码非常快速，同时具有便利的垃圾回收和强大的运行 时反射。它是快速的、静态类型编译语言，但是感觉上是动态类型 的，解释型语言。Go 1 是 Go 语言的第一个稳定发布版本。基本上任何一个语言最开始的学习的时候都离不开那个“Hello World！”程序，既然找不到其它的方法，那么，学习Go的时候还是也来一个Hello World吧。package main import "fmt" /* 打印出 Hello World 的各种语言的版本 */ func main() { fmt.Printf("Hello, world; or 你好，世界n") } 在上面的代码段中，首行是必须的，任何 Go 文件都必须以 package 开头，对于需要独立运行的执行文件，则必须是 package main；接着我们导入了 fmt 包，这就是说需要将 "fmt" 包加入 main，不是 main 的其它包都称之为库，其它许多语言中都有着类似的概念，比如Python中的 module。后面跟着的是一行注释。最后面我们定义了一个名为 main() 的函数，当 Go程序在执行的时候，首先调用的函数就是 main.main()，这是从C语言中继承而来的，在这里我们使用关键字 func 定义了这个函数，这个函数不接受任何参数，也不返回任何类型的值，该函数将打印出 “Hello, world; or 你好，世界”字符串，你可以发现，在Go语言中，可以直接使用非ASCII字符，这是因为Go支持任何编码的字符串，你甚至可以使用非ASCII编码的字符作为变量或者函数名称。将上面的代码保存为一个helloworld.go 文件，然后编辑它：go build helloworld.go 这会在当前文件夹中生成一个同名的可执行文件，我现在使用的是Windows 7，所以，这个可执行文件的文件名为 helloworld.exe 。如果不想编译它即执行，可以使用 go run 命令：go run helloworld.go 它会直接运行 helloworld 程序，而不是生成可执行文件后手工去执行。
直接进入正题，在任何一个地方创建一个名为 helloworld.go 的文件，然后立马执行它：cox@Cox:~/workspace/go/src$ touch helloworld.go cox@Cox:~/workspace/go/src$ go run helloworld.go package : helloworld.go:1:1: expected 'package', found 'EOF' 简单吧，看错误信息，你现在应该知道，没有绑定到某一个 package 的文件是不被 Go 认可的，所以，我们都需要加上这么一个，我们在前面的那个 helloworld.go 文件里面加上：package main 之后再来执行：cox@Cox:~/workspace/go/src$ vi helloworld.go cox@Cox:~/workspace/go/src$ go run helloworld.go # command-line-arguments runtime.main: undefined: main.main 恩，Go程序需要从某一个位置开始执行，就像电脑有电源开关一样简单，在 Go 中，程序都是从一个名为 main 的函数开始执行，所以，再加上这个函数：package main func main() {} 再来执行它：cox@Cox:~/workspace/go/src$ vi helloworld.go cox@Cox:~/workspace/go/src$ go run helloworld.go 恩，没报任何错误了，那也就是说我们现在的程序已经正确运行，但是因为 main() 没有做任何事情，所以我们现在什么也看不到，我们现在让它来打印出 “Hello World! 您好！世界。” 这个字符串：package main func main() { Println("Hello World! 您好！世界。") } 又出错了：# command-line-arguments ./helloworld.go:4: undefined: Println Println 未定义，恩，从我们上面的代码中确实看得到，从来没有它，突然就这么一下子就拿来用了，不成，就像你从来没有买过车，一辈子都没见过车，怎么可能走着走着就突然在开车了呢，我们来买一辆车：package main import "fmt" func main() { fmt.Println("Hello World! 您好！世界。") } 再来运行 helloworld.go：cox@Cox:~/workspace/go/src$ go run helloworld.go Hello World! 您好！世界。 成了，这个 fmt 就是我们买来的车，有了车我们才能开车，我们这里用了 fmt 里面的 Println 函数。你学会了吗？ 任何一个 Go 文件都必须被关联到某一个包上 任何一个 Go 程序都必须有一个开始点，就是那个 main 函数 任何一个函数或者变量都必须被定义之后才能使用
Golang 是一个以类BSD许可发布的程序开发语言，官方提供有两个编译器 gc Go编译器和gccgo编译器，其中gccgo编译器是GNU C 编译器的一部分，而 gc 编译器比较成熟并且它进行了更多的测试，本文所使用的是 gc 编译器安装一个以二进制包发布的 Go 版本。系统需求gc 编译器可以在下面这些操作系统和架构上面运行，请确定您当前的系统满足这些需求，如果你所使用的系统不在当前列表中，那么你就应该试试 gcc 编译器安装，这有可能有适合您的系统。 操作系统名称 系统架构 说明 FreeBSD 7+ amd64,386 Debian GNU/kFreeBSD不被支持 Linux 2.6.23+(glibc) amd64,386,arm CentOS/RHEL 5.x不被支持，还无ARM版本的二进制包 Mac OS X 10.6/10.7 amd64,386 使用XCode的gcc(1) Windows 2000+ amd64,386 使用mingw gcc(2)，cygwin或msys不是必需的 1) 只有你想使用 cgo 时，gcc才是必须的；2) 你仅仅只需要安装命令行工具即可，如果你已经嬢了 Ｘcode 4.3+，那么你可以在组件面板中添加命令行工具下载 Go Tools访问 Go 项目下载页面：http://code.google.com/p/go/downloads/list，选择一个适合你的操作系统或者CPU架构的发行版，官方的发行版有FreeBSD，Linux，Mac OS X(Snow Leopard/Lion)以及Windows系统，同时支持 32位与64位CPU架构，如果没有适合你所使用的操作系统的二进制安装包，你可以需要从源代码安装或者使用gccgo而不是gc。安装 Go ToolsGo的发行布将被安装至 /usr/local/go目录（Windows下为c:Go），但是你是可以修改该安装目录的，使用Go时，你需要指定 GOROOT 环境变量至你使用 Go 的目录，比如，如果你将 Go 安装至你的home目录，那么你需要在 $HOME/.profile文件中添加下面这两行：export GOROOT=$HOME/go export PATH=$PATH:$GOROOT/bin Windows用户请设置环境变量，怎么设置我也不太明白，很少用那个东西……FreeBSD, Linux以及Mac OS X压缩包如果你是从一个老版本的Go进升级，那么你需要在安装之前先删除原先版本：rm -r /usr/local/go 将你获得的压缩包文件解压至 /usr/local：tar -C /usr/local -xvf go1.0.3.linux-amd64.tar.gz 包文件的名称可能和上面我所使用的不一样，因为我们的系统可能不一样，之后你可以将 /usr/local/go/bin目录添加至你的环境变更 PATH 中，比如添加至 /etc/profile或者$HOME/.profile。Mac OS X 包安装工具如果 Mac OS X 的包安装工具的话只需要接照安装工具的流程进行即可，默认会安装至 /usr/local/go 目录中。Windows 下面自己研究测试你的安装要测试你的Go是否已经正确安装的最简单的办法就是写一个Go程序，比如，Hello Golangpackage main import "fmt" func main() { fmt.Printf("Hello, Golang!n") } 然后测试运行：$ go run hello.go Hello, Golang! OK，这表示你安装成功了。