Golang 新手教程:入门速成指南

您所在的位置:网站首页 golangorg Golang 新手教程:入门速成指南

Golang 新手教程:入门速成指南

#Golang 新手教程:入门速成指南 | 来源: 网络整理| 查看: 265

file

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

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

— 关于 go

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

然而,Go却是只有一种正确的方法来解决问题的语言。

这节省了开发人员的时间,并使大型代码库易于维护。 Go中没有地图和过滤器等“富有表现力”的功能。

“如果你有增加表现力的功能,通常会增加费用”

— Rob Pike

file

最近发布了 golang 的新 logo: blog.golang.org/go-brand

入门

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: 您可以使用 https://play.golang.org 来运行本文提到的代码。

变量

变量在 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。

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

file

图片地址: blog.golang.org/go-slices-usage-an...

通过使用 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 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 // 例子 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 http://localhost:8080/pkg/ 然后你就可以看到我们刚创建的包文档了。

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 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


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3