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 == 0, nil
}
函数是 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{1, 2, 3, 4}
大多数场景你会更喜欢使用 slice,有点类似其他语言(比如 Python)中的 list,支持自动扩容。
创建 slice 时可以不用指定大小:
xs := []int{1, 2, 3, 4}
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 篇优质文章。
如果你有想交流的话题,欢迎留言。
如果我的文章对你有所帮助,点赞、转发都是一种支持!
给个[在看],是对四哥最大的支持