1. 变量基本语法
- 变量声明:使用
var关键字可以声明一个或多个变量,并指定它们的类型。如果同时声明多个变量,可以使用逗号分隔它们。
var identifier1, identifier2 type
- 变量初始化:在声明变量时,可以同时初始化它们。如果没有初始化,则变量默认为零值,即该类型的默认值。
var a string = "Runoob"
var b, c int = 1, 2
- 零值:每种类型的零值不同,例如:
-
- 数值类型为
0 - 布尔类型为
false - 字符串为
""(空字符串) - 指针、切片、映射、通道和函数为
nil。
- 数值类型为
- 类型推断:如果声明变量时不指定类型,编译器会根据赋值表达式推断变量的类型。
var d = true
- 短变量声明:在函数内部,可以使用
:=进行简短变量声明,此时编译器会自动推断变量的类型。
a := 1
- 全局变量:全局变量只能通过
var声明,并且如果希望全局变量能被外部包访问,变量名必须以大写字母开头。
var A int = 1
- 批量声明:可以使用括号将多个变量声明放在一起,这通常用于全局变量的声明。
var (
a int
b string
c []float32
)
- 匿名变量:在某些情况下,可能会使用匿名变量(通常写作
_),主要用于忽略不需要的返回值。
_, err := os.Open("file.txt")
if err != nil {
log.Fatal(err)
}
- 复合类型的初始化:可以一次性初始化数组、切片、映射等复合类型。
arr := [3]int{1, 2, 3}
slice := []int{1, 2, 3}
mapVar := map[string]int{"one": 1, "two": 2, "three": 3}
var 在Go语言中的基本用法和特点。
package main
import (
"fmt"
"math"
)
func main() {
// 初始化字符串变量a
var a = "initial"
// 同时初始化两个整型变量b和c
var b, c int = 1, 2
// 初始化布尔型变量d
var d = true
// 初始化浮点型变量e,其零值为0.0
var e float64
// 初始化变量f,类型为float32,值为e的浮点数转换
f := float32(e)
// 初始化变量g,通过将字符串a与"foo"拼接得到
g := a + "foo"
// 打印变量a、b、c、d、e、f的值
fmt.Println(a, b, c, d, e, f) // initial 1 2 true 0 0
// 打印变量g的值,显示字符串拼接结果
fmt.Println(g) // initialapple
// 定义字符串常量s
const s string = "constant"
// 定义整型常量h
const h = 500000000
// 定义浮点型常量i,计算结果为3e20除以h的值
const i = 3e20 / h
// 打印常量s、h、i的值及它们的正弦值
fmt.Println(s, h, i, math.Sin(h), math.Sin(i))
}
2. for循环
for 循环是Go语言中最基本的循环结构,它提供了传统意义上的循环控制。for 循环有三种形式:
基本 for 循环
for initialisation; condition; post {
// 循环体
}
initialisation:初始化语句,通常用于声明循环变量。condition:条件表达式,如果为true,则执行循环体。post:每次循环结束后执行的语句,通常用于更新循环变量。
for i := 0; i < 10; i++ {
fmt.Println(i)
}
只有条件的 for 循环
for condition {
// 循环体
}
这种形式的 for 循环不需要初始化和后置语句,只有条件表达式。
for i := 0; i < 10 {
fmt.Println(i)
i++
}
无限循环
for {
// 循环体
}
range循环
range 循环是Go语言中特有的一种循环结构,用于遍历数组、切片、字符串、映射和通道等集合类型的元素。
数组、切片和字符串的 range 循环
for index, value := range collection {
// 循环体
}
index:可选的,表示元素的索引。value:表示元素的值。
arr := []int{1, 2, 3, 4, 5}
for index, value := range arr {
fmt.Printf("Index: %d, Value: %d\n", index, value)
}
如果不需要索引,可以忽略它:
for _, value := range arr {
fmt.Println(value)
}
映射的 range 循环
for key, value := range map {
// 循环体
}
key:映射的键。value:映射的值。
m := map[string]int{"one": 1, "two": 2}
for key, value := range m {
fmt.Printf("Key: %s, Value: %d\n", key, value)
}
通道的 range 循环
for value := range channel {
// 循环体
}
控制流语句
这种形式的 range 循环用于从通道接收数据,直到通道被关闭。
ch := make(chan int)
go func() {
for i := 0; i < 5; i++ {
ch <- i
}
close(ch)
}()
for v := range ch {
fmt.Println(v)
}
在循环中,可以使用 break 和 continue 来控制循环的执行:
break:终止当前循环。continue:跳过当前循环的剩余部分,直接进入下一次循环。
for i := 0; i < 10; i++ {
if i == 5 {
break // 终止循环
}
fmt.Println(i)
}
for i := 0; i < 10; i++ {
if i%2 == 0 {
continue // 跳过偶数
}
fmt.Println(i)
}
这些是Go语言中循环的基本用法和特点,通过这些控制结构,可以灵活地实现各种循环逻辑。
package main
import (
"fmt"
)
func main() {
// 初始化变量i,用于后续的循环控制
i := 1
// 无限循环,但内部没有操作,直接使用break语句跳出循环
for {
fmt.Println("loop")
break
}
// 遍历7到8的数字,并打印出来
for j := 7; j < 9; j++ {
fmt.Println(j) // 7 8
}
// 遍历0到4的数字,仅打印奇数
for n := 0; n < 5; n++ {
if n%2 == 0 {
continue
}
fmt.Println(n) //1 3
}
// 循环直到i大于3,每次循环后增加i的值
for i <= 3 {
fmt.Println(i)
i++
}
}
3. if基本语法
- 在Go语言中,
if语句用于基于条件执行不同的代码块。以下是关于Go中if语句的详细信息:
基本语法
Go中的if语句基本语法如下:
if condition {
// 如果condition为true,则执行这里的代码
} else {
// 如果condition为false,则执行这里的代码
}
condition:必须是一个布尔表达式,如果结果为true,则执行if代码块中的代码。
没有花括号的if语句
在Go中,if语句不需要使用圆括号()包围条件表达式,但必须使用花括号{}包围代码块。
初始化语句
Go的if语句允许在条件之前包含一个初始化语句,这通常用于声明局部变量。这个初始化语句只在if语句中可见。
if x := 10; x > 5 {
fmt.Println("x is greater than 5")
} else {
fmt.Println("x is not greater than 5")
}
在这个例子中,x只在if语句中声明和可见。
没有else的if语句
if语句可以没有else部分,这使得if语句更加灵活。
if y := 5; y > 3 {
fmt.Println("y is greater than 3")
}
// 这里可以执行其他代码,即使y不大于3
if-else if-else链
Go支持if-else if-else链,用于多个条件的判断。
if a := 10; a > 15 {
fmt.Println("a is greater than 15")
} else if a > 5 {
fmt.Println("a is greater than 5 but less than or equal to 15")
} else {
fmt.Println("a is 5 or less")
}
短变量声明在if语句中
在if语句中使用短变量声明:=时,声明的变量仅在if语句的作用域内可见。
if b := 20; b > 15 {
fmt.Println("b is greater than 15")
} // b 在这里不可见
if语句作为语句使用
在Go中,if语句可以像其他任何语句一样被使用,包括在另一个if语句中,或者作为函数调用的一部分。
func checkValue(value int) {
if value > 10 {
fmt.Println("Value is greater than 10")
}
}
func main() {
checkValue(15) // 输出 "Value is greater than 10"
}
switch语句作为替代
虽然switch语句在功能上与if-else if-else链类似,但在处理多个条件时,switch语句通常更加清晰和简洁。
package main
import "fmt"
func main() {
// 判断数字7是否为偶数
if 7%2 == 0 {
fmt.Println("even")
} else {
fmt.Println("odd")
}
// 检查数字8是否能被4整除
if 8%4 == 0 {
fmt.Println("divisible by 4")
}
// 对数字9进行正负及位数判断
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")
}
}
4. switch基本语法
switch 语句的基本语法如下:
switch expression {
case value1:
// 如果expression的值等于value1,则执行这里的代码
case value2:
// 如果expression的值等于value2,则执行这里的代码
// ...
default:
// 如果expression的值与所有case都不匹配,则执行这里的代码
}
expression:需要评估的表达式。value1,value2, ...:与expression的结果进行比较的值。default:可选的,如果没有case匹配,则执行default块中的代码。
没有花括号的switch语句
在Go中,switch语句不需要使用圆括号()包围表达式,但必须使用花括号{}包围代码块。
没有break的情况
在传统的switch语句中,每个case块的末尾通常需要一个break语句来防止代码继续执行到下一个case块。但在Go中,不需要显式地写break,因为每个case块会自动在执行完毕后退出switch语句。
多个条件的case
在Go中,一个case可以匹配多个值,只需用逗号分隔这些值。
switch i {
case 1, 2, 3:
fmt.Println("i is 1, 2, or 3")
// ...
}
没有条件的switch
Go中的switch可以没有条件表达式,这种情况下,switch会根据case块中的代码来匹配执行。
switch {
case a > b:
fmt.Println("a is greater than b")
case a == b:
fmt.Println("a is equal to b")
default:
fmt.Println("a is less than b")
}
fallthrough 关键字
在Go中,fallthrough 关键字可以用来强制执行下一个case块的代码,即使当前的case已经匹配。
switch i {
case 1:
fmt.Println("i is 1")
fallthrough // 即使i是1,也会继续执行下一个case
case 2:
fmt.Println("i is 2")
// ...
}
switch 语句中的变量声明
在switch语句中,可以在case或default块中声明变量,这些变量仅在switch语句的作用域内可见。
switch result := someFunction(); result {
case "success":
fmt.Println("Operation was successful")
case "error":
fmt.Println("An error occurred")
// ...
}
在这个例子中,result变量仅在switch语句中声明和可见。
switch 语句与类型断言
switch 语句也可以用来执行类型断言,这在处理接口类型的值时非常有用。
var v interface{} = "hello"
switch v.(type) {
case nil:
fmt.Println("v is nil")
case string:
fmt.Printf("v is a string: %s\n", v)
case int:
fmt.Printf("v is an int: %d\n", v)
// ...
}
package main
import "time"
func main() {
// 定义变量a并初始化为2,用于后续的switch语句判断
a := 2
// 根据变量a的值执行不同的代码块
switch a {
case 1:
// 如果a为1,输出"one"
println("one")
case 2:
// 如果a为2,输出"two"
println("two")
case 4, 5:
// 如果a为4或5,输出"four or five"
println("four or five")
default:
// 如果a的值不属于上述任何情况,输出"other"
println("other")
}
// 获取当前时间,用于判断当前时间是否在中午之前
t := time.Now()
// 根据当前时间的小时数执行不同的代码块
switch {
case t.Hour() < 12:
// 如果当前时间在中午之前,输出"before noon"
println("before noon")
default:
// 如果当前时间在中午之后,输出"after noon"
println("after noon")
}
}
5. array基本语法
数组的声明需要指定数组的类型、名称和长度。语法如下:
var arrayName [arrayLength]arrayType
arrayName:数组的名称。arrayLength:数组的长度,必须是一个常量表达式。arrayType:数组元素的类型。
例如,声明一个长度为5的整数数组:
var myArray [5]int
初始化数组
数组可以在声明时初始化:
myArray := [5]int{1, 2, 3, 4, 5}
如果省略了数组的长度,Go会根据初始化值的数量来确定长度:
myArray := [...]int{1, 2, 3, 4, 5}
使用...可以省略数组的长度,Go编译器会计算数组的长度。
默认值初始化
如果数组的元素没有被显式初始化,它们将被自动初始化为该类型的零值:
myArray := [5]int{} // myArray 将被初始化为 [0, 0, 0, 0, 0]
访问数组元素
数组的元素可以通过索引访问,索引从0开始:
element := myArray[0] // 获取数组的第一个元素
遍历数组
可以使用for循环遍历数组:
for i := 0; i < len(myArray); i++ {
fmt.Println(myArray[i])
}
或者使用range关键字:
for index, value := range myArray {
fmt.Printf("Index: %d, Value: %d\n", index, value)
}
数组的长度
数组的长度可以通过内置的len()函数获取:
length := len(myArray) // 获取数组的长度
数组是值类型
在Go中,数组是值类型,这意味着当数组被传递给函数时,会复制整个数组。
多维数组
Go支持多维数组,声明方式类似于一维数组,只需在数组类型中添加额外的维度和长度:
var twoDimArray [3][4]int
这声明了一个3x4的二维数组。
数组作为参数传递
当数组作为参数传递给函数时,实际上是传递了数组的一个拷贝。如果需要在函数内部修改数组,应该传递数组的指针。
package main
import "fmt"
func main() {
// 声明一个长度为5的整型数组
var a [5]int
// 给数组a的第4个元素赋值为100
a[4] = 100
// 打印数组a的第2个元素,默认为0,因为Go数组元素在未赋值前默认为零值
fmt.Println("get:", a[2]) //0
// 打印数组a的长度,为5
fmt.Println("len:", len(a)) //5
// 声明并初始化一个长度为5的整型数组b,同时赋予初始值
b := [5]int{1, 2, 3, 4, 5}
// 打印数组b的所有元素
fmt.Println(b) //[1 2 3 4 5]
// 声明一个2x3的二维整型数组
var twoD [2][3]int
// 使用嵌套循环为二维数组twoD的每个元素赋值
for i := 0; i < 2; i++ {
for j := 0; j < 3; j++ {
// 每个元素的值为其行索引和列索引之和
twoD[i][j] = i + j
}
}
// 打印二维数组twoD的所有元素
fmt.Println("2d:", twoD) //[[0 1 2] [1 2 3]]
}
6. slice基本语法
声明切片
var sliceName []elementType
sliceName:切片的名称。elementType:切片中元素的类型。
使用 make 函数初始化切片
sliceName := make([]elementType, length, capacity)
length:切片的初始长度。capacity:切片的初始容量(可选)。如果不指定,容量将等于长度。
例如,创建一个长度和容量都为5的整数切片:
s := make([]int, 5)
使用字面量初始化切片
sliceName := []elementType{value1, value2, value3, ...}
例如,创建一个包含三个整数的切片:
s := []int{1, 2, 3}
访问和修改切片元素
切片的元素可以通过索引访问和修改,类似于数组:
element := sliceName[index] // 获取索引为index的元素
sliceName[index] = value // 将索引为index的元素设置为value
切片的长度和容量
切片的长度和容量可以通过内置的 len() 和 cap() 函数获取:
length := len(sliceName) // 获取切片的长度
capacity := cap(sliceName) // 获取切片的容量
切片的扩展和收缩
扩展切片
可以通过追加元素来扩展切片:
sliceName = append(sliceName, value1, value2, ...)
如果切片没有足够的容量来存储新元素,append 函数会自动分配新的底层数组。
收缩切片
可以通过设置新的切片长度来收缩切片:
sliceName = sliceName[:newLength]
这会创建一个新的切片,包含原切片的前 newLength 个元素。
切片的遍历
可以使用 for 循环或 range 循环遍历切片:
for index := 0; index < len(sliceName); index++ {
fmt.Println(sliceName[index])
}
for index, value := range sliceName {
fmt.Printf("Index: %d, Value: %d\n", index, value)
}
切片的复制
可以使用 copy 函数复制切片:
copy(destination, source)
destination:目标切片。source:源切片。
copy 函数会将 source 切片中的元素复制到 destination 切片中,直到 destination 切片的容量限制或 source 切片的末尾。
多维切片
虽然Go没有内置的多维数组,但可以通过切片的切片来创建多维切片:
var twoDimSlice [][]int
切片和数组的转换
切片可以基于数组创建,也可以将切片转换为数组:
array := [5]int{1, 2, 3, 4, 5}
slice := array[:] // 基于数组创建切片
slice := []int{1, 2, 3}
array := slice // 将切片转换为数组(注意:这仅在切片长度等于数组长度时有效)
package main
import "fmt"
func main() {
// 初始化一个长度为5的字符串切片
s := make([]string, 5)
// 为切片s的前三个元素赋值
s[0] = "a"
s[1] = "b"
s[2] = "c"
// 打印切片s的第一个元素
fmt.Println("get:", s[0]) //get: a
// 打印切片s的长度
fmt.Println("len:", len(s)) //len: 5
// 打印切片s的内容
fmt.Println("slice:", s) //slice: [a b c]
// 向切片s追加一个元素
s = append(s, "d")
// 向切片s追加多个元素
s = append(s, "e", "f")
// 打印更新后的切片s的内容
fmt.Println("slice:", s) //slice: [a b c d e f]
// 创建一个新的切片c,长度与s相同,并将s的元素复制到c
c := make([]string, len(s))
copy(c, s)
// 打印复制后的切片c的内容
fmt.Println("copy:", c)
// 打印切片s的子切片,从索引2到4的元素
fmt.Println(s[2:5]) // [c d e]
// 打印切片s的子切片,从索引0到4的元素
fmt.Println(s[:5]) // [a b c d e]
// 打印切片s的子切片,从索引2到末尾的元素
fmt.Println(s[2:]) // [c d e f]
// 初始化一个预定义元素的切片good
good := []string{"g", "o", "o", "d"}
// 打印切片good的内容
fmt.Println(good) // [g o o d]
}
7. map 基本语法
在Go语言中,map 是一种内置的数据类型,它存储键值对(key-value pairs)。map 是无序的,并且每个键都是唯一的。以下是Go中map的基本语法和操作:
声明map
var mapName map[keyType]valueType
mapName:map的名称。keyType:键的类型,必须支持比较操作(即不能是切片、映射、接口等)。valueType:值的类型。
使用 make 函数初始化map
mapName := make(map[keyType]valueType)
使用字面量初始化map
mapName := map[keyType]valueType{
key1: value1,
key2: value2,
// ...
}
例如,创建一个存储字符串到整数映射的map:
ages := map[string]int{
"alice": 30,
"bob": 25,
"charlie": 35,
}
访问map元素
访问map中的元素会返回两个值:一个是元素的值,另一个是布尔值,指示键是否存在于map中。
value, ok := mapName[key]
value:如果key存在,则为对应的值,否则为valueType的零值。ok:如果key存在,则为true,否则为false。
age, ok := ages["bob"]
if ok {
fmt.Println("Bob's age is", age)
} else {
fmt.Println("Bob is not in the map")
}
设置map元素
mapName[key] = value
删除map元素
delete(mapName, key)
遍历map
for key, value := range mapName {
// 使用key和value
}
map的长度
可以通过内置的len()函数获取map的长度:
length := len(mapName)
检查键是否存在
可以使用if语句和ok变量检查键是否存在:
if value, ok := mapName[key]; ok {
// 键存在
} else {
// 键不存在
}
map是引用类型
map是引用类型,当map作为参数传递给函数时,传递的是引用的副本,但这个副本指向同一个底层数据结构。
map的容量和扩容
map的容量会自动增长以容纳更多的键值对,但可以通过预先设置容量来优化性能:
mapName := make(map[keyType]valueType, initialCapacity)
map和nil
map变量可以是nil,这表示它没有被初始化。尝试访问nil的map会导致运行时错误。
package main
import "fmt"
// main函数是程序的入口点
func main() {
// 创建一个映射(map),用于存储字符串键和整数值
m := make(map[string]int)
// 向map中添加键值对
m["a"] = 1
m["b"] = 2
m["c"] = 3
// 打印map的当前状态
fmt.Println("map:", m)
// 打印map的长度
fmt.Println("len:", len(m))
// 通过键访问并打印map中的值
fmt.Println("a:", m["a"])
fmt.Println("b", m["b"])
// 尝试访问一个不存在的键,并打印结果
fmt.Println("unknow:", m["unknow"])
// 使用r, ok模式检查键是否存在
r, ok := m["unknow"]
fmt.Println("r", r, "ok", ok)
// 遍历map,并打印所有键值对
for k, v := range m {
fmt.Println(k, v)
}
fmt.Println("删除后")
// 从map中删除键为"a"的键值对
delete(m, "a")
// 遍历map,并打印所有键值对
for k, v := range m {
fmt.Println(k, v)
}
// 创建并初始化两个map,使用不同的初始化语法
m2 := map[string]int{"one": 1, "two": 2}
var m3 = map[string]int{"one": 1, "two": 2}
// 打印新创建的maps
fmt.Println(m2, m3)
}
8. range基本语法
在Go语言中,range 关键字用于 for 循环中迭代数组、切片、通道或映射的元素。以下是 range 的基本语法和用法:
基本语法
对于数组、切片和字符串,range 返回两个值:索引和值。对于映射,它返回键和值。基本语法如下:
for key, value := range collection {
// 使用 key 和 value
}
如果只需要其中一个值,可以使用下划线 _ 忽略另一个值:
for _, value := range collection // 只获取值
for key := range collection // 只获取键
遍历数组和切片
当 range 用于数组或切片时,它返回每个元素的索引和值:
nums := []int{1, 2, 3, 4}
for index, value := range nums {
fmt.Printf("Index: %d, Value: %d\n", index, value)
}
如果只需要值,可以忽略索引:
for _, value := range nums {
fmt.Println(value)
}
遍历字符串
当 range 用于字符串时,它返回每个字符的索引和 Unicode 代码点(rune):
str := "hello"
for index, char := range str {
fmt.Printf("Index: %d, Character: %c\n", index, char)
}
遍历映射(Map)
当 range 用于映射时,它返回键和值:
fruits := map[string]string{"a": "apple", "b": "banana"}
for key, value := range fruits {
fmt.Printf("Key: %s, Value: %s\n", key, value)
}
如果只需要键或值,可以使用下划线 _ 忽略不需要的部分:
for _, value := range fruits { // 忽略键,只关心值
fmt.Println(value)
}
for key := range fruits { // 忽略值,只关心键
fmt.Println(key)
}
遍历通道(Channel)
range 也可以用于遍历通道,前提是该通道是关闭的,否则它会一直阻塞等待新数据。range 会从通道中接收数据,直到通道被关闭:
ch := make(chan int, 2)
ch <- 1
ch <- 2
close(ch) // 关闭通道
for value := range ch {
fmt.Println(value)
}
忽略值
在遍历时可以使用 _ 来忽略索引或值:
nums := []int{2, 3, 4}
for _, num := range nums {
fmt.Println("value:", num)
}
for i := range nums {
fmt.Println("index:", i)
}
package main
import "fmt"
// main 是程序的入口点。
func main() {
// 初始化一个整数数组 nums。
nums := []int{2, 3, 4}
// 初始化 sum 变量为 0,用于计算数组元素的总和。
sum := 0
// 遍历 nums 数组,计算总和,并打印等于 3 的元素及其索引。
for i, num := range nums {
sum += num
if num == 3 {
fmt.Println("index:", i, "num:", num)
}
}
// 打印数组元素的总和。
fmt.Println("sum:", sum)
// 初始化一个映射 m,用于演示映射的遍历和元素删除。
m := map[string]int{"a": 1, "b": 2}
// 遍历映射 m,删除键为 "b" 的元素,并打印每个键值对。
for k, v := range m {
if k == "b" {
delete(m, "b")
}
fmt.Println("key:", k, "val:", v)
}
// 再次遍历映射 m,仅打印键,以显示前一次删除的效果。
for k := range m {
fmt.Println("key:", k)
}
}
9. func
基本语法
func functionName(parameter1 type1, parameter2 type2, ...) returnType {
// 函数体
}
func:声明函数的关键字。functionName:函数的名称。parameter1 type1,parameter2 type2, ...:函数的参数列表,每个参数包括名称和类型。returnType:函数的返回值类型。如果函数没有返回值,则可以省略返回类型。// 函数体:函数的代码块。
无参数和无返回值的函数
如果函数没有参数和返回值,可以省略参数列表和返回类型:
func functionName() {
// 函数体
}
多返回值
Go函数可以返回多个值:
func functionName() (returnType1, returnType2) {
return value1, value2
}
命名返回值
在函数声明时,可以为返回值指定名称,这样在 return 语句中可以省略这些值:
func functionName() (result int, error error) {
// ...
return // 返回 result 和 error 的当前值
}
匿名函数
Go支持匿名函数,即没有名称的函数:
func(param1 type1, param2 type2) returnType {
// 函数体
}
匿名函数经常用作回调函数或直接作为参数传递给其他函数。
变长参数
函数可以接受变长参数,即参数数量不固定:
func functionName(args ...type) {
// 函数体
}
args 将接收所有传递给函数的参数作为一个切片。
递归函数
Go函数可以递归调用自身:
func factorial(n int) int {
if n == 0 {
return 1
}
return n * factorial(n-1)
}
函数作为类型
在Go中,函数也是一等公民,可以像任何其他值一样传递和返回:
func functionName(f func(type1) returnType1) {
// 使用函数 f
}
defer, panic, 和 recover
Go提供了 defer, panic, 和 recover 关键字来处理异常情况和资源清理:
defer用于确保函数在返回前执行一些代码(例如关闭文件)。panic用于触发一个panic事件,通常用于错误处理。recover用于捕获panic,阻止它冒泡到更高层。
示例
package main
import "fmt"
// 无参数和返回值的函数
func sayHello() {
fmt.Println("Hello, World!")
}
// 带参数和返回值的函数
func add(a int, b int) int {
return a + b
}
// 多返回值的函数
func minMax(list []int) (int, int) {
minX, maxX := list[0], list[0]
for _, v := range list {
if v < minX {
minX = v
}
if v > maxX {
maxX = v
}
}
return minX, maxX
}
func main() {
sayHello()
fmt.Println(add(1, 2))
minX, maxX := minMax([]int{1, 3, 2, 8, 4})
fmt.Println("Min:", minX, "Max:", maxX)
}
package main
import "fmt"
// main 是程序的入口点。
func main() {
// 初始化变量a和b用于后续的加法操作。
a := 1
b := 2
// 调用add函数计算a和b的和,并打印结果。
c := add(a, b)
fmt.Println(c)
// 调用add1函数通过引用计算a和b的和,并打印结果。
d := add1(&a, &b)
fmt.Println(d)
// 调用exists函数检查键'a'是否存在于给定的映射中,并打印结果。
val, exit := exists(map[string]string{"a": "apple"}, "a")
fmt.Println(val, exit)
}
// add 函数接收两个整数参数并返回它们的和。
// 参数:
//
// a - 第一个加数。
// b - 第二个加数。
//
// 返回值:
//
// 两个参数的和。
func add(a int, b int) int {
return a + b
}
// add1 函数接收两个整数的指针并返回它们的和。
// 这个函数展示如何通过引用操作数值。
// 参数:
//
// a - 第一个加数的指针。
// b - 第二个加数的指针。
//
// 返回值:
//
// 两个参数的和。
func add1(a *int, b *int) int {
return *a + *b
}
// exists 函数检查一个键是否存在于给定的映射中。
// 参数:
//
// m - 包含键值对的映射。
// key - 要检查的键。
//
// 返回值:
//
// val - 键对应的值,如果键存在。
// exit - 布尔值,指示键是否存在。
func exists(m map[string]string, key string) (val string, exit bool) {
val, exit = m[key]
return val, exit
}
10. point
结构体
type Point struct {
X int
Y int
}
这里定义了一个名为Point的结构体,它有两个字段:X和Y,通常用来表示二维空间中的一个点。
创建点的实例
p := Point{X: 1, Y: 2}
这行代码创建了一个Point类型的实例p,并将X字段设置为1,Y字段设置为2。
使用字段初始化器
p := Point{Y: 2, X: 1} // 也可以先Y后X,只要保证结构体定义的顺序一致
省略字段名
如果字段名作为变量名引入(通常是短变量声明:=),则可以省略字段名:
p := Point{1, 2} // X:1, Y:2
创建点并指定变量名
var p Point
p.X = 1
p.Y = 2
访问点的字段
fmt.Println(p.X) // 输出点的X坐标
fmt.Println(p.Y) // 输出点的Y坐标
方法和点
您可以为Point结构体添加方法,使其行为更丰富:
func (p Point) String() string {
return fmt.Sprintf("(%d, %d)", p.X, p.Y)
}
这个String方法允许Point类型的实例以字符串的形式描述自己。
使用方法
fmt.Println(p.String()) // 输出 "(1, 2)"
点的比较
结构体可以被比较,如果它们是可导出的(字段名以大写字母开头),则可以比较两个点是否相等:
type Point struct {
X, Y int
}
p1 := Point{X: 1, Y: 2}
p2 := Point{X: 1, Y: 2}
if p1 == p2 {
fmt.Println("p1 and p2 are equal")
} else {
fmt.Println("p1 and p2 are not equal")
}
package main
import "fmt"
// main 是程序的入口点。
func main() {
// 初始化变量 n 为 5。
n := 5
// 调用 add2 函数,传入 n 的值。
// 由于 add2 函数内部修改的是 n 的副本,因此这里的 n 不会被改变。
add2(n)
// 输出 n 的值,预期为 5。
fmt.Println(n)
// 调用 add2ptr 函数,传入 n 的地址。
// 由于 add2ptr 函数内部修改的是 n 本身,因此这里的 n 会被改变。
add2ptr(&n)
// 输出 n 的值,预期为 7。
fmt.Println(n)
}
// add2 函数接收一个整数参数 n,并将其值增加 2。
// 这个函数演示了值传递的例子,即在函数内部修改参数不会影响到原始变量。
func add2(n int) {
n += 2
}
// add2ptr 函数接收一个指向整数的指针 n,并将该整数的值增加 2。
// 这个函数演示了引用传递的例子,即在函数内部通过指针修改变量会反映到原始变量上。
func add2ptr(n *int) {
*n += 2
}
11. struct基本语法
定义结构体
type StructName struct {
Field1 Type1
Field2 Type2
// ...
}
StructName是结构体的名称。Field1,Field2, ... 是结构体的字段名。Type1,Type2, ... 是字段对应的类型。
例如,定义一个表示人的Person结构体:
type Person struct {
Name string
Age int
City string
}
创建结构体实例
有几种方式可以创建结构体实例:
使用字段名初始化
p := Person{Name: "Alice", Age: 30, City: "Wonderland"}
使用字段名缩写
如果变量名和字段名相同,可以省略字段名:
p := Person{Name: "Bob", Age: 25}
使用&操作符创建指针
p := &Person{Name: "Charlie", Age: 35}
使用字面量初始化
如果结构体的所有字段都是可导出的(首字母大写),则可以省略字段名:
p := Person{"Dave", 40, "Somewhere"}
访问结构体字段
使用点(.)操作符来访问结构体的字段:
fmt.Println(p.Name) // 输出 "Alice"
修改结构体字段
可以直接修改结构体的字段值:
p.Age = 31
使用结构体字面量创建实例
p := Person{Name: "Eve", Age: 28}
结构体指针
如果你需要修改结构体的字段,可以通过结构体指针来实现:
p := &Person{Name: "Frank", Age: 22}
p.City = "Nowhere" // 修改City字段
结构体方法
可以为结构体定义方法,方法是特殊的函数,它接受一个隐藏的参数,即接收者:
func (p *Person) SetCity(city string) {
p.City = city
}
使用结构体指针作为接收者,可以在方法内部修改结构体的字段。
结构体方法调用
p.SetCity("Gotham")
结构体组合
结构体可以包含其他结构体作为字段:
type ContactInfo struct {
Email string
Phone string
}
type Person struct {
Name string
Age int
ContactInfo
}
在这个例子中,Person 结构体包含了 ContactInfo 结构体。
匿名字段
当结构体包含另一个结构体作为字段时,可以省略字段名,这样的字段称为匿名字段:
type Person struct {
Name string
Age int
ContactInfo // 匿名字段
}
嵌入结构体方法
嵌入的结构体可以为其包含的结构体提供方法:
func (ci ContactInfo) Format() string {
return fmt.Sprintf("Email: %s, Phone: %s", ci.Email, ci.Phone)
}
p := Person{Name: "Grace", Age: 27, ContactInfo: ContactInfo{Email: "email@example.com", Phone: "1234567890"}}
fmt.Println(p.Format()) // 调用嵌入的ContactInfo的方法
package main
import "fmt"
// user 定义了一个用户结构体,包含用户名和密码字段
type user struct {
name string
password string
}
// main 是程序的入口点
func main() {
// 初始化用户a,包含用户名和密码
a := user{name: "aaaaaa", password: "111111"}
// 初始化用户b,仅包含用户名,密码为空
b := user{name: "bbbbbb"}
// 初始化用户c,仅包含密码,用户名为空
c := user{password: "333333"}
// 初始化用户d,包含用户名和密码,使用位置语法
d := user{"dddddd", "444444"}
// 打印用户a、b、c、d的信息
fmt.Println(a, b, c, d)
// 更新用户b的密码
b.password = "222222"
// 初始化用户e,并设置其用户名和密码
var e user
e.name = "eeeeee"
e.password = "eeeeee"
// 打印用户e的信息
fmt.Println(e)
// 再次打印用户a、b、c、d的信息,此时用户b的密码已被更新
fmt.Println(a, b, c, d)
// 检查用户a的密码是否匹配
fmt.Println(checkPassword(a, "111111"))
// 检查用户a的密码是否匹配,使用指针调用函数
fmt.Println(checkPassword2(&a, "222222")) // false
}
// checkPassword 检查给定的密码是否与用户密码匹配
// 参数a: 用户结构体
// 参数password: 需要检查的密码
// 返回值: 布尔值,表示密码是否匹配
func checkPassword(a user, password string) bool {
return a.password == password
}
// checkPassword2 检查给定的密码是否与用户密码匹配,使用用户指针作为参数
// 参数u: 用户结构体的指针
// 参数password: 需要检查的密码
// 返回值: 布尔值,表示密码是否匹配
func checkPassword2(u *user, password string) bool {
return u.password == password
}
12. struct-method
package main
import "fmt"
// man 结构体代表一个用户,包含用户名和密码字段。
type man struct {
name string
password string
}
// checkPassword 检查提供的密码是否与用户密码匹配。
// 参数:
// password - 需要验证的密码。
// 返回值:
// 如果密码匹配,则返回 true;否则返回 false。
func (u man) checkPassword(password string) bool {
return u.password == password
}
// resetPassword 用于重置用户的密码。
// 参数:
// password - 用户的新密码。
// 请注意,此方法修改用户密码,因此需要以指针方式调用以确保更改生效。
func (u *man) resetPassword(password string) {
u.password = password
}
func main() {
// 创建一个名为 "wang",密码为 "1024" 的 man 实例。
a := man{name: "wang", password: "1024"}
// 调用 resetPassword 方法将用户密码重置为 "2048"。
a.resetPassword("2048")
// 验证用户密码是否已成功更新为 "2048"。
fmt.Println(a.checkPassword("2048")) // 输出: true
}
13. error
package main
import (
"errors"
"fmt"
)
// er 结构体用于表示用户,包含用户名和密码字段。
type er struct {
name string
password string
}
// findEr 函数用于在用户切片中查找指定用户名的用户。
// 参数:
// - ers: 用户切片,包含多个用户信息。
// - name: 要查找的用户名。
//
// 返回值:
// - *er: 如果找到用户,则返回用户指针。
// - error: 如果未找到用户,则返回错误信息。
func findEr(ers []er, name string) (v *er, err error) {
for _, e := range ers {
if e.name == name {
return &e, nil
}
}
return nil, errors.New("not found")
}
func main() {
// 调用 findEr 函数查找用户名为 "wang" 的用户。
e, err := findEr([]er{{"wang", "1024"}, {"li", "512"}}, "wang")
if err != nil {
fmt.Println(err)
return
}
fmt.Println(e.name, e.password)
fmt.Println(e)
// 调用 findEr 函数查找用户名为 "li" 的用户,使用了内部函数调用和条件语句。
if e, err := findEr([]er{{"wang", "1024"}, {"zhang", "512"}}, "li"); err != nil {
fmt.Println(err)
return
} else {
fmt.Println(e.name, e.password)
}
}
14. string
package main
import (
"fmt"
"strings"
)
// main 是程序的入口点
func main() {
// 初始化字符串变量a
a := "hello"
// 检查字符串a中是否包含子串"ll"
fmt.Println(strings.Contains(a, "ll")) // 输出: true
// 计算字符串a中'l'字符出现的次数
fmt.Println(strings.Count(a, "l")) // 输出: 2
// 检查字符串a是否以"he"开头
fmt.Println(strings.HasPrefix(a, "he")) // 输出: true
// 检查字符串a是否以"llo"结尾
fmt.Println(strings.HasSuffix(a, "llo")) // 输出: true
// 找到子串"ll"在字符串a中首次出现的索引
fmt.Println(strings.Index(a, "ll")) // 输出: 2
// 将字符串切片连接成单一字符串,中间用"-"分隔
fmt.Println(strings.Join([]string{"he", "llo"}, "-")) // 输出: he-llo
// 重复字符串a两次
fmt.Println(strings.Repeat(a, 2)) // 输出: hellohello
// 将字符串a中的所有'e'字符替换为'E'
fmt.Println(strings.Replace(a, "e", "E", -1)) // 输出: hEllo
// 将字符串"a-b-c"按"-"分割为字符串切片
fmt.Println(strings.Split("a-b-c", "-")) // 输出: [a b c]
// 将字符串a转换为小写
fmt.Println(strings.ToLower(a)) // 输出: hello
// 将字符串a转换为大写
fmt.Println(strings.ToUpper(a)) // 输出: HELLO
// 计算字符串a的长度
fmt.Println(len(a)) // 输出: 5
// 初始化字符串变量b,包含中文字符
b := "你好"
// 计算字符串b的长度,中文字符占两个字节
fmt.Println(len(b)) // 输出: 6
}
15. fmt
package main
import "fmt"
// point 结构体代表二维空间中的一个点
type point struct {
x, y int
}
func main() {
// 初始化字符串变量s
s := "hello"
// 初始化整数变量n
n := 123
// 初始化point类型的结构体变量p
p := point{1, 2}
// 打印变量s和n的值
fmt.Println(s, n) // hello 123
// 打印结构体变量p的值
fmt.Println(p) // {1 2}
// 使用fmt.Printf函数格式化输出变量s的值
fmt.Printf("s=%v\n", s) // s=hello
// 使用fmt.Printf函数格式化输出变量n的值
fmt.Printf("n=%v\n", n) // n=123
// 使用fmt.Printf函数格式化输出结构体变量p的值
fmt.Printf("p=%v\n", p) // p={1 2}
// 使用fmt.Printf函数格式化输出结构体变量p的值,包含字段名
fmt.Printf("p=%+v\n", p) // p={x:1 y:2}
// 使用fmt.Printf函数格式化输出结构体变量p的值,使用Go语法表示
fmt.Printf("p=%#v\n", p) // p=main.point{x:1, y:2}
// 初始化浮点数变量f
f := 3.141592653
// 打印浮点数变量f的值
fmt.Println(f) // 3.141592653
// 使用fmt.Printf函数格式化输出浮点数变量f的值,保留两位小数
fmt.Printf("%.2f\n", f) // 3.14
}
16. json
package main
import (
"encoding/json"
"fmt"
)
// userInfo 结构体定义了用户信息的格式
// 包含用户名、年龄和爱好
type userInfo struct {
Name string
Age int `json:"age"` // Age 字段在 JSON 中对应 "age" 键
Hobby []string
}
func main() {
// 创建一个 userInfo 实例
a := userInfo{Name: "wang", Age: 18, Hobby: []string{"Golang", "TypeScript"}}
// 将 userInfo 实例序列化为 JSON 格式
buf, err := json.Marshal(a)
if err != nil {
panic(err)
}
// 输出原始的字节流
fmt.Println(buf) // [123 34 78 97...
// 输出格式化后的 JSON 字符串
fmt.Println(string(buf)) // {"Name":"wang","age":18,"Hobby":["Golang","TypeScript"]}
// 使用缩进格式序列化 JSON,以便更易读
buf, err = json.MarshalIndent(a, "", "\t")
if err != nil {
panic(err)
}
// 输出格式化后的 JSON 字符串
fmt.Println(string(buf))
// 创建一个 userInfo 的空实例来存储反序列化的数据
var b userInfo
// 将 JSON 字符串反序列化为 userInfo 实例
err = json.Unmarshal(buf, &b)
if err != nil {
panic(err)
}
// 输出反序列化后的 userInfo 实例
fmt.Printf("%#v\n", b) // main.userInfo{Name:"wang", Age:18, Hobby:[]string{"Golang", "TypeScript"}}
}
17. time
package main
import (
"fmt"
"time"
)
func main() {
// 获取当前时间
now := time.Now()
fmt.Println(now) // 输出当前时间的详细信息
// 创建一个特定的时间点t
t := time.Date(2022, 3, 27, 1, 25, 36, 0, time.UTC)
// 创建另一个特定的时间点t2
t2 := time.Date(2022, 3, 27, 2, 30, 36, 0, time.UTC)
// 输出时间点t的详细信息
fmt.Println(t)
// 分别输出时间点t的年、月、日、小时和分钟
fmt.Println(t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute())
// 以特定格式输出时间点t
fmt.Println(t.Format("2006-01-02 15:04:05"))
// 计算时间点t2和t之间的差异
diff := t2.Sub(t)
fmt.Println(diff) // 输出时间差异的详细信息
// 输出时间差异的分钟数和秒数
fmt.Println(diff.Minutes(), diff.Seconds())
// 解析一个时间字符串为时间点t3
t3, err := time.Parse("2006-01-02 15:04:05", "2022-03-27 01:25:36")
if err != nil {
panic(err) // 如果解析出错,抛出panic
}
// 比较时间点t3和t是否相等
fmt.Println(t3 == t)
// 输出当前时间的Unix时间戳
fmt.Println(now.Unix())
}
18. strconv
package main
import (
"fmt"
"strconv"
)
// main函数是程序的入口点
func main() {
// 将字符串"1.234"解析为64位浮点数
f, _ := strconv.ParseFloat("1.234", 64)
fmt.Println(f) // 输出: 1.234
// 将字符串"111"解析为10进制的64位整数
n, _ := strconv.ParseInt("111", 10, 64)
fmt.Println(n) // 输出: 111
// 将字符串"0x1000"解析为十六进制表示的64位整数
n, _ = strconv.ParseInt("0x1000", 0, 64)
fmt.Println(n) // 输出: 4096
// 将字符串"123"解析为整数
n2, _ := strconv.Atoi("123")
fmt.Println(n2) // 输出: 123
// 尝试将字符串"AAA"解析为整数,这将导致解析错误
n2, err := strconv.Atoi("AAA")
fmt.Println(n2, err) // 输出: 0 strconv.Atoi: parsing "AAA": invalid syntax
}