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

76 阅读10分钟

Go 语言是一门由 Google 开发的开源编程语言,它以简洁高效的设计理念和强大的并发支持而著称。本篇笔记介绍了 Go 语言的基础语法和常用特性。

一、Hello World

了解一门语言都要先从最简单的程序开始。Go 语言的 Hello World 程序非常简单:

package main

import "fmt"

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

以上的代码通过 package main 定义了一个包名为 main 的包,而且包名只能为main,这是 Go 语言执行程序的入口。import "fmt" 导入了 fmt 包,该包提供了格式化输入输出的函数。func main() 是程序的入口函数,在程序执行时会首先执行这个函数。

二、变量和常量

Go 语言是静态类型语言,声明变量时需要指定变量的类型。变量的声明格式如下:

var name type

即:var 变量名 变量类型

例如:

var age int
var name string

Go 语言还支持短变量声明,它可以自动推断变量的数据类型。注意该方式只能用在函数内部:

name := "Tom"

常量的声明方式如下:

const pi = 3.1415926

三、数据类型

Go 语言支持多种基本数据类型,包括整数类型、浮点数类型、布尔类型、字符串类型和数组类型等。还支持引用类,包括指针类型、切片、映射、管道等。注意,在其他语言中,数组往往是引用类型。

// 整数类型
var num1 int   // 有符号整数
var num2 uint  // 无符号整数

// 浮点数类型
var f1 float32
var f2 float64

// 布尔类型
var isTrue bool

// 字符串类型
var str string

// 声明一个长度为 5 的整数数组
var arr [5]int
// 给数组元素赋值
arr[0] = 1 

1.字符串

两种声明方式:

str1 := "Hello, World!"
str2 := `Hello, World!`

获取字符串的长度:

str := "Hello, World!"
length := len(str)
fmt.Println(length) // 输出:13

访问字符串中的字符:

在 Golang 中,字符串底层是以字节数组的形式存储的,因此可以通过索引访问其中的字符,但要注意字符串可能包含多字节字符,索引访问可能导致乱码。

str := "Hello, 世界"
fmt.Println(str[0]) // 输出:72('H' 的 ASCII 码)
fmt.Println(str[7]) // 输出:228('世' 的 UTF-8 编码的第一个字节)

字符串连接

可以使用 + 运算符将多个字符串连接成一个新的字符串:

str1 := "Hello, "
str2 := "World!"
result := str1 + str2
fmt.Println(result) // 输出:Hello, World!

字符串切片

使用切片可以获取字符串的一部分子串:

str := "Hello, World!"
subStr := str[0:5]
fmt.Println(subStr) // 输出:Hello

字符串遍历

可以使用 for range 结构遍历字符串,其中每次迭代会返回字符的 Unicode 编码点:

str := "Hello, 世界"
for _, char := range str {
    fmt.Println(char)
}

将字符串转换为字节数组:

str := "Hello, World!"
byteArr := []byte(str)
fmt.Println(byteArr) // 输出:[72 101 108 108 111 44 32 87 111 114 108 100 33]

将字节数组转换为字符串:

byteArr := []byte{72, 101, 108, 108, 111, 44, 32, 87, 111, 114, 108, 100, 33}
str := string(byteArr)
fmt.Println(str) // 输出:Hello, World!

判断字符串是否包含子串:

str := "Hello, World!"
contains := strings.Contains(str, "World")
fmt.Println(contains) // 输出:true

查找子串在字符串中的位置:

str := "Hello, World!"
index := strings.Index(str, "World")
fmt.Println(index) // 输出:7

替换字符串中的子串:

str := "Hello, World!"
newStr := strings.Replace(str, "World", "Golang", 1)
fmt.Println(newStr) // 输出:Hello, Golang!

使用分隔符将字符串分割成子串:

str := "apple,banana,orange"
fruits := strings.Split(str, ",")
fmt.Println(fruits) // 输出:[apple banana orange]

字符串大小写转换

str := "Hello, World!"
lower := strings.ToLower(str)
upper := strings.ToUpper(str)
fmt.Println(lower) // 输出:hello, world!
fmt.Println(upper) // 输出:HELLO, WORLD!

可以使用 strings.TrimSpace 去除字符串开头和结尾的空白字符,包括换行符:

str := "  Hello, World!  "
trimmed := strings.TrimSpace(str)
fmt.Println(trimmed) // 输出:Hello, World!

2.切片

创建切片

  1. 使用字面量创建切片:
slice := []int{1, 2, 3, 4, 5}
  1. 使用 make 函数创建切片:
slice := make([]int, 5) // 创建一个长度为 5 的切片
  1. 创建指定长度和容量的切片:
slice := make([]int, 5, 10) // 创建一个长度为 5,容量为 10 的切片

切片操作

  1. 访问元素:
slice := []int{1, 2, 3, 4, 5}
fmt.Println(slice[0]) // 输出:1
  1. 切片截取:
slice := []int{1, 2, 3, 4, 5}
subSlice := slice[1:3] // 截取索引 1 到索引 2 的子切片
fmt.Println(subSlice) // 输出:[2 3]
  1. 动态增加元素:
slice := []int{1, 2, 3, 4, 5}
slice = append(slice, 6) // 在切片末尾追加元素
fmt.Println(slice) // 输出:[1 2 3 4 5 6]
  1. 动态删除元素:
slice := []int{1, 2, 3, 4, 5}
index := 2
slice = append(slice[:index], slice[index+1:]...) // 删除索引为 2 的元素
fmt.Println(slice) // 输出:[1 2 4 5]
  1. 切片长度和容量:

切片的长度是指切片当前包含的元素个数,而容量是指切片底层数组的长度。

slice := []int{1, 2, 3, 4, 5}
fmt.Println(len(slice)) // 输出:5
fmt.Println(cap(slice)) // 输出:5

切片的底层原理

在 Go 语言中,切片是对底层数组的一个引用,因此对切片的修改会影响底层数组的内容。当切片的长度超过了底层数组的容量时,Go 会重新分配一个更大的底层数组,并将原有的数据复制过去。

slice := []int{1, 2, 3, 4, 5}
subSlice := slice[1:3]
fmt.Println(subSlice) // 输出:[2 3]

subSlice[0] = 100
fmt.Println(slice) // 输出:[1 100 3 4 5]

切片的共享和复制

当多个切片指向同一个底层数组时,它们会共享数据。

slice1 := []int{1, 2, 3, 4, 5}
slice2 := slice1 // slice2 和 slice1 共享同一个底层数组
slice2[0] = 100
fmt.Println(slice1) // 输出:[100 2 3 4 5]
fmt.Println(slice2) // 输出:[100 2 3 4 5]

如果需要复制切片的内容而不共享数据,可以使用 copy 函数。

slice1 := []int{1, 2, 3, 4, 5}
slice2 := make([]int, len(slice1))
copy(slice2, slice1) // 复制 slice1 的内容到 slice2
slice2[0] = 100
fmt.Println(slice1) // 输出:[1 2 3 4 5]
fmt.Println(slice2) // 输出:[100 2 3 4 5]

四、流程控制

Go 语言的流程控制与其他编程语言类似,包括条件语句、循环语句和跳转语句。

1. 条件语句 - if...else

if age >= 18 {
    fmt.Println("成年人")
} else {
    fmt.Println("未成年人")
}

2. 循环语句 - for,跳转语句 - break、continue

for i := 0; i < 5; i++ {
    fmt.Println(i)
}

for i := 0; i < 5; i++ {
    if i == 3 {
        break // 跳出循环
    }
    fmt.Println(i)
}

五、函数

Go 语言中函数的定义格式如下:

func functionName(parameters) returnType {
    // 函数体
}

六、go语言JSON处理的序列化和反序列化

在 Go 语言中,JSON 的序列化和反序列化是将数据结构与 JSON 字符串之间进行转换的过程。序列化将 Go 中的数据结构转换为 JSON 格式的字符串,而反序列化则将 JSON 字符串转换为 Go 中的数据结构。这在处理 API 请求和响应、配置文件读写等场景中非常常见。

Golang 提供了标准库 encoding/json 来处理 JSON 的序列化和反序列化。以下是详细的示例和说明:

一、JSON 序列化

将 Go 中的数据结构转换为 JSON 字符串称为 JSON 序列化。可以通过 json.Marshal() 函数来实现。

package main

import (
    "encoding/json"
    "fmt"
)

type Person struct {
    Name  string `json:"name"`
    Age   int    `json:"age"`
    Email string `json:"email,omitempty"` // 忽略空字段
}

func main() {
    p := Person{
        Name: "John",
        Age:  30,
    }

    // 序列化为 JSON 字符串
    data, err := json.Marshal(p)
    if err != nil {
        fmt.Println("序列化出错:", err)
        return
    }

    fmt.Println(string(data))
}

输出结果:

{"name":"John","age":30}

在上面的示例中,我们定义了一个 Person 结构体,并使用 json 标签来定义字段在 JSON 中的键名。json.Marshal() 函数将 Person 实例 p 序列化为 JSON 格式的字符串,并输出结果。

二、JSON 反序列化

将 JSON 字符串转换为 Go 中的数据结构称为 JSON 反序列化。可以通过 json.Unmarshal() 函数来实现。

package main

import (
    "encoding/json"
    "fmt"
)

type Person struct {
    Name  string `json:"name"`
    Age   int    `json:"age"`
    Email string `json:"email,omitempty"`
}

func main() {
    jsonData := `{"name":"Alice","age":25}`

    // 反序列化为 Go 中的结构体
    var p Person
    err := json.Unmarshal([]byte(jsonData), &p)
    if err != nil {
        fmt.Println("反序列化出错:", err)
        return
    }

    fmt.Println("Name:", p.Name)
    fmt.Println("Age:", p.Age)
}

输出结果:

Name: Alice
Age: 25

在上面的示例中,我们定义了一个 Person 结构体,并使用 json.Unmarshal() 函数将 JSON 字符串 jsonData 反序列化为 Person 结构体,并输出结果。

三、空字段处理

在序列化时,可以使用 omitempty 选项来忽略空字段,即将空字段排除在生成的 JSON 字符串中。这在避免输出不必要的空字段时很有用。

在上面的 Person 结构体中,我们为 Email 字段添加了 omitempty 选项:

type Person struct {
    Name  string `json:"name"`
    Age   int    `json:"age"`
    Email string `json:"email,omitempty"` // 忽略空字段
}

如果我们定义的 Person 结构体中 Email 字段为空(零值),则在序列化为 JSON 时会忽略该字段。

四、JSON 字符串中的未知字段

在反序列化 JSON 字符串时,如果 JSON 中有一些未知的字段,可以使用 json.RawMessage 来处理这些字段。

package main

import (
    "encoding/json"
    "fmt"
)

type Person struct {
    Name     string          `json:"name"`
    Age      int             `json:"age"`
    Unknown  json.RawMessage `json:"-"`
}

func main() {
    jsonData := `{"name":"Bob","age":35,"address":"123 Main St"}`

    var p Person
    err := json.Unmarshal([]byte(jsonData), &p)
    if err != nil {
        fmt.Println("反序列化出错:", err)
        return
    }

    fmt.Println("Name:", p.Name)
    fmt.Println("Age:", p.Age)
    fmt.Println("Unknown:", string(p.Unknown))
}

输出结果:

Name: Bob
Age: 35
Unknown: {"address":"123 Main St"}

Person 结构体中添加了一个 Unknown 字段,并使用 json.RawMessage 类型来存储未知的 JSON 字段。在反序列化时,未知的字段会被保存在 Unknown 字段中,我们可以通过 string(p.Unknown) 将其输出。

七、go语言的时间处理

在 Go 语言中,时间处理是一个常见且重要的任务,用于处理日期、时间、时间间隔等相关操作。Go 标准库 time 提供了丰富的函数和类型,使得时间处理变得非常简单和高效。以下是 Go 语言的时间处理功能的一些常见用法和示例:

一、获取当前时间

可以使用 time.Now() 函数获取当前的本地时间:

package main

import (
	"fmt"
	"time"
)

func main() {
	now := time.Now()
	fmt.Println(now)
}

输出结果:2023-07-30 12:34:56.789012345 +0800 CST m=+0.000000001

二、格式化时间

使用 time.Format() 方法可以将时间按照指定的格式进行格式化:

package main

import (
	"fmt"
	"time"
)

func main() {
	now := time.Now()
	formatted := now.Format("2006-01-02 15:04:05")
	fmt.Println(formatted)
}

输出结果:2023-07-30 12:34:56

在这个例子中,我们使用了一个特殊的日期格式字符串 "2006-01-02 15:04:05",这是因为在 Go 语言中,日期和时间的格式化需要使用一个特定的参考日期 "2006-01-02 15:04:05"。

三、解析时间字符串

使用 time.Parse() 函数可以将时间字符串解析为时间对象:

package main

import (
	"fmt"
	"time"
)

func main() {
	timeStr := "2023-07-30 12:34:56"
	parsedTime, err := time.Parse("2006-01-02 15:04:05", timeStr)
	if err != nil {
		fmt.Println("解析时间出错:", err)
		return
	}
	fmt.Println(parsedTime)
}

输出结果:2023-07-30 12:34:56 +0000 UTC

四、时间加减

使用 time.Add() 方法可以对时间进行加减操作,返回一个新的时间对象:

package main

import (
	"fmt"
	"time"
)

func main() {
	now := time.Now()
	oneHourLater := now.Add(time.Hour)
	fmt.Println("One hour later:", oneHourLater)
}

输出结果类似:One hour later: 2023-07-30 13:34:56.789012345 +0800 CST m=+3600.000000001

五、时间间隔

在 Go 语言中,时间间隔可以使用 time.Duration 类型表示。可以使用 time.Sleep() 方法来暂停程序的执行一段时间:

package main

import (
	"fmt"
	"time"
)

func main() {
	fmt.Println("Start...")
	time.Sleep(2 * time.Second)
	fmt.Println("End.")
}

这段代码会暂停程序执行 2 秒,然后输出 "End."。

六、定时器和计时器

在 Go 语言中,可以使用 time.NewTimer() 创建一个定时器,以在指定时间后触发某个操作:

package main

import (
	"fmt"
	"time"
)

func main() {
	timer := time.NewTimer(3 * time.Second)
	fmt.Println("Start...")
	<-timer.C
	fmt.Println("End.")
}

这段代码会在启动后等待 3 秒后输出 "End."。

另外,time.Tick() 函数可以创建一个计时器,以固定时间间隔触发某个操作:

package main

import (
	"fmt"
	"time"
)

func main() {
	ticker := time.Tick(1 * time.Second)
	for tick := range ticker {
		fmt.Println(tick)
	}
}

这段代码会每隔 1 秒输出一次当前时间。