目录
一、判断语句
1.if语句
Go
package main
import "fmt"
func main() {
fmt.Println("请输入你的年龄:")
var age int
fmt.Scan(&age)
if age <= 18 {
if age <= 10 {
fmt.Println("儿童")
} else {
fmt.Println("未成年")
}
} else if age <= 35 {
fmt.Println("青年")
} else {
fmt.Println("中老年")
}
}
2.switch语句
可以理解为case的值就是switch的枚举结果
一般来说,go的switch的多选一,满足其中一个结果之后,就结束switch了
Go
package main
import "fmt"
func main() {
fmt.Println("请输入你的年龄:")
var age int
fmt.Scan(&age)
switch {
case age <= 0:
fmt.Println("未出生")
case age <= 18:
fmt.Println("未成年")
case age <= 35:
fmt.Println("青年")
default:
fmt.Println("中年")
}
}
我输入一个12,我希望它能输出满足的所有条件,例如我希望它输出,未成年,青年
Go
package main
import "fmt"
func main() {
fmt.Println("请输入你的年龄:")
var age int
fmt.Scan(&age)
switch {
case age <= 0:
fmt.Println("未出生")
fallthrough
case age <= 18:
fmt.Println("未成年")
fallthrough
case age <= 35:
fmt.Println("青年")
default:
fmt.Println("中年")
}
}
二、循环语句
1.传统for循环
Go
package main
import "fmt"
func main() {
var sum = 0
for i := 0; i <= 100; i++ {
sum += i
}
fmt.Println(sum)
}
2.死循环
Go
package main
import (
"fmt"
"time"
)
func main() {
//每隔1秒打印当前的时间
for {
time.Sleep(1 * time.Second)
fmt.Println(time.Now().Format("2006-01-02 15:04:05")) // 年月日时分秒的固定格式
}
}
3.while模式
Go
package main
import "fmt"
func main() {
//由于golang没有while循环,如果需要,则是由for循环稍微变化得来
i := 0
sum := 0
for i <= 100 {
sum += i
i++
}
fmt.Println(sum)
}
4.do-while模式
Go
package main
import "fmt"
func main() {
//do-while模式就是先执行一次循环体,再判断
i := 0
sum := 0
for {
sum += i
i++
if i == 101 {
break
}
}
fmt.Println(sum)
}
5.遍历切片
Go
package main
import "fmt"
func main() {
// 第一个参数是索引,第二个参数是值
s := []string{"os", "lee"}
for index, s2 := range s {
fmt.Println(index, s2)
}
}
6.遍历map
Go
package main
import "fmt"
func main() {
//第一个参数就是key,第二个就是value
s := map[string]int{
"age": 24,
"price": 1000,
}
for key, val := range s {
fmt.Println(key, val)
}
}
7.break,continue
- break用于跳出当前循环
- continue用于跳过本轮循环
三、函数,指针
函数是一段封装了特定功能的可重用代码块,用于执行特定的任务或计算
函数接受输入(参数)并产生输出(返回值)
1.函数定义
Go
package main
import "fmt"
func add(n1 int, n2 int) int {
fmt.Println(n1, n2)
return n1 + n2
}
// 参数类型一样,可以合并在一起
func add1(n1, n2 int) int {
fmt.Println(n1, n2)
return n1 + n2
}
// 多个参数
func add2(numList ...int) {
fmt.Println(numList)
}
func main() {
i := add(1, 2)
fmt.Println(i)
i2 := add1(1, 2)
fmt.Println(i2)
add2(1, 2, 3, 4)
}
2.匿名函数
匿名函数(Anonymous Function)是一种没有命名的函数定义。
特点:
- 无名称:匿名函数没有显式的名字,因此不能像普通函数那样通过名字被调用或在其他地方被引用。
- 内联定义:可以在需要使用函数的地方直接定义,无需单独的函数声明或定义部分。
- 简洁性:特别适合用于一次性使用的简单功能,避免了为一个小功能特意命名一个函数的繁琐。
- 可赋值给变量:匿名函数可以赋值给一个变量,通过变量名来调用,此时变量充当了函数的"代理名"。
- 可作为参数或返回值:匿名函数可以作为其他函数的参数或返回值,这是高阶函数的基础。
- 访问外部变量:匿名函数可以访问其定义时所在作用域内的变量,形成闭包。
Go
package main
import "fmt"
func main() {
var add = func(a, b int) int {
return a + b
}
fmt.Println(add(1, 2))
}
3.高阶函数
高阶函数是指可以接收一个或多个函数作为参数,或者返回一个函数作为结果的函数。这样的函数提升了代码的抽象层次,使得我们能够以更通用和灵活的方式处理函数,从而简化代码并促进复用。
Go
package main
import "fmt"
func ApplyToIntList(list []int, f func(int) int) []int {
result := make([]int, len(list))
for i, v := range list {
result[i] = f(v)
}
return result
}
func main() {
// 编写一个高阶函数,接受一个整数列表和一个处理函数作为参数,返回处理函数对该列表所有元素应用后的结果列表。 示例代码:
squareAll := ApplyToIntList([]int{1, 2, 3, 4}, func(x int) int { return x * x })
fmt.Println(squareAll) // 输出:[1, 4, 9, 16]
}
4.闭包
闭包是一种特殊的函数,是该函数和其周围环境(即自由变量)组成的绑定关系。当一个函数访问其外部作用域中的变量时,即使在其外部作用域已经结束(函数返回)后,只要闭包还在使用,这些变量仍会保持活跃。闭包常与高阶函数一起使用,作为高阶函数的返回值或参数。
Go
package main
import "fmt"
func counter() func() int {
count := 0
return func() int {
count++
return count
}
}
func main() {
// counter高阶函数,func() int是闭包
counterFunc := counter()
fmt.Println(counterFunc()) // 输出:1
fmt.Println(counterFunc()) // 输出:2
fmt.Println(counterFunc()) // 输出:3
// 创建另一个计数器实例
anotherCounter := counter()
fmt.Println(anotherCounter()) // 输出:1
}
5.值传递和引用传递
- 值传递:函数接收到的是实参的副本,对形参的修改不影响实参。
- 引用传递:函数接收到的是实参的引用,对形参的修改直接影响实参。
稍微了解过编程的都应该知道,计算机上显示的所有的数据都是在内存里面的
也就是说,我们定义的一个变量,它也在内存里面有一块地
正常情况下来说,函数的参数是将之前那块地复制了一份出来
如何证明呢
Go
package main
import "fmt"
func add(num int) {
fmt.Println(&num) // 可以看到,这个n的内存地址和外面num的内存地址是明显不一样的
num = 2 // 这里的修改不会影响外面的num
}
func main() {
num := 20
fmt.Println(&num)
add(num)
fmt.Println(num) // 20
}
也就是说,在函数里面不管怎么修改这个参数,都不会影响原来的那个值
但是,如果我需要在函数体内修改变量的值呢?
这就需要用到引用传递了
我们直接将变量的内存地址传递进去
Go
package main
import "fmt"
func add(num *int) {
fmt.Println(num) // 内存值是一样的
*num = 2 // 这里的修改会影响外面的num
}
func main() {
num := 20
fmt.Println(&num)
add(&num)
fmt.Println(num) // 成功修改 2
}
6.指针
指针是一种特殊的数据类型,它存储的是另一个变量的内存地址。简单来说,指针就是一个变量的"地址标签",通过这个标签,程序可以直接访问和操作该变量在内存中的值。理解指针的关键在于以下几个方面:
-
内存地址与值:
- 变量在内存中占据一块特定的空间,每个变量都有一个唯一的内存地址。
- 变量的值就是存储在这块内存空间中的数据。
-
指针变量声明:
- 声明一个指针变量时,使用星号
*
作为前缀来表示这是一个指针类型,并指定它指向的值的类型。 - 声明格式为
var pointerName *typeName
,其中pointerName
是指针变量名,typeName
是它所指向的值的类型。 - 例如,
var xPtr *int
声明了一个名为xPtr
的指针,它指向一个整型(int
)变量。
- 声明一个指针变量时,使用星号
-
指针初始化与赋值:
- 初始化指针时,通常使用取地址运算符
&
来获取一个变量的内存地址,并将其赋值给指针变量。 - 例如,
x := 10; xPtr := &x
,这里xPtr
被初始化为变量x
的内存地址。
- 初始化指针时,通常使用取地址运算符
-
解引用与指针操作:
- 使用指针访问或修改其指向的值时,需要通过解引用操作。解引用是使用指针变量前的星号
*
运算符来实现的。 - 例如,
*xPtr = 20
会通过xPtr
修改其指向的整型变量的值为 20。 - 同样,
y := *xPtr
会通过xPtr
获取其指向的值,并将其赋值给变量y
。
- 使用指针访问或修改其指向的值时,需要通过解引用操作。解引用是使用指针变量前的星号
-
指针运算限制:
- Go语言中的指针不能进行算术运算(如加减法),这是与C语言的重要区别。试图对指针进行算术运算会在编译阶段报错。
-
指针用途:
- 提高性能:通过指针传递大对象时,避免了值拷贝,提高了效率。
- 修改函数外部变量:函数参数为指针时,函数内部可以修改外部变量的值。
- 实现动态数据结构:如链表、树等,节点间通过指针连接。
- 回调函数和闭包:闭包常常需要捕获并保持对外部变量的引用,这通常是通过指针实现的。
我们只需要记住
&是取地址,*是解引用,获取这个地址指向的值
四、init函数和defer函数
1.init函数
init函数用于在程序启动时执行一些初始化任务,如设置默认值、打开数据库连接、加载配置等。
init函数具有以下特点:
- 无须显式调用:init函数由Go运行时自动调用,无需在代码中显式调用。
- 无参数、无返回值:init函数没有参数,也不返回任何值。
- 可出现在任何包、任何源文件中:一个包(package)中可以有多个init函数,分布在不同的源文件中。
执行顺序:init函数的执行顺序遵循以下规则:
- 包内顺序:同一个包中,init函数按照它们在源文件中出现的顺序依次执行。如果一个源文件中有多个init函数,那么它们按照从上到下的顺序执行。
- 包间顺序:不同包之间的init函数按照包导入的依赖关系进行排序。首先执行被导入包(间接或直接)的所有init函数,然后才执行导入包本身的init函数。具体而言,如果包A导入了包B,那么包B中的所有init函数将在包A中的所有init函数之前执行。
- 主包(main包):主包(包含main()函数的包)的init函数总是在所有非主包的init函数之后执行。
2.defer函数
defer
语句用于延迟执行一个函数调用,直到包含该defer
语句的函数返回时才执行。defer
语句通常用于资源清理、解锁、关闭文件、网络连接等需要确保在函数结束时执行的操作。
defer
函数具有以下特点:
延迟执行 :
defer
语句指定的函数调用会在包含它的函数(或方法)即将返回时,无论正常返回还是通过panic
异常返回,都会被执行。这种特性使得defer
函数特别适合用于确保资源的正确释放,即使在函数执行过程中发生错误或异常。后进先出(LIFO)顺序 :在同一函数中,如果有多个
defer
语句,它们的执行顺序与声明顺序相反,即最后声明的defer
函数最先执行,最先声明的defer
函数最后执行。这种顺序类似于栈的行为。参数求值 :
defer
语句中的函数参数在defer
语句执行时(即函数调用前)就已经求值,但函数体的执行是在函数返回时。
注意事项:
- defer函数的返回值会被计算,但会被丢弃,因此defer通常用于无返回值的函数调用。
- 大量使用defer可能会对函数的性能造成轻微影响,尤其是在函数调用非常频繁的场景。应合理使用,避免过度依赖。
- 如果defer函数内部引发了panic,后续的defer函数仍会按照后进先出的顺序执行,直到遇到recover函数捕获该panic,或者函数结束时panic传播到上层调用者。
3.defer面试题
Go
package main
func DeferFunc1(i int) (t int) {
// t = 1
t = i
defer func() {
t += 3
}()
return t
}
func DeferFunc2(i int) int {
t := i
defer func() {
t += 3
}()
return t
}
func DeferFunc3(i int) (t int) {
defer func() {
t += i
}()
return 2
}
// DeferFunc1中,有名返回(指定返回值命名func test() (t int)),执行 return 语句时,并不会再创建临时变量保存,defer 语句修改了t,即对返回值产生了影响,所以返回4。
// DeferFunc2中,无名返回(返回值没有指定命名),执行Return语句后,Go会创建一个临时变量保存返回值,defer 语句修改的是 t,而不是临时变量,所以返回1。
// DeferFunc3中,有名返回,执行 return 语句时,把2赋值给t,defer 语句再执行t+1,所以返回3。
func main() {
println(DeferFunc1(1))
println(DeferFunc2(1))
println(DeferFunc3(1))
}