vlambda博客
学习文章列表

5 分钟内学习 Go 语言?

加个“星标”,天天 15 分钟,掌握 Go 语言

上周五的分享预告点赞已破百,成功解锁了今天的文章。

前几天看到优质文章推送,注意到这篇文章(主要是标题吸引了我),文章非常简洁,让我们一起来回顾下相关的基础知识。

原文如下:


基本

作为经典的 “Hello World”,你的第一个 Go 程序非常简单:

先为我们的项目创建 workspace:

$ mkdir hello
$ cd hello

接着创建并初始化 Go module:

go mod init hello

使用你最喜欢的编辑器创建 main.go 文件,输入如下代码,并将文件保存在上面创建的 hello 目录下。

package main

import "fmt"

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

最后编译产生一个二进制文件:

go build

在 workspace 下会生成一个名为 hello 的二进制可执行文件,运行它会输出:

$ ./hello
Hello World!

变量

在 Go 语言里,你可以使用下面两种方法创建变量:

var x int

其他类型,包括 int、int32、int64、float32、float64、bool和 string 等,还有带 u 前缀的无符号整型类型,例如:uint8 与 byte 是同一种类型(ps:可以认为 type 是 uint8 的别称,见源码 type byte = uint8)

或者创建变量时直接赋值,Go 编译器能自动推导变量类型:

x := 42

通过操作符 = 赋值:

x = 1

注意: 大部分时候你都不需要使用 var 关键字,除非创建变量之后再赋值或者需要使用变量的零值。

函数

使用关键字 func 声明函数:

func hello(name string) string {
  return fmt.Sprintf("Hello %s", name)
}

函数如果有返回值,必须显示地指明返回值类型。

函数可以有多个返回值(一般返回错误和值):

func isEven(n int) (bool, error) {
  if n <= 0 {
    return false, fmt.Errorf("error: n must be > 0")
  }
  return n % 2 == 0nil
}

函数是 Go 语言的“一等公民”,因此支持很多函数式编程的很多特性,包括闭包、将函数作为参数传递或者返回等,例如:

func AddN(n int) func(x int) int {
  return func(x int) int {
    return x + n
  }
}

结构体

由于 Go 是一种多范式语言,因此它还通过结构体实现“面向对象”编程,使用关键字 struct 定义结构体:

type Account struct {
  Id      int
  Balance float64
}

结构体字段的定义与变量定义类似,使用操作符 . 访问字段:

account := Account{}
fmt.Printf("Balance: $%0.2f", account.Balance)

方法

结构体可以拥有自己的方法。不像其他语言,Go 语言不支持继承也没有类(我们可以通过结构体嵌套实现“继承”)。

type Account struct {
  id  int
  bal float64
}

func (a *Account) String() string {
  return fmt.Sprintf("Account[%d]: $0.2f", a.id, a.bal)
}

func (a *Account) Deposit(amt flaot64) float64 {
  a.bal += amt
  return a.bal
}

func (a *Account) Withdraw(amt float64) float64 {
  a.bal -= amt
  return a.bal
}

func (a *Account) Balance() float64 {
  return a.bal
}

这些方法称为指针接收者方法,因为第一个参数是指向 Account 结构体的指针。

你还可以像下面这样定义方法:

type Circle struct {
  Radius float64
}

func (c Circle) Area() float64 {
  return 3.14 * c.Radius * c.Radius
}

刚刚定义的是值接收者方法,在方法中对结构体修改不会影响到原结构体,是一份拷贝。

关于结构体、指针接收者方法和值接收者方法可以查看文末推荐阅读了解更多。

数组和 Slice

与其他语言类似,Go 支持数组,不同的是 Go 语言数组是固定长度的(声明长度之后不支持动态变化),与 C 语言数组类似。

可以指定大小和类型创建数组:

xs := [4]int{1234}

大多数场景你会更喜欢使用 slice,有点类似其他语言(比如 Python)中的 list,支持自动扩容。

创建 slice 时可以不用指定大小:

xs := []int{1234}

slice 也可以通过 append() 函数创建:

xs := []int{}
xs = append(xs, 1)
xs = append(xs, 2)
xs = append(xs, 3)

可以通过索引范围 slice:

xs[1]  // 2

你还可以通过“切片”操作获取数组或者 slice 的子集:

ys := xs[1:] // [2, 3]

可以使用 range 关键字迭代 数组或者 slice:

for i, x := range xs {
  fmt.Printf("xs[%d] = %d\n", i, x)
}

map

Go 语言里存储键值对的内置数据结构称为 map(类似其他语言的 hash table、hash map、字典或者关联数组)。

可以通过关键字 map 创建 map,需要分别定义键值的类型,比如定义键类型为 string、值类型为 int 的 map:

var counts map[string]int

也可以像下面这样创建 map:

counts := map[string]int{
  "Apples"4,
  "Oranges"7,
}

类似数组和 slice,也可以通过键访问值:

counts["Apples"]  // 4

像数组和 slice 一样,迭代 map:

for key, value := range counts {
  fmt.Printf("%s: %d\n", key, value)
}

唯一需要注意的是,map 使用之前需要先初始化,操作 nil map 将会导致 error 和 panic:

var counts map[string]int
counts["Apples"] = 7  // This will cause an error and panic!

使用之前必须使用 make() 函数初始化:

counts := make(map[string]int)
counts["Apples"] = 7

关于数组、slice 和 map 更详细的用法可以查看文末推荐阅读掌握。

流控制

for

Go 语言里面只有一种循环结构,在之前的章节里或许你也看到了:

sum := 0
for i := 0; i < 10; i++ {
  sum += i
}

for 循环语句由三部分组成:

  • 初始化语句:第一次迭代之前执行;

  • 条件表达式:每次迭代之前都会执行;

  • post 语句:每次迭代之后都会执行;

如果忽略循环条件,得到的是无限循环:

for {
}
// This line is never reached!

if-else

Go 语言支持 if-else 语句:

N := 42
func Guess(n int) string {
  if n == 42 {
    return "You got it!"
  } else if n < N {
    return "Too low! Try again..."
  } else {
    return "Too high! Try again..."
  }
}        

注意:可以忽略最后一个 else,直接像这样返回 return "Too high~ Try again…",两者是等价的。

switch

Go 语言提供的 switch 语句可以代替 if-else,比如:

func FizzBuzz(n int) string {
  switch n {
  case n % 15 == 0:
    return "FizzBuzz"
  case n % 3 == 0:
    return "Fizz"
  case n % 5 == 0:
    return "Buzz"
  default:
    return fmt.Sprintf("%d", n)
  }
}

defer

关键字 defer 可以定义延迟函数,在调用函数返回之前调用,通常在调用函数执行结束之前自动关闭资源(比如文件句柄、数据库连接等),像下面这样:

package main

import (
  "os"
  "fmt"
)

func Goodbye(name string) {
  fmt.Printf("Goodbye %s", name)
}

func Hello(name string) {
  defer Goodbye(name)
  fmt.Printf("Hello %s", name)
}

func main() {
  user := os.Getenv("User")
  Hello(user)
}

执行会输出:

$ ./hello
Hello prologic
Goodbye prologic

关于 defer 语句的详细使用方法可以阅读文末推荐阅读掌握。

错误处理

Go 语言里 Errors 代表的是值,比如,使用 os.Open 打开一个文件,如果成功的话会返回一个指向文件的指针和 nil;否则返回 nil 指针和具体的错误。

f, err := os.Open("/path/to/file")

类似其他值类型一样检查错误:

f, err := os.Open("/path/to/file")
if err == nil {
  // do something with f
}

Go 语言里,通常的做法是检查函数是否返回非 nil 错误,是的话就直接返回,比如:

func AppendFile(fn, text string) error {
  f, err := os.OpenFile(fn, os.O_CREATE|os.O_APPEND|os.WR_ONLY, 0644)
  if err != nil {
    return fmt.Errorf("error opening file for writing: %w", err)
  }
  defer f.Close()

  if _, err := f.Write([]byte(text)); err != nil {
    return fmt.Errorf("error writing text to fiel: %w", err)
  }

  return nil
}

创建和导入包

Go 支持 module(包管理),允许你创建并导入相关包,在刚开始的基本章节中,我们已经知道如何在启动新项目时使用 go mod init 创建模块。

Go 的包仅仅是一个包含源代码目录:

创建 Go 包的第一步是创建一个目录,就像下面这样:

$ mkdir shapes

使用命令 go mod init 初始化:

$ cd shapes
go mod init github.com/prologic/shapes

现在让我们一起使用你最喜欢的编辑器创建一个 circle.go 模块:

package shapes

type Circle struct {
  Radius float64
}

func (c Circle) String() string {
  return fmt.Sprintf("Circle(%0.2f)", c.Radius)
}

func (c Circle) Area() float64 {
  return 3.14 * c.Radius * c.Radius
}

需要特别注意的是,包里的函数、结构体、变量和常量的首字母必须是大写才是可导出的;否则即使你导入了相关包也无法访问。

现在,在 Git 上创建名为 shapes 的仓库并 push 你的包代码:

$ git init
$ git commit -a -m "Initial Commit"
$ git remote add origin [email protected]:prologic/shapes.git
$ git push -u origin master

这样的话,你就可以使用全路径 github.com/prologic/shapes 导入 shapes 包,编译时会根据路径自动拉取并构建包。

例如:

我们使用 github.com/prologic/shapes 包做一个例子:

$ mkdir hello
go mod init hello

编写 main.go:

package main

import (
  "fmt"

  "github.com/prologic/shapes"
)

func main() {
  c := shapes.Circle{Radius: 5}
  fmt.Printf("Area of %s: %0.2f\n", c, c.Area())
}

编译:

go build

运行二进制文件:

$ ./hello
Area of Circle(5.00): 78.50

Congratulations!

Now you're a Gopher!

ps: 看原文评论,作者反馈接下来还会继续更新原文,我们拭目以待,如果更新的话第一时间给大家分享。


推荐阅读:


结构体、方法:





数组、map 和 slice:








defer:






这是持续翻译的第 13/100 篇优质文章。
如果你有想交流的话题,欢迎留言。



如果我的文章对你有所帮助,点赞、转发都是一种支持!

      
        
        
      
给个[在看],是对四哥最大的支持