前言
普通字体内容为查阅无误,斜体部分为个人理解
电脑端可以于左栏“文章目录”来索引到指定小节,手机端可以通过左上角按钮唤出菜单索引到指定小节
正文
注释
单行注释
单行注释往往用于解释一行代码的作用,格式为两个双斜杠后跟想要注释的内容,例:
//控制台打印输出一行话
fmt.Print("I am an example !")
多行注释
多行注释以/*开头,以*/结尾,注释括中的内容,常用于解释一个函数的作用等用途,如:
/*定义一个对两个int类型参数求和并返回int类型的函数
参数a:加数之一
参数b:加数之一*/
func sum(a, b int) int {
return a+b
}
常见用法
包
每个包都应该有一个包注释,注释里包含文件名称,文件描述,姓名和修改时间等,如:
//@Title HelloWorld
//@Description 在控制台输出HelloWorld
//@Author LuckyQu
//@Update 2024-11-9
结构体和接口
每个自定义的结构体或者接口都需要用注释说明,对结构进行简要介绍,如:
// User 用户对象,定义了用户的基本信息
type User struct{
Username string //用户名
Email string //邮箱
}
函数和方法
每个函数和方法都应当用注释说明,如:
//定义一个对两个int类型参数求和并返回int类型的函数
//参数a int型 加数之一
//参数b int型 被加数
func sum(a, b int) int {
return a+b
}
逻辑
可以用TODO表示这个区块需要完成,用FIXME表示这个模块需要修复或者改进,需要特别说明的问题可以用NOTE表示,如
// TODO 求和函数
func sum(a, b int) int {}
// FIXME 未在控制台输出值
func post(a int){
print(a)
}
// NOTE 想说的
func something(){}
用法规范
- 所有导出对象都需要注释说明其用途;非导出对象根据情况进行注释
- 如果对象可数且无明确指定数量的情况下,一律使用单数形式和一般进行时描述;否则使用复数形式
- 包、函数、方法和类型的注释说明都是一个完整的句子
- 句子类型的注释首字母均需大写;短语类型的注释首字母需小写
- 注释的单行长度不能超过80个字符
- 在书写项目时,为了风格统一,可以统一使用一种,如行注释
标识符的声明
声明规则
- 字符组成:标识符可以由字母(a-z, A-Z)、数字(0-9)、下划线(_)组成,但不能以数字开头
- 大小写敏感:Go语言是大小写敏感的,因此
myVar
和myvar
被视为不同的标识符 - 保留字:标识符不能与Go语言的关键字或保留字相同,例如
func
,var
,const
,type
等 - 首字母约定:
- 如果标识符首字母小写(如
myVar
),则该标识符仅在定义它的包内可见 - 如果标识符首字母大写(如
MyVar
),则该标识符在其他包中也可见(即对外导出)
- 如果标识符首字母小写(如
分类
Go中标识符分为基本类型(值类型)和引用类型
基本类型(值类型)
数值类型:
- 整型:
int
,int8
,int16
,int32
,int64
,uint
,uint8
,uint16
,uint32
,uint64
,uintptr
- 浮点型:
float32
,float64
- 复数型:
complex64
,complex128
- 布尔型:
bool
字符串类型:
- string
字符类型:
- rune (本质是int32)
数组类型:
- [长度]类型
引用类型
引用类型在内存中存储的是指向实际数据的指针。当将一个引用类型的值赋给另一个变量时,实际上是复制了指针,而不是实际的数据
指针类型
- 指针:
*T
,其中T
是任何类型
接口类型
- 接口:
interface{}
,以及其他自定义接口
切片类型
- 切片:
[]T
,其中T
是任何类型
映射类型
- 映射:
map[K]V
,其中K
是键类型,V
是值类型
通道类型
- 通道:
chan T
,其中T
是任何类型
结构体类型
- 结构体:
struct
,虽然结构体本身是值类型,但可以通过指针来引用结构体
变量
声明
可以使用var关键字来声明一个变量,若未初始化值,则值为类型对应零值,规定变量被声明之后必须使用,有如下几种声明方式:
//var + 变量名 + 类型名
var a int = 1 //此时a的值为int类型的默认零值0
//var + 变量名
var a = 1 //此时编译器会自动推断a的类型为int
//使用海象符号 := 进行短声明
//短声明不能用于声明全局变量(Go中函数体外必须以关键字为开头)
a := 1 //此时编译器会自动推断a的类型为int
//也可以一次声明多个变量
a, b := 1, 2 //此时会自动推断类型并将1,2分别赋值给a,b
//可以用小括号增强可读性
var(
a int
b float
c bool
)
常量
使用const关键字以声明一个常量,常量用于存储不可变的数据,如:
//声明单一常量
const pi = 3.14
//或者一次声明多个
const{
e = 2.718
pi = 3.14
}
函数
使用func来定义一个函数,函数的格式如下所示:
//func关键字 + 接受者类型(可省略) + 函数名 + (传入参数) + 返回参数(可以在此处定义其名字) + 函数体({})
//定义一个温度类型
type temperature float64
func (temperature) post () (t temperature) {
//some code
return t
}
类型
可以使用type关键字来定义一个类型,可以是结构体struct或者是已有的类型如int64,例:
type Temp float64
type People struct(
Name string
Age int64
)
其中,结构体就是一个字段,结构体字段可以用.来访问,也可以用指向结构体的指针来引用,如 (*p).X
包
使用package来声明包
数组&切片&Map
数组
在Go语言中,数组是一种基本类型,用于存储固定数量的相同类型的数据。数组的大小在声明时确定,并且在运行时不能改变。数组在内存中是连续存储的,这意味着你可以通过索引来快速访问数组中的元素
数组的声明和初始化
1. 声明数组
你可以使用以下语法声明一个数组:
var arrayName [size]elementType
其中:
arrayName
是数组的名称size
是数组的大小,必须是一个常量elementType
是数组中元素的类型
2. 初始化数组
数组可以在声明时进行初始化,也可以在声明后进行初始化
声明并初始化
var arr [5]int = [5]int{1, 2, 3, 4, 5}
省略大小
如果在声明时初始化数组,可以省略数组的大小,编译器会根据初始化的元素个数自动推断数组的大小:
var arr [5]int = [5]int{1, 2, 3, 4, 5} // 显式指定大小
var arr = [5]int{1, 2, 3, 4, 5} // 省略大小,编译器推断
部分初始化
你还可以部分初始化数组,未初始化的元素会被自动设置为该类型的零值:
var arr [5]int = [5]int{1, 2, 3} // arr 的值为 [1, 2, 3, 0, 0]
访问数组元素
数组元素可以通过索引来访问和修改。索引从0开始,最后一个元素的索引是数组大小减1
var arr [5]int = [5]int{1, 2, 3, 4, 5}
fmt.Println(arr[0]) // 输出 1
arr[0] = 10
fmt.Println(arr[0]) // 输出 10
数组的遍历
可以使用 for
循环来遍历数组:
var arr [5]int = [5]int{1, 2, 3, 4, 5}
for i := 0; i < len(arr); i++ {
fmt.Println(arr[i])
}
数组的多维
Go语言支持多维数组,可以通过嵌套的方括号来声明和初始化多维数组
var matrix [3][3]int = [3][3]int{
{1, 2, 3},
{4, 5, 6},
{7, 8, 9},
}
// 访问多维数组的元素
fmt.Println(matrix[0][0]) // 输出 1
matrix[0][0] = 10
fmt.Println(matrix[0][0]) // 输出 10
示例
以下是一个完整的示例,展示了数组的声明、初始化、访问和遍历:
package main
import "fmt"
func main() {
// 声明并初始化数组
var arr [5]int = [5]int{1, 2, 3, 4, 5}
// 访问数组元素
fmt.Println("arr[0]:", arr[0])
arr[0] = 10
fmt.Println("arr[0] after modification:", arr[0])
// 遍历数组
for i := 0; i < len(arr); i++ {
fmt.Printf("arr[%d]: %d\n", i, arr[i])
}
// 多维数组
var matrix [3][3]int = [3][3]int{
{1, 2, 3},
{4, 5, 6},
{7, 8, 9},
}
// 访问多维数组的元素
fmt.Println("matrix[0][0]:", matrix[0][0])
matrix[0][0] = 10
fmt.Println("matrix[0][0] after modification:", matrix[0][0])
// 遍历多维数组
for i := 0; i < len(matrix); i++ {
for j := 0; j < len(matrix[i]); j++ {
fmt.Printf("matrix[%d][%d]: %d\n", i, j, matrix[i][j])
}
}
}
切片
在Go语言中,切片(slice)是一种引用类型,它提供了一种灵活的方式来处理动态数组。切片是对底层数组的抽象,允许你在运行时动态地调整大小。切片的三个重要属性是:指向底层数组的指针、长度(len)和容量(cap)
切片的基本概念
- 底层数组:切片内部引用了一个数组
- 长度(len):切片当前包含的元素个数
- 容量(cap):切片可以增长的最大长度,即底层数组从切片起始位置到数组末尾的元素个数
切片的声明和初始化
1. 声明切片
你可以使用以下语法声明一个切片:
var sliceName []elementType
其中:
sliceName
是切片的名称elementType
是切片中元素的类型
2. 初始化切片
切片可以通过多种方式初始化:
使用 make
函数
slice := make([]int, length, capacity)
length
是切片的初始长度capacity
是切片的容量(可选)
从数组创建切片
array := [5]int{1, 2, 3, 4, 5}
slice := array[1:4] // 创建一个包含 array[1], array[2], array[3] 的切片
直接初始化
slice := []int{1, 2, 3, 4, 5}
访问和修改切片元素
切片元素可以通过索引来访问和修改,索引从0开始,最后一个元素的索引是切片长度减1
slice := []int{1, 2, 3, 4, 5}
fmt.Println(slice[0]) // 输出 1
slice[0] = 10
fmt.Println(slice[0]) // 输出 10
切片的遍历
你可以使用 for
循环来遍历切片:
slice := []int{1, 2, 3, 4, 5}
for i := 0; i < len(slice); i++ {
fmt.Println(slice[i])
}
切片的动态调整
切片可以通过 append
函数动态地增加元素
slice := []int{1, 2, 3}
slice = append(slice, 4) // 添加一个元素
fmt.Println(slice) // 输出 [1, 2, 3, 4]
slice = append(slice, 5, 6) // 添加多个元素
fmt.Println(slice) // 输出 [1, 2, 3, 4, 5, 6]
切片的子切片
你可以通过切片操作符来创建子切片
slice := []int{1, 2, 3, 4, 5}
subSlice := slice[1:4] // 创建一个包含 slice[1], slice[2], slice[3] 的子切片
fmt.Println(subSlice) // 输出 [2, 3, 4]
示例
以下是一个完整的示例,展示了切片的声明、初始化、访问、遍历和动态调整:
package main
import "fmt"
func main() {
// 使用 make 函数创建切片
slice1 := make([]int, 3, 5)
slice1[0] = 1
slice1[1] = 2
slice1[2] = 3
fmt.Println("slice1:", slice1)
// 从数组创建切片
array := [5]int{1, 2, 3, 4, 5}
slice2 := array[1:4]
fmt.Println("slice2:", slice2)
// 直接初始化切片
slice3 := []int{1, 2, 3, 4, 5}
fmt.Println("slice3:", slice3)
// 访问和修改切片元素
fmt.Println("slice3[0]:", slice3[0])
slice3[0] = 10
fmt.Println("slice3[0] after modification:", slice3[0])
// 遍历切片
for i := 0; i < len(slice3); i++ {
fmt.Printf("slice3[%d]: %d\n", i, slice3[i])
}
// 动态调整切片
slice3 = append(slice3, 6)
fmt.Println("slice3 after appending 6:", slice3)
// 创建子切片
subSlice := slice3[1:4]
fmt.Println("subSlice:", subSlice)
}
数组和切片的区别
切片:动态大小,引用类型,赋值和传递时只复制指针
数组:固定大小,值类型,赋值和传递时会进行深拷贝
Map
在Go语言中,map
是一种内置的数据结构,用于存储键值对(key-value pairs)。map
是一种引用类型,可以在运行时动态地添加、删除和修改键值对。map
的键和值可以是任何类型,但键必须是可比较的类型(例如,基本类型、指针、接口、结构体等)
声明和初始化 map
1. 声明 map
你可以使用以下语法声明一个 map
:
var mapName map[keyType]valueType
其中:
mapName
是map
的名称keyType
是键的类型valueType
是值的类型
2. 初始化 map
map
可以通过多种方式初始化:
使用 make
函数
mapName := make(map[keyType]valueType)
直接初始化
mapName := map[keyType]valueType{
key1: value1,
key2: value2,
// ...
}
访问和修改 map
1. 访问 map
中的值
你可以通过键来访问 map
中的值:
value := mapName[key]
2. 检查键是否存在
你可以使用多重赋值来检查键是否存在:
value, exists := mapName[key]
value
是键对应的值exists
是一个布尔值,表示键是否存在
3. 修改 map
中的值
你可以通过键来修改 map
中的值:
mapName[key] = newValue
4. 删除 map
中的键值对
可以使用 delete
函数来删除 map
中的键值对:
delete(mapName, key)
遍历 map
可以使用 for
循环来遍历 map
:
for key, value := range mapName {
fmt.Println("Key:", key, "Value:", value)
}
示例
以下是一个完整的示例,展示了 map
的声明、初始化、访问、修改、删除和遍历:
package main
import "fmt"
func main() {
// 使用 make 函数创建 map
ages := make(map[string]int)
ages["Alice"] = 30
ages["Bob"] = 25
ages["Charlie"] = 35
fmt.Println("ages:", ages)
// 直接初始化 map
scores := map[string]int{
"Alice": 90,
"Bob": 85,
"Charlie": 95,
}
fmt.Println("scores:", scores)
// 访问 map 中的值
age := ages["Alice"]
fmt.Println("Alice's age:", age)
// 检查键是否存在
score, exists := scores["David"]
if exists {
fmt.Println("David's score:", score)
} else {
fmt.Println("David not found")
}
// 修改 map 中的值
ages["Alice"] = 31
fmt.Println("Updated Alice's age:", ages["Alice"])
// 删除 map 中的键值对
delete(ages, "Bob")
fmt.Println("ages after deleting Bob:", ages)
// 遍历 map
fmt.Println("Scores:")
for name, score := range scores {
fmt.Printf("%s: %d\n", name, score)
}
}
总结
- 声明和初始化:可以使用
make
函数或直接初始化来创建ma
p - 访问和修改:通过键来访问和修改
map
中的值 - 检查存在:使用多重赋值来检查键是否存在
- 删除键值对:使用
delete
函数来删除map
中的键值对 - 遍历:使用
for
循环和range
关键字来遍历map
结构体
定义
可以用type关键字 + 结构体名 + struct{}来声明一个结构体,如:
type User struct{
Name string
Age int64
}
使用
在编码中使用结构体可以完成组合等功能,在成员字段类型后可以用“指定转化格式时候的字段名,如
type User struct{
Name string `json:Name`
Age int64 `json:Age`
}
流程控制语句
for循环
在Go语言中只存在有for循环一种循环,格式为:
for 初始化语句;条件判定语句;每次循环后执行的语句{
somecode
}
其中遍历可以使用range关键字:
for i,j := range something{
somecode
}
其中,i为次数,j为遍历中的值
switch语句
在Go语言中,switch
语句是一种控制结构,用于根据不同的条件执行不同的代码块。switch
语句提供了比多个 if-else
语句更清晰和简洁的语法,特别适用于多个分支的情况
基本语法
switch expression {
case expression1:
// 代码块1
case expression2:
// 代码块2
case expression3:
// 代码块3
default:
// 默认代码块
}
关键点
- 表达式:
switch
后面的表达式可以是任何类型,但所有case
后面的表达式必须与switch
表达式的类型相同 - 匹配:
switch
会依次检查每个case
后面的表达式,如果匹配,则执行相应的代码块 - 默认分支:
default
分支是可选的,如果没有匹配的case
,则执行default
分支 - 自动打破:与C语言不同,Go语言的
switch
语句在每个case
之后会自动打破(即不会继续执行下一个case
),除非使用fallthrough
关键字 - 在switch后面可以执行一个表达式,用分号与判定的表达式分开
示例
基本用法
package main
import "fmt"
func main() {
day := "Monday"
switch day {
case "Monday":
fmt.Println("It's Monday!")
case "Tuesday":
fmt.Println("It's Tuesday!")
case "Wednesday":
fmt.Println("It's Wednesday!")
default:
fmt.Println("It's another day.")
}
}
输出
It's Monday!
使用 fallthrough
package main
import "fmt"
func main() {
grade := 85
switch {
case grade >= 90:
fmt.Println("A")
case grade >= 80:
fmt.Println("B")
fallthrough
case grade >= 70:
fmt.Println("C")
default:
fmt.Println("D")
}
}
输出
B
C
无表达式的 switch
你也可以使用无表达式的 switch
语句,这种情况下 case
后面的表达式会被直接评估为布尔值
package main
import "fmt"
func main() {
number := 10
switch {
case number > 0:
fmt.Println("Positive")
case number < 0:
fmt.Println("Negative")
default:
fmt.Println("Zero")
}
}
输出
Positive
多个条件
你可以在一个 case
中列出多个条件,使用逗号分隔
package main
import "fmt"
func main() {
day := "Saturday"
switch day {
case "Monday", "Tuesday", "Wednesday", "Thursday", "Friday":
fmt.Println("It's a weekday.")
case "Saturday", "Sunday":
fmt.Println("It's a weekend.")
default:
fmt.Println("Invalid day.")
}
}
输出
It's a weekend.
总结
- 基本语法:
switch
语句根据不同的条件执行不同的代码块 - 自动打破:每个
case
之后会自动打破,除非使用fallthrough
- 无表达式的
switch
:可以在switch
语句中直接使用布尔表达式 - 多个条件:一个
case
中可以列出多个条件,使用逗号分隔
if语句
当然可以,以下是修改后的版本:
在Go语言中,if
语句是一种常用的控制结构,用于根据条件执行不同的代码块。if
语句的基本语法非常简单,但也有一些高级用法,如在 if
语句中声明变量。
基本语法
if condition {
// 代码块1
} else if condition2 {
// 代码块2
} else {
// 代码块3
}
关键点
- 条件:
if
后面的条件必须是一个布尔表达式。 - 代码块:如果条件为
true
,则执行相应的代码块。 else if
:可以有多个else if
分支,用于处理多个条件。else
:else
分支是可选的,如果没有匹配的if
或else if
条件,则执行else
分支。
示例
基本用法
package main
import "fmt"
func main() {
age := 25
if age >= 18 {
fmt.Println("You are an adult.")
} else {
fmt.Println("You are a minor.")
}
}
输出:
You are an adult.
多个 else if
分支
package main
import "fmt"
func main() {
score := 85
if score >= 90 {
fmt.Println("Grade: A")
} else if score >= 80 {
fmt.Println("Grade: B")
} else if score >= 70 {
fmt.Println("Grade: C")
} else {
fmt.Println("Grade: D")
}
}
输出:
Grade: B
在 if
语句中声明变量
在 if
语句中,你可以在条件之前声明并初始化一个变量。这个变量的作用域仅限于 if
语句块及其 else
和 else if
分支。
package main
import "fmt"
func main() {
if num := 10; num > 0 {
fmt.Println("Number is positive.")
} else {
fmt.Println("Number is non-positive.")
}
}
输出:
Number is positive.
使用逻辑运算符
你可以在条件中使用逻辑运算符(如 &&
, ||
, !
)来组合多个条件。
package main
import "fmt"
func main() {
age := 25
hasLicense := true
if age >= 18 && hasLicense {
fmt.Println("You can drive.")
} else {
fmt.Println("You cannot drive.")
}
}
输出:
You can drive.
嵌套 if
语句
你可以在 if
语句中嵌套另一个 if
语句,以实现更复杂的逻辑。
package main
import "fmt"
func main() {
age := 25
hasLicense := true
if age >= 18 {
if hasLicense {
fmt.Println("You can drive.")
} else {
fmt.Println("You are old enough to drive, but you don't have a license.")
}
} else {
fmt.Println("You are too young to drive.")
}
}
输出:
You can drive.
总结
- 基本语法:
if
语句根据条件执行不同的代码块。 - 多个
else if
分支:可以处理多个条件。 else
分支:如果没有匹配的if
或else if
条件,则执行else
分支。- 在
if
语句中声明变量:可以在条件之前声明并初始化一个变量。 - 使用逻辑运算符:可以组合多个条件。
- 嵌套
if
语句:可以实现更复杂的逻辑。
方法
在Go语言中,方法是一种特殊的函数,它与特定的类型关联。方法可以为任何类型(包括内置类型、自定义类型、结构体等)定义行为。方法的定义和调用与普通函数类似,但有一些关键的区别。
方法的基本概念
- 接收者:方法的第一个参数称为接收者,它指定了方法作用于哪个类型。
- 方法集:一个类型的方法集包括所有为该类型定义的方法。
定义方法
方法的定义语法如下:
func (receiverType receiverName) methodName(parameters) returnType {
// 方法体
}
其中:
receiverType
是接收者的类型。receiverName
是接收者的名称,可以在方法体内使用。methodName
是方法的名称。parameters
是方法的参数列表。returnType
是方法的返回类型。
示例
为结构体定义方法
package main
import "fmt"
// 定义一个结构体
type Person struct {
Name string
Age int
}
// 为 Person 结构体定义一个方法
func (p Person) SayHello() {
fmt.Printf("Hello, my name is %s and I am %d years old.\n", p.Name, p.Age)
}
func main() {
// 创建一个 Person 实例
p := Person{Name: "Alice", Age: 30}
// 调用 SayHello 方法
p.SayHello()
}
输出
Hello, my name is Alice and I am 30 years old.
指针接收者 vs 值接收者
方法的接收者可以是值类型或指针类型,这会影响方法的行为。
值接收者
- 方法接收者是值类型时,方法会接收一个副本。
- 修改接收者不会影响原始值。
package main
import "fmt"
type Person struct {
Name string
Age int
}
// 值接收者
func (p Person) ChangeName(newName string) {
p.Name = newName
}
func main() {
p := Person{Name: "Alice", Age: 30}
p.ChangeName("Bob")
fmt.Println(p.Name) // 输出 "Alice"
}
指针接收者
- 方法接收者是指针类型时,方法会接收一个指针。
- 修改接收者会影响原始值。
package main
import "fmt"
type Person struct {
Name string
Age int
}
// 指针接收者
func (p *Person) ChangeName(newName string) {
p.Name = newName
}
func main() {
p := Person{Name: "Alice", Age: 30}
p.ChangeName("Bob")
fmt.Println(p.Name) // 输出 "Bob"
}
方法集
一个类型的方法集包括所有为该类型定义的方法。例如:
package main
import "fmt"
type MyInt int
// 为 MyInt 类型定义一个方法
func (mi MyInt) Double() MyInt {
return mi * 2
}
func main() {
var a MyInt = 5
fmt.Println(a.Double()) // 输出 10
}
接口和方法
方法在实现接口时非常有用。接口定义了一组方法,任何实现了这些方法的类型都可以满足该接口。
package main
import "fmt"
// 定义一个接口
type Greeter interface {
Greet() string
}
// 定义一个结构体
type Person struct {
Name string
}
// 为 Person 结构体实现 Greeter 接口
func (p Person) Greet() string {
return "Hello, my name is " + p.Name
}
func main() {
p := Person{Name: "Alice"}
var g Greeter = p
fmt.Println(g.Greet()) // 输出 "Hello, my name is Alice"
}
总结
- 方法:为特定类型定义的行为。
- 接收者:方法的第一个参数,指定了方法作用于哪个类型。
- 值接收者:方法接收一个副本,修改不会影响原始值。
- 指针接收者:方法接收一个指针,修改会影响原始值。
- 方法集:一个类型的所有方法。
- 接口:定义了一组方法,任何实现了这些方法的类型都可以满足该接口。
Goroutine&Chan
在Go语言中,Goroutine 是轻量级的线程,由Go运行时管理和调度。Goroutine 的设计目的是为了简化并发编程,使开发者能够轻松地编写高效的并发代码。Goroutine 的创建和管理成本非常低,可以同时运行成千上万个Goroutine
Goroutine 的基本概念
- 轻量级:Goroutine 的开销远小于操作系统线程,因为它们是由Go运行时管理和调度的。
- 并发:Goroutine 之间可以并发执行,充分利用多核处理器的性能。
- 调度:Go运行时负责Goroutine的调度,确保它们在可用的处理器核心上高效运行。
创建 Goroutine
要创建一个Goroutine,只需在函数调用前加上 go
关键字。
go functionCall()
示例
基本用法
package main
import (
"fmt"
"time"
)
func say(s string) {
for i := 0; i < 5; i++ {
time.Sleep(100 * time.Millisecond)
fmt.Println(s)
}
}
func main() {
go say("world") // 创建一个新的Goroutine
say("hello") // 在主Goroutine中执行
// 主Goroutine需要等待一段时间,以便其他Goroutine完成
time.Sleep(1 * time.Second)
}
输出
hello
world
hello
world
hello
world
hello
world
hello
通道(Channel)
Goroutine 之间的通信通常通过通道(Channel)来实现。通道是一种类型安全的管道,可以在Goroutine之间发送和接收值。
创建通道
ch := make(chan int)
发送和接收
ch <- value // 发送值到通道
value := <-ch // 从通道接收值
示例:使用通道
package main
import (
"fmt"
"time"
)
func sum(s []int, c chan int) {
sum := 0
for _, v := range s {
sum += v
}
c <- sum // 将结果发送到通道
}
func main() {
s := []int{7, 2, 8, -9, 4, 0}
c := make(chan int)
go sum(s[:len(s)/2], c)
go sum(s[len(s)/2:], c)
x, y := <-c, <-c // 从通道接收值
fmt.Println(x, y, x+y)
}
输出
5 -5 0
通道的方向
通道可以有方向,用于限制通道的操作:
chan int
:双向通道,可以发送和接收。chan<- int
:只写通道,只能发送。<-chan int
:只读通道,只能接收。
示例:使用方向通道
package main
import (
"fmt"
)
func producer(c chan<- int) {
for i := 0; i < 5; i++ {
c <- i
}
close(c) // 关闭通道,表示没有更多的值发送
}
func consumer(c <-chan int) {
for v := range c {
fmt.Println(v)
}
}
func main() {
c := make(chan int)
go producer(c)
consumer(c)
}
输出
0
1
2
3
4
选择(Select)
select
语句用于在多个通道操作之间进行选择。当多个通道操作都准备好时,select
会随机选择一个执行。
示例:使用 select
package main
import (
"fmt"
"time"
)
func main() {
c1 := make(chan string)
c2 := make(chan string)
go func() {
time.Sleep(time.Second * 1)
c1 <- "one"
}()
go func() {
time.Sleep(time.Second * 2)
c2 <- "two"
}()
select {
case msg1 := <-c1:
fmt.Println(msg1)
case msg2 := <-c2:
fmt.Println(msg2)
}
}
输出
one
总结
- Goroutine:轻量级的线程,由Go运行时管理和调度。
- 创建 Goroutine:在函数调用前加上
go
关键字。 - 通道:用于Goroutine之间的通信。
- 方向通道:限制通道的操作。
- 选择:
select
语句用于在多个通道操作之间进行选择。