Go 语言入门指南:基础语法和常用特性解析
前言
面向用户
- 有C、C++或Java基础
笔者环境
- macos 10.15.7
- Golang 1.18
- GoLand 2022.01
读完本文可以获得
- 掌握Go的安装和配置
- 掌握Go的基本语法
- 熟悉Go的常用特性
一、简介&安装
介绍
Golang(下文简称Go)语言是一种高性能、高并发的语言,语法简单且拥有丰富的标准库以及完善的工具链,同时可以实现跨平台、快速编译和垃圾自动回收。
安装
1. 下载&安装
通过浏览器下载对应系统版本的GO安装包,并根据导引安装
(下列链接任选其一进行下载)
2. 配置环境变量
go的安装过程默认会将GOROOT/bin目录加入到PATH(export PATH=$PATH:/usr/local/go/bin),这使得我们可以在控制台的任意路径使用go命令。
我们仍需要配置几个Go的环境变量,了让Go和第三方工具能够正确地找到源代码、编译器、库文件、可执行文件的位置。
环境变量
-
GOROOT
-
GOROOT 是 Go 的安装根目录,需要在环境变量中手动明确指定。
-
Go命令需要依赖这个变量设置来工作。第三方工具(如IDE)也需要知道GOROOT的路径
-
-
GOPATH
GOPATH是你的代码仓库所在的位置。go tool会在这个路径下查找和安装代码包。
GOPATH 是用来存放你的 Go 项目的工作目录,你的代码、依赖包、测试文件等都会放在这个目录下。如果你使用了 Go modules,那么 GOPATH 就不是必须的了,你可以在任意目录下开发你的代码,Go 会自动管理你的依赖包。但是如果你没有使用 Go modules,或者你想使用一些第三方的工具,比如 go get,那么 GOPATH 还是需要配置的。
-
GOBIN
-
GOBIN是用来指定可执行文件的安装目录的
-
GOBIN默认路径是空的,这意味着当你使用go install命令编译和安装可执行文件时,它们会被放在GOPATH/bin目录中,但有可能当前用户对GOPATH/bin目录没有写权限,因此执行go install命令会出现访问拒绝。
-
通过设置GOBIN的值可以自定义到有写权限的目录。
-
同时将GOBIN添加到系统PATH,可以在任意目录下执行GOBIN目录下的可执行文件,而不用输入完整路径。
-
-
GO111MODULE
- GO111MODULE是一个环境变量,它用来控制Go的模块功能。
- 当它的值为on时,表示启用模块支持,这样Go会忽略GOPATH和vendor目录,只根据go.mod文件来管理依赖。
- go.mod文件是一个文本文件,它记录了当前模块的名称、版本和依赖的其他模块。
-
GOPROXY
-
GOPROXY是一个环境变量,它用来指定Go模块的代理服务器。
-
当你使用go命令获取、编译或运行依赖的模块时,Go会通过GOPROXY指定的代理服务器来下载模块,而不是直接从源码仓库下载。这样可以加速模块的获取,也可以避免一些网络问题或源码仓库的限制。
-
国内代理服务器
# 配置 GOPROXY 环境变量,以下三选一 # 1. 七牛 CDN export GOPROXY=https://goproxy.cn,direct # 2. 阿里云 export GOPROXY=https://mirrors.aliyun.com/goproxy/,direct # 3. 官方 export GOPROXY=https://goproxy.io,direct # 查看当前环境变量配置 go env | grep GOPROXY
-
操作步骤
-
进入terminal,输入
go env查看Go的环境变量信息spaceqi@macbookpro ~ % go env GO111MODULE="on" GOARCH="amd64" GOBIN="" GOCACHE="/Users/spaceqi/Library/Caches/go-build" GOENV="/Users/spaceqi/Library/Application Support/go/env" GOEXE="" GOEXPERIMENT="" GOFLAGS="" GOHOSTARCH="amd64" GOHOSTOS="darwin" GOINSECURE="" GOMODCACHE="/Users/spaceqi/go/pkg/mod" GONOPROXY="" GONOSUMDB="" GOOS="darwin" GOPATH="/Users/spaceqi/go" GOPRIVATE="" GOPROXY="https://goproxy.cn,direct" GOROOT="/usr/local/go" ... -
参数设置
把配置写到profile文件,实现每次登录自动加载环境。
~/.bash_profile文件用于设置环境变量等配置信息,每次打开终端时都会自动加载vi ~/.bash_profileexport GOROOT=/usr/local/go # 默认 export GOPATH=/Users/spaceqi/go # 你的工作区,/Users/spaceqi该目录下具有root权限 export GOBIN=$GOPATH/bin # 重定向可执行文件 export PATH=$GOBIN:$PATH # 加入系统PATH,便于访问GOBIN目录下的可执行文件 export GO111MODULE=on # 启用Go Modules 功能 export GOPROXY=https://goproxy.cn # 使用国内代理#退出文件后,并重新加载文件 source ~/.bash_profile
开发工具
- VSCode编辑器(需要添加插件)
- Golang(IDE)
二、基础语法
2.1 输出
package main
import "fmt"
func main() {
fmt.Println("hello word!")
}
package mainmain包是程序的入口包import "fmt"导入输入输出包,用于向屏幕输入输出字符串,格式化字符串fmt.Println("hello word!")向控制台输出内容
2.2 变量
常见数据类型
- 整数
- 浮点
- 字符串
- 布尔
变量声明
// 方式1 var name [type]=value, type类型一般由编译器自动推导
var nickname string="samuelZhang"
var age=20
// 方式2 name:=value
gender:="male"
flag:=false
常量声明
const name=value
// 常量没有确定的类型,会根据上下午自动推断
运算符&优先级
与C和C++类似,不同点在于
- Go没有前缀自增运算符(++i)
2.3 if-else
与C和C++类似,不同的点在于
- 分支条件不需要使用小括号包裹,手动写上小括号,也会被编译器自动去掉
- if后面必须跟大括号
package main
import "fmt"
func main() {
score := 87
if score >= 90 {
fmt.Println("excellent!")
} else if score >= 80 {
fmt.Println("good")
} else if score >= 70 {
fmt.Println("pass")
} else {
fmt.Println("fail")
}
}
2.4 循环
与C和C++类似,不同的点在于
- 循环条件不需要使用小括号包裹,手动写上小括号,也会被编译器自动去掉
- for后面必须跟大括号
- 没有while、do-while循环
package main
import "fmt"
func main() {
sum := 0
for i := 1; i <= 100; i++ {
sum += i
}
fmt.Println(sum)
}
2.5 Switch
与C和C++类似,不同的点在于
- Switch 条件 不需要手动写上小括号,手动写上小括号,也会被编译器自动去掉
- case中没有break,当一个case语句执行完后,会自动跳出当前switch
- case的表达式可以是任意类型
package main
import (
"fmt"
)
func main() {
grade := 5
switch grade {
case 1:
fmt.Println("大众")
case 2:
fmt.Println("黄金")
case 3:
fmt.Println("铂金")
case 4, 5, 6:
fmt.Println("钻石")
default:
fmt.Println("异常")
}
}
2.6 数组
与C和C++类似,数组是一个连续序列。
在实际开发中很少使用数组,因为是固定长度的,通常使用切片。
package main
import (
"fmt"
)
func main() {
var name [5]string
name[2] = "samuelZhang"
fmt.Println(name, name[2], len(name)) // [ samuelZhang ] samuelZhang 5
score := [5]int{60, 70, 80, 90, 100}
fmt.Println(score) // [60 70 80 90 100]
var twoD [2][3]int
for i := 0; i < 2; i++ {
for j := 0; j < 3; j++ {
twoD[i][j] = i + j
}
}
fmt.Println(twoD) // [[0 1 2] [1 2 3]]
}
2.7 切片
切片(slice)不同于数组,它可以任意更改长度。
通过make可以创建一个切片
- 可以像数组一样取值和赋值
- 也可以使用append进行追加,当容量不够后,会进行扩容,并返回一个新的切片。
- 可以像Python一样进行切片操作,但不支持负索引
package main
import (
"fmt"
)
func main() {
var name = make([]string, 2) // 创建一个长度为2的切片
name[0] = "samuelZhang"
name[1] = "alexWang"
fmt.Println(name, len(name)) // [samuelZhang alexWang] 2
name = append(name, "tonyBao", "amyChen")
fmt.Println(name, len(name)) // [samuelZhang alexWang tonyBao amyChen] 4
fmt.Println(name[1:4]) // [alexWang tonyBao amyChen]
}
2.8 map
map类似Python里的字典和Java里的Hashmap。
Go中的map是无序的(插入、遍历都是随机顺序)
package main
import (
"fmt"
)
func main() {
var score = make(map[string]int) // 使用make创建一个map
score["math"] = 99
score["english"] = 97
score["writing"] = 90
fmt.Println(score, score["math"], len(score)) // map[english:97 math:99 writing:90] 99 3
r, ok := score["reading"] // 声明r,ok两个变量并初始化,r是score["reading"]的值,如果key存在,ok为true,否则为false
fmt.Println(r, ok) // 0 false
r, ok = score["writing"] // 为r,ok两个变量赋值
fmt.Println(r, ok) // 90 true
goods := map[string]string{"brand": "apple", "Model": "X"} // 声明一个map并初始化
goods["price"] = "$999"
fmt.Println(goods, len(goods)) // map[Model:X brand:apple price:$999] 3
}
使用make创建的map和直接使用map字面量创建map的主要区别:
- 使用make: 返回的是指向map的指针
- 使用字面量:返回的是map的值
2.9 range
- 通过使用range来对切片(数组)、map进行遍历
- 对于切片,range会返回两个值,索引和对应位置的值
- 对于map,range会返回两个值,key和对应的value
- 如果不需要某个值,使用下划线“_”进行忽略
package main
import (
"fmt"
)
func main() {
var brand [2]string
brand[0] = "apple"
brand[1] = "huawei"
for index, value := range brand {
fmt.Println("index:", index, ",value:", value)
// index: 0 ,value: apple
// index: 1 ,value: huawei
}
temperature := make([]float64, 5)
copy(temperature, []float64{26, 29.5, 35, 37, 39.9, 41.5}[:]) // 用copy函数把数组复制到切片中
for _, value := range temperature {
fmt.Println("value:", value)
// value: 26
// value: 29.5
// value: 35
// value: 37
// value: 39.9
}
var score = make(map[string]int)
score["math"] = 99
score["english"] = 97
score["writing"] = 90
for key, value := range score {
fmt.Println("key:", key, ",value:", value)
// key: math ,value: 99
// key: english ,value: 97
// key: writing ,value: 90
}
goods := map[string]string{"brand": "apple", "Model": "X"}
for _, value := range goods {
fmt.Println("value:", value)
// value: apple
// value: X
}
}
2.10 函数
Go的函数和C和Python的函数使用起来类似,不同点在于
-
函数的定义,方法参数类型和返回值类型都后置
func name(param1 tyep,parm2 type) (returnTYpe){ return ... } -
函数支持返回多个值,通常返回两个(1个是结果,一个是错误信息)
package main
import (
"fmt"
)
func sum(t []float64) float64 {
res := 0.0
for i := 0; i < len(t); i++ {
res += t[i]
}
return res / float64(len(t))
}
func exists(m map[string]string, k string) (string, bool) {
v, ok := m[k]
return v, ok
}
func main() {
temperature := make([]float64, 5)
copy(temperature, []float64{26, 29.5, 35, 37, 39.9, 41.5}[:])
fmt.Println(sum(temperature))
goods := map[string]string{"brand": "apple", "Model": "X"}
v, ok := exists(goods, "brand")
fmt.Println(v, ok) // apple true
v, ok = exists(goods, "price")
fmt.Println(v, ok) // false
}
2.11 指针
Go的指针和C和C++的指针使用起来类似,不同点在于
- Go的指针操作有限,主要用途是对传入的参数进行修改
package main
import (
"fmt"
)
func swap0(a int, b int) {
a, b = b, a
}
func swap(a *int, b *int) {
*a, *b = *b, *a
}
func main() {
a := 1
b := 5
fmt.Println(a, b) // 1 5
swap0(a, b)
fmt.Println(a, b) // 1 5
fmt.Println("============")
fmt.Println(a, b) // 1 5
swap(&a, &b)
fmt.Println(a, b) // 5 1
}
2.12 结构体
Go的指针和C的结构体使用起来类似
package main
import (
"fmt"
)
type user struct {
id string
name string
gender string
age int
}
func find(u user, id string) (user, bool) {
if u.id == id {
return u, true
} else {
return user{}, false
}
}
func updateAge(u *user, id string, age int) {
if u.id == id {
u.age = age
}
}
func main() {
samuel := user{id: "1001", name: "samuelZhang", gender: "Male"}
samuel.age = 20
fmt.Println(samuel) // {1001 samuelZhang Male 20}
chen := user{} // 表示一个空的user值,它的所有字段都是零值
fmt.Println(chen) // { 0}
alex := user{"1002", "alexWang", "Male", 19} // 如果不显示指定字段名,需要传入所有的字段进行初始化
fmt.Println(alex) // {1002 alexWang Male 19}
u, ok := find(samuel, "1001")
fmt.Println(u, ok) // {1001 samuelZhang Male 20} true
u, ok = find(samuel, "1002")
fmt.Println(u, ok) // { 0} false
updateAge(&samuel, "1001", 21)
fmt.Println(samuel) // {1001 samuelZhang Male 21}
}
2.13 结构体方法
Go的结构体方法和其他面向对象语言的的成员函数使用起来类似,不同点在于
Go的结构体方法在普通方法上的方法名前加上结构体变量
func (name,nameStruct) name(param1 tyep,parm2 type) returnTYpe{
return ...
}
package main
import (
"fmt"
)
type user struct {
id string
name string
gender string
age int
}
func (u user) find(id string) bool {
if u.id == id {
return true
} else {
return false
}
}
func (u *user) updateAge(age int) {
u.age = age
}
func main() {
samuel := user{id: "1001", name: "samuelZhang", gender: "Male"}
samuel.age = 20
fmt.Println(samuel) // {1001 samuelZhang Male 20}
ok := samuel.find("1001")
fmt.Println(ok) // true
ok = samuel.find("1002")
fmt.Println(ok) // false
samuel.updateAge(21)
fmt.Println(samuel) // {1001 samuelZhang Male 21}
}
2.14 错误处理
Go的错误处理使用error对象,常用于函数的第二个返回值。
- 在函数实现的时候,如果出现错误,就可以返回nil和error,如果没有的话就返回结果和nil
- 调用端通过if-else就可以对错误进行处理
package main
import (
"errors"
"fmt"
)
type user struct {
id string
name string
gender string
age int
}
func find(users []user, id string) (*user, error) {
for _, v := range users {
if v.id == id {
return &v, nil
}
}
return nil, errors.New("No find user")
}
func main() {
samuel := user{id: "1001", name: "samuelZhang", gender: "Male"}
samuel.age = 20
fmt.Println(samuel) // {1001 samuelZhang Male 20}
alex := user{"1002", "alexWang", "Male", 19}
users := make([]user, 5)
users[0] = samuel
users[1] = alex
fmt.Println(users) // [{1001 samuelZhang Male 20} {1002 alexWang Male 19} { 0} { 0} { 0}]
v, err := find(users, "1001")
if err != nil {
fmt.Println(err)
return
}
fmt.Println(v.name) // samuelZhang
v, err = find(users, "1003")
if err != nil {
fmt.Println(err) // // No find user
return
}
fmt.Println(v.name)
}
2.15 字符串操作
Go语言的strings标准库有非常多的字符串操作,如join,count,index,replace等
package main
import (
"fmt"
"strings"
)
func main() {
a := "hello"
fmt.Println(strings.Contains(a, "ll")) // true
fmt.Println(strings.Count(a, "l")) // 2
fmt.Println(strings.HasPrefix(a, "he")) // true
fmt.Println(strings.HasSuffix(a, "llo")) // true
fmt.Println(strings.Index(a, "ll")) // 2
fmt.Println(strings.Join([]string{"he", "llo"}, "-")) // he-llo
fmt.Println(strings.Repeat(a, 2)) // hellohello
fmt.Println(strings.Replace(a, "e", "E", -1)) // hEllo
fmt.Println(strings.Split("a-b-c", "-")) // [a b c]
fmt.Println(strings.ToLower(a)) // hello
fmt.Println(strings.ToUpper(a)) // HELLO
fmt.Println(len(a)) // 5
b := "你好"
fmt.Println(len(b)) // 6
}
2.16 字符串格式化
Go的字符串格式化和C的字符串格式化使用起来类似,不同点在于
- 使用%v来打印任意变量,
- 使用%+v来打印详细变量的结果
- 使用%#v来打印更详细变量的结果
package main
import "fmt"
type point struct {
x, y int
}
func main() {
s := "hello"
n := 123
p := point{1, 2}
fmt.Println(s, n) // hello 123
fmt.Println(p) // {1 2}
fmt.Printf("s=%v\n", s) // s=hello
fmt.Printf("n=%v\n", n) // n=123
fmt.Printf("p=%v\n", p) // p={1 2}
fmt.Printf("p=%+v\n", p) // p={x:1 y:2}
fmt.Printf("p=%#v\n", p) // p=main.point{x:1, y:2}
f := 3.141592653
fmt.Println(f) // 3.141592653
fmt.Printf("%.2f\n", f) // 3.14
}
2.17 JSON处理
Go语言中的JSON操作非常简单
- json.Marshal可以将结构体中以大写字母开头的字段进行序列化
- json.MarshalIndent可以将结构体中以大写字母开头的字段进行序列化,并且指定前缀和缩进来美化输出
- json.Unmarshal可以将一个JSON格式的字节数组反序列化成结构体变量
package main
import (
"encoding/json"
"fmt"
)
type user struct {
id string
Name string
Gender string
Age int
Hobby []string
}
func main() {
samuel := user{id: "1001", Name: "samuelZhang", Gender: "Male"}
samuel.Age = 20
samuel.Hobby = make([]string, 2)
copy(samuel.Hobby, []string{"Jogging", "snorkeling"})
fmt.Println(samuel) // {1001 samuelZhang Male 20 [Jogging snorkeling]}
buf, err := json.Marshal(samuel) // [序列化]将samuel转换为JSON格式的字节数组,并存储在buf中。
if err != nil {
panic(err)
}
fmt.Println(buf) // [123 34 78 97 109 101 34 58 ...]
fmt.Println(string(buf)) // {"Name":"samuelZhang","Gender":"Male","Age":20,"Hobby":["Jogging","snorkeling"]}
buf, err = json.MarshalIndent(samuel, "", "\t") // [序列化]将samuel转换为JSON格式的字节数组且指定前缀和缩进来美化输出,并存储在buf中。
if err != nil {
panic(err)
}
fmt.Println(buf) // [123 10 9 34 78 97 109 101 34 58 ...]
fmt.Println(string(buf))
// {
// "Name": "samuelZhang",
// "Gender": "Male",
// "Age": 20,
// "Hobby": [
// "Jogging",
// "snorkeling"
// ]
// }
var toSamuel user
err = json.Unmarshal(buf, &toSamuel) // [反序列化]把一个JSON格式的字节数组buf转换为user类型的变量toSamuel,并存储在toSamuel的地址中
if err != nil {
panic(err)
}
fmt.Printf("toSamuel: %v\n", toSamuel) // toSamuel: { samuelZhang Male 20 [Jogging snorkeling]}
}
2.18 时间处理
Go语言的time标准库有非常多的日期和时间操作,如对日期进行处理,例如获取当前时间、格式化时间、解析时间、计算时间差等。
package main
import (
"fmt"
"time"
)
func main() {
now := time.Now()
fmt.Println(now) // 2023-07-26 17:43:44.778416 +0800 CST m=+0.000124636
t := time.Date(2022, 3, 27, 1, 25, 36, 0, time.UTC)
t2 := time.Date(2022, 3, 27, 2, 30, 36, 0, time.UTC)
fmt.Println(t) // 2022-03-27 01:25:36 +0000 UTC
fmt.Println(t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute()) // 2022 March 27 1 25
fmt.Println(t.Format("2006-01-02 15:04:05")) // 2022-03-27 01:25:36
diff := t2.Sub(t)
fmt.Println(diff) // 1h5m0s
fmt.Println(diff.Minutes(), diff.Seconds()) // 65 3900
t3, err := time.Parse("2006-01-02 15:04:05", "2022-03-27 01:25:36")
if err != nil {
panic(err)
}
fmt.Println(t3 == t) // true
fmt.Println(now.Unix()) // 1690364624
}
2.19 数字解析
Go语言中提供strconv标准库对字符串和数字进行相互转换,例如strconv.Atoi()、strconv.Itoa()、strconv.ParseInt()、strconv.FormatInt()等函数。你可以使用这些函数来处理不同进制和位数的数字,以及不同格式的字符串。
package main
import (
"fmt"
"strconv"
)
func main() {
f, _ := strconv.ParseFloat("1.234", 64)
fmt.Println(f) // 1.234
n, _ := strconv.ParseInt("111", 10, 64)
fmt.Println(n) // 111
n, _ = strconv.ParseInt("0x1000", 0, 64)
fmt.Println(n) // 4096
n2, _ := strconv.Atoi("123")
fmt.Println(n2) // 123
n2, err := strconv.Atoi("AAA")
fmt.Println(n2, err) // 0 strconv.Atoi: parsing "AAA": invalid syntax
n3 := strconv.FormatInt(1024, 16)
fmt.Println(n3) // 400
}
2.20 环境变量
Go语言中提供os和os/exec包是Go语言中的两个标准库,它们提供了操作系统相关的功能接口,例如文件、环境变量、进程、命令等。
package main
import (
"fmt"
"os"
"os/exec"
)
func main() {
// go run main.go a b c d
fmt.Println(os.Args) // 显示命令行参数,包括程序名和传入的参数a b c d [/var/folders/qk/lq98x2wj1rl6vlghzvwms_h40000gn/T/go-build1407077754/b001/exe/main.go a b c d]
fmt.Println(os.Getenv("PATH")) // 显示环境变量PATH的值,即可执行文件的搜索路径 /usr/local/go/bin...
fmt.Println(os.Setenv("AA", "BB")) // 设置环境变量AA的值为BB,并返回nil表示成功
buf, err := exec.Command("grep", "127.0.0.1", "/etc/hosts").CombinedOutput() // 创建一个命令对象,用于在/etc/hosts文件中查找127.0.0.1这个字符串
if err != nil {
panic(err)
}
fmt.Println(string(buf)) // 127.0.0.1 localhost
}
三、常用特性解析
- goroutine - 轻量级线程支持大规模的并发处理。
- channel - goroutine间的通信和数据传递渠道,支持基于CSP的并发。
- interface - 接口,提供了非常灵活的组件解耦能力。
- struct - 轻量可组合的数据结构,是对面向对象编程的支持。
- range - 遍历数据结构的便捷方式,可以用于数组、切片、map、channel等。
- select - 监听channel操作的选择器,支持非阻塞的channel通信。
- defer - 函数退出前执行代码的机制,常用于释放资源。
- error - 用error类型表示错误对象,与异常有区别。
- slice - 动态数组切片,灵活传递和处理序列数据。
- map - 快速查找的哈希表实现,支持各种键值对数据。