Go 语言入门指南:基础语法和常用特性解析 | 豆包MarsCode AI刷题

130 阅读17分钟

零、配置

编译器下载——windows

Download GoLand: A Go IDE with extended support for JavaScript, TypeScript, and databases (jetbrains.com.cn)

语言包下载

去 golang 的网站 studygolang.com/dl wget studygolang.com/dl/golang/g… 解压至 /usr/local tar -C /usr/local -xzf go1.20.3.linux-amd64.tar.gz

环境变量——linux

  1. 临时添加环境变量 PATH
export GOROOT=/usr/local/go
# 配置工作目录
export PATH=$PATH:$GOROOT/bin 
go env -w GO111MODULE=on
go env -w GOPROXY=https://goproxy.cn,direct
export CGO_ENABLED=1
  1. 所有用户永久添加环境变量
# 执行
vim /etc/profile
# 添加
export PATH=$PATH:/usr/local/go/bin
# 执行
sourse /etc/profile
  1. golang 工作目录——go1.1 后面一般用 go mod 管理环境。

GOPATH 是一个环境变量,用于指定Go项目的工作目录,默认情况下,GOPATH是 $HOME是当前用户的主目录。(/root 或者 /home/user1)

获取:

go env GOPATH

更改:编辑 .bashrc 或者 .zshrc

# 找到 GOPATH 这行,将其修改为 /path/to/your/gopath
export GOPATH=$HOME/go
export PATH=$PATH:$GOPATH/bin
  1. 开启 go module
# 添加一个全局变量 
export GO111MODULE=on

环境变量——windows

gopath 和 goroot

注意在 path 中进一步配置相应的 bin

go module

临时:

# cmd
set GO111MODULE=on
# powershell 
$env:GO111MODULE="on"

永久:添加环境变量:GO111MODULE 值:on

环境变量——可选(但是一般都会配)

go env -w GOPROXY=https://goproxy.cn,direct 
go env -w GOSUMDB=sum.golang.google.cn
go env -w CGO_ENABLED=1

windows 下 vscode 配置

  1. 安装 Code Runner 插件

  2. 安装 Go 插件

  3. 配置 go 插件

// 添加
    "go.gopath": "",
    "go.goroot": "D:\\Program Files\\Go"

验证环境

go env 

若可以打印 Go 的版本、安装路径等信息,则说明Go安装成功!

基本工具安装

常见的go工具有:gopls、gotests、impl、goplay、dlv、staticcheck

 go install golang.org/x/tools/gopls@latest
 go  install -v golang.org/x/tools/cmd/goimports@latest
 go install -v github.com/go-delve/delve/cmd/dlv@latest   

go.sum 与 go.mod

前提

开启 go111module

介绍

go.sumgo.mod 是 Go 语言中用于管理模块依赖的文件。

  • go.mod 文件:go.mod 文件用于定义和管理项目的模块依赖关系。它位于项目的根目录下。使用 go get 命令来安装或更新依赖包时,go.mod 文件会被更新。它记录了项目所依赖的模块及其版本。可以手动编辑 go.mod 文件来添加、移除或升级依赖包。在构建项目时,Go 会根据 go.mod 文件获取所需的依赖包。

  • go.sum 文件:go.sum 文件用于记录项目所使用模块的校验和信息。每个模块都有一个唯一的校验和,用于确保下载的模块的完整性和安全性。go.sum 文件会自动生成并更新,其中包含了所有依赖模块的校验和。构建项目时,Go 会根据 go.sum 文件验证模块的完整性,以确保下载的模块与之前的校验和匹配。

新建项目——模块管理

  1. 初始化模块:在项目的根目录下执行 go mod init 命令,它会根据项目的路径和名称创建一个新的 go.mod 文件。如果 go.mod 文件已经存在,此步不用执行

    go mod init myproject
    
  2. 添加依赖包:执行 go get 命令来添加所需的依赖包。例如,go get github.com/example/package。这会自动更新 go.mod 文件并下载依赖包。

  3. 移除依赖包:执行 go mod tidy 命令来移除不再使用的依赖包。它会自动更新 go.mod 文件并删除不需要的依赖。

  4. 升级依赖包:执行 go get -u 命令来升级依赖包到最新版本。这会自动更新 go.mod 文件。

  5. 构建项目:使用 go buildgo run 命令来构建或运行项目。Go 会根据 go.mod 文件下载依赖包并构建项目。

    需要注意的是,go.modgo.sum 文件对于保证项目的可复现性和依赖包的一致性非常重要。当你与他人共享项目时,确保将 go.modgo.sum 文件一同共享,以便其他人可以获取相同的依赖包版本。

已有项目——模块管理

  1. 下载依赖项

当你首次克隆一个 Go 项目或者在已有项目中更新了依赖项时,可以使用以下命令来下载 go.mod 文件中指定的所有依赖项:

go mod download

这个命令会将依赖项下载到本地的 Go 模块缓存中,但不会在项目目录中创建任何文件。

  1. 同步依赖项

如果你想确保项目目录下的依赖项与 go.mod 文件中定义的依赖项完全同步(即添加缺失的模块,删除不需要的模块,并更新 go.sum),可以使用:

go mod tidy

go mod tidy 命令会添加缺少的模块,移除未使用的模块,并生成一个新的 go.sum 文件。这是确保 go.modgo.sum 文件准确反映项目依赖项的好方法。

  1. 查看依赖项

如果你想查看当前项目的依赖树,可以使用:

go mod graph

这个命令会打印项目的所有依赖项及其版本,帮助你理解项目依赖的结构。

  1. 更新依赖项

要更新项目中的某个依赖项到最新版本,可以使用:

go get -u package@version

其中 package 是依赖项的路径,version 是你想要更新到的版本。如果你想更新所有依赖项到最新版本,可以使用:

go get -u

这可能会引入重大更改,建议仔细测试更新后的依赖项。

变量

变量声明

  1. 标准格式
var name type // 注意:声明变量时将变量的类型放在变量的名称之后
var a,b *int // 将 a b 同时声明为指针类型

// 初始化
var hp int = 100 // 指定类型
var hp = 100 // 省略类型,编译器会尝试为变量推到类型
  1. 简短格式
// 名字 := 表达式
/*
1. 定义变量,同时显式初始化。
2. 不能提供数据类型
3. 只能用在函数内部。
*/

// 同时声明+初始化
i, j := 0, 1
func main() {
   x:=100
   a,s:=1, "abc"
}
// 注意  :=  左面必须是新变量
var hp int // 声明 hp 变量
hp := 10  // 再次声明并赋值  报错! 左面没有新变量出现

// 错误!!!!其实 只要有新变量就行!
var conn net.Conn
var err error
conn, err = net.Dial("tcp", "127.0.0.1:8080")  // 不会报错

声明注意

  1. 变量的命名规则遵循骆驼命名法,即首个单词小写,每个新单词的首字母大写,例如:numShips 和 startDate 。
  2. 匿名变量不占用内存空间,不分配内存
  3. 作用域:
    1. 全局变量声明必须以 var 关键字开头,如果想要在外部包中使用全局变量的首字母必须大写。
    2. 特殊变量定义位置
if firstIdx, err := Func(); err != nil {
    log.Fatal(err)
    return nil
}
// 这里的 firstIdx 只能在 if 里面的大括号用,一旦出去就使用不了

比较

只有同类型的变量才能进行比较 包括 0 1 到 true false 的转换

类型转换

  1. bool 不能强转为 int
var n bool
n := (int)n
//会报错
  1. 不能获取数组某个位置的的地址

bool

string

多行

const str = `第一行
第二行
第三行
` 
fmt.Println(str);  // 输出上面的内容
// 注意 !!!!!!
// 此时所有的 转义都会失效,即 `` 内部的全部都视为 字符串一部分

大小写

input := "Hello, World!" 
output := strings.ToLower(input)

正则匹配

import "regexp"
// 编译正则
re := regexp.MustCompile(pattern)
// 匹配
match := re.MatchString(str) 
if match { 
	fmt.Printf("Matched: %s\n", str) 
}

字符串长度与基本处理

  1. len(), 判断的是 ASCII 长度 或者是 字节长度

    1. 在 go 语言中, 所有字符串都是以 utf-8 的格式保存,因此 汉字 3 字节,字符 1 字节

    2. 若要将汉字作为一个整体统计, 可以使用下面的函数

  2. RuneCountInString() , 判断的是字符的个数,或者说是 Unicode 字符串长度

package main

import (
        "fmt"
        "strings"
)

// 遍历字符串
// 按ASCII 字符
theme := "狙击 start"
for i := 0; i < len(theme); i++ {//使用 for 的数值循环进行遍历--
    fmt.Printf("ascii: %c  %d\n", theme[i], theme[i])
}// 这里 汉字为乱码

// 按 Unicode  字符
theme := "狙击 start"
for _, s := range theme {
    fmt.Printf("Unicode: %c  %d\n", s, s)
}// 这里汉字正常显示


// 字符串截取
newtr := theme[i:]  // 截取部分包括 i

// 字符串搜索
strings.Index(strings ,substring); // 返回 下标
strings.LastIndex(strings, substrings); // 反向搜索

// 字符串修改
s1 := "abc"
//     注意:字符串无法修改,只能在复制的基础上进行修改
//  方法一  转为 []byte()
s2 := []byte(s1)
s2[1] = 'B'
fmt.Println(string(s2)) //aBc
// 方法二  转为 []runej() 
s3 := []rune(s1)
s3[1] = 'B'
fmt.Println(string(s3)) //aBc
 // 方法三 字符串替换
new_str := "ABC"
old_str := "abc"
s4 := strings.Replace(s1, old_str , new_str , 2)
fmt.Println(s4) //ABC

// 字符串拼接
str1 := "hello"
str2 := "world"
 // 方法一 使用 + 直接拼接
str3 := str1 + str2
 // 方法二 使用 bytes.Buffer
var stringBuilder bytes.Buffer
stringBuilder.WriteString(str1)
stringBuilder.WriteString(str2)
str4 := stringBuilder.String()  

字符串处理

// 字符串替换
str := strings.ReplaceAll(str, " ", "")
// 使用空格切分 并且去掉空格
fields := strings.Fields(str)

正则匹配

regex := regexp.MustCompile(`\?([a-z])\[(\d+(?:-\d+)?(?:,\d+)*)\]`) 
matches := regex.FindAllStringSubmatchIndex(pattern, -1)



对于一个具体的匹配,match 数组将包含以下信息:

match[0]: 匹配到的整个表达式的起始索引。
match[1]: 匹配到的整个表达式的结束索引。
match[2]: 第一个捕获组(单个字符)的起始索引。
match[3]: 第一个捕获组的结束索引。
match[4]: 第二个捕获组(数字范围或列表)的起始索引。
match[5]: 第二个捕获组的结束索引。

int、int8、int16、int32、int64

uint、uint8、uint16、uint32、uint64、uintptr

- uint64 最大值 18446744073709551615  0xffffffffffffffff
- strconv.FormatUint(num, 10) // 转换为十进制字符串
- strconv.FormatUint(num, 16) // 转换为十六进制字符串
- strconv.ParseUint(benchmark, 10, 64) // uint64 转换为 string


// StrToHex converts a normal string to a hexadecimal string
func StrToHex(s string) string {
	return hex.EncodeToString([]byte(s))
}

byte

// uint8 的别名

rune

// int32 的别名 代表一个 Unicode 码 不对 是 UTF-8

float32、float64

将 float64 数组格式化存储:

// 创建一个空的字符串切片用于存储格式化后的浮点数
strSlice := make([]string, len(floatSlice))

// 将每个浮点数转换为字符串,并存储到字符串切片中
for i, num := range floatSlice {
    strSlice[i] = strconv.FormatFloat(num, 'f', -1, 64)
}

// 使用 strings.Join 将字符串切片连接成一个单独的字符串
result := strings.Join(strSlice, ",")

将 float 类型转换为 其他整形

vara num int = int(math.Round(float64))

complex64、complex128

内建函数

interface

接口类型 learnku.com/go/t/38843

	type Animal interface {
	    Speak() string
	}

变量赋值:

b, a = a, b  // 这里 可以实现交换操作
a, _ := GetData()  // 这里 匿名变量

注意:

  • 当一个变量被声明之后,系统自动赋予它该类型的零值: int 为 0,float 为 0.0,bool 为 false,string 为空字符串,指针为 nil 等
  • 所有的内存在 Go 中都是经过初始化的。

将 interface 转换为对应的类型:

// 将interface 转换为 []float64 
func ConvertToFloat64Slice(data interface{}) ([]float64, error) {
 // 首先,尝试将 interface{} 类型断言为 []interface{}
 slice, ok := data.([]interface{})
 if !ok {
 	return nil, fmt.Errorf("data is not a slice")
 }

 // 创建一个空的 []float64 用于存储转换后的数据
 floatSlice := []float64{}
 for _, v := range slice {
 	// 将每个元素转换为 float64
 	floatVal, ok := v.(float64)
 	if ok {
 		// return nil, fmt.Errorf("type assertion to float64 failed at index %d", i)
 		floatSlice = append(floatSlice, floatVal)
 	}
 }

 return floatSlice, nil
}

结构体变量

  1. 结构体声明
// 结构体成员变量
type MyStruct struct{
	age int     // 注意没有 逗号
	name string
}
// 结构体函数
func (ms *MyStruct) grow(int a) bool{
	ms.age += a
	return true
}

// 结构体的指针继承
type MyStruct2 struct{
	*MyStruct  // 通过指针的方式,父亲结构体作为一个无名成员变量,
			   // 赋值时使用 父亲对象 赋值; 
			   // 使用时使用 孩子对象 直接使用(不需要先点出来父亲)
	age int     // 注意没有 逗号
	name string
}
// 匿名结构体
twResponse := struct {
	TaskListResponse 
	TimeLeft string `json:"timeLeft"` 
	}{
	 TaskListResponse: taskListResponse, 
	 TimeLeft: "2 hours", 
	 }

// 结构体部分属性获取
func createPartialResponse(task TaskListResponse, fields []string, timeLeft string) (map[string]interface{}, error) {
	response := make(map[string]interface{})
	val := reflect.ValueOf(task)

	for _, field := range fields {
		f := val.FieldByName(field)
		if f.IsValid() {
			response[field] = f.Interface()
		} else {
			return nil, fmt.Errorf("field %s does not exist", field)
		}
	}

	response["TimeLeft"] = timeLeft
	return response, nil
}
  1. 结构体实例化
    // 方法一:var 声明
    var fan MyStruct   // 为 fan 分配内存并且零化值
    fan.age = 26
    fan.name = "fan"
    
    // 方法二:new 关键字
    // 11
    fan := &MyStruct{   // 返回一个结构体指针,带有初始值
    	26,
    	"fan",
    }
    // 22 
    fan := new(MyStruct) // 返回一个结构体指针,零值
    fan.name = "fan"
    fan.age = 26
    
    // 方法三:赋值初始化
    // 11
    fan := MyStruct{
    	name: "fan",
    	age: 26,
    }
    // 22:必须要对应结构体定义顺序,并且每一个都必须进行初始化。
    fan := MyStruct{
    	26,
    	"fan",
    }
    
  2. 匿名结构体
    profile := &struct {
            name string
            age   int
    }{
            name: "rat",
            age:   150,
    }
    // 打印值
    fmt.Printf("使用'%%+v' %+v\n", profile)
    fmt.Printf("使用'%%#v' %#v\n", profile)
    fmt.Printf("使用'%%T' %T\n", profile)
    
  3. 结构体返回值
    func newStruct() * MyStruct{
    	// 此处替换为三种不同方式
        var fan MyStruct   // 为 fan 分配内存并且零化值
    	fan.age = 26
    	fan.name = "fan"
    	fmt.Printf("函数内变量 fan 的地址为:%p\n", &fan)
    	return &fan
    }
    
    // 测试
    func main() {
        fan := newStruct()
        fmt.Printf("函数外变量 fan 的地址为:%p\n", fan)
    	fmt.Printf("fan使用'%%+v' %+v\n", fan)
    	fan.age = 18
    	fmt.Printf("fan使用'%%+v' %+v\n", fan)
    
    }
    

Map 键值对类型

map[string] *sync.WaitGroup 键值对类型 键值对获取 键值对变量

package main

import (
        "fmt"
)

type MyStruct struct {
    a, b uint64
}
	
func main() {
	// !! 定义与使用 
    //  初始 正常运行
    stru := make(map[uint64]*MyStruct)
    stru[2] = &MyStruct{
        1,1,
    }
    stru[2].a = 1
    stru[2].b  = 1
    fmt.Printf("值为:%+v",stru[2])
    
    //  这样也可以
    var stru map[uint64]*MyStruct 
    stru = make(map[uint64]*MyStruct) // 没有这行运行不通 !!!!!
    stru[2] = &MyStruct{   // 去掉这行也不行
        1,1,
    }  
    stru[2].a = 1
    stru[2].b  = 1
    fmt.Printf("值为:%+v",stru[2])
	
	// 或者
	stru := map[uint64]*MyStruct{ 0: &MyStruct{1,1,}}
	// 或者
	var stru = map[uint64]*MyStruct{ 0: &MyStruct{1,1,}}
    // 结论: map 要么通过 make ,要么定义时初始化。才能修改
	
	// 判断键值是否存在
	if _, ok := stru[2]; !ok {
		return
	}
	
}
	

键值对初始化

// 初始化时判断是否存在
twAsMap := make(map[int][]models.Assignment)
for _, assignment := range assignments {
	if _, exists := twAsMap[assignment.TaskWrapperId]; !exists {
		twAsMap[assignment.TaskWrapperId] = []models.Assignment{assignment}
	} else {
		twAsMap[assignment.TaskWrapperId] = append(twAsMap[assignment.TaskWrapperId], assignment)
	}
}
// 实际上 切片会自动初始化,即不需要判断
twAsMap := make(map[int][]models.Assignment)
for _, assignment := range assignments {
    twAsMap[assignment.TaskWrapperId] = append(twAsMap[assignment.TaskWrapperId], assignment)
}

键值对作为结构体成员变量


type MyStruct struct {
    a, b uint64
}
	
type MS struct{
    stu  map[uint64]*MyStruct
}
func main() {
    var stru MS
    // 1 2 这两步都是必须的,且可以多次执行不会冲突
    stru.stu = make(map[uint64]*MyStruct) // 1 
    stru.stu[2]  = & MyStruct{ // 2 
           1,1,
       }
    stru.stu[2].a =1
    stru.stu[2].b =1

    fmt.Printf("值为:%+v",stru.stu[2])
    
}

键值对获取

// 获取 kv
for k, v := range data {
     existingData[k] = v
}
// 获取 k
for k := range data {
     existingData[k] = v
}
// 获取 v
for _, v := range data {
     existingData[k] = v
}

键值对与 json 格式的转换

// json 解析为键值对 KV 形式
str := `{"name": "@2023"}`
var data map[string]interface{}
errr := json.Unmarshal([]byte(str), &data)
if errr != nil {
    // 处理解析错误
}
fmt.Println(data["name"])
// 将 KV 对 处理为 json格式
jsonData, err := json.Marshal(data)
if err != nil {
        // 处理编码错误
}

数组

  1. 定义
// 构造特定大小的数组,初始会有 size 的大小,每一个元素会有一个初始值,默认0
ids := make([]uint64, size)
// 构造大小为 0 的数组
var   ids  []uint64
//! 注意两种 定义方式 都可以用下面的方法增加元素 
// 增加元素
ids = append(ids,1)
  1. 遍历
// 使用传统的for循环
for i := 0; i < len(ids); i++ {
    fmt.Println(ids[i])
}

// 使用 range 循环
for index, value := range ids {
    fmt.Printf("Index: %d, Value: %d\n", index, value)
}
  1. 字符串拼接和切分
// 拼接
elements := []string{"apple", "banana", "cherry"}
result := strings.Join(elements, ", ")
fmt.Println(result) // 输出: apple, banana, cherry
// 切分
fruits := "apple, banana, cherry"
elements := strings.Split(fruits, ", ")
fmt.Println(elements) // 输出: [apple banana cherry]

管道变量

定义

无缓冲管道 表示发送和接收操作是同步的,即发送操作会阻塞直到有接收方接收数据,接收操作会阻塞直到有发送放发送数据。

// 无缓冲管道
ch := make(chan T) // 这里 T 是管道中数据的类型

带缓冲管道 表示发送操作只有当管道已满时才会阻塞,接收操作只有当管道为空时才会阻塞。

ch := make(chan T, capacity) // capacity 表示管道的容量

单向管道:只发送 只能发送数据不能接收数据。

ch := make(chan<- T)

单向管道:只接收 只能接收数据不能发送数据。

ch := make(<-chan T)

构造

c1 := make(chan string)

输入

c1 <- "one"
// 注意无论 <- 前面有没有变量接受,管道内都会少一个

输出

msg1 := <-c1

select 关键字

	select {
		case <-closeCh:
			return
		case msg := <-rw.raftCh:
			msgs = append(msgs, msg)
	}
  • 每个 case 都必须是一个通道
  • 所有 channel 表达式都会被求值
  • 所有被发送的表达式都会被求值
  • 如果任意某个通道可以进行,它就执行,其他被忽略。
  • 如果有多个 case 都可以运行,select 会随机公平地选出一个执行,其他不会执行。
    否则:
    1. 如果有 default 子句,则执行该语句。
    2. 如果没有 default 子句,select 将阻塞,直到某个通道可以运行;Go 不会重新对 channel 或值进行求值。

接口类型

使用——类型断言
value.(*peerState)
// 成功返回 对应类型的变量
// 失败返回 nil
接口检查

检查 regionItem 结构体是否满足 Item 接口的要求

var _ Item = &regionItem{}
如果 `regionItem` 类型缺少某些方法,编译器将会报告错误

语法

switch

  1. 支持多条件匹配
switch{
    case 1,2,3,4:
    default:
}
  1. 不同的 case 之间不使用 break 分隔,默认只会执行一个 case。

  2. 如果想要执行多个 case,需要使用 fallthrough 关键字,也可用 break 终止。

switch{
    case 1:
    ...
    if(...){
        break
    }
    fallthrough // 此时switch(1)会执行case1和case2,但是如果满足if条件,则只执行case1
    case 2:
    ...
    case 3:
}

for

i := 0
for i < n {
    fmt.Println("i:", i)
    i++
}

输入、输出

  1. fmt.Sprintf(格式化样式 , 参数列表)

    1. 和 C 语言类似 printf
  2. fmt.Println(str)

package main

import (
        "fmt"
)

func main() {
        var progress = 2
        var target = 8
        // 两参数格式化
        title := fmt.Sprintf("已采集%d个药草, 还需要%d个完成任务", progress, target)
        fmt.Println(title)
        pi := 3.14159
        // 按数值本身的格式输出
        variant := fmt.Sprintf("%v %v %v", "月球基地", pi, true) //月球基地 3.14159 true
        fmt.Println(variant)
        // 匿名结构体声明, 并赋予初值
        profile := &struct {
                Name string
                HP   int
        }{
                Name: "rat",
                HP:   150,
        }
        // 双百分号表示转义
        fmt.Printf("使用'%%+v' %+v\n", profile)  // 使用'%+v' &{Name:rat HP:150}
        fmt.Printf("使用'%%#v' %#v\n", profile)  // 使用'%#v' &struct { Name string; HP int }{Name:"rat", HP:150}
        fmt.Printf("使用'%%T' %T\n", profile)  // 使用'%T' *struct { Name string; HP int }
}
  1. 动 词 功 能
%v 按值的本来值输出

%+v 在 %v 基础上,对结构体字段名和值进行展开

%#v 输出 Go 语言语法格式的值

%T 输出 Go 语言语法格式的类型和值

%% 输出 % 本体

%b 整型以二进制方式显示

%o 整型以八进制方式显示

%d 整型以十进制方式显示

%x 整型以十六进制方式显示

%X 整型以十六进制、字母大写方式显示

%U Unicode 字符

%f 浮点数

%p 指针,十六进制方式显示

常见技巧

常见调用

  1. 获取变量类型 varb.(type)
  2. 获取数组长度 len(arr)

键值对KV

go语言中可以使用map[string]interface{}类型来实现,键的索引和值的多种类型

package main
import (
        "fmt"
)

func main() {
        // 定义
        data := map[string]interface{}{
                "myname": "@2023",
                "tasks":  []string{"task 1", "task 2", "task 3"},
                "age":    25,
                "isStudent": true,
        }
        // 增加
        data["newKey"] = "New Value"
        
        // 删除
        delete(data, "age") // 删除 data 中键为 “age” 的键值对
        // 查找
        key := "tasks"
        value, found := data[key]
        if found {
                fmt.Printf("Value of key '%s': %v\n", key, value)
        } else {
                fmt.Printf("Key '%s' not found\n", key)
        }
}

数据排序

对数据集合(包括自定义数据类型的集合)排序需要实现 sort.Interface 接口的三个方法,以下为该接口的定义:

type Interface interface {
        // 获取数据集合元素个数
        Len() int
        // 如果 i 索引的数据小于 j 索引的数据,返回 true 且不会调用下面的 Swap(),即数据升序排序。
        Less(i, j int) bool
        // 交换 i 和 j 索引的两个元素的位置
        Swap(i, j int)
}

数据集合实现了这三个方法后,即可调用该包的 Sort() 方法进行排序。 Sort() 方法定义如下:

func Sort(data Interface)

例子:

// 定义
type messageSlice []pb.Message

func (s messageSlice) Len() int           { return len(s) }
func (s messageSlice) Less(i, j int) bool { return fmt.Sprint(s[i]) < fmt.Sprint(s[j]) }
func (s messageSlice) Swap(i, j int)      { s[i], s[j] = s[j], s[i] }

// 使用
sort.Sort(messageSlice(msgs)) // msgs 为 []pb.Message 类型, 先强转再使用

数据比较

reflect包中的DeepEqual函数完美的解决了比较问题。

函数签名:

func DeepEqual(a1, a2 interface{}) bool

文档中对该函数的说明: DeepEqual函数用来判断两个值是否深度一致:除了类型相同;在可以时(主要是基本类型)会使用==;但还会比较array、slice的成员,map的键值对,结构体字段进行深入比对。map的键值对,对键只使用==,但值会继续往深层比对。DeepEqual 函数可以正确处理循环的类型。函数类型只有都会nil时才相等;空切片不等于nil切片;还会考虑array、slice的长度、map键值对数。 示例:

func main() {
	m1 := map[int]interface{}{1: []int{1, 2, 3}, 2: 3, 3: "a"}
	m2 := map[int]interface{}{1: []int{1, 2, 3}, 2: 3, 3: "a"}
	if reflect.DeepEqual(m1, m2) {
		fmt.Println("相等")
	}
}

定时器功能

done := make(chan bool)
go func() {
	ticker := time.NewTicker(30 * time.Second)
	defer ticker.Stop()
	ticker2 := time.NewTicker(36000 * time.Second)
	defer ticker2.Stop()
	for {
		select {
		case success := <-done:
			if !success {
				log.Error("Download failed.")
			}
			return
		case <-ticker.C:
			query["taskId"] = -1
			req := NewJsonRequest(f.config, query)
			req.Execute()
			log.Info("Download is still in progress...")
		case <-ticker2.C:
			log.Info("Download time over limit, stop...")
			done <- false
		}
	}
}()