通过示例学习-Go-语言-2023-二十五-
通过示例学习 Go 语言 2023(二十五)
Go 语言中的 Panic 和 Recover
这是第二十八章,也是 Go 语言综合教程系列的最后一章。请参考此链接获取该系列的其他章节 – Go 语言综合教程系列
上一个教程 – 错误 - 第二部分
现在让我们来查看当前的教程。下面是当前教程的目录。
目录
概述
-
运行时错误 Panic
-
显式调用 panic 函数
-
使用 defer 进行 Panic
-
在 Go 语言中恢复
-
Panic/Recover 和 Goroutine
-
打印堆栈跟踪
-
当 panic 被恢复时函数的返回值
-
结论 * *# 概述
Go 语言中的 panic 类似于异常。panic 旨在在异常条件下退出程序。panic 可以通过两种方式在程序中发生
-
程序中的运行时错误
-
通过显式调用 panic 函数。当程序无法继续并且必须退出时,程序员可以调用此函数
Go 提供了一种特殊的函数来创建 panic。以下是该函数的语法
func panic(v interface{})
该函数可以被程序员显式调用以创建 panic。它接受一个空接口作为参数。当程序发生 panic 时,它输出两件事
-
传递给 panic 函数的错误信息作为参数
-
panic 发生时的堆栈跟踪
运行时错误 Panic
程序中的运行时错误可能发生在以下情况下
-
超出范围的数组访问
-
在 nil 指针上调用函数
-
在关闭的通道上发送
-
不正确的类型断言
让我们来看一个由于超出范围的数组访问导致的运行时错误示例。
package main
import "fmt"
func main() {
a := []string{"a", "b"}
print(a, 2)
}
func print(a []string, index int) {
fmt.Println(a[index])
}
输出
panic: runtime error: index out of range [2] with length 2
goroutine 1 [running]:
main.checkAndPrint(...)
main.go:12
main.main()
/main.go:8 +0x1b
exit status 2
在上面的程序中,我们有一个长度为 2 的切片,我们试图在 print 函数中访问索引为 3 的切片。超出边界的访问是不允许的,这将引发 panic,如输出所示。请注意,输出中有两件事
-
错误信息
-
panic 发生时的堆栈跟踪
在程序中可能发生运行时错误的情况还有很多。我们不会提到所有情况,但你可以理解大概
显式调用 panic 函数
程序员可以显式调用 panic 函数的一些情况有:
-
该函数期望一个有效的参数,但却传入了 nil 参数。在这种情况下,程序无法继续,将为传入的 nil 参数抛出 panic。
-
任何其他无法继续的场景。
让我们来看一个例子。
package main
import "fmt"
func main() {
a := []string{"a", "b"}
checkAndPrint(a, 2)
}
func checkAndPrint(a []string, index int) {
if index > (len(a) - 1) {
panic("Out of bound access for slice")
}
fmt.Println(a[index])
}
输出
panic: Out of bound access for slice
goroutine 1 [running]:
main.checkAndPrint(0xc000104f58, 0x2, 0x2, 0x2)
main.go:13 +0xe2
main.main()
main.go:8 +0x7d
exit status 2
在上述程序中,我们再次有一个函数 checkAndPrint,它接受一个切片作为参数和一个索引。然后它检查传入的索引是否大于切片长度减去 1。如果是,则为切片的越界访问,因此会抛出 panic。如果不是,则打印该索引处的值。请注意,在输出中有两件事情。
-
错误消息
-
panic 发生位置的堆栈跟踪
使用 defer 的 Panic
当函数中抛出 panic 时,该函数的执行将停止,任何已延迟的函数将被执行。实际上,堆栈中所有函数调用的延迟函数也会被执行,直到所有函数都返回。此时程序将退出,并打印 panic 消息。
如果存在 defer 函数,它将被执行,控制将返回给调用函数,如果调用函数中也有 defer 函数,则会再次执行,链条将继续,直到程序退出。
让我们来看一个例子。
package main
import "fmt"
func main() {
defer fmt.Println("Defer in main")
panic("Panic with Defer")
fmt.Println("After painc in f2")
}
输出
Defer in main
panic: Panic Create
goroutine 1 [running]:
main.main()
/Users/slohia/go/src/github.com/golang-examples/articles/tutorial/panicRecover/deferWithPanic/main.go:7 +0x95
exit status 2
在上述程序中,我们首先有一个 defer 函数,然后我们手动触发 panic。正如你在输出中看到的,defer 函数得到了执行,下面的行在输出中被打印。
Defer in main
让我们理解当程序中发生 panic 时会发生什么。想象一下从 main 函数到 f1 函数再到 f2 函数的函数调用。
main->f1->f2
现在假设在函数 f2 中发生了 panic,那么接下来将发生的事件顺序如下。
-
f2 的执行将停止。如果 f2 中有 defer 函数,它将被执行。控制将返回给调用者,也就是函数 f1。
-
f1 函数将以类似的方式行为,仿佛在该函数中发生了 panic,然后控制将返回给调用者,也就是 main 函数。请注意,如果中间有更多函数,过程将以类似方式继续向上堆栈。
-
main 函数的行为将如同在该函数中发生了 panic,之后程序将崩溃。
-
一旦程序崩溃,它将打印 panic 消息以及该堆栈跟踪。
让我们看看一个程序。
package main
import "fmt"
func main() {
f1()
}
func f1() {
defer fmt.Println("Defer in f1")
f2()
fmt.Println("After painc in f1")
}
func f2() {
defer fmt.Println("Defer in f2")
panic("Panic Demo")
fmt.Println("After painc in f2")
}
输出
Defer in f2
Defer in f1
panic: Panic Demo
goroutine 1 [running]:
main.f2()
main.go:17 +0x95
main.f1()
main.go:11 +0x96
main.main()
main.go:6 +0x20
exit status 2
在上述程序中,panic 在 f2 函数中发生,如下所示。
panic("Panic Demo")
f2 中的 defer 函数在此之后被调用,并打印以下消息。
Defer in f2
请注意,一旦 f2 函数中发生 panic,其执行将停止,因此下面的代码行将不会执行 f2。
fmt.Println("After painc in f2")
控制返回到 f1,如果它有 defer 函数,则会执行该 defer 函数,并打印以下消息。
Defer in f1
控制权随后返回到 main 函数,然后程序崩溃。输出打印了 panic 消息以及从 main 到 f1 再到 f2 的整个堆栈跟踪。
在 Golang 中使用 Recover
Go 提供了一个内置函数recover用于从 panic 中恢复。以下是该函数的签名
func recover() interface{}
我们已经了解到,defer函数是唯一在panic之后被调用的函数。因此,将recover函数放在defer函数中是合理的。如果recover函数不在 defer 函数内,则不会停止panic。
让我们来看一个 recover 的例子
package main
import "fmt"
func main() {
a := []string{"a", "b"}
checkAndPrint(a, 2)
fmt.Println("Exiting normally")
}
func checkAndPrint(a []string, index int) {
defer handleOutOfBounds()
if index > (len(a) - 1) {
panic("Out of bound access for slice")
}
fmt.Println(a[index])
}
func handleOutOfBounds() {
if r := recover(); r != nil {
fmt.Println("Recovering from panic:", r)
}
}
输出
Recovering from panic: Out of bound access for slice
Exiting normally
在上面的程序中,我们有一个函数checkAndPrint,它检查并打印传入参数中的切片元素。如果传入的索引大于数组的长度,则程序将引发 panic。我们在函数checkAndPrint的开始处添加了一个名为handleOutIfBounds的 defer 函数。此函数包含如下的 recover 函数调用。
if r := recover(); r != nil {
fmt.Println("Recovering from panic:", r)
}
recover函数将捕获 panic,我们还可以打印来自 panic 的消息。
Recovering from panic: Out of bound access for slice
在 recover 函数之后,程序继续运行,控制权返回到调用的函数,即这里的main。这就是为什么我们会得到如下输出
Exiting normally
recover 函数返回传递给 panic 函数的值。因此,检查 recover 函数的返回值是一种好习惯。如果返回值为 nil,则表示没有发生 panic,且 recover 函数没有与 panic 一起被调用。这就是为什么在 defer 函数handleOutOfBounds中有以下代码。
if r := recover(); r != nil
如果r为 nil,则表示没有发生 panic。因此,如果没有 panic,则对 recover 的调用将返回 nil。
请注意,如果 defer 函数和 recover 函数不是从引发 panic 的函数中调用的,那么在被调用函数中也可以恢复 panic。实际上,可以在调用栈的后续链中恢复 panic。
让我们来看这个的一个例子。
package main
import "fmt"
func main() {
a := []string{"a", "b"}
checkAndPrintWithRecover(a, 2)
fmt.Println("Exiting normally")
}
func checkAndPrintWithRecover(a []string, index int) {
defer handleOutOfBounds()
checkAndPrint(a, 2)
}
func checkAndPrint(a []string, index int) {
if index > (len(a) - 1) {
panic("Out of bound access for slice")
}
fmt.Println(a[index])
}
func handleOutOfBounds() {
if r := recover(); r != nil {
fmt.Println("Recovering from panic:", r)
}
}
输出
Recovering from panic: Out of bound access for slice
Exiting normally
上面的程序与前一个程序非常相似,唯一不同的是我们添加了一个额外的函数checkAndPrintWithRecover,其中包含对该函数的调用。
-
使用handleOutOfBounds的 defer 函数与 recover
-
调用checkAndPrint函数
基本上,checkAndPrint函数引发 panic,但没有 recover 函数,而是对 recover 的调用在checkAndPrintWithRecover函数中。但是,程序仍然能够从 panic 中恢复,因为 panic 也可以在被调用的函数中以及随后在链中恢复。
我们上面提到,如果 recover 函数不在 defer 函数中,则不会停止 panic。
让我们来看一个示例程序
package main
import "fmt"
func main() {
a := []string{"a", "b"}
checkAndPrint(a, 2)
fmt.Println("Exiting normally")
}
func checkAndPrint(a []string, index int) {
handleOutOfBounds()
if index > (len(a) - 1) {
panic("Out of bound access for slice")
}
fmt.Println(a[index])
}
func handleOutOfBounds() {
if r := recover(); r != nil {
fmt.Println("Recovering from panic:", r)
}
}
输出
panic: Out of bound access for slice
goroutine 1 [running]:
main.checkAndPrint(0xc000104f58, 0x2, 0x2, 0x2)
/Users/slohia/go/src/github.com/golang-examples/articles/tutorial/panicRecover/recoverNegativeExample/main.go:15 +0xea
main.main()
/Users/slohia/go/src/github.com/golang-examples/articles/tutorial/panicRecover/recoverNegativeExample/main.go:8 +0x81
exit status 2
在上面的程序中,recover 函数不在 defer 函数中。如您所见,输出表明它没有停止 panic,因此您看到了上面的输出。
Panic/Recover 与 Goroutine
关于 recover 函数需要注意的一个重要点是,它只能恢复同一 goroutine 中发生的 panic。如果 panic 发生在不同的 goroutine 中,而 recover 在另一个 goroutine 中,那么它不会停止 panic。让我们看一个程序来演示这一点。
package main
import "fmt"
func main() {
a := []string{"a", "b"}
checkAndPrintWithRecover(a, 2)
time.Sleep(time.Second)
fmt.Println("Exiting normally")
}
func checkAndPrintWithRecover(a []string, index int) {
defer handleOutOfBounds()
go checkAndPrint(a, 2)
}
func checkAndPrint(a []string, index int) {
if index > (len(a) - 1) {
panic("Out of bound access for slice")
}
fmt.Println(a[index])
}
func handleOutOfBounds() {
if r := recover(); r != nil {
fmt.Println("Recovering from panic:", r)
}
}
输出
Exiting normally
panic: Out of bound access for slice
goroutine 18 [running]:
main.checkAndPrint(0xc0000a6020, 0x2, 0x2, 0x2)
/Users/slohia/go/src/github.com/golang-examples/articles/tutorial/panicRecover/goroutine/main.go:19 +0xe2
created by main.checkAndPrintWithRecover
/Users/slohia/go/src/github.com/golang-examples/articles/tutorial/panicRecover/goroutine/main.go:14 +0x82
exit status 2
在上面的程序中,我们在 goroutine 中有 checkAndPrint,它在该 goroutine 中引发了 panic。recover 函数在调用的 goroutine 中。如你从输出中看到的,它并没有停止 panic,因此你看到了上面的输出。
打印堆栈跟踪
golang 的 Debug 包还提供 StackTrace 函数,可以用来在 recover 函数中打印 panic 的堆栈跟踪。
package main
import (
"fmt"
"runtime/debug"
)
func main() {
a := []string{"a", "b"}
checkAndPrint(a, 2)
fmt.Println("Exiting normally")
}
func checkAndPrint(a []string, index int) {
defer handleOutOfBounds()
if index > (len(a) - 1) {
panic("Out of bound access for slice")
}
fmt.Println(a[index])
}
func handleOutOfBounds() {
if r := recover(); r != nil {
fmt.Println("Recovering from panic:", r)
fmt.Println("Stack Trace:")
debug.PrintStack()
}
}
输出
Recovering from panic: Out of bound access for slice
Stack Trace:
goroutine 1 [running]:
runtime/debug.Stack(0xd, 0x0, 0x0)
stack.go:24 +0x9d
runtime/debug.PrintStack()
stack.go:16 +0x22
main.handleOutOfBounds()
main.go:27 +0x10f
panic(0x10ab8c0, 0x10e8f60)
/Users/slohia/Documents/goversion/go1.14.1/src/runtime/panic.go:967 +0x166
main.checkAndPrint(0xc000104f58, 0x2, 0x2, 0x2)
main.go:18 +0x111
main.main()
main.go:11 +0x81
Exiting normally
在上面的程序中,我们使用 StackTrace 函数在 recover 函数中打印 panic 的堆栈跟踪。它打印出的堆栈跟踪是正确的,如输出所示。
当 panic 被恢复时函数的返回值
当 panic 被恢复时,导致 panic 的函数的返回值将是该函数返回类型的默认值。
让我们看一个程序来演示这一点。
package main
import (
"fmt"
)
func main() {
a := []int{5, 6}
val, err := checkAndGet(a, 2)
fmt.Printf("Val: %d\n", val)
fmt.Println("Error: ", err)
}
func checkAndGet(a []int, index int) (int, error) {
defer handleOutOfBounds()
if index > (len(a) - 1) {
panic("Out of bound access for slice")
}
return a[index], nil
}
func handleOutOfBounds() {
if r := recover(); r != nil {
fmt.Println("Recovering from panic:", r)
}
}
输出
Recovering from panic: Out of bound access for slice
Val: 0
Error:
在上面的程序中,我们有一个 checkAndGet 函数,它在 int 切片的特定索引获取值。如果传递给此函数的索引大于(切片长度 - 1),那么它会引发 panic。还有一个 handleOutOfBounds 函数用于从 panic 中恢复。因此,我们将索引 2 传递给 checkAndGet 函数,它引发的 panic 在 handleOutOfBounds 函数中恢复。这就是我们首先得到这个输出的原因。
Recovering from panic: Out of bound access for slice
请注意在主函数中,我们以这样的方式重新获取 checkAndGet 的返回值。
val, err := checkAndGet(a, 2)
checkAndGet 有两个返回值
-
int
-
错误
由于 checkAndGet 创建的 panic 在 handleOutOfBounds 函数中被恢复,因此 checkAndGet 的返回值将是其类型的默认值。
因此
fmt.Printf("Val: %d\n", val)
输出
Val: 0
因为零是 int 类型的默认值。
而且
fmt.Println("Error: ", err)
输出
Error:
因为 nil 是 error 类型的默认值。
如果你不想返回类型的默认零值,可以使用命名返回值。让我们看一个程序来演示这一点。
package main
import (
"fmt"
)
func main() {
a := []int{5, 6}
val, err := checkAndGet(a, 2)
fmt.Printf("Val: %d\n", val)
fmt.Println("Error: ", err)
}
func checkAndGet(a []int, index int) (value int, err error) {
value = 10
defer handleOutOfBounds()
if index > (len(a) - 1) {
panic("Out of bound access for slice")
}
value = a[index]
return value, nil
}
func handleOutOfBounds() {
if r := recover(); r != nil {
fmt.Println("Recovering from panic:", r)
}
}
输出
Recovering from panic: Out of bound access for slice
Val: 10
Error:
这个程序与之前的程序相同,唯一的区别是我们在 checkAndGet 函数中使用了命名返回值。
func checkAndGet(a []int, index int) (value int, err error)
我们在 checkAndGet 函数中将命名返回值设置为 10。
value = 10
这就是为什么在这个程序中我们得到下面的输出,因为引发了 panic 并且它被恢复。
Recovering from panic: Out of bound access for slice
Val: 10
Error:
还请注意,如果程序中没有引发 panic,那么它将输出索引的正确值。
结论
这就是关于 golang 中的 panic 和 recover 的全部内容。希望你喜欢这篇文章。请在评论中分享反馈/改进/错误。
上一个教程 – 错误 - 第二部分
Go (Golang) 中的 Panic 格式字符串
目录
-
概述**
-
示例
概述
下面是 panic 函数的语法
func panic(v interface{})
它将空接口作为参数。它并没有提供任何格式化错误字符串的方法。不过有一个变通办法。可以使用fmt包的Sprintf函数在将错误消息传递给 panic 函数之前进行格式化。让我们看看一个程序
示例
package main
import (
"fmt"
)
func main() {
a := []string{"a", "b"}
checkAndPrint(a, 2)
fmt.Println("Exiting normally")
}
func checkAndPrint(a []string, index int) {
if index > (len(a) - 1) {
errorString := fmt.Sprintf("Out of bounds access for slice. Index passed: %d", index)
panic(errorString)
}
fmt.Println(a[index])
}
输出
panic: Out of bounds access for slice. Index passed: 2
goroutine 1 [running]:
main.checkAndPrint(0xc00009af58, 0x2, 0x2, 0x2)
main.go:17 +0x157
main.main()
main.go:10 +0x81
exit status 2
在上面的程序中,我们有一个函数checkAndPrint,它检查并打印在参数中传递的索引处的切片元素。如果传递的索引大于数组的长度,则程序将发生 panic。注意我们是如何在checkAndPrint函数中格式化错误字符串然后传递给 panic 的
errorString := fmt.Sprintf("Out of bounds access for slice. Index passed: %d", index)
程序也输出正确格式化的消息
panic: Out of bounds access for slice. Index passed: 2
Go(Golang)中的崩溃堆栈跟踪
目录
-
概述
-
示例
概述
golang 的debug包提供了一个StackTrace函数,可以用来打印 recover 函数中崩溃的堆栈跟踪。
示例
让我们看看一个程序
package main
import (
"fmt"
"runtime/debug"
)
func main() {
a := []string{"a", "b"}
checkAndPrint(a, 2)
fmt.Println("Exiting normally")
}
func checkAndPrint(a []string, index int) {
defer handleOutOfBounds()
if index > (len(a) - 1) {
panic("Out of bound access for slice")
}
fmt.Println(a[index])
}
func handleOutOfBounds() {
if r := recover(); r != nil {
fmt.Println("Recovering from panic:", r)
fmt.Println("Stack Trace:")
debug.PrintStack()
}
}
输出
Recovering from panic: Out of bound access for slice
Stack Trace:
goroutine 1 [running]:
runtime/debug.Stack(0xd, 0x0, 0x0)
stack.go:24 +0x9d
runtime/debug.PrintStack()
stack.go:16 +0x22
main.handleOutOfBounds()
main.go:27 +0x10f
panic(0x10ab8c0, 0x10e8f60)
/Users/slohia/Documents/goversion/go1.14.1/src/runtime/panic.go:967 +0x166
main.checkAndPrint(0xc000104f58, 0x2, 0x2, 0x2)
main.go:18 +0x111
main.main()
main.go:11 +0x81
Exiting normally
在上述程序中,我们有一个函数checkAndPrint,它检查并打印传入参数的索引处的切片元素。如果传入的索引大于数组的长度,程序将会出现崩溃。我们在函数checkAndPrint的开始处添加了一个名为handleOutIfBounds的延迟函数。这个函数包含了下面的 recover 函数调用。
if r := recover(); r != nil {
fmt.Println("Recovering from panic:", r)
}
recover函数将捕获崩溃,我们也可以打印崩溃中的消息。
Recovering from panic: Out of bound access for slice
我们使用StackTrace函数在 recover 函数中打印崩溃的堆栈跟踪。它打印出正确的堆栈跟踪,如输出所示。
在 Go 中使用 Defer 处理 Panic (Golang)
目录
-
概述
-
示例
-
使用 defer 从 panic 中恢复
概述
即使程序发生 panic,defer 函数也会被执行。事实上,defer 函数是唯一在 panic 之后被调用的函数。当在一个函数中引发 panic 时,该函数的执行将停止,任何被延迟的函数将被执行。实际上,所有堆栈中的延迟函数也将被执行,直到所有函数都返回。此时,程序将退出,并打印 panic 消息。
因此,如果存在 defer 函数,它将被执行,控制将返回到调用函数,如果存在的话,调用函数将再次执行其 defer 函数,这个链条将继续,直到程序退出。
示例
让我们看一个例子。
package main
import "fmt"
func main() {
defer fmt.Println("Defer in main")
panic("Panic with Defer")
fmt.Println("After painc in f2")
}
输出
Defer in main
panic: Panic Create
goroutine 1 [running]:
main.main()
/Users/slohia/go/src/github.com/golang-examples/articles/tutorial/panicRecover/deferWithPanic/main.go:7 +0x95
exit status 2
在上述程序中,我们首先有一个 defer 函数,然后手动引发 panic。正如你从输出中看到的,defer 函数被执行,输出中打印了如下行。
Defer in main
使用 defer 从 panic 中恢复
Go 提供了一个内置函数 recover 用于从 panic 中恢复。下面是这个函数的签名。
func recover() interface{}
defer 函数是唯一在 panic 之后被调用的函数。因此,将 recover 函数放在 defer 函数中是合理的。如果 recover 函数不在 defer 函数中,它将无法停止 panic。
让我们看看 recover 的一个例子。
package main
import "fmt"
func main() {
a := []string{"a", "b"}
checkAndPrint(a, 2)
fmt.Println("Exiting normally")
}
func checkAndPrint(a []string, index int) {
defer handleOutOfBounds()
if index > (len(a) - 1) {
panic("Out of bound access for slice")
}
fmt.Println(a[index])
}
func handleOutOfBounds() {
if r := recover(); r != nil {
fmt.Println("Recovering from panic:", r)
}
}
输出
Recovering from panic: Out of bound access for slice
Exiting normally
在上述程序中,我们有一个函数 checkAndPrint,它检查并打印传入参数的索引处的切片元素。如果传入的索引大于数组的长度,则程序会发生 panic。我们在函数 checkAndPrint 的开头添加了一个名为 handleOutIfBounds 的 defer 函数。这个函数包含了对 recover 函数的调用,如下所示。
if r := recover(); r != nil {
fmt.Println("Recovering from panic:", r)
}
recover 函数将捕获 panic,我们还可以打印 panic 中的消息。
Recovering from panic: Out of bound access for slice
在 recover 函数之后,程序继续执行,控制返回到调用的函数,即这里的 main。这就是我们获得输出的原因。
Exiting normally
在 Go (Golang) 中解析布尔值或检查给定字符串是否为布尔值
strconv.ParseBool() 函数可以用于解析布尔值的字符串表示。
golang.org/pkg/strconv/#ParseBool
下面是该函数的签名
func ParseBool(str string) (bool, error)
让我们看看一个工作代码
package main
import (
"fmt"
"strconv"
)
func main() {
input := "true"
if val, err := strconv.ParseBool(input); err == nil {
fmt.Printf("%T, %v\n", val, val)
}
input = "false"
if val, err := strconv.ParseBool(input); err == nil {
fmt.Printf("%T, %v\n", val, val)
}
input = "garbage"
if val, err := strconv.ParseBool(input); err == nil {
fmt.Printf("%T, %v\n", val, val)
} else {
fmt.Println("Given input is not a bool")
}
}
输出:
bool, true
bool, false
Given input is not a bool
在 Go (Golang)中将字符串解析为浮点数
来源:
golangbyexample.com/parse-string-representation-float-go/
strconv.ParseFloat()函数可以用于解析浮点数的字符串表示。
golang.org/pkg/strconv/#ParseFloat
以下是函数的签名
func ParseFloat(s string, bitSize int) (float64, error)
一些值得注意的要点
-
第一个参数是浮点数的字符串表示。
-
第二个参数是 bitSize,指定精度。float32 为 32,float64 为 64。
-
返回值始终为 float64,但可以无损地转换为 float32。
让我们看看一个工作代码
package main
import (
"fmt"
"strconv"
)
func main() {
e1 := "1.3434"
if s, err := strconv.ParseFloat(e1, 32); err == nil {
fmt.Printf("%T, %v\n", s, s)
}
if s, err := strconv.ParseFloat(e1, 64); err == nil {
fmt.Printf("%T, %v\n", s, s)
}
}
输出
float64, 1.343400001525879
float64, 1.3434
解析一个 URL 并提取所有部分,使用 Go (Golang)
目录
**概述
- 程序
概述
net/url包中包含一个 Parse 函数,可以用来解析给定的 URL 并返回 URL 结构的实例
一旦给定的 URL 被正确解析,它将返回 URI 对象。然后我们可以从 URI 访问以下信息
-
方案
-
用户信息
-
主机名
-
端口
-
路径名
-
查询参数
-
片段
让我们来看一个工作程序:
我们将解析以下 URL
https://test:abcd123@golangbyexample.com:8000/tutorials/intro?type=advance&compact=false#history
然后
-
方案是 HTTPS
-
用户信息 – 用户名是 test,密码是 abcd123。用户名和密码用冒号分隔:
-
端口是 8000
-
路径是 tutorials/intro
-
查询参数是type=advance和compact=false。它们用和号分隔
-
片段是历史。它将直接带你到该页面的历史部分。历史是一个标识符,指向该页面中的那个部分
程序
package main
import (
"fmt"
"log"
"net/url"
)
func main() {
input_url := "https://test:abcd123@golangbyexample.com:8000/tutorials/intro?type=advance&compact=false#history"
u, err := url.Parse(input_url)
if err != nil {
log.Fatal(err)
}
fmt.Println(u.Scheme)
fmt.Println(u.User)
fmt.Println(u.Hostname())
fmt.Println(u.Port())
fmt.Println(u.Path)
fmt.Println(u.RawQuery)
fmt.Println(u.Fragment)
fmt.Println(u.String())
}
输出
https
test:abcd123
golangbyexample.com
8000
/tutorials/intro
type=advance&compact=false
history
https://test:abcd123@golangbyexample.com:8000/tutorials/intro?type=advance&compact=false#history
它正确地输出所有信息,如输出所示
在 Golang 中解析时间
目录
概述
- 代码
概述
time 包中的 Parse 函数可以用来解析时间的字符串表示为 time.Time 对象
包链接 – golang.org/pkg/time/#Parse
如果你曾在其他语言中处理时间/日期格式化/解析,你可能会注意到其他语言使用特殊占位符来格式化时间/日期。例如,Ruby 语言使用
-
%d 表示天
-
%Y 表示年份
等等。Golang 不使用上述代码,而是使用看起来像日期和时间的日期和时间格式占位符。Go 使用标准时间,标准时间是:
Mon Jan 2 15:04:05 MST 2006 (MST is GMT-0700)
or
01/02 03:04:05PM '06 -0700
所以如果你注意到 Go 使用
-
01 表示月份中的天数,
-
02 为月份
-
03 表示小时,
-
04 表示分钟
-
05 表示秒
-
依此类推
下面的占位符表描述了确切的映射。Go 采用了更务实的方法,你无需记住或查找其他语言中的传统格式代码
类型 | 占位符 |
---|---|
天 | 2 或 02 或 _2 |
星期几 | 星期一 或 Mon |
月 | 01 或 1 或 Jan 或 January |
年 | 2006 或 06 |
小时 | 03 或 3 或 15 |
分钟 | 04 或 4 |
秒 | 05 或 5 |
毫秒 (ms) | .000 //尾随零将包含或 .999 //尾随零将省略 |
微秒 (μs) | .000000 //尾随零将包含或 .999999 //尾随零将省略 |
纳秒 (ns) | .000000000 //尾随零将包含或 .999999999 //尾随零将省略 |
上午/下午 | PM 或 pm |
时区 | MST |
时区偏移 | Z0700 或 Z070000 或 Z07 或 Z07:00 或 Z07:00:00 或 -0700 或 -070000 或 -07 或 -07:00 或 -07:00:00 |
现在回到 time.Parse
time.Parse 函数接受两个参数
-
第一个参数是包含时间格式占位符的布局
-
第二个参数是表示时间的实际格式化字符串。
你需要确保布局字符串(第一个参数)与要解析为 time.Time 的时间字符串表示(第二个参数)相匹配。
-
对于解析 2020-01-29,布局字符串应为 06-01-02 或 2006-01-02,或基于上述占位符表正确映射的其他格式。
-
类似地,对于解析 “2020-Jan-29 星期三 12:19:25”,布局字符串可以是 “2006-Jan-02 星期一 03:04:05”
如果 time.Parse 在解析时间时遇到错误,将会抛出错误。
代码
下面是 time.Parse() 的工作代码示例。
package main
import (
"fmt"
"time"
)
func main() {
//Parse YYYY-MM-DD
timeT, _ := time.Parse("2006-01-02", "2020-01-29")
fmt.Println(timeT)
//Parse YY-MM-DD
timeT, _ = time.Parse("06-01-02", "20-01-29")
fmt.Println(timeT)
//Parse YYYY-#{MonthName}-DD
timeT, _ = time.Parse("2006-Jan-02", "2020-Jan-29")
fmt.Println(timeT)
//Parse YYYY-#{MonthName}-DD WeekDay HH:MM:SS
timeT, _ = time.Parse("2006-Jan-02 Monday 03:04:05", "2020-Jan-29 Wednesday 12:19:25")
fmt.Println(timeT)
//Parse YYYY-#{MonthName}-DD WeekDay HH:MM:SS PM Timezone TimezoneOffset
timeT, _ = time.Parse("2006-Jan-02 Monday 03:04:05 PM MST -07:00", "2020-Jan-29 Wednesday 12:19:25 AM IST +05:30")
fmt.Println(timeT)
}
输出:
2020-01-29 00:00:00 +0000 UTC
2020-01-29 00:00:00 +0000 UTC
2020-01-29 00:00:00 +0000 UTC
2020-01-29 12:19:25 +0000 UTC
2020-01-29 00:19:25 +0530 IST
```*
<!--yml
分类:未分类
日期:2024-10-13 06:48:19
-->
# 在 Go (Golang)中对链表进行分区
> 来源:[`golangbyexample.com/partition-linked-list-golang/`](https://golangbyexample.com/partition-linked-list-golang/)
目录
+ 概述
+ 程序
## **概述**
给定一个链表,同时给定一个目标值。将给定的链表分区,使得所有小于目标值的元素都在所有大于目标值的元素之前。
示例
```go
Input: 4->5->3->1
Output: 2
Target: 1->4->5->3
原始顺序也应予以保留。
程序
这里是相应的程序。
package main
import "fmt"
func main() {
first := initList()
first.AddFront(1)
first.AddFront(2)
first.AddFront(3)
first.AddFront(4)
first.Head.Traverse()
newHead := partition(first.Head, 2)
fmt.Println("")
newHead.Traverse()
}
func initList() *SingleList {
return &SingleList{}
}
type ListNode struct {
Val int
Next *ListNode
}
func (l *ListNode) Traverse() {
for l != nil {
fmt.Println(l.Val)
l = l.Next
}
}
type SingleList struct {
Len int
Head *ListNode
}
func (s *SingleList) AddFront(num int) {
ele := &ListNode{
Val: num,
}
if s.Head == nil {
s.Head = ele
} else {
ele.Next = s.Head
s.Head = ele
}
s.Len++
}
func partition(head *ListNode, x int) *ListNode {
if head == nil {
return nil
}
curr := head
var prev *ListNode
for curr != nil {
if curr.Val >= x {
break
}
prev = curr
curr = curr.Next
}
if curr == nil {
return head
}
firstLargeValueNode := curr
prev2 := firstLargeValueNode
for curr != nil {
if curr.Val < x {
prev2.Next = curr.Next
if prev != nil {
prev.Next = curr
prev = prev.Next
prev.Next = firstLargeValueNode
} else {
if head == firstLargeValueNode {
head = curr
}
curr.Next = firstLargeValueNode
prev = curr
}
}
prev2 = curr
curr = curr.Next
}
return head
}
输出
4
5
3
1
1
4
5
3
注意: 请查看我们的 Golang 高级教程。本系列教程详细而全面,涵盖了所有概念及示例。本教程适合那些希望掌握 Golang 并获得扎实理解的人 - Golang 高级教程
如果你有兴趣了解所有设计模式如何在 Golang 中实现。 如果是的话,那么这篇文章适合你 - 所有设计模式 Golang
将接口作为参数传递给 Go(Golang)中的函数。
来源:
golangbyexample.com/pass-interface-as-argument-function-go/
函数可以接受接口类型的参数。该接口类型可以是任意类型。
-
常规接口
-
空接口
让我们逐个看一下这两个示例。
目录
** 常规接口
- 空接口
常规接口
函数可以接受接口类型的参数。任何实现该接口的类型都可以作为参数传递给该函数。让我们通过一个示例来理解。假设我们有如下动物接口。
type animal interface {
breathe()
walk()
}
我们有两个结构类型实现该接口。
type lion struct {
age int
}
和
type dog struct {
age int
}
在下面的代码中,我们有callBreathe和callWalk函数,它们接受动物接口类型的参数。狮子和狗实例都可以传递给该函数。我们创建狮子和狗类型的实例并将其传递给该函数。在编译期间,调用函数时不会检查类型,而只需检查传递给函数的类型是否实现了呼吸和行走方法。
package main
import "fmt"
type animal interface {
breathe()
walk()
}
type lion struct {
age int
}
func (l lion) breathe() {
fmt.Println("Lion breathes")
}
func (l lion) walk() {
fmt.Println("Lion walk")
}
type dog struct {
age int
}
func (l dog) breathe() {
fmt.Println("Dog breathes")
}
func (l dog) walk() {
fmt.Println("Dog walk")
}
func main() {
l := lion{age: 10}
callBreathe(l)
callWalk(l)
d := dog{age: 5}
callBreathe(d)
callWalk(d)
}
func callBreathe(a animal) {
a.breathe()
}
func callWalk(a animal) {
a.breathe()
}
输出
Lion breathes
Lion walk
Dog breathes
Dog walk
空接口
一个空接口没有方法,因此默认情况下所有类型都实现空接口。如果你编写一个接受空接口的函数,那么你可以将任何类型传递给该函数。请参见下面的工作代码。
package main
import "fmt"
func main() {
test("thisisstring")
test("10")
test(true)
}
func test(a interface{}) {
fmt.Printf("(%v, %T)\n", a, a)
}
输出:
(thisisstring, string)
(10, string)
(true, bool)
在 Go(Golang)中将函数作为参数传递给另一个函数。
Golang 函数是一级变量,意味着。
-
它们可以被赋值给一个变量。
-
作为函数参数传递。
-
从函数返回。
在 Go 中,函数也是一种类型。如果两个函数具有相同的参数和相同的返回值,它们就是同一种类型。在将一个函数作为参数传递给另一个函数时,必须在参数列表中指定函数的确切签名。如下例所示,print 函数接受的第一个参数是类型为 func(int, int) int 的函数。
func print(f func(int, int) int, a, b int)
关于下面程序还有一些需要注意的事项。
-
函数 area 是类型为 func(int, int) int 的函数。
-
函数 sum 是类型为 func(int, int) int 的函数。
-
area 和 sum 是同一种类型,因为它们具有相同的参数类型和相同的返回值类型。
-
print 函数接受一个类型为 func(int, int) int 的函数作为其第一个参数。
-
因此,area 和 sum 函数可以作为参数传递给 print 函数。
代码:
package main
import "fmt"
func main() {
print(area, 2, 4)
print(sum, 2, 4)
}
func print(f func(int, int) int, a, b int) {
fmt.Println(f(a, b))
}
func area(a, b int) int {
return a * b
}
func sum(a, b int) int {
return a + b
}
输出
8
6
在 Go (Golang) 中向函数传递可变数量的参数。
目录
-
概述
-
不同数量的参数但类型相同
-
不同数量的参数且类型不同
概述
在 Go 中,能够接受动态数量参数的函数称为变长函数。下面是变长函数的语法。三个点用作类型前缀。
func add(numbers ...int)
上述函数可以用零个或多个参数调用。
add()
add(1,2)
add(1,2,3,4)
在此情况下,变长参数numbers会在函数内部转换为切片,可以使用range进行迭代。
func add(numbers ...int) int {
sum := 0
for _, num := range numbers {
sum += num
}
return sum
}
如果你已经有一个切片,并且需要将其作为变长参数传递,那么可以在调用函数时在参数后添加三个点(…)。
var numbers := []int{2,3,5}
add(numbers...)
如果需要将变长参数和非变长参数传递给函数,则非变长参数需要作为初始参数传递,而变长参数需要作为最后一个参数传递。
func add(val string, numbers ...int)
在 GO 库中,变长函数的最佳示例是fmt.Println()函数。以下是该函数的签名。
func Println(a ...interface{}) (n int, err error)
使用变长函数时,可能会出现与变长参数相关的一些情况。
不同数量的参数但类型相同
上述案例可以很容易地通过变长函数处理。注意下面的代码中参数是单一类型,即int。
package main
import "fmt"
func main() {
fmt.Println(add(1, 2))
fmt.Println(add(1, 2, 3))
fmt.Println(add(1, 2, 3, 4))
}
func add(numbers ...int) int {
sum := 0
for _, num := range numbers {
sum += num
}
return sum
}
输出:
3
6
10
不同数量的参数且类型不同
这个案例可以使用变长函数和空接口来处理。
package main
import "fmt"
func main() {
handle(1, "abc")
handle("abc", "xyz", 3)
handle(1, 2, 3, 4)
}
func handle(params ...interface{}) {
fmt.Println("Handle func called with parameters:")
for _, param := range params {
fmt.Printf("%v\n", param)
}
}
输出:
Handle func called with parameters:
1
abc
Handle func called with parameters:
abc
xyz
3
Handle func called with parameters:
1
2
3
4
我们还可以使用 switch case 来获取精确的参数并相应地使用它们。请参见下面的示例。
package main
import "fmt"
type person struct {
name string
gender string
age int
}
func main() {
err := addPerson("Tina", "Female", 20)
if err != nil {
fmt.Println("PersonAdd Error: " + err.Error())
}
err = addPerson("John", "Male")
if err != nil {
fmt.Println("PersonAdd Error: " + err.Error())
}
err = addPerson("Wick", 2, 3)
if err != nil {
fmt.Println("PersonAdd Error: " + err.Error())
}
}
func addPerson(args ...interface{}) error {
if len(args) > 3 {
return fmt.Errorf("Wront number of arguments passed")
}
p := &person{}
//0 is name
//1 is gender
//2 is age
for i, arg := range args {
switch i {
case 0: // name
name, ok := arg.(string)
if !ok {
return fmt.Errorf("Name is not passed as string")
}
p.name = name
case 1:
gender, ok := arg.(string)
if !ok {
return fmt.Errorf("Gender is not passed as string")
}
p.gender = gender
case 2:
age, ok := arg.(int)
if !ok {
return fmt.Errorf("Age is not passed as int")
}
p.age = age
default:
return fmt.Errorf("Wrong parametes passed")
}
}
fmt.Printf("Person struct is %+v\n", p)
return nil
}
注意: 如果没有传递参数,则用默认值替代。
输出:
Person struct is &{name:Tina gender:Female age:20}
Person struct is &{name:John gender:Male age:0}
PersonAdd Error: Gender is not passed as string
```*
<!--yml
类别:未分类
日期:2024-10-13 06:34:50
-->
# 暂停 Goroutine 的执行,直到 Go(Golang)中的活动或事件完成。
> 来源:[`golangbyexample.com/pause-goroutine/`](https://golangbyexample.com/pause-goroutine/)
目录
+ 概述
+ 程序
## **概述**
通道可用于暂停 Goroutine 的执行,直到活动完成。对于一个
**无缓冲通道**
+ 当没有其他 Goroutine 接收时,Goroutine 将在发送操作上阻塞。
+ 当没有其他 Goroutine 在另一端发送时,Goroutine 将在接收操作上阻塞。
**缓冲通道**
+ 当缓冲区满时,Goroutine 将在发送操作上阻塞。
+ 当缓冲区为空时,Goroutine 将在接收操作上阻塞。
如果我们希望 Goroutine 暂停直到活动完成,我们可以使用上述四种方法中的任何一种。为了简化,本教程中我们将使用一个无缓冲通道。Goroutine 将在该通道上进行接收操作并暂停。只有在另一个 Goroutine 在该通道上完成发送操作时,它才会恢复。
## **程序**
```go
package main
import (
"fmt"
"time"
)
func main() {
ch := make(chan bool)
go test(ch)
time.Sleep(time.Second * 1)
fmt.Printf("Send Value: %t\n", true)
ch <- true
time.Sleep((time.Second * 3))
}
func test(ch chan bool) {
var value = <-ch
fmt.Printf("Received Value: %t\n", value)
fmt.Println("Doing some work")
}
输出
Send Value: true
Received Value: true
在上述程序中,我们创建了一个无缓冲通道。我们启动一个 Goroutine,等待该通道上的接收操作。它将在发送操作完成之前被阻塞。
然后我们在主 Goroutine 中发送值。一旦发送完成,Goroutine 恢复执行。这就是为什么
Send Value: true
始终在之前打印
Send Value: true
Received Value: true
另外,请查看我们的 Golang 综合教程系列 - Golang 综合教程
Go(Golang)中的圆周率值
目录
-
概述
-
代码:
概述
GO 的math包提供了圆周率常量。
它在 math 包的 constant.go 文件中是这样定义的。它是一个 float64 值。
Pi = 3.14159265358979323846264338327950288419716939937510582097494459
代码:
package main
import (
"fmt"
"math"
)
func main() {
pi := math.Pi
fmt.Println(pi)
}
输出:
3.141592653589793
在 Go (Golang) 中随机选择字符串中的字符
目录
-
概述
-
代码
概述
‘mat/rand’ 包含一个 Intn 函数,可以用来生成一个在 [0,n) 之间的伪随机数。末尾的括号意味着 n 是不包括的。这个函数可以用来从字符串中选择一个随机元素。我们可以在 0 到字符串长度减 1 之间生成一个随机数。然后我们可以用这个随机数来索引字符串并得到结果。
但是上述方法存在一个问题。在 Golang 中,字符串是字节的序列。字符串字面量实际上表示的是 UTF-8 字节序列。在 UTF-8 中,ASCII 字符是对应于前 128 个 Unicode 字符的单字节。所有其他字符占用 1 到 4 个字节。因此,无法在字符串中索引一个字符。在 Go 中,rune 数据类型表示一个 Unicode 点。一旦字符串被转换为 rune 数组,就可以在该数组中索引字符。
你可以在这里了解更多关于上述问题的信息 – golangbyexample.com/number-characters-string-golang/
因此,在下面的程序中,为了从给定字符串中选择一个随机字符,我们首先将字符串转换为 rune 数组,以便能够索引这个 rune 数组,然后返回随机字符。
代码
package main
import (
"fmt"
"math/rand"
)
func main() {
in := "abcdedf£"
inRune := []rune(in)
randomIndex := rand.Intn(len(inRune))
pick := inRune[randomIndex]
fmt.Println(string(pick))
}
输出:
One of a,b,c,d,e,f,£
在 Go (Golang) 中随机选择数组或切片中的元素
目录
-
概述
-
代码
概述
Go 的 ‘mat/rand’ 包包含一个 Intn 函数,可以用来生成一个在 [0,n) 范围内的伪随机数。末尾的括号意味着 n 是排除在外的。此函数可用于在整型或字符串的数组或切片中选择一个随机元素。
要了解更多关于伪随机数的含义,请查看这篇文章 – golangbyexample.com/generate-random-number-golang
下面是此方法的签名。它接收一个数字 n,并将返回一个范围在 0<=x<n 内的数字 x。
func Intn(n int) int
代码
我们可以直接通过索引在一个整型切片中选择元素。请参见下面的程序,从整型切片中随机选择。
package main
import (
"fmt"
"math/rand"
)
func main() {
in := []int{2, 5, 6}
randomIndex := rand.Intn(len(in))
pick := in[randomIndex]
fmt.Println(pick)
}
输出:
Between 2, 5 or 6
加一程序或在 Go 语言中给整数数组加一
目录
-
概述
-
程序
概述
给定一个整数数组。总体来说,这个整数数组表示一个数字。那么假设这个整数数组的名字是 digits,digits[i]表示这个整数的第 i 位。目标是将这个整数数组加 1。必须在不将数组转换为 int 类型数字的情况下完成这个任务。
示例
Input: [1, 2]
Output: [1, 3]
Input: [9, 9]
Output: [1, 0, 0]
程序
这里是相应的程序。
package main
import "fmt"
func plusOne(digits []int) []int {
lenDigits := len(digits)
output := make([]int, 0)
lastDigit := digits[lenDigits-1]
add := lastDigit + 1
carry := add / 10
lastDigit = add % 10
output = append(output, lastDigit)
for i := lenDigits - 2; i >= 0; i-- {
o := digits[i] + carry
lastDigit = o % 10
carry = o / 10
output = append(output, lastDigit)
}
if carry == 1 {
output = append(output, 1)
}
return reverse(output, len(output))
}
func reverse(input []int, length int) []int {
start := 0
end := length - 1
for start < end {
input[start], input[end] = input[end], input[start]
start++
end--
}
return input
}
func main() {
output := plusOne([]int{1, 2})
fmt.Println(output)
output = plusOne([]int{9, 9})
fmt.Println(output)
}
输出
[1 3]
[1 0 0]
注意: 请查看我们的 Golang 高级教程。本系列教程内容详尽,我们努力涵盖所有概念及示例。本教程适合希望获得 Golang 专业知识和扎实理解的人 - Golang 高级教程
如果你有兴趣了解所有设计模式如何在 Golang 中实现。如果是的话,这篇文章就是为你准备的 - 所有设计模式 Golang
Go 语言中的指针算术运算
目录
-
概述
-
程序
概述
与 C 语言不同,Golang 中不支持指针算术运算,这会引发编译错误。
程序
package main
func main() {
a := 1
b := &a
b = b + 1
}
输出
上述程序引发编译错误
invalid operation: b + 1 (mismatched types *int and int)
Go 语言中的指针
这是 Go 语言综合教程系列的第十五章。有关该系列其他章节,请参阅此链接 – Golang 综合教程系列
下一教程 – 结构体
上一篇教程 – Defer 关键字
现在让我们看看当前的教程。以下是当前教程的目录。
目录
-
概述
-
指针的声明
-
指针的初始化
-
使用 new 运算符
-
使用符号 ‘&’ 运算符
-
-
关于 * 或解引用指针
-
指向指针的指针
-
指针算术
-
指针的默认零值
-
结论
概述
指针是一个变量,它保存另一个变量的内存地址。
指针的声明
在下面的示例中,ex 是 T 类型的指针。
var ex *T
在声明中,指针值被设置为其默认零值,即 nil。
指针的初始化
初始化指针有两种方法
-
使用 new 运算符
-
使用符号 ‘&’ 运算符
使用 new 运算符
指针可以使用 new 运算符进行初始化
a := new(int)
*a = 10
fmt.Println(*a) //Output will be 10
***** 运算符可用于解引用指针,意味着获取存储在指针中的地址的值
fmt.Println(*a) //Print the value stored at address a
使用符号 ‘&’ 运算符
& 用于获取变量的地址
a := 2
b := &a
fmt.Println(*b) //Output will be 2
让我们看看一个涵盖以上所有要点的工作代码
package main
import "fmt"
func main() {
//Declare
var b *int
a := 2
b = &a
//Will print a address. Output will be different everytime.
fmt.Println(b)
fmt.Println(*b)
b = new(int)
*b = 10
fmt.Println(*b)
}
输出:
2
10
0xc0000b0018
关于 * 或解引用指针
***** 运算符可用于:
-
解引用指针意味着获取存储在指针中的地址的值。
-
也可以更改该指针位置的值
package main
import "fmt"
func main() {
a := 2
b := &a
fmt.Println(a)
fmt.Println(*b)
*b = 3
fmt.Println(a)
fmt.Println(*b)
a = 4
fmt.Println(a)
fmt.Println(*b)
}
输出
2
2
3
3
4
4
a 和 b 在内部指向同一个变量。因此更改一个的值会反映在另一个上。此外,**** 和 & 也可以一起使用,但它们会互相抵消。
因此下面的两种方法是等效的,将打印 2
-
a
-
*&a
下面的三种方法是等效的,并将打印存储在变量 b 中的变量 a 的地址
-
b
-
*&b
-
&*b
注意: *a 不是有效操作,因为 a 不是指针
指向指针的指针
在 Go 中也可以创建指向指针的指针
a := 2
b := &a
c := &b
c在这里是一个指向指针的指针。它存储b的地址,而b又存储a的地址。使用***运算符进行双重解引用可以打印指向指针的值。因此,****c将打印值 2
以下图示描绘了指向指针的指针。
-
b包含a的地址
-
c包含b的地址
让我们来看看,一个描绘指向指针的程序
package main
import "fmt"
func main() {
a := 2
b := &a
c := &b
fmt.Printf("a: %d\n", a)
fmt.Printf("b: %x\n", b)
fmt.Printf("c: %x\n", c)
fmt.Println()
fmt.Printf("a: %d\n", a)
fmt.Printf("*&a: %d\n", *&a)
fmt.Printf("*b: %d\n", *b)
fmt.Printf("**c: %d\n", **c)
fmt.Println()
fmt.Printf("&a: %d\n", &a)
fmt.Printf("b: %d\n", b)
fmt.Printf("&*b: %d\n", &*b)
fmt.Printf("*&b: %d\n", *&b)
fmt.Printf("*c: %d\n", *c)
fmt.Println()
fmt.Printf("b: %d\n", &b)
fmt.Printf("*c: %d\n", c)
}
输出
a: 2
b: c000018078
c: c00000e028
a: 2
*&a: 2
*b: 2
**c: 2
&a: 824633819256
b: 824633819256
&*b: 824633819256
*&b: 824633819256
*c: 824633819256
b: 824633778216
*c: 824633778216
从输出可以清楚地看到
以下是与变量 a 的值相等且等同于 2
-
a
-
*&a
-
*b
-
**c
以下是与变量 b 的值相等且等同于 a 的地址
*** &a
-
b
-
&*b
-
*&b
-
*c
以下是与变量 c 的值相等且等同于 b 的地址
***** b
- *c
指针算术
在 golang 中,指针算术是不可能的,这与 C 语言不同。这会引发编译错误。
package main
func main() {
a := 1
b := &a
b = b + 1
}
输出
上述程序引发编译错误
invalid operation: b + 1 (mismatched types *int and int)
指针的默认零值
指针的默认零值是 nil。让我们来看一个示例程序
package main
import "fmt"
func main() {
var a *int
fmt.Print("Default Zero Value of a pointer: ")
fmt.Println(a)
}
输出:
Default value of pointer:
结论
这就是 golang 中的指针。希望你喜欢这篇文章。请在评论中分享反馈/改进/错误
下一教程 – 结构体
上一篇教程 – defer 关键字
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具