Go 语言入门指南:基础语法和常用特性解析
Go语言(Golang)由Google开发,以简洁高效的语法和强大的并发支持为特色。它通过goroutine和channel轻松实现并发操作,自动垃圾回收简化了内存管理,减少内存泄漏的风险。作为静态类型语言,Go在编译阶段进行类型检查,提升了代码的安全性和性能。Go拥有丰富的标准库,支持跨平台编译,生成的单一二进制文件不依赖额外库,部署方便。接口和结构体代替了传统的类和继承,提供灵活的面向对象设计。错误处理上,Go采用返回值处理,代码更健壮。快速编译和高效的内存分配让它在高性能场景中表现出色,这些特性使Go特别适合服务器端开发、微服务和分布式系统。
之前没有接触过Go,这次借助青训营的资源顺便学习一下Go,这篇文章内容主要是Go语言的环境配置和Go语言的常见基础用法。
1. Vscode配置Go开发环境
安装Go语言
下载地址:Go下载 - Go语言中文网 - Golang中文社区 (studygolang.com)
windows直接点这个推荐下载包就行。设置好安装路径,安装过程一路往下点就行。安装完成后,打开cmd输入
go env查看是否安装成功
配置代理
打开Goproxy.cn,照它的步骤操作就行,这一步是为了方便导包,因为有可能包在国外,用代理更快。
Vscode里面配置Go
安装好Go之后,可以在vscode里面配置了,先安装Go的插件;
创建一个新项目,vscode里面打开一个空文件夹,文件夹可以命名为hello world,然后在terminal中输入
go mod init 包的名字,很多时候项目都会在github上有相应的库,那么可以将包的名字命名为github.com/你的github用户名/项目名字,比如go mod init github.com/findrito/hello ,
点击回车后会生成一个go.mod的文件, 里面包含对项目的说明。
然后可以创建一个go文件,输入hello world的代码,运行这个文件,看go文件是否正确编译并输出结果
2. Go语言常见基础语法
变量
变量的定义方式有很多种,具体看注释。要明白的是Go是一种强类型的语言。
/*
`package main` 是一个特别的包声明,用于定义一个可独立执行的程序。
Go语言中的包(package)是代码的组织单元,所有Go代码都必须属于某个包。
*/
package main
//导包
import (
"fmt" //包含println这些函数的包
"math"
)
func main() {
//var是用来声明变量的关键字
var a = "initial" //方式1:编译器会自动推断类型为`string`
var b, c int = 1, 2 //方式2:可以显式地声明类型为int,但是类型是后置的
var d = true //布尔类型
var e float64 //显式地声明类型为浮点
f := float32(e) //方式3:":=",这种应该用的最多,编译器会自动推断类型
g := a + "foo" //字符串可以通过+号直接拼接
fmt.Println(a, b, c, d, e, f) // initial 1 2 true 0 0
fmt.Println(g) // initialapple
const s string = "constant" //常量,把var改成const就行
const h = 500000000
const i = 3e20 / h
fmt.Println(s, h, i, math.Sin(h), math.Sin(i))
}
if else
package main
import "fmt"
func main() {
//go里面的if后面不要加括号,而且大括号不能省略
if 7%2 == 0 {
fmt.Println("7 is even")
} else {
fmt.Println("7 is odd")
}
if 8%4 == 0 {
fmt.Println("8 is divisible by 4")
}
//多条件if
if num := 9; num < 0 {
fmt.Println(num, "is negative")
} else if num < 10 {
fmt.Println(num, "has 1 digit")
} else {
fmt.Println(num, "has multiple digits")
}
}
for循环
Go里面只有for循环,没有while
package main
import "fmt"
func main() {
i := 1
for {
fmt.Println("loop")
break
}
//经典for循环
for j := 7; j < 9; j++ {
fmt.Println(j)
}
for n := 0; n < 5; n++ {
if n%2 == 0 {
continue
}
fmt.Println(n)
}
//有点像while
for i <= 3 {
fmt.Println(i)
i = i + 1
}
}
switch case
和其他语言不同的是,go里面的switch case不需要加break,他运行完相应的case会自动出去.case后面也能跟多种数据类型或者条件语句。
package main
import (
"fmt"
"time"
)
func main() {
a := 2
switch a {
case 1:
fmt.Println("one")
case 2:
fmt.Println("two")
case 3:
fmt.Println("three")
case 4, 5:
fmt.Println("four or five")
default:
fmt.Println("other")
}
t := time.Now()
switch {
case t.Hour() < 12:
fmt.Println("It's before noon")
default:
fmt.Println("It's after noon")
}
}
数组
数组长度固定,用法和其它语言基本相同,只是要注意数组定义时候的写法,用的很少,一般都使用切片(slice),可以理解为动态数组。
package main
import "fmt"
func main() {
var a [5]int
a[4] = 100
fmt.Println("get:", a[2])
fmt.Println("len:", len(a))
b := [5]int{1, 2, 3, 4, 5}
fmt.Println(b)
var twoD [2][3]int //二维数组
for i := 0; i < 2; i++ {
for j := 0; j < 3; j++ {
twoD[i][j] = i + j
}
}
fmt.Println("2d: ", twoD)
}
slice(切片)
slice需要注意它的append操作,Go切片的底层实现是基于数组的。当你向切片追加元素时,如果原数组有足够的容量,append 会直接在原数组上追加,并返回一个指向同一数组的新切片。但如果原数组容量不足,append 会分配一个新的、更大的数组,将原切片的元素复制到新数组,再将新元素追加到新数组中。此时,append 返回的新切片引用的就是新数组,而不是原来的数组。
因此,为了保持 s 指向更新后的切片,通常赋值回去。
package main
import "fmt"
func main() {
s := make([]string, 3) //用make来创建切片
s[0] = "a"
s[1] = "b"
s[2] = "c"
fmt.Println("get:", s[2]) // c
fmt.Println("len:", len(s)) // 3
s = append(s, "d") //使用append追加元素
s = append(s, "e", "f")
fmt.Println(s) // [a b c d e f]
c := make([]string, len(s)) //也能规定切片的长度
copy(c, s) //两个slice之间拷贝数据
fmt.Println(c) // [a b c d e f]
fmt.Println(s[2:5]) // [c d e] 切片操作和python一样
fmt.Println(s[:5]) // [a b c d e]
fmt.Println(s[2:]) // [c d e f]
good := []string{"g", "o", "o", "d"}
fmt.Println(good) // [g o o d]
}
map
map非常重要,使用的很多,map里面是无序的
package main
import "fmt"
func main() {
m := make(map[string]int) //make创建空map,第一个是key,第二个是value
m["one"] = 1
m["two"] = 2
fmt.Println(m) // map[one:1 two:2]
fmt.Println(len(m)) // 2
fmt.Println(m["one"]) // 1
fmt.Println(m["unknow"]) // 0
r, ok := m["unknow"] //ok:表示布尔值,用于判断键是否存在。如果键存在,则ok为true;如果键不存在,则ok为false。
fmt.Println(r, ok) // 0 false ,r为0
delete(m, "one")
m2 := map[string]int{"one": 1, "two": 2}
var m3 = map[string]int{"one": 1, "two": 2}
fmt.Println(m2, m3)
}
range
用range可以快速遍历一个数组或者map,遍历数组时,会返回两个值,第一个是索引,第二个是对应得值,遍历map时返回k和v
package main
import "fmt"
func main() {
nums := []int{2, 3, 4}
sum := 0
for i, num := range nums {
sum += num
if num == 2 {
fmt.Println("index:", i, "num:", num) // index: 0 num: 2
}
}
fmt.Println(sum) // 9
m := map[string]string{"a": "A", "b": "B"}
for k, v := range m {
fmt.Println(k, v) // b 8; a A
}
for k := range m {
fmt.Println("key", k) // key a; key b
}
}
函数
注意数据类型是后置的,
package main
import "fmt"
func add(a int, b int) int {
return a + b
}
//如果多个参数是相同的类型,可以省略前面参数的类型,只在最后一个参数后写一次类型即可。
func add2(a, b int) int {
return a + b
}
//一般go里面得函数会返回两个值
func exists(m map[string]string, k string) (v string, ok bool) {
v, ok = m[k]
return v, ok
}
func main() {
res := add(1, 2)
fmt.Println(res) // 3
v, ok := exists(map[string]string{"a": "A"}, "a")
fmt.Println(v, ok) // A True
}
指针
和c++类似
package main
import "fmt"
func add2(n int) {
n += 2
}
func add2ptr(n *int) {
*n += 2
}
func main() {
n := 5
add2(n)
fmt.Println(n) // 5
add2ptr(&n)
fmt.Println(n) // 7
}
结构体
package main
import "fmt"
// 定义 user 结构体,包含用户名和密码字段
type user struct {
name string // 用户名
password string // 密码
}
func main() {
// 使用不同的方式创建 user 实例
a := user{name: "wang", password: "1024"} // 指定字段名称的方式初始化
b := user{"wang", "1024"} // 按顺序初始化,不指定字段名称
c := user{name: "wang"} // 只初始化部分字段,password 字段使用默认零值 ""
c.password = "1024" // 后续再对 password 字段赋值
var d user // 声明一个 user 类型变量,使用零值初始化
d.name = "wang" // 给字段赋值
d.password = "1024"
// 输出所有 user 实例
fmt.Println(a, b, c, d) // {wang 1024} {wang 1024} {wang 1024} {wang 1024}
// 调用 checkPassword 和 checkPassword2 函数进行密码检查
fmt.Println(checkPassword(a, "haha")) // false,传值调用
fmt.Println(checkPassword2(&a, "haha")) // false,传指针调用
}
// 检查给定的密码是否与 user 实例的密码匹配
// 传递的是 user 的值拷贝
func checkPassword(u user, password string) bool {
return u.password == password
}
// 检查给定的密码是否与 user 实例的密码匹配
// 传递的是 user 的指针,避免拷贝
func checkPassword2(u *user, password string) bool {
return u.password == password
}
结构体方法
package main
import "fmt"
type user struct {
name string
password string
}
//注意这里的写法,将一个普通函数变成一个类成员函数
func (u user) checkPassword(password string) bool {
return u.password == password
}
//带指针得结构体方法,能对指针进行修改
func (u *user) resetPassword(password string) {
u.password = password
}
func main() {
a := user{name: "wang", password: "1024"}
a.resetPassword("2048")
fmt.Println(a.checkPassword("2048")) // true
}
错误处理
这和java里面得异常不太一样,go是使用返回值来处理错误信息
package main
import (
"errors"
"fmt"
)
type user struct {
name string
password string
}
//如果这个函数可能出现错误得话,就在参数里面加一个error
func findUser(users []user, name string) (v *user, err error) {
for _, u := range users {
if u.name == name {
return &u, nil //没有错误
}
}
return nil, errors.New("not found") //有错误,nil表示空值或者0值
}
func main() {
u, err := findUser([]user{{"wang", "1024"}}, "wang") //两个变量接收返回值
if err != nil {
fmt.Println(err)
return
}
fmt.Println(u.name) // wang
if u, err := findUser([]user{{"wang", "1024"}}, "li"); err != nil {
fmt.Println(err) // not found
return
} else {
fmt.Println(u.name)
}
}
字符串
package main
import (
"fmt"
"strings"
)
func main() {
// 定义字符串变量 a
a := "hello"
// strings.Contains:检查字符串是否包含子串 "ll",返回布尔值 !!!!!!!!!
fmt.Println(strings.Contains(a, "ll")) // true
// strings.Count:统计字符串中子串 "l" 的出现次数
fmt.Println(strings.Count(a, "l")) // 2
// strings.HasPrefix:检查字符串是否以前缀 "he" 开头
fmt.Println(strings.HasPrefix(a, "he")) // true
// strings.HasSuffix:检查字符串是否以后缀 "llo" 结尾
fmt.Println(strings.HasSuffix(a, "llo")) // true
// strings.Index:返回子串 "ll" 在字符串中的起始位置索引,如果不存在返回 -1 !!!!!!
fmt.Println(strings.Index(a, "ll")) // 2
// strings.Join:将字符串切片用 "-" 连接成一个字符串 !!!!!
fmt.Println(strings.Join([]string{"he", "llo"}, "-")) // he-llo
// strings.Repeat:重复字符串 a 两次
fmt.Println(strings.Repeat(a, 2)) // hellohello
// strings.Replace:将字符串中的 "e" 替换为 "E",-1 表示替换所有匹配项
fmt.Println(strings.Replace(a, "e", "E", -1)) // hEllo
// strings.Split:将字符串按 "-" 分割成切片
fmt.Println(strings.Split("a-b-c", "-")) // [a b c]
// strings.ToLower:将字符串转换为小写
fmt.Println(strings.ToLower(a)) // hello
// strings.ToUpper:将字符串转换为大写
fmt.Println(strings.ToUpper(a)) // HELLO
// len:返回字符串的字节长度 !!!!!!!
fmt.Println(len(a)) // 5
// 定义包含中文字符的字符串变量 b,一个中文可能对应多个字符
b := "你好"
// len:返回字符串 b 的字节长度(中文字符占多个字节)
fmt.Println(len(b)) // 6
}
字符串格式化
需要注意的是go不需要区别d%或者f%,因为他全部用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
}
json处理
json和结构体之间得转换,具体看注释
package main
import (
"encoding/json"
"fmt"
)
//要想和json转换必须变量名首字母大写,想得到小写可以像这里age一样后面添加一个tag
type userInfo struct {
Name string
Age int `json:"age"`
Hobby []string
}
func main() {
a := userInfo{Name: "wang", Age: 18, Hobby: []string{"Golang", "TypeScript"}}
buf, err := json.Marshal(a) //注意这个返回的是byte数组,不是字符串
if err != nil {
panic(err)
}
fmt.Println(buf) // [123 34 78 97...]
fmt.Println(string(buf)) // {"Name":"wang","age":18,"Hobby":["Golang","TypeScript"]}
buf, err = json.MarshalIndent(a, "", "\t")
if err != nil {
panic(err)
}
fmt.Println(string(buf)) //注意要先转成string,否则会打印出16进制得编码
var b userInfo
err = json.Unmarshal(buf, &b)
if err != nil {
panic(err)
}
fmt.Printf("%#v\n", b) // main.userInfo{Name:"wang", Age:18, Hobby:[]string{"Golang", "TypeScript"}}
}
时间处理
package main
import (
"fmt"
"time"
)
func main() {
now := time.Now()
fmt.Println(now) // 2022-03-27 18:04:59.433297 +0800 CST m=+0.000087933
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
// 使用自定义格式将时间对象 t 格式化为字符串
// "2006-01-02 15:04:05" 是 Go 中规定的时间格式模板(固定的日期)
fmt.Println(t.Format("2006-01-02 15:04:05")) // 2022-03-27 01:25:36
diff := t2.Sub(t) //计算 t2 和 t 之间的时间差
fmt.Println(diff) // 1h5m0s
fmt.Println(diff.Minutes(), diff.Seconds()) // 65 3900
// 解析时间字符串并转为时间对象 t3
// "2006-01-02 15:04:05" 是格式模板
t3, err := time.Parse("2006-01-02 15:04:05", "2022-03-27 01:25:36")
if err != nil {
panic(err) // 如果解析出错,则触发 panic
}
fmt.Println(t3 == t) // true
fmt.Println(now.Unix()) // 1648738080 方便跨平台
}
字符串和数字之间的转换
package main
import (
"fmt"
"strconv"
)
func main() {
f, _ := strconv.ParseFloat("1.234", 64)
fmt.Println(f) // 1.234
n, _ := strconv.ParseInt("111", 10, 64) //第二个参数代表进制,64表示精度,第二个返回值是错误
fmt.Println(n) // 111
n, _ = strconv.ParseInt("0x1000", 0, 64)
fmt.Println(n) // 4096
n2, _ := strconv.Atoi("123") //还有itoA
fmt.Println(n2) // 123
n2, err := strconv.Atoi("AAA") //不是数字符串
fmt.Println(n2, err) // 0 strconv.Atoi: parsing "AAA": invalid syntax
}
进程信息
package main
import (
"fmt"
"os"
"os/exec"
)
func main() {
// 输出命令行参数
// os.Args 是一个字符串切片,包含运行程序时传入的命令行参数
// 例如:go run example/20-env/main.go a b c d
fmt.Println(os.Args) // 输出:[/var/folders/.../main a b c d]
// 获取并输出系统环境变量 "PATH" 的值
fmt.Println(os.Getenv("PATH")) // 输出系统 PATH 变量内容
// 设置一个新的环境变量 "AA" 为 "BB"
// os.Setenv 设置环境变量,返回 error 类型的错误对象,若设置成功则返回 nil
fmt.Println(os.Setenv("AA", "BB"))
// 使用 exec.Command 执行外部命令 "grep"
// 在 "/etc/hosts" 文件中查找包含 "127.0.0.1" 的行
// CombinedOutput 执行命令并获取标准输出和标准错误的组合结果
buf, err := exec.Command("grep", "127.0.0.1", "/etc/hosts").CombinedOutput()
if err != nil {
panic(err) // 如果执行命令出错,则触发 panic,终止程序
}
// 输出 grep 命令的执行结果
fmt.Println(string(buf)) // 例如输出:127.0.0.1 localhost
}
总结
感觉Go和其它语言还是有不少类似的地方,要注意的就是数据类型后置,可能刚开始不太适应。