Go语言自定义错误类型和错误处理

Go 程序使用 error 类型来表示错误状态。
error 类型是一个内建接口(interface)

type error interface {
    Error() string
}

你可以在自己的函数定义返回 error 类型,通常我们将函数或方法的最后一个返回值类型定义为 error

func Sqrt(f float64) (float64, error) {
    // example 1
    if f < 0 {
        return 0, errors.New("math: square root of negative number")
    }
    // example 2
    if f < 0 {
        return 0, fmt.Errorf("math: square root of negative number %g", f)
    }
}

在标准包内所有可能出错的API都会返回一个 error 变量

f, err := os.Open("/test.txt")
if err != nil {
    fmt.Println(err)
    return
}

你可以自定义 Error

// errorString is a trivial implementation of error.
type errorString struct {
    s string
}
func (e *errorString) Error() string {
    return e.s
}
// errorString is a trivial implementation of error.
type errorString struct {
    s string
}
func (e *errorString) Error() string {
    return e.s
}

通常函数会返回一个 error 值,调用的它的代码应当判断这个错误是否等于 nil 来进行错误处理。

i, err := strconv.Atoi("42")
if err != nil {
    fmt.Printf("couldn't convert number: %v\n", err)
    return
}
fmt.Println("Converted integer:", i)

error 为 nil 时表示成功;非 nil 的 error 表示失败。

自定义错误

go 语言的 error 是一个 interface ,这意味着我们可以通过自定义错误类型的方式去获取更多相关的错误信息

type PathError struct {
    Op   string
    Path string
    Err  error
}
func (e *PathError) Error() string { return e.Op + " " + e.Path + ": " + e.Err.Error() }

下面是一个计算圆面积的程序,如果半径的负数则返回 error

package main
import (
    "errors"
    "fmt"
    "math"
)
func circleArea(radius float64) (float64, error) {
    if radius < 0 {
        return 0, errors.New("Area calculation failed, radius is less than zero")
    }
    return math.Pi * radius * radius, nil
}
func main() {
    radius := -20.0
    area, err := circleArea(radius)
    if err != nil {
        fmt.Println(err)
        return
    }
    fmt.Printf("Area of circle %0.2f", area)
}

上面代码得到的结果将是 Area calculation failed, radius is less than zero

Errorf 函数

package main
import (
    "fmt"
    "math"
)
func circleArea(radius float64) (float64, error) {
    if radius < 0 {
        return 0, fmt.Errorf("Area calculation failed, radius %0.2f is less than zero", radius)
    }
    return math.Pi * radius * radius, nil
}
func main() {
    radius := -20.0
    area, err := circleArea(radius)
    if err != nil {
        fmt.Println(err)
        return
    }
    fmt.Printf("Area of circle %0.2f", area)
}

运行结果 Area calculation failed, radius -20.00 is less than zero

自定义 error 结构类型

我们定义一个结构

type areaError struct {
    err    string    // 实际错误信息
    radius float64   // 半径
}

实现错误信息

func (e *areaError) Error() string {
    return fmt.Sprintf("radius %0.2f: %s", e.radius, e.err)
}

我们编写 main 函数和 circleArea 函数来完成程序。

func circleArea(radius float64) (float64, error) {
    if radius < 0 {
        return 0, &areaError{"radius is negative", radius}
    }
    return math.Pi * radius * radius, nil
}
func main() {
    radius := -20.0
    area, err := circleArea(radius)
    if err != nil {
        if err, ok := err.(*areaError); ok {
            fmt.Printf("Radius %0.2f is less than zero", err.radius)
            return
        }
        fmt.Println(err)
        return
    }
    fmt.Printf("Area of rectangle1 %0.2f", area)
}

运行结果 Radius -20.00 is less than zero

使用类型断言的方式

完整程序

package main
import "fmt"
type areaError struct {
    err    string  //error description
    length float64 //length which caused the error
    width  float64 //width which caused the error
}
func (e *areaError) Error() string {
    return e.err
}
func (e *areaError) lengthNegative() bool {
    return e.length < 0
}
func (e *areaError) widthNegative() bool {
    return e.width < 0
}
func rectArea(length, width float64) (float64, error) {
    err := ""
    if length < 0 {
        err += "length is less than zero"
    }
    if width < 0 {
        if err == "" {
            err = "width is less than zero"
        } else {
            err += ", width is less than zero"
        }
    }
    if err != "" {
        return 0, &areaError{err, length, width}
    }
    return length * width, nil
}
func main() {
    length, width := -5.0, -9.0
    area, err := rectArea(length, width)
    if err != nil {
        if err, ok := err.(*areaError); ok {
            if err.lengthNegative() {
                fmt.Printf("error: length %0.2f is less than zero\n", err.length)
            }
            if err.widthNegative() {
                fmt.Printf("error: width %0.2f is less than zero\n", err.width)
            }
            return
        }
        fmt.Println(err)
        return
    }
    fmt.Println("area of rect", area)
}

运行结果

error: length -5.00 is less than zero
error: width -9.00 is less than zero

减少代码重复

Go语言中的错误处理采用的与C类似的检查返回值的方式,这样使得错误处理代码冗长且重复,例如如下所示

err := doStuff1()
if err != nil {
    //handle error...
}
err = doStuff2()
if err != nil {
    //handle error...
}
err = doStuff3()
if err != nil {
    //handle error...
}

通常,我们采用复用检测函数来减少类似代码,例如下面的 checkerror 的方式

func checkError(err error) {
    if err != nil {
        fmt.Println("Error is ", err)
        os.Exit(-1)
    }
}
func foo() {
    err := doStuff1()
    checkError(err)
    err = doStuff2()
    checkError(err)
    err = doStuff3()
    checkError(err)
}

Go语言入门学习(七)——接口


在Go语言中,一个接口类型总是代表着某一种类型(即所有实现它的类型)的行为。一个接口类型的声明通常会包含关键字 type 、类型名称、关键字 interface 以及由花括号包裹的若干方法声明。示例如下:

type Animal interface {
    Grow()
    Move(string) string
}

注意,接口类型中的方法声明是普通的方法声明的简化形式。它们只包括方法名称、参数声明列表和结果声明列表。其中的参数的名称和结果的名称都可以被省略。不过,出于文档化的目的,我还是建议大家在这里写上它们。因此,Move方法的声明至少应该是这样的:

Move(new string) (old string)

如果一个数据类型所拥有的方法集合中包含了某一个接口类型中的所有方法声明的实现,那么就可以说这个数据类型实现了那个接口类型。所谓实现一个接口中的方法是指,具有与该方法相同的声明并且添加了实现部分(由花括号包裹的若干条语句)。相同的方法声明意味着完全一致的名称、参数类型列表和结果类型列表。其中,参数类型列表即为参数声明列表中除去参数名称的部分。一致的参数类型列表意味着其长度以及顺序的完全相同。对于结果类型列表也是如此。

/* 这是接口 */
type Animal interface {
    Grow()
    Move(string) string
}
/* 定义结构体 */
type Cat struct {
    Name     string
    Age      uint8
    Location string
}
/* 实现接口方法 */
func (cat *Cat) Grow() {
    /* 方法实现 */
    cat.Age++
}
func (cat *Cat) Move(new string) string {
    old := cat.Location
    cat.Location = new
    return old
}

如上代码所示,Cat 类型就实现了接口 Animal。

类型断言

好了,现在我们已经认为 Cat 类型实现了 Animal 接口。但是Go语言编译器是否也这样认为呢?这显然需要一种显式的判定方法。在Go语言中,这种判定可以用类型断言来实现。不过,在这里,我们是不能在一个非接口类型的值上应用类型断言来判定它是否属于某一个接口类型的。我们必须先把前者转换成空接口类型的值。这又涉及到了Go语言的类型转换。
Go语言的类型转换规则定义了是否能够以及怎样可以把一个类型的值转换另一个类型的值。另一方面,所谓空接口类型即是不包含任何方法声明的接口类型,用 interface{} 表示,常简称为空接口。正因为空接口的定义,Go语言中的包含预定义的任何数据类型都可以被看做是空接口的实现。我们可以直接使用类型转换表达式把一个 Person 类型转换成空接口类型的值,就像这样:

p := Person{"Robert", "Male", 33, "Beijing"}
v := interface{}(&p)

请注意第二行。在类型字面量后跟由圆括号包裹的值(或能够代表它的变量、常量或表达式)就构成了一个类型转换表达式,意为将后者转换为前者类型的值。在这里,我们把表达式&p的求值结果转换成了一个空接口类型的值,并由变量 v 代表。注意,表达式&p(&是取址操作符)的求值结果是一个 Person 类型的值,即p的指针。
在这之后,我们就可以在v上应用类型断言了,即:

h, ok := v.(Animal)

类型断言表达式v.(Animal)的求值结果可以有两个。第一个结果是被转换后的那个目标类型(这里是Animal)的值,而第二个结果则是转换操作成功与否的标志。显然,ok代表了一个 bool 类型的值。它也是这里判定实现关系的重要依据。
就如上面实现接口的代码中,我们使用类型断言去判定 Cat 是否实现了 Animal :

myCat := Cat{"Little C", 2, "In the house"}
animal, ok := interface{}(&myCat).(Animal)

得到的结果是:

true, &{Little C 2 In the house}

Go语言入门学习(六)——集合map、范围range

集合(map)

Map 是一种无序的键值对的集合,使用 hash 表来实现的。
Map 是一种集合,所以我们可以像迭代数组和切片那样迭代它。不过,Map 是无序的,我们无法决定它的返回顺序。
可以使用内建函数 make 也可以使用 map 关键字来定义 Map:

/* 声明变量,默认 map 是 nil */
var map_variable map[key_data_type]value_data_type
/* 使用 make 函数 */
map_variable := make(map[key_data_type]value_data_type)

使用 map 例子:

package main
import "fmt"
func main() {
    /* 创建map */
    countryCapitalMap := map[string]string{"France": "Paris", "Italy": "Rome", "Japan": "Tokyo", "India": "New delhi"}
    fmt.Println("原始地图")
    /* 打印地图 */
    for country := range countryCapitalMap {
        fmt.Println(country, "首都是", countryCapitalMap [ country ])
    }
    /*删除元素*/ delete(countryCapitalMap, "France")
    fmt.Println("法国条目被删除")
    fmt.Println("删除元素后地图")
    /*打印地图*/
    for country := range countryCapitalMap {
        fmt.Println(country, "首都是", countryCapitalMap [ country ])
    }
}

运行结果:

原始地图
India 首都是 New delhi
France 首都是 Paris
Italy 首都是 Rome
Japan 首都是 Tokyo
法国条目被删除
删除元素后地图
Italy 首都是 Rome
Japan 首都是 Tokyo
India 首都是 New delhi

范围(range)

Go 语言中 range 关键字用于 for 循环中迭代数组(array)、切片(slice)、通道(channel)或集合(map)的元素。在数组和切片中它返回元素的索引和索引对应的值,在集合中返回 key-value 对的 key 值。
实例:

package main
import "fmt"
func main() {
    //这是我们使用range去求一个slice的和。使用数组跟这个很类似
    nums := []int{2, 3, 4}
    sum := 0
    for _, num := range nums {
        sum += num
    }
    fmt.Println("sum:", sum)
    //在数组上使用range将传入index和值两个变量。上面那个例子我们不需要使用该元素的序号,所以我们使用空白符"_"省略了。有时侯我们确实需要知道它的索引。
    for i, num := range nums {
        if num == 3 {
            fmt.Println("index:", i)
        }
    }
    //range也可以用在map的键值对上。
    kvs := map[string]string{"a": "apple", "b": "banana"}
    for k, v := range kvs {
        fmt.Printf("%s -> %s\n", k, v)
    }
    //range也可以用来枚举Unicode字符串。第一个参数是字符的索引,第二个是字符(Unicode的值)本身。
    for i, c := range "go" {
        fmt.Println(i, c)
    }
}

结果:

sum: 9
index: 1
a -> apple
b -> banana
0 103
1 111

参考

Go语言入门学习(五)——结构体与切片(slice)

结构体

结构体是由一系列具有相同类型或不同类型的数据构成的数据集合。
定义一个结构体:

type struct_variable_type struct {
   member definition;
   member definition;
   ...
   member definition;
}

一旦定义了结构体类型,它就能用于变量的声明,语法格式如下:

variable_name := structure_variable_type {value1, value2...valuen}

使用结构体:

package main
import "fmt"
type Person struct {
   name string
   age int
   sex string
   blog string
}
func main() {
   var zrdn Person        /* 声明 zrdn 为 Person 类型 */
   /* book 1 描述 */
   zrdn.name = "昨日当年"
   zrdn.age = 18
   zrdn.sex = "male"
   zrdn.blog = "http://zuoridangnian.com"
   /* 打印 zrdn 信息 */
   fmt.Printf( "zrdn'name : %s\n", zrdn.name)
   fmt.Printf( "zrdn'age : %s\n", zrdn.age)
   fmt.Printf( "zrdn'sex : %s\n", zrdn.sex)
   fmt.Printf( "zrdn'blog : %d\n", zrdn.blog)
}

感觉一个结构体和一个对象有点像,里面的数据就像是对应的属性。

切片(Slice)

Go的切片类型为处理同类型数据序列提供一个方便而高效的方式。 切片有些类似于其他语言(例如JS)中的数组,但是有一些不同寻常的特性。
在Go语言中,数组是固定长度的,而切片的长度不是固定的,可以追加元素。
声明一个切片:

var identifier []type

例如定义一个字符串切片

letters := []string{"a", "b", "c", "d"}

或使用内置的 make() 函数来创建切片

slice1 := make([]T, length, capacity)
  • T 为 type,代表被创建的切片元素的类型
  • length 是数组的长度并且也是切片的初始长度。
  • capacity 为可选参数,说明切片可达到的最大数。

例如:

s := make([]byte, 5)

可以使用内置函数 len 和 cap 获取切片的长度和容量信息。

len(s) == 5
cap(s) == 5

初始化的切片的值为 nil 。对于切片的零值, len 和 cap 都将返回0。

len 和 cap

  • 切片是可索引的, len() 方法用于获取其长度
  • cap() 方法可以测量切片最长可以达到多少

例子:

package main
import "fmt"
func main() {
   var numbers = make([]int,3,5)
   printSlice(numbers)
}
func printSlice(x []int){
   fmt.Printf("len=%d cap=%d slice=%v\n",len(x),cap(x),x)
}

结果:

len=3 cap=5 slice=[0 0 0]

Go语言入门学习(四)——指针

内存地址

在Go语言中,可以使用&符来表示一个变量在内存中的地址。比如

package main
import "fmt"
func main() {
   var a int = 10
   fmt.Printf("Address of a variable: %x\n", &a  )
}

执行上面的代码,就会输出变量 a 的内存地址

Address of a variable: c042052058

指针

指针是一个指向变量在内存中实际地址的对象。和其它变量一样,要先声明一个指针变量才能使用。

声明一个指针变量:

var var-name *var-type
  • var-type 是指针的基类型,它必须是Go的数据类型,例如 int , float32
  • var-name 是指针变量的名称

使用指针

可以在指针变量前加 * 输出其所存储的变量的值

package main
import "fmt"
func main() {
   var a int= 20   /* 声明实际变量 */
   var ip *int        /* 声明指针变量 */
   ip = &a  /* 将实际变量的存储地址赋给指针变量 */
   fmt.Printf("a 变量的地址是: %x\n", &a  )
   /* 指针变量存储的指针地址 */
   fmt.Printf("ip 变量储存的指针地址: %x\n", ip )
   /* 使用指针访问值 */
   fmt.Printf("*ip 变量的值: %d\n", *ip )
}

上面的代码执行的结果是:

a 变量的地址是: 20818a220
ip 变量储存的指针地址: 20818a220
*ip 变量的值: 20

nil指针

nil 是未分配地址的指针变量的值。
值为 nil 的指针变量被称为 nil 指针。

package main
import "fmt"
func main() {
   var  ptr *int
   fmt.Printf("The value of ptr is : %x\n", ptr  )
}

上面的代码执行的结果是:

The value of ptr is 0

同时,可以使用 ptr == nil 来判断一个指针变量是否为 nil 指针。

引用传递

Go 语言允许向函数传递指针,只需要在函数定义的参数上设置为指针类型即可。向这种函数传递参数称为引用传递,引用传递在函数中进行的修改将影响到实际参数。

package main
import "fmt"
func main() {
   /* 定义局部变量 */
   var a int = 100
   var b int= 200
   fmt.Printf("交换前 a 的值 : %d\n", a )
   fmt.Printf("交换前 b 的值 : %d\n", b )
   /* 调用函数用于交换值
   * &a 指向 a 变量的地址
   * &b 指向 b 变量的地址
   */
   swap(&a, &b);
   fmt.Printf("交换后 a 的值 : %d\n", a )
   fmt.Printf("交换后 b 的值 : %d\n", b )
}
func swap(x *int, y *int) {
   var temp int
   temp = *x    /* 保存 x 地址的值 */
   *x = *y      /* 将 y 赋值给 x */
   *y = temp    /* 将 temp 赋值给 y */
}```
以上实例允许输出结果为:
```go
交换前 a 的值 : 100
交换前 b 的值 : 200
交换后 a 的值 : 200
交换后 b 的值 : 100

Go语言入门学习(三)——闭包

在计算机科学中,闭包(英语:Closure),又称词法闭包(Lexical Closure)或函数闭包(function closures),是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。

Go 语言支持匿名函数,可作为闭包。匿名函数是一个”内联”语句或表达式。匿名函数的优越性在于可以直接使用函数内的变量,不必申明。
闭包也是函数内部通过引用的方式调用外部变量的一种方式。

func f(i int) func() int {
    sum := 0
    return func() int {
        sum+=i
        fmt.Println(sum)
        return 0
     }
}

上面的代码中定义了一个f(i int)函数,该函数接收一个参数i,并返回一个函数,返回的函数就是匿名函数,在这个返回的函数中引用了上一个函数中的参数i。
执行结果

a1 := f(5)
a1()
a1()
// 返回
// 5
// 10
  • 在每一次执行的时候,sum都会进行增加,这是内函数对外部函数做出的修改,称为引用。

  • 即便函数执行完毕,这个变量也不会被销毁,也可以称为“内存泄漏”

Go语言入门教程学习(二)——函数

函数是基本的代码块,用于执行一个任务。
Go 语言最少有个main()函数。
你可以通过函数来划分不同功能,逻辑上每个函数执行的是指定的任务。
函数声明告诉了编译器函数的名称,返回类型,和参数。
Go 语言标准库提供了多种可动用的内置的函数。例如len()函数可以接受不同类型参数并返回该类型的长度。如果我们传入的是字符串则返回字符串的长度,如果传入的是数字,则返回数组中包含的函数个数。

函数定义

Go 语言函数定义格式如下:

func function_name( [parameter list] ) [return_types] {
   /* 函数体 */
}

函数定义解析:

  • func:函数由 func 开始声明
  • function_name:函数名称,函数名和参数列表一起构成了函数签名。
  • [parameter list]:参数列表,参数就像一个占位符,当函数被调用时,你可以将值传递给参数,这个值被称为实际参数。参数列表指定的是参数类型、顺序、及参数个数。参数是可选的,也就是说函数也可以不包含参数。
  • return_types:返回类型,函数返回一列值。return_types 是该列值的数据类型。有些功能不需要返回值,这种情况下 return_types 不是必须的。
  • 函数体:函数定义的代码集合。

例子:
下面为一个返回两数最大值的函数:

/* 函数返回两个数的最大值 */
func max(num1, num2 int) int {
   /* 声明局部变量 */
   result int
   if (num1 > num2) {
      result = num1
   } else {
      result = num2
   }
   return result
}

返回值

Go语言函数可以返回多个值:

package main
import "fmt"
func swap(x, y string) (string, string) {
   return y, x
}
func main() {
   a, b := swap("Mahesh", "Kumar")
   fmt.Println(a, b)
}

函数参数

函数如果使用参数,该变量可称为函数的形参。
形参就像定义在函数体内的局部变量。
调用函数,可以通过两种方式来传递参数:

传递类型描述
值传递值传递是指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数。
引用传递引用传递是指在调用函数时将实际参数的地址传递到函数中,那么在函数中对参数所进行的修改,将影响到实际参数。

值传递

值传递是指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数。
默认情况下,Go 语言使用的是值传递,即在调用过程中不会影响到实际参数。
下面我们定义一个交换两个值的函数,并在mian()函数中调用它,然后在最后再返回这两个值

package main
import "fmt"
func main() {
   /* 定义局部变量 */
   var a int = 100
   var b int = 200
   fmt.Printf("交换前 a 的值为 : %d\n", a )
   fmt.Printf("交换前 b 的值为 : %d\n", b )
   /* 通过调用函数来交换值 */
   swap(a, b)
   fmt.Printf("交换后 a 的值 : %d\n", a )
   fmt.Printf("交换后 b 的值 : %d\n", b )
}
/* 定义相互交换值的函数 */
func swap(x, y int) int {
   var temp int
   temp = x /* 保存 x 的值 */
   x = y    /* 将 y 值赋给 x */
   y = temp /* 将 temp 值赋给 y*/
   return temp;
}

以下代码执行结果为:

交换前 a 的值为 : 100
交换前 b 的值为 : 200
交换后 a 的值 : 100
交换后 b 的值 : 200

我们看到,执行swap()函数后,两值并没有发生变换。

引用传递

引用传递是指在调用函数时将实际参数的地址传递到函数中,那么在函数中对参数所进行的修改,将影响到实际参数。
引用传递将指针参数传递到函数内,以下是交换函数 swap() 使用了引用传递:

/* 定义交换值函数*/
func swap(x *int, y *int) {
   var temp int
   temp = *x    /* 保持 x 地址上的值 */
   *x = *y      /* 将 y 值赋给 x */
   *y = temp    /* 将 temp 值赋给 y */
}

以下我们通过使用引用传递来调用 swap() 函数:

package main
import "fmt"
func main() {
   /* 定义局部变量 */
   var a int = 100
   var b int= 200
   fmt.Printf("交换前,a 的值 : %d\n", a )
   fmt.Printf("交换前,b 的值 : %d\n", b )
   /* 调用 swap() 函数
   * &a 指向 a 指针,a 变量的地址
   * &b 指向 b 指针,b 变量的地址
   */
   swap(&a, &b)
   fmt.Printf("交换后,a 的值 : %d\n", a )
   fmt.Printf("交换后,b 的值 : %d\n", b )
}
func swap(x *int, y *int) {
   var temp int
   temp = *x    /* 保存 x 地址上的值 */
   *x = *y      /* 将 y 值赋给 x */
   *y = temp    /* 将 temp 值赋给 y */
}

以上代码执行结果为:

交换前,a 的值 : 100
交换前,b 的值 : 200
交换后,a 的值 : 200
交换后,b 的值 : 100

将函数作为值传递

Go 语言可以很灵活的创建函数,并作为值使用。以下实例中我们在定义的函数中初始化一个变量,该函数仅仅是为了使用内置函数 math.sqrt() ,实例为:

package main
import (
   "fmt"
   "math"
)
func main(){
   /* 声明函数变量 */
   getSquareRoot := func(x float64) float64 {
      return math.Sqrt(x)
   }
   /* 使用函数 */
   fmt.Println(getSquareRoot(9))
}

以上代码执行结果为:

3

函数方法

Go 语言中同时有函数和方法。一个方法就是一个包含了接受者的函数,接受者可以是命名类型或者结构体类型的一个值或者是一个指针。所有给定类型的方法属于该类型的方法集。语法格式如下:

func (variable_name variable_data_type) function_name() [return_type]{
   /* 函数体*/
}

下面定义一个结构体类型和该类型的一个方法:

package main
import (
   "fmt"
)
/* 定义函数 */
type Circle struct {
  radius float64
}
func main() {
  var c1 Circle
  c1.radius = 10.00
  fmt.Println("Area of Circle(c1) = ", c1.getArea())
}
//该 method 属于 Circle 类型对象中的方法
func (c Circle) getArea() float64 {
  //c.radius 即为 Circle 类型对象中的属性
  return 3.14 * c.radius * c.radius
}

以上代码执行结果为:

Area of Circle(c1)  =  314

参考

在Debian中安装Golang

通过APT安装

这是最简单直接的方式,通过自带的APT包管理器进行安装

sudo apt-get install golang

这样安装还会自动检测并安装所需依赖,但是安装的并不是最新版本,例如我现在的最新版本是golang 1.10.2 而通过APT安装的则是1.7版本

下载编译好的二进制文件

这是第二种比较省心的方法,你可以在Golang官网或者国内中文站(推荐)下载到各个平台已经编译好的文件,记住源代码很小,只有十几兆大小,而编译好的Linux平台的有一百多兆。
下载好压缩包后,解压得到go文件夹,将go文件夹移动到/usr/local文件夹内(也可以是其它文件夹)

tar zxvf go1.10.1.linux-amd64.tar.gz
sudo cp -rf go /usr/local

然后将bin文件夹添加至用户环境变量
编辑~/.profile文件,加入如下内容

export PATH=$PATH:/usr/local/go/bin

你放到其它路径,记得修改路径

然后更新~/.profile文件即可

source ~/.profile

编译安装

这是我不太推荐的方式之一,因为我觉得没必要选择去编译安装Golang,但是我这里还是简述一下步骤

依赖关系

执行一遍命令确保你已经安装好依赖

sudo apt-get install bison ed gawk gcc libc6-dev make

接着下载最新的Golang源码,你可以在官网或者中文网或者Github下载到

编译

下载好后解压,进入解压文件夹内的src文件夹,里面有一个all.bash文件,直接执行即可

sudo ./all.bash

环境变量

然后同样安装第二种安装方式那样配置好环境变量