作者:Milap Neupane

链接:https://medium.freecodecamp.org/learning-go-from-zero-to-hero-d2a3223b3d86


开始

  想到刚开始学习Go的时候,也是不清不楚地本着拿来能用就行的心态,没有系统学习,导致学习过程中踩坑无数。今天发现一篇文章写的很好,Go语言的特征讲得很细,翻译给需要的初学Go的同学。

前言

  让我们从Go(或者称为Golang)的一个小介绍开始。Go由Google工程师Robert Griesemer,Rob Pike和Ken Thompson设计。它是一种静态类型的编译语言。第一个版本于2012年3月作为开源发布。

  “ Go是一种开源编程语言,可以轻松构建简单,可靠,高效的软件 ”

  在许多语言中,有许多方法可以解决给定的问题。程序员可以花很多时间思考解决问题的最佳方法。

  另一方面,Go相信更少的功能 — 只有一种正确的方法来解决问题。

  这节省了开发人员的时间,并使大型代码库易于维护。

“ 功能越多,成本越高 ” — Rob Pike

入门

  Go由package组成。名为main的包告诉Go编译器被编译为可执行文件,而不是作为library被引用。它是应用程序的主入口。主包定义为:

package main

  让我们在Go工作区中创建一个简单的hello world示例。

工作区

  Go中的工作空间由环境变量定义,称为 GOPATH

  你的任何代码都将写在工作区内。Go将搜索GOPATH目录中的任何包,或者GOROOT在安装Go时默认设置的目录。注:GOROOT 是安装go的路径。

  设置GOPATH为所需的目录。现在,让我们将其添加到文件夹中~/workspace

# 定义GOPATH目录
export GOPATH=~/workspace
# 进入工作区目录
cd ~/workspace

  在我们刚刚创建的工作区文件夹中创建main.go文件并写入以下代码。

Hello World!

package main

import (
 "fmt"
)

func main(){
  fmt.Println("Hello World!")
}

  在上面的示例中,Go中的官方包fmt实现了格式化I/O的函数。

  我们使用import 关键字在Go中导入fmt包。func main是代码执行的主入口。Printlnfmt包内的一个函数,我们用它来打印“hello world”。

  让我们先运行这个文件看看。可以通过两种方式运行Go命令。众所周知,Go是编译型语言,所以我们要在执行之前编译它。

go build main.go

  这将创建一个二进制可执行文件main,现在我们可以运行:

./main
# Hello World!

  还有另一种更简单的方式来运行程序。使用go run命令有助于抽象编译步骤。只需运行以下命令即可编译并执行该程序。

go run main.go
# Hello World!

  注意: 你可以在 https://play.golang.org/ 练习以上代码

变量

  Go中的变量是明确声明的。Go是一种静态类型语言。这意味着在变量声明时检查变量类型。

  声明变量方法如下:

var a int

  在这种情况下,a将自动设默认值为0。

  使用以下语法声明变量并赋值:

var a = 1

  这里变量自动指定为int类型。我们可以使用变量声明的简写方式:

message := "hello world"

  还可以在同一行中声明多个变量:

var b, c int = 2, 3

数据类型

  与任何其他编程语言一样,Go支持各种不同的数据结构。让我们继续学习:

数字,字符串和布尔值

  Go支持的整数类型包括

int, int8, int16, int32, int64,uint, uint8, uint16, uint32, uint64, uintptr ...

  字符串类型存储一系列字节。它用关键字string声明

  布尔值使用关键字bool声明

  Go还支持复数类型数据类型,可以使用complex64complex128声明。

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)

Array, Slice, 和 Map

  数组array是相同数据类型的元素序列。数组具有在声明中定义的固定长度,因此不能进行扩展。数组声明为:

var a [5]int

  数组也可以是多维的。我们可以使用以下格式创建多维数组:

var multiD [2][3]int

  数组的值在运行时无法更改,也不提供获取子数组的能力。为此,Go有一个名为切片slices的数据类型。

  切片slices存储一系列元素,可以随时扩展。切片声明类似于数组声明

  未定义容量的slice:

var b []int

  这将创建一个零容量和零长度的切片。

  切片也可以定义容量和长度。我们可以使用以下语法:

numbers := make([]int,5,10)

  上面定义的切片的初始长度为5,容量为10。

  切片是数组的抽象。切片使用数组作为底层结构。切片包含三个组件:容量,长度和指向底层数组的指针,如下图所示:

    图片源: https://blog.golang.org/go-slices-usage-and-internals

  通过使用append()copy()函数可以增加切片的容量。append函数可以为数组的末尾增加值,并在需要时增加容量:

numbers = append(numbers, 1, 2, 3, 4)

  增加切片容量的另一种方法是使用copy()函数。只需创建另一个具有更大容量的切片,并将原始切片复制到新创建的切片:

// 创建新的切片
number2 := make([]int, 15)
// 将原始切片复制到新创建的切片
copy(number2, number)

  我们可以创建切片的子切片。这可以使用以下命令完成:

// 声明一个内含{1,2,3,4}共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中的数据类型,它将键映射到值。我们可以使用以下代码定义键值对映射:

var m map[string]int

  我们声明了一个名为mmap类型的变量,其键是string类型,值是int类型。我们可以轻松地将键和值添加到map中:

// 添加 key/value
m['clearity'] = 2
m['simplicity'] = 3
// 打印输出 value
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, "小于0")
} else if num < 10 {
	fmt.Println(num, "是一位数")
} else {
	fmt.Println(num, "是多位数")
}

switch case

  switch-case有助于组织多个条件语句。以下实例表示一个简单的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

  在将结构作为参数传递或者为已定义类型声明方法时,通常首选指针。

  1. 传递值时,实际复制的值意味着更多的内存
  2. 传递指针后,函数更改的值将反映在方法/函数调用者中

  例如:

func increment(i *int) {
	*i++
}
func main() {
    i := 10
    increment(&i)
    fmt.Println(i)
}
// 11

  注意: 在博客中尝试示例代码时,不要忘记将其包含在main包中,并在需要时导入fmt或其他包,如上面第一个main.go示例所示。