golang 新手教程:入门速成指南 | go优质外文翻译 | go 技术论坛-大发黄金版app下载
让我们从go(或 golang)的一个小介绍开始。 go 由 google 工程师 robert griesemer,rob pike 和 ken thompson 设计。 它是一种静态类型的编译语言。 第一个版本于2012年3月作为开源发布。
“go 是一种开源编程语言,可以轻松构建的简单,可靠,高效的软件”。
— 关于 go
在许多语言中,有许多方法可以解决某些给定的问题。因此 程序员可以花很多时间思考解决问题的最佳方法。
然而,go却是只有一种正确的方法来解决问题的语言。
这节省了开发人员的时间,并使大型代码库易于维护。 go中没有地图和过滤器等“富有表现力”的功能。
“如果你有增加表现力的功能,通常会增加费用”
— rob pike
最近发布了 golang 的新 logo:
入门
go 是由包组成的。 main 包告诉 go 编译器该程序可以被编译成可执行文件,而不是一个共享的库。它是应用程序的入口。main 包被定义为如下格式:
package main接下来,让我们通过在 go 工作区中创建一个文件 main.go 来编写一个简单的 hello world 示例。
go 的工作区
go 中的工作空间由环境变量「gopath」定义。你写的任何代码都将写在工作区内。go 将搜索 gopath 目录中的任何包,或者在安装 go 时默认设置的goroot 目录。 goroot 是安装go的路径。
将 gopath 设置为你想要的目录。 现在,让我们将它添加到文件夹〜/ workspace 中。
# 写入 env
export gopath=~/workspace
# cd 到工作区目录\
cd ~/workspace使用我们刚刚创建的工作空间文件夹中的以下代码创建文件 main.go。
hello world!
package main
import (
 "fmt"
)
func main(){
  fmt.println("hello world!")
}在上面的 demo 中, fmt 是 go 中的内置包,它实现了格式化 i / o 的功能。
在 go 中我们导入一个包使用 import 关键字。func main 是代码执行的入口。println 是 fmt 包中的一个函数,它为我们打印 “hello world”。
让我们看一下运行这个文件。 我们可以通过两种方式运行 go 命令。 我们知道,go 是一种编译语言,所以我们首先需要在执行之前编译它。
> go build main.go这会创建一个二进制可执行文件 main,现在我们可以运行它:
> ./main
 # hello world!还有另一种更简单的方法来运行程序。 go run 命令有助于抽象编译步骤。 您只需运行以下命令即可执行该程序。
go run main.go
 # hello world!note: 您可以使用 来运行本文提到的代码。
变量
变量在 go 语言中是一个很明确的定义。 go 是一种静态类型的语言。这意味着在声明变量时我们就需要明确变量的类型。一般一个变量的定义如下:
var a int上面的实例中,我们定义了一个 int 类型的变量 a ,默认会被赋值成 0 。使用以下语法可以初始化改变变量的值:
var a = 1这里我们没有制定变量 a 的类型,在我们给它初始化为1时,它就自动被定义成了 int 类型的变量。我们也可以使用一种更简短的语法来定义它:
message := "hello world"我们也可以在同一行声明多个同类型变量:
var b, c int = 2, 3数据类型
跟任何其他编程语言一样,go 语言支持各种不同的数据结构。 让我们探讨一下:
number, string, and boolean
一些受支持的 number 存储类型是:int, int8, int16, int32, int64,
uint, uint8, uint16, uint32, uint64, uintptr…
string 类型存储一些列的字节。 它用关键字 string 表示和声明。
boolean 使用关键字 bool 存储布尔值。
go 还支持复数类型数据类型,可以使用 complex64 和 complex128 声明。
var a bool = true
var b int = 1
var c string = 'hello world'
var d float32 = 1.222
var x complex128 = cmplx.sqrt(-5  12i)数组, 切片, 以及 maps
数组是相同数据类型的元素序列。 数组在声明中定义要指定长度,因此不能进行扩展。 数组声明为:
var a [5]int数组也可以是多维的。 我们可以使用以下格式创建它们:
var multid [2][3]int当数组的值在运行时不能进行更改。 数组也不提供获取子数组的能力。 为此,go 有一个名为切片的数据类型。
切片存储一系列元素,可以随时扩展。 切片声明类似于数组声明—没有定义容量:
var b []int这将创建一个零容量和零长度的切片。 切片也可以定义容量和长度。 我们可以使用以下语法:
numbers := make([]int,5,10)这里,切片的初始长度为 5,容量为 10。
切片是数组的抽象。 切片使用数组作为底层结构。 切片包含三个组件:容量,长度和指向底层数组的指针,如下图所示:
图片地址:
通过使用  append  或  copy  函数可以增加切片的容量。 append 函数可以为数组的末尾增加值,并在需要时增加容量。
numbers = append(numbers, 1, 2, 3, 4)增加切片容量的另一种方法是使用复制功能。 只需创建另一个具有更大容量的切片,并将原始切片复制到新创建的切片:
// 创建切片
number2 := make([]int, 15)
// 将原始切片复制到新切片
copy(number2, numbers)我们可以创建切片的子切片。 这可以使用以下命令完成:
// 初始化长度为 4,以及赋值
number2 := []int{1,2,3,4}
fmt.println(numbers) // -> [1 2 3 4]
// 创建子切片
slice1 := number2[2:]
fmt.println(slice1) // -> [3 4]
slice2 := number2[:3]
fmt.println(slice2) // -> [1 2 3]
slice3 := number2[1:4]
fmt.println(slice3) // -> [2 3 4]map 是 go 的一种 key-value类型的数据结构,我们可以通过下面的命令声明一个 map :
m := make(map[string]int) m 是 一个 key 类型为 string、value 类型为 int的 map 类型的变量。我们可以很容易地添加键值对到 map 中:
// adding key/value
m["clearity"] = 2
m["simplicity"] = 3
// printing the values
fmt.println(m["clearity"]) // -> 2
fmt.println(m["simplicity"]) // -> 3类型转化
通过类型转化,能将一种类型转为另一种类型。让我们来看一个简单的例子:
a := 1.1
b := int(a)
fmt.println(b)
//-> 1并不是所有类型都可以转为另一种类型。需要确保数据类型是可以转化的。
流程控制
if else
对于流程控制,我们可以使用 if-else 语句,如下例所示。 确保花括号与条件位于同一行。
if num := 9; num < 0 {
 fmt.println(num, "is negative")
} else if num < 10 {
 fmt.println(num, "has 1 digit")
} else {
 fmt.println(num, "has multiple digits")
}switch case
switch cases 有助于组织多个条件语句。 以下示例显示了一个简单的 switch case 语句:
i := 2
switch i {
case 1:
 fmt.println("one")
case 2:
 fmt.println("two")
default:
 fmt.println("none")
}循环
go 为循环设置了一个关键字。 单个 for 循环命令有助于实现不同类型的循环:
i := 0
sum := 0
for i < 10 {
 sum  = 1
  i
}
fmt.println(sum)上面的示例类似于 c 中的 while 循环。对于 for 循环,可以使用相同的 for 语句
sum := 0
for i := 0; i < 10; i {
  sum  = i
}
fmt.println(sum)go 中的无限循环:
for {
}指针
go 支持指针。指针是保存值的地址的地方。 一个指针用 * 定义 。根据数据类型定义指针。 例:
var ap *int上面的  ap  是指向整数类型的指针。& 运算符可用于获取变量的地址。
a := 12
ap = &a可以使用 * 运算符访问指针指向的值:
fmt.println(*ap)
// => 12在将结构体作为参数传递或者为已定义类型声明方法时,通常首选指针。
- 传递值时,实际复制的值意味着更多的内存
- 传递指针后,函数更改的值将反映在方法/函数调用者中。
例子:
func increment(i *int) {
  *i
}
func main() {
  i := 10
  increment(&i)
  fmt.println(i)
}
//=> 11note: 当你在博客中尝试示例代码时,不要忘记将其包含在 main 包中,并在需要时导入 fmt 或其他包,如上面第一个 main.go 示例所示。
函数
main 函数 定义在 main 包中,是程序执行的入口。可以定义和使用更多功能。 让我们看一个简单的例子:
func add(a int, b int) int {
  c := a  b
  return c
}
func main() {
  fmt.println(add(2, 1))
}
//=> 3上面的例子中可以看到,使用 func 关键字后面跟函数名定义 go 的函数
函数的返回值也可以在函数中预先定义:
func add(a int, b int) (c int) {
  c = a  b
  return
}
func main() {
  fmt.println(add(2, 1))
}
//=> 3这里 c 被定义为返回变量。 因此,定义的变量 c 将自动返回,而无需在结尾的return 语句中再次定义。
你还可以从单个函数返回多个返回值,将返回值与逗号分隔开。
func add(a int, b int) (int, string) {
  c := a  b
  return c, "successfully added"
}
func main() {
  sum, message := add(2, 1)
  fmt.println(message)
  fmt.println(sum)
}方法,结构体,以及接口
go 不是绝对的面向对象的语言, 但是使用结构体,接口和方法,它有很多面向对象的风格以及对面向对象的支持。
结构体
结构体是不同字段的类型集合。 结构用于将数据分组在一起。 例如,如果我们想要对 person 类型的数据进行分组,我们会定义一个 person 的属性,其中可能包括姓名,年龄,性别。 可以使用以下语法定义结构:
type person struct {
  name string
  age int
  gender string
}在定义了 person 结构体的情况下,现在让我们创建一个 person 实例 p:
//方式1:指定属性和值
p := person{name: "bob", age: 42, gender: "male"}
//方式2:指定值
person{"bob", 42, "male"}我们可以用英文的点号(.)轻松访问这些数据
p.name
//=> bob
p.age
//=> 42
p.gender
//=> male你还可以使用其指针直接访问结构体里面的属性:
pp = &person{name: "bob", age: 42, gender: "male"}
pp.name
//=> bob方法
方法是一个特殊类型的带有返回值的函数。返回值既可以是值,也可以是指针。让我们创建一个名为 describe 的方法,它具有我们在上面的例子中创建的 person 结构体类型的返回值:
package main
import "fmt"
//定义结构体
type person struct {
  name   string
  age    int
  gender string
}
// 方法定义
func (p *person) describe() {
  fmt.printf("%v is %v years old.", p.name, p.age)
}
func (p *person) setage(age int) {
  p.age = age
}
func (p person) setname(name string) {
  p.name = name
}
func main() {
  pp := &person{name: "bob", age: 42, gender: "male"}
  pp.describe()
  // => bob is 42 years old
  pp.setage(45)
  fmt.println(pp.age)
  //=> 45
  pp.setname("hari")
  fmt.println(pp.name)
  //=> bob
}从上面的例子中可以看到, 现在可以使用点运算符 调用该方法,就像作为 pp.describe 这样。请注意,返回值是指针类型。使用指针,我们传递对值的引用,因此如果我们对方法进行任何更改,它将反映在返回值 pp 中。指针类型的返回值也不会创建对象的新副本,从而节省了内存。
请注意,在上面的示例中,age 的值已更改,而 name 的值不会改变。因为方法 setname 是返回值是值类型,而 setage 方法的返回值是类型指针。
接口
go 的接口是一系列方法的集合。接口有助于将类型的属性组合在一起。下面,我们以接口 animal 为例:
type animal interface {
  description() string
}这里的 animal 是一个接口。现在,我们用两个不同的实例来实现 animal 这个接口:
package main
import (
  "fmt"
)
type animal interface {
  description() string
}
type cat struct {
  type  string
  sound string
}
type snake struct {
  type      string
  poisonous bool
}
func (s snake) description() string {
  return fmt.sprintf("poisonous: %v", s.poisonous)
}
func (c cat) description() string {
  return fmt.sprintf("sound: %v", c.sound)
}
func main() {
  var a animal
  a = snake{poisonous: true}
  fmt.println(a.description())
  a = cat{sound: "meow!!!"}
  fmt.println(a.description())
}
//=> poisonous: true
//=> sound: meow!!!在 main 函数中, 我们创建了一个 animal 接口类型的变量 a。我们为 animal 接口指定了 snake 和 cat 两个实例对象,并使用 println 方法打印 a.description 。
包
我们所有用 go 语言写的代码都是在包含在对应的包中。 main 包是程序执行的入口。go中有很多内置包。 我们使用的一个最常见的包是 fmt 包
「go 的包主要是用来进行大规模编程,并且可以将大型项目分成更小的部分。」
— robert griesemer
包的安装
go get <package-url-github>
// 例子
go get [github.com/satori/go.uuid](https://github.com/satori/go.uuid)我们安装的包保存在环境变量 env 的 gopath  目录下,这是我们的工作目录。 你可以通过我们的工作目录 cd $gopath/pkg 中的 pkg 文件夹查看到下载的包。
创建自定义包
我们从创建 custom_package 文件夹开始:
> mkdir custom_package
> cd custom_package要创建自定义包,首先我们需要创建一个和包名一样的文件夹。假设我们要创建一个 person 包,那么我们得在 custom_package 文件夹里创建一个名为 person 的文件夹。
> mkdir person
> cd person现在我们在该文件夹中,创建一个 person.go 文件。
package person
func description(name string) string {
  return "the person name is: "  name
}
func secretname(name string) string {
  return "do not share"
}我们现在需要安装这个包,这样它才可被引入和使用。我们安装一下:
> go install现在,我们回到 custom_package 文件夹中,创建 main.go 文件。
package main
import(
  "custom_package/person"
  "fmt"
)
func main(){ 
  p := person.description("milap")
  fmt.println(p)
}
// => the person name is: milap至此,我们已经可以引入创建的 person 包了,并且使用包中的 description 方法。注意,我们在包中创建的 secretname 方法是无法被访问的。在 go 语言中,方法名称为非大写字母开头的,即为私有方法。
包文档
go 拥有内建的包文档支持功能。运行如下命令生成文档。
godoc person description它将会为我们的 person 包内部的 description 函数生成文档。查看文档的话只需要使用如下命令启动一个 web 服务器就可以:
godoc -http=":8080"现在去访问这个 url 然后你就可以看到我们刚创建的包文档了。
go 中部分常见的内置包
fmt
fmt 包实现了格式化 i/o 的功能。我们可以使用这个包来打印到标准输出。
json
go 中另外一个有用的常见的包就是 json 包,用来编码和解码 json 数据。 接下来,让我们举一个例子来编码和解码一个 json:
编码
package main
import (
  "fmt"
  "encoding/json"
)
func main(){
  mapa := map[string]int{"apple": 5, "lettuce": 7}
  mapb, _ := json.marshal(mapa)
  fmt.println(string(mapb))
}解码
package main
import (
  "fmt"
  "encoding/json"
)
type response struct {
  pagenumber int `json:"page"`
  fruits []string `json:"fruits"`
}
func main(){
  str := `{"page": 1, "fruits": ["apple", "peach"]}`
  res := response{}
  json.unmarshal([]byte(str), &res)
  fmt.println(res.pagenumber)
}
//=> 1在使用 unmarshal 函数解码 json 字节时,第一个参数是 json 字节,第二个参数是我们希望 json 映射到的响应类型 struct 的地址。 请注意,json:"page" 将 page 的键映射到结构体中的 pagenumber 的键。
错误处理
错误是程序不希望出现的意外结果。假设我们正在对一个外部服务的 api 进行调用。 当然,api 调用可能会成功或者失败。当出现错的时候,go 语言可以识别程序中的错误。 我们来看看这个例子:
resp, err := http.get("http://example.com/")这里的 api 调用返回的错误对象可能存在或者不存在。 我们可以检查错误是否为 nil 值,并相应地处理响应:
package main
import (
  "fmt"
  "net/http"
)
func main(){
  resp, err := http.get("http://example.com/")
  if err != nil {
    fmt.println(err)
    return
  }
  fmt.println(resp)
}从函数返回自定义错误
当我们写一个自己的函数时, 在有些情况下存在错误要处理,我们利用 error 对象返回这些错误:
func increment(n int) (int, error) {
  if n < 0 {
    // 返回一个 error 对象
    return nil, errors.new("math: cannot process negative number")
  }
  return (n  1), nil
}
func main() {
  num := 5
  if inc, err := increment(num); err != nil {
    fmt.printf("failed number: %v, error message: %v", num, err)
  }else {
    fmt.printf("incremented number: %v", inc)
  }
}在 go 中内置的包或我们使用的外部的包都有一个错误处理机制。因为我们调用的任何函数都可能存在错误。而且这些错误永远不应该被忽略,并且总是在我们称之为函数的地方优雅地处理,就像我们在上面的例子中所做的那样。
panic
panic 是在程序执行期间突然遇到,未经处理的异常。 在 go 中,panic 不是处理程序中异常的理想方式。 建议使用 error 对象。 发生 panic 时,程序执行停止。 panic 之后要继续执行的程序就使用 defer。
defer
defer 总是在函数结束时执行。
//go
package main
import "fmt"
func main() {
    f()
    fmt.println("returned normally from f.")
}
func f() {
    defer func() {
        if r := recover(); r != nil {
            fmt.println("recovered in f", r)
        }
    }()
    fmt.println("calling g.")
    g(0)
    fmt.println("returned normally from g.")
}
func g(i int) {
    if i > 3 {
        fmt.println("panicking!")
        panic(fmt.sprintf("%v", i))
    }
    defer fmt.println("defer in g", i)
    fmt.println("printing in g", i)
    g(i  1)
}在上面的例子中,我们使用 panic()来执行程序。 正如你所注意到的一样,有一个延迟语句,它将使程序在程序执行结束时执行该行。 当我们需要在函数结束时执行某些操作时,也可以使用 defer,例如关闭文件。
并发
go 是建立在并发的基础上的。go 中的并发可以通过轻量级线程的 go routine 来实现。
go routine
go routine 是可以与另一个函数并行或并发的函数。 创建 go routine非常简单。 只需在函数前面添加关键字go,我们就可以使它并行执行。 go routine 非常简单非常轻量级,因此我们可以创建数千个例程。 让我们看一个简单的例子:
package main
import (
  "fmt"
  "time"
)
func main() {
  go c()
  fmt.println("i am main")
  time.sleep(time.second * 2)
}
func c() {
  time.sleep(time.second * 2)
  fmt.println("i am concurrent")
}
//=> i am main
//=> i am concurrent就像你在上面的示例中所看到的,函数 c 是一个go routine,它与 go程序的主线程并行执行。 有时我们希望在多个线程之间共享资源。 go 不是将一个线程的变量与另一个线程共享,因为这会增加死锁和资源等待的可能性。 还有另一种在 go routine之间共享资源的方法:通过 go 语言的通道。
通道
我们可以使用通道在两个 go routine之间传递数据。 在创建通道时,必须指定通道接收的数据类型。 让我们创建一个 string 类型的简单通道,如下所示:
c := make(chan string)有了这个通道,我们可以发送 string 类型数据。 我们都可以在此通道中发送和接收数据:
package main
import "fmt"
func main(){
  c := make(chan string)
  go func(){ c <- "hello" }()
  msg := <-c
  fmt.println(msg)
}
//=>"hello"接收方通道将会一直等待发送方向通道发送数据。
单向通道
在某些情况下,我们希望 go routine 通过通道接收数据但不发送数据,反之亦然。 为此,我们还可以创建单向通道。 让我们看一个简单的例子:
package main
import (
 "fmt"
)
func main() {
 ch := make(chan string)
 go sc(ch)
 fmt.println(<-ch)
}
func sc(ch chan<- string) {
 ch <- "hello"
}在上面的例子中,sc 是一个 go routine,它只能向通道发送消息但不能接收消息。
使用 select 为 go routine 处理多个通道
一个进程里面可能有多个通道正在等待。为此,我们可以使用 select 语句。 让我们看一个更清晰的例子:
package main
import (
 "fmt"
 "time"
)
func main() {
 c1 := make(chan string)
 c2 := make(chan string)
 go speed1(c1)
 go speed2(c2)
 fmt.println("the first to arrive is:")
 select {
 case s1 := <-c1:
  fmt.println(s1)
 case s2 := <-c2:
  fmt.println(s2)
 }
}
func speed1(ch chan string) {
 time.sleep(2 * time.second)
 ch <- "speed 1"
}
func speed2(ch chan string) {
 time.sleep(1 * time.second)
 ch <- "speed 2"
}在上面的示例中,main 方法正在等待读取 c1 和 c2 通道的数据。 使用 select case 语句打印出结果,消息会通过通道发送过来,会打印出先发送过来的消息。
缓冲通道
有些情况下,我们需要向一个通道发送多个数据。 你可以为此创建一个缓冲通道。使用缓冲通道, 在缓冲区满之前接受方不会收到任何消息。 让我们看一下这个例子:
package main
import "fmt"
func main(){
  ch := make(chan string, 2)
  ch <- "hello"
  ch <- "world"
  fmt.println(<-ch)
}为什么golang成功?
simplicity… — rob-pike
很好!
我们学习了 go 的一些主要组成部分和特性。
- 变量,数据类型
- 数组 切片和映射
- 函数
- 流程控制语句
- 指针
- 包
- 方法,结构体和接口
- 错误处理
- 并发— go 的线程和通道
恭喜你, 你现在对 go 有了不错的认识.
我效率最高的一天是抛弃 1000 行代码。
— ken thompson
不要止步于此。继续前进。 考虑一个小的应用程序并开始构建它吧。
本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 cc 协议,如果我们的工作有侵犯到您的权益,请及时联系大发黄金版app下载。
原文地址:
 
 
nice!!!
非常好的教程,加油!
nice
这是个什么语法
教程很好
@lovecn
感谢分享。
目前基本语法认识的差不多了,但是感觉不知道该如何进阶。能大概看懂别人的代码,可如果要自己来写,却不知道如何下笔。尴尬
很棒
教程很nine
总结的很好,点赞!
nice
不错支持一下
非常好
nice !!! 就是有错别字 panc => panic ,像 => 向。 :see_no_evil:
nice,教程很棒~