Go 语言入门基础语法 | 豆包MarsCode AI刷题

134 阅读33分钟
1. 变量基本语法
  1. 变量声明:使用 var 关键字可以声明一个或多个变量,并指定它们的类型。如果同时声明多个变量,可以使用逗号分隔它们。
var identifier1, identifier2 type
  1. 变量初始化:在声明变量时,可以同时初始化它们。如果没有初始化,则变量默认为零值,即该类型的默认值。
var a string = "Runoob"
var b, c int = 1, 2
  1. 零值:每种类型的零值不同,例如:
    • 数值类型为 0
    • 布尔类型为 false
    • 字符串为 ""(空字符串)
    • 指针、切片、映射、通道和函数为 nil
  1. 类型推断:如果声明变量时不指定类型,编译器会根据赋值表达式推断变量的类型。
var d = true
  1. 短变量声明:在函数内部,可以使用 := 进行简短变量声明,此时编译器会自动推断变量的类型。
a := 1
  1. 全局变量:全局变量只能通过 var 声明,并且如果希望全局变量能被外部包访问,变量名必须以大写字母开头。
var A int = 1
  1. 批量声明:可以使用括号将多个变量声明放在一起,这通常用于全局变量的声明。
var (
    a int
    b string
    c []float32
)
  1. 匿名变量:在某些情况下,可能会使用匿名变量(通常写作 _),主要用于忽略不需要的返回值。
_, err := os.Open("file.txt")
if err != nil {
    log.Fatal(err)
}
  1. 复合类型的初始化:可以一次性初始化数组、切片、映射等复合类型。
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 {
    // 循环体
}
  1. 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)
}

在循环中,可以使用 breakcontinue 来控制循环的执行:

  • 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基本语法
  1. 在Go语言中,if 语句用于基于条件执行不同的代码块。以下是关于Go中if语句的详细信息:

基本语法

Go中的if语句基本语法如下:

if condition {
    // 如果conditiontrue,则执行这里的代码
} else {
    // 如果conditionfalse,则执行这里的代码
}
  • 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语句中声明和可见。

没有elseif语句

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语句中,可以在casedefault块中声明变量,这些变量仅在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
  • mapNamemap的名称。
  • 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,这表示它没有被初始化。尝试访问nilmap会导致运行时错误。

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的结构体,它有两个字段:XY,通常用来表示二维空间中的一个点。

创建点的实例

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
}