Go语言学习
前提条件
默认编译环境已经安装,vscode已经配置完成
结构示例
新建模块hello包为main,注意main函数应当在main包中,main.go为测试,go.mod为当前项目模块配置。
一. 基础语法
1.1 Go语言中的25个关键字及其功能描述
关键字 | 功能介绍 |
---|---|
break | 用于中断当前循环或跳出选择结构。 |
default | 在switch语句中表示默认情况。 |
func | 用于声明函数或方法。 |
interface | 用于声明接口类型。 |
select | 用于在多个通道操作中进行选择。 |
case | 在switch语句中表示某种情况。 |
defer | 用于延迟执行函数调用,一般用于资源释放等操作。 |
go | 用于启动一个新的goroutine并发执行。 |
map | 用于声明和操作映射类型。 |
struct | 用于声明和操作结构体类型。 |
chan | 用于声明和操作通道类型。 |
else | 在if语句中表示条件不成立时的分支。 |
goto | 用于无条件跳转到指定的标签。 |
package | 用于声明包并定义包级别的变量、函数和类型。 |
switch | 用于根据不同的条件执行不同的分支。 |
const | 用于声明常量。 |
fallthrough | 在switch语句中用于执行下一个case的代码块。 |
if | 用于条件判断并执行相应的分支。 |
range | 用于迭代数组、切片、字符串、映射或通道中的元素。 |
type | 用于声明自定义类型。 |
continue | 在循环语句中用于跳过当前迭代并开始下一次迭代。 |
for | 用于循环执行代码块。 |
import | 用于导入其他包。 |
return | 用于从函数中返回值并结束函数的执行。 |
var | 用于声明变量。 |
1.2 Go语言中36个预定义标识符的功能描述
预定义标识符 | 描述 |
---|---|
append | 用于将元素追加到切片(slice)或连接两个切片。 |
cap | 返回切片、数组或通道的容量。 |
close | 用于在发送操作完成后关闭通道。 |
complex | 用于创建复数。 |
copy | 用于将元素从一个切片复制到另一个切片。 |
delete | 用于从映射(map)中删除键值对。 |
imag | 返回复数的实部。 |
len | 返回字符串、切片、数组、映射或通道的长度。 |
make | 用于创建切片、映射或通道。 |
new | 用于创建指向指定类型的指针,并返回该指针。 |
panic | 触发运行时错误,导致程序中断。 |
print | 将指定的值打印到标准输出。 |
println | 将指定的值打印到标准输出并换行。 |
real | 返回复数的虚部。 |
recover | 用于从panic状态中恢复,使程序可以继续执行。 |
byte | 无符号8位整数类型。 |
complex64 | 表示具有float32实部和虚部的复数类型。 |
complex128 | 表示具有float64实部和虚部的复数类型。 |
error | 预定义的错误类型,表示某种错误值。 |
false | 表示布尔类型的假值。 |
float32 | 表示32位浮点数类型。 |
float64 | 表示64位浮点数类型。 |
int | 表示带符号整数类型,大小根据平台而变化。 |
int8 | 表示带符号8位整数类型。 |
int16 | 表示带符号16位整数类型。 |
int32 | 表示带符号32位整数类型。 |
int64 | 表示带符号64位整数类型。 |
rune | 表示Unicode字符类型,等同于int32。 |
string | 表示字符串类型。 |
true | 表示布尔类型的真值。 |
uint | 表示无符号整数类型,大小根据平台而变化。 |
uint8 | 表示无符号8位整数类型。 |
uint16 | 表示无符号16位整数类型。 |
uint32 | 表示无符号32位整数类型。 |
uint64 | 表示无符号64位整数类型。 |
uintptr | 表示可以容纳指针值的整数类型。 |
二. 数据类型
2.1基本类型
类型 | 描述 | 示例 |
---|---|---|
bool | 布尔类型 | true, false |
int | 整数类型 | 10, -5 |
float64 | 浮点数类型 | 3.14, -0.5 |
string | 字符串类型 | “Hello”, “” |
2.2 聚合类型
类型 | 描述 | 示例 |
---|---|---|
array | 数组类型 | [1, 2, 3], [“apple”, “banana”] |
slice | 切片类型 | []int{1, 2, 3}, []string{“a”, “b”} |
map | 映射类型 | map[string]int{“apple”: 1} |
struct | 结构体类型 | type Person struct {name string} |
2.3引用类型
类型 | 描述 | 示例 |
---|---|---|
pointer | 指针类型 | &x(x 是某个变量的地址) |
slice | 切片类型 | []int{1, 2, 3} |
map | 映射类型 | map[string]int{“apple”: 1} |
channel | 通道类型 | make(chan int) |
2.4 接口类型
类型 | 描述 | 示例 |
---|---|---|
interface | 接口类型 | type Writer interface {Write([]byte) (int, error)} |
2.5 函数类型
类型 | 描述 | 示例 |
---|---|---|
function | 函数类型 | func add(a, b int) int {…} |
三. 基础使用
3.1 变量使用
// 使用 var 关键字声明变量
var age int // 声明一个整数类型的变量 age
var name string // 声明一个字符串类型的变量 name
var score float64 // 声明一个浮点数类型的变量 score
var isPassed bool // 声明一个布尔类型的变量 isPassed
age = 20 // 变量赋值
name = "John" // 变量赋值
score = 95.5 // 变量赋值
isPassed = true // 变量赋值
fmt.Println(age) // 打印变量 age 的值,输出:20
fmt.Println(name) // 打印变量 name 的值,输出:"John"
fmt.Println(score) // 打印变量 score 的值,输出:95.5
fmt.Println(isPassed) // 打印变量 isPassed 的值,输出:true
// 使用简短声明声明变量
age := 20 // 简短声明并初始化整数类型的变量 age
name := "John" // 简短声明并初始化字符串类型的变量 name
score := 95.5 // 简短声明并初始化浮点数类型的变量 score
isPassed := true // 简短声明并初始化布尔类型的变量 isPassed
fmt.Println(age) // 打印变量 age 的值,输出:20
fmt.Println(name) // 打印变量 name 的值,输出:"John"
fmt.Println(score) // 打印变量 score 的值,输出:95.5
fmt.Println(isPassed) // 打印变量 isPassed 的值,输出:true
3.2 常量使用
// 使用 const 关键字声明常量
const pi = 3.14159 // 声明一个浮点数类型的常量 pi
const (
daysOfWeek = 7 // 声明一个整数类型的常量 daysOfWeek
appName = "MyApp" // 声明一个字符串类型的常量 appName
)
fmt.Println(pi) // 打印常量 pi 的值,输出:3.14159
fmt.Println(daysOfWeek) // 打印常量 daysOfWeek 的值,输出:7
fmt.Println(appName) // 打印常量 appName 的值,输出:"MyApp"
// 常量可以用于各种表达式中
const radius = 5.0
const circumference = 2 * pi * radius // 通过计算得到圆的周长常量
fmt.Println(circumference) // 打印圆的周长,输出:31.4159
3.2 iota使用
iota 是一种特殊的常量生成器,用于简化枚举类型的定义。当使用 const 关键字定义一系列常量时,可以使用 iota 来自动生成连续的值
package main
import "fmt"
func main() {
const (
Monday = iota + 1 // iota 的初始值为 0,可以通过加上偏移量来设置起始值
Tuesday // 没有显式赋值时,默认按照 iota 自增规则生成值
Wednesday
Thursday
Friday
Saturday
Sunday
)
fmt.Println(Monday) // 输出:1
fmt.Println(Tuesday) // 输出:2
fmt.Println(Wednesday) // 输出:3
fmt.Println(Thursday) // 输出:4
fmt.Println(Friday) // 输出:5
fmt.Println(Saturday) // 输出:6
fmt.Println(Sunday) // 输出:7
}
示例中,使用 iota 定义了一组代表星期的常量,通过逐行增加 iota 的使用,每个常量都会递增生成一个连续的值。需要注意的是,当遇到新的 const 声明块时,iota 会重置为 0。
3.3 运算符
运算符类型 | 运算符 | 示例 | 含义描述 |
---|---|---|---|
算术运算符 | + | a + b = 15 | 执行加法运算,将 a 和 b 相加 |
- | a - b = 5 | 执行减法运算,将 a 和 b 相减 | |
* | a * b = 50 | 执行乘法运算,将 a 和 b 相乘 | |
/ | a / b = 2 | 执行除法运算,将 a 除以 b | |
% | a % b = 0 | 执行取模运算,计算 a 对 b 的取余 | |
比较运算符 | == | a == b -> false | 判断 a 是否等于 b,返回布尔值 false 或 true |
!= | a != b -> true | 判断 a 是否不等于 b,返回布尔值 true 或 false | |
> | a > b -> true | 判断 a 是否大于 b,返回布尔值 true 或 false | |
< | a < b -> false | 判断 a 是否小于 b,返回布尔值 false 或 true | |
>= | a >= b -> true | 判断 a 是否大于或等于 b,返回布尔值 true 或 false | |
<= | a <= b -> false | 判断 a 是否小于或等于 b,返回布尔值 false 或 true | |
逻辑运算符 | && | x && y -> false | 执行逻辑与运算,判断 x 和 y 是否都为真,返回布尔值 false |
|| | x || y -> true | 执行逻辑或运算,判断 x 和 y 是否有一个为真,返回布尔值 true | |
! | !x -> false | 执行逻辑非运算,对 x 进行取反操作,返回布尔值 false | |
位运算符 | & | a & b = 1 | 执行位与运算,对 a 和 b 进行按位与操作 |
| | a | b = 7 | 执行位或运算,对 a 和 b 进行按位或操作 | |
^ | a ^ b = 6 | 执行位异或运算,对 a 和 b 进行按位异或操作 | |
<< | a << 2 = 20 | 执行左移运算,将 a 的二进制表示向左移动指定的位数 | |
>> | a >> 1 = 2 | 执行右移运算,将 a 的二进制表示向右移动指定的位数 |
3.4 条件与循环使用
以下是以 Markdown 表格形式展示的 Go 语言条件语句和循环语句的使用示例及其含义描述:
类型 | 示例 | 含义描述 |
---|---|---|
条件语句 | if condition {<br> // code to be executed<br>} | 如果条件为真,则执行指定代码块 |
if condition {<br> // code to be executed<br>} else {<br> // code to be executed<br>} | 如果条件为真,则执行第一个代码块,否则执行第二个代码块 | |
if condition1 {<br> // code to be executed<br>} else if condition2 {<br> // code to be executed<br>} else {<br> // code to be executed<br>} | 如果条件1为真,则执行第一个代码块;如果条件1为假且条件2为真,则执行第二个代码块;否则执行最后一个代码块 | |
循环语句 | for initialization; condition; post {<br> // code to be executed<br>} | 初始化变量;如果条件为真,则执行指定代码块;执行后续操作 |
for condition {<br> // code to be executed<br>} | 如果条件为真,则执行指定代码块,无需初始化或后续操作 | |
for {<br> // code to be executed<br>} | 无限循环,会持续执行指定代码块 | |
for index, value := range iterable {<br> // code to be executed<br>} | 遍历可迭代对象,并将索引和值赋给相应的变量,执行指定代码块 |
3.4.1 条件语句select使用
package main
import (
"fmt"
"time"
)
func main() {
ch1 := make(chan int)
ch2 := make(chan string)
go func() {
time.Sleep(2 * time.Second)
ch1 <- 100
}()
go func() {
time.Sleep(1 * time.Second)
ch2 <- "Hello, Golang!"
}()
select {
case num := <-ch1:
fmt.Println("Received from ch1:", num)
case str := <-ch2:
fmt.Println("Received from ch2:", str)
default:
fmt.Println("No channel is ready")
}
}
上面的示例中,创建了两个通道 ch1 和 ch2,分别用于接收整数和字符串类型的数据。然后启动两个线程分别向通道发送数据,但它们具有不同的等待时间。通过使用 select 语句,我们可以同时监听多个通道的操作。在这个例子中,select 会等待并尝试从 ch1 和 ch2 中接收数据。如果有可用的数据,将会执行相应的 case 语句;如果没有任何通道准备好,则会执行 default 语句块。
请注意,select 语句只会选择其中一个 case 执行,即使多个 case 同时准备好。这使得我们可以根据情况选择不同的操作路径。
3.5 函数使用与作用域
1:全局作用域和局部作用域
package main
import "fmt"
// 全局变量,可以在整个包内被访问
var globalVariable = "Global"
func main() {
// 局部变量,只能在 main 函数内部被访问
var localVariable = "Local"
fmt.Println(globalVariable) // 输出: Global
fmt.Println(localVariable) // 输出: Local
anotherFunction()
}
func anotherFunction() {
fmt.Println(globalVariable) // 输出: Global
// fmt.Println(localVariable) // 编译错误,无法访问局部变量
}
解释:
- 在上述示例中,
globalVariable
是一个全局变量,它可以在整个包内任何函数中被访问。 localVariable
是一个局部变量,在 main 函数内部声明,并且只能在该函数内部被访问。main
函数中可以访问全局变量和局部变量,因为它们都在其作用域范围内。anotherFunction
函数中只能访问全局变量,无法访问main
函数内的局部变量。
2:块级作用域
package main
import "fmt"
func main() {
if true {
// 在 if 语句块中声明的变量,只能在该语句块内部被访问
var blockVariable = "Block"
fmt.Println(blockVariable) // 输出: Block
}
// fmt.Println(blockVariable) // 编译错误,无法访问块级作用域内的变量
}
解释:
- 在这个示例中,
blockVariable
是在 if 语句块内部声明的变量,它只能在该语句块内部被访问。 - 出了 if 语句块之后,无法访问
blockVariable
变量,因为它已经超出了其作用域范围。
通过使用全局变量、局部变量和块级作用域,可以根据需求灵活地管理变量的可见性和生命周期,在不同的代码块中使用不同的变量。
3: 数组使用
在 Go 语言中,数组是一个固定长度且类型相同的数据序列。以下是一个示例,演示如何声明、初始化和使用数组。
package main
import "fmt"
func main() {
// 声明一个包含5个整数的数组
var numbers [5]int
// 通过索引位置赋值
numbers[0] = 10
numbers[1] = 20
numbers[2] = 30
numbers[3] = 40
numbers[4] = 50
// 打印整个数组
fmt.Println(numbers) // 输出: [10 20 30 40 50]
// 获取数组长度
fmt.Println(len(numbers)) // 输出: 5
// 通过索引位置访问数组元素
fmt.Println(numbers[2]) // 输出: 30
// 声明并初始化一个数组
fruits := [3]string{"Apple", "Banana", "Orange"}
fmt.Println(fruits) // 输出: [Apple Banana Orange]
// 使用循环遍历数组
for i := 0; i < len(fruits); i++ {
fmt.Println(fruits[i])
}
}
解释:
- 在示例中,我们首先声明了一个包含 5 个整数的数组
numbers
。 - 通过将值分配给索引位置,我们设置了数组的每个元素的值。
- 要打印整个数组,可以使用
fmt.Println(numbers)
。 - 使用
len(numbers)
获取数组的长度,这里输出为 5。 - 可以通过索引位置访问数组的特定元素,例如
numbers[2]
输出为 30。 - 我们还展示了声明、初始化和打印字符串数组
fruits
的方法。 - 使用
for
循环遍历数组,并使用索引对应的值进行操作。
3.6 指针使用
指针是一个变量,它存储了另一个变量的内存地址。通过指针,可以间接地访问或修改变量的值
- 定义指针:可以使用
*
符号声明指针类型。
var ptr *int // 声明一个整型指针
var ptr2 *string // 声明一个字符串指针
- 获取变量的指针:可以使用
&
符号获取变量的内存地址。
x := 10
ptr := &x // 获取变量 x 的指针
- 访问指针指向的变量:可以使用
*
符号解引用指针来访问它所指向的变量。
x := 10
ptr := &x
fmt.Println(*ptr) // 输出: 10
- 修改指针指向的变量的值:可以通过指针解引用的方式修改指向的变量的值。
x := 10
ptr := &x
*ptr = 20 // 修改指针指向的变量的值
fmt.Println(x) // 输出: 20
完整示例代码:
package main
import "fmt"
func main() {
x := 10
ptr := &x // 获取变量 x 的指针
fmt.Println(*ptr) // 输出: 10
*ptr = 20 // 修改指针指向的变量的值
fmt.Println(x) // 输出: 20
y := 5
ptr = &y // 可以将指针重新指向另一个变量
fmt.Println(*ptr) // 输出: 5
}
解释:
- 声明了一个整型变量
x
,然后通过&x
获取了它的指针,并将其赋值给 ptr