链接:https://medium.freecodecamp.org/learning-go-from-zero-to-hero-d2a3223b3d86
函数
main.go
包中定义的func 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 不是一个完全面向对象的语言,但是它的结构,接口和方法,和面向对象有异曲同工之妙。
结构
结构struct
是不同类型字段的集合。结构用于将数据分组在一起。例如,如果我们想要对Person
类型的数据进行分组,我们会定义一个人的属性,其中可能包括姓名,年龄,性别。可以使用以下语法定义结构:
type person struct {
name string
age int
gender string
}
在定义了人类型结构的情况下,现在让我们创建一个person
:
//方式 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
方法
方法Method
是具有接收器的特殊类型的功能*。*接收器既可以是值,也可以是指针。让我们创建一个名为 describe 的方法,它具有我们在上面的例子中创建的接收器类型:
package main
import "fmt"
// struct defination
type person struct {
name string
age int
gender string
}
// method 定义
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 接口interface
是方法的集合。接口有助于将类型的属性组合在一起。我们以接口animal
为例:
type animal interface {
description() string
}
这里的animal
是一种接口interface
类型。现在我们创建两种不同类型的动物来实现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
函数中,我们创建了一个a
类型为animal
的变量。我们为动物分配蛇和猫类型,并使用 Println 打印a.description()
。由于我们以不同的方式实现了两种类型(猫和蛇)中描述的方法,我们得到了不同动物的属性。
包
我们在 Go 中编写所有代码。main
包是程序执行的入口点。Go 中有很多内置包Package
。我们一直使用的就是著名的fmt包。
在主要机制中使用 Go 的包进行大规模编程,可以将大型项目分成更小的部分。
安装包
go get <package-url-github>
// 示例
go get github.com/satori/go.uuid
我们安装的软件包保存在GOPATH env
中,这是我们的工作目录。通过我们的工作目录中的 pkg 文件夹进入包cd $GOPATH/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 中,以大写字母
开头的方法名称将是私有的private
。
包文档
Go 内置了对包文档的支持。运行以下命令以生成文档:
godoc person Description
这将为我们的包人员生成 Description 函数的文档。要查看文档,请使用以下命令运行 Web 服务器:
godoc -http=":8080"
现在转到http://localhost:8080/pkg
查看我们刚创建的包的文档。
Go 的内置包
fmt
该包实现了格式化的 I/O 功能,我们已经使用该包打印出 stdout。
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"
映射页面键是结构中的PageNumber
键。
错误处理
Error
错误Error
是程序不被希望出现的意外的结果。假设我们正在对外部服务进行 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)
}
自定义错误
当我们编写自己的函数时,有些情况下我们会遇到错误。可以在错误对象的帮助下返回这些错误:
func Increment(n int) (int, error) {
if n < 0 {
// return error object
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
不是处理程序中异常的理想方式。建议使用错误对象。发生panic
时程序会停止执行。panic
之后执行的事件就是defer
。
Defer
defer
总是在函数结束时执行。
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
语句,它将使程序在程序执行结束时执行该行。当我们需要在函数结束时执行某些操作时,也可以使用defer
,例如关闭文件。
并发
Go 是建立在并发性的基础上的。Go 中的并发可以通过轻量级线程的 Go 例程来实现。
协程 (go routine)
go 协程routine
是可以与另一个函数并行或同时运行的函数。创建 go 协程非常简单。只需在函数前面添加关键字go
,我们就可以使它并行执行。go 协程非常轻量级,因此我们可以创建数千个协程。让我们看一个简单的例子:
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 协程,它与主 Go 线程并行执行。有时我们希望在多个线程之间共享资源。Go 更倾向于一个线程的变量不与另一个线程共享,因为这会增加死锁和资源等待的可能性。还有另一种在 Go 协程之间共享资源的方法:管道Channels
。
管道(channel)
我们可以使用通道在两个 Go 协程之间传递数据。在创建channel
时,必须指定channel
接收的数据类型。让我们创建一个字符串类型的简单channel
,如下所示:
c := make(chan string)
使用此channel
,我们可以发送字符串类型数据。可以在此频道中发送
和接收
数据:
package main
import "fmt"
func main(){
c := make(chan string)
go func(){ c <- "hello" }()
msg := <-c
fmt.Println(msg)
}
//"hello"
接收方等待发送方向channel
发送数据。
单向通道
在某些情况下,我们希望 Go 协程通过channel
接收数据但不发送数据,反之亦然。为此,我们还可以创建单向通道。让我们看一个简单的例子:
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 协程,它只能向通道发送消息但不能接收消息。
使用 select 为 Go 例程组织多个通道
函数可能有多个通道正在等待执行。为此,我们可以使用 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
语句打印主函数,消息从管道发送,无论它先收到哪个。
缓冲通道
有些情况下我们需要向管道发送多个数据。可以为此创建缓冲通道buffered channel
。使用缓冲通道,接收器在缓冲区已满之前不会收到消息。我们来看看这个例子:
package main
import "fmt"
func main(){
ch := make(chan string, 2)
ch <- "hello"
ch <- "world"
fmt.Println(<-ch)
}
结尾
为什么 Golang 会成功?
简单… — Rob-pike
目前为止我们已经了解了 Go 的一些主要组件和功能:
- 变量,数据类型
- Array、Slices 和 Map
- 函数
- 循环和条件语句
- 指针
- 包
- 结构、方法和接口
- 错误处理
- 并发 - Go routine 和 channel
恭喜你,你现在对 Go 有了不错的认识。
抛弃了 1000 行代码的那天是我最富有成效的日子之一。
— Ken Thompson
不要止步于此,继续前进。在大脑中思考一个小规模的应用程序并开始构建。