Go 语言入门指南:基础语法和常用特性解析

70 阅读12分钟

Go 语言入门指南:基础语法和常用特性解析

Go语言(Golang)由Google开发,以简洁高效的语法和强大的并发支持为特色。它通过goroutine和channel轻松实现并发操作,自动垃圾回收简化了内存管理,减少内存泄漏的风险。作为静态类型语言,Go在编译阶段进行类型检查,提升了代码的安全性和性能。Go拥有丰富的标准库,支持跨平台编译,生成的单一二进制文件不依赖额外库,部署方便。接口和结构体代替了传统的类和继承,提供灵活的面向对象设计。错误处理上,Go采用返回值处理,代码更健壮。快速编译和高效的内存分配让它在高性能场景中表现出色,这些特性使Go特别适合服务器端开发、微服务和分布式系统。

之前没有接触过Go,这次借助青训营的资源顺便学习一下Go,这篇文章内容主要是Go语言的环境配置和Go语言的常见基础用法。

1. Vscode配置Go开发环境

安装Go语言

下载地址:Go下载 - Go语言中文网 - Golang中文社区 (studygolang.com)

image.png windows直接点这个推荐下载包就行。设置好安装路径,安装过程一路往下点就行。安装完成后,打开cmd输入go env查看是否安装成功

image.png

配置代理

打开Goproxy.cn,照它的步骤操作就行,这一步是为了方便导包,因为有可能包在国外,用代理更快。

image.png

Vscode里面配置Go

安装好Go之后,可以在vscode里面配置了,先安装Go的插件;

image.png 创建一个新项目,vscode里面打开一个空文件夹,文件夹可以命名为hello world,然后在terminal中输入 go mod init 包的名字,很多时候项目都会在github上有相应的库,那么可以将包的名字命名为github.com/你的github用户名/项目名字,比如go mod init github.com/findrito/hello , 点击回车后会生成一个go.mod的文件, 里面包含对项目的说明。

然后可以创建一个go文件,输入hello world的代码,运行这个文件,看go文件是否正确编译并输出结果

image.png

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和其它语言还是有不少类似的地方,要注意的就是数据类型后置,可能刚开始不太适应。