通过示例学习-Go-语言-2023-五-
通过示例学习 Go 语言 2023(五)
Go 语言中的通道(Channel)
这是 Go 语言综合教程系列的第二十四章。有关该系列其他章节,请参考此链接 – Go 语言综合教程系列
下一个教程 – 选择语句
上一个教程 – 协程
现在让我们来看一下当前教程。以下是当前教程的目录
目录
-
概述
-
声明通道
-
通道上的操作
-
发送操作
-
接收操作
-
-
通道方向
-
仅发送通道
-
仅接收通道
-
-
使用 cap()函数获取通道的容量 function")
-
使用 len()函数获取通道的长度 function")
-
关闭通道的操作
-
在通道上的范围循环
-
空通道
-
摘要表
-
结论
概述
通道是 Go 中的一种数据类型,提供了协程之间的同步和通信。可以将其视为管道,供协程之间进行通信。协程之间的这种通信不需要任何显式锁定,锁由通道自身内部管理。通道与协程一起使 Go 编程语言具有并发性。因此我们可以说,Go 语言有两个并发原语:
-
协程 – 轻量级独立执行以实现并发/并行。
-
通道 – 提供协程之间的同步和通信。
声明通道
每个通道变量只能容纳特定类型的数据。Go 在声明通道时使用特殊关键字chan。下面是声明通道的格式
var variable_name chan type
这仅仅声明了一个可以容纳
package main
import "fmt"
func main() {
var a chan int
fmt.Println(a)
}
输出
{nil}
为了定义通道,我们可以使用内置函数make.
package main
import "fmt"
func main() {
var a chan int
a = make(chan int)
fmt.Println(a)
}
输出
0xc0000240c0
在你的机器上,输出的地址可能会不同。
那么这里的 make 是干什么的呢?通道在内部由hchan结构表示,其主要元素是:
type hchan struct {
qcount uint // total data in the queue
dataqsiz uint // size of the circular queue
buf unsafe.Pointer // points to an array of dataqsiz elements
elemsize uint16
closed uint32 // denotes weather channel is closed or not
elemtype *_type // element type
sendx uint // send index
recvx uint // receive index
recvq waitq // list of recv waiters
sendq waitq // list of send waiters
lock mutex
}
使用make时,会创建一个hchan结构的实例,所有字段都初始化为其默认值。
通道上的操作
通道上可以执行两个主要操作。
-
发送
-
接收
让我们逐一查看每个部分。
发送操作
发送操作用于将数据发送到通道。以下是向通道发送的格式。
ch <- val
在这里
-
ch是通道变量
-
val是发送到通道的数据。
请注意val的数据类型和通道的数据类型应该匹配。
接收操作
接收操作用于从通道读取数据。以下是从通道接收的格式。
val := <- ch
在这里
-
ch是通道变量
-
val是一个变量,用于存储从通道读取的数据。
让我们看一个示例,在这个示例中,我们将在一个 goroutine 中发送数据,并在另一个 goroutine 中接收该数据。
package main
import (
"fmt"
"time"
)
func main() {
ch := make(chan int)
fmt.Println("Sending value to channel")
go send(ch)
fmt.Println("Receiving from channel")
go receive(ch)
time.Sleep(time.Second * 1)
}
func send(ch chan int) {
ch <- 1
}
func receive(ch chan int) {
val := <-ch
fmt.Printf("Value Received=%d in receive function\n", val)
}
输出
Sending value to channel
Receiving from channel
Value Received=1 in receive function
在上面的程序中,我们创建了一个名为ch的通道,其数据类型为int,这意味着它只能传输类型为int的数据。函数send()和receive()作为一个 goroutine 启动。我们在 send()的 goroutine 中向通道ch发送数据,并在 receive()的 goroutine 中从ch接收数据。
关于接收操作要注意一个非常重要的点是,发送到通道的特定值在任何 goroutine 中只能接收一次。正如你所见,在发送和接收通道时,goroutine 中没有使用锁。锁是由通道内部管理的,代码中不需要使用显式锁。
默认情况下,当我们使用 make 创建通道时,它会创建一个无缓冲的通道,这意味着创建的通道不能存储任何数据。因此,任何对通道的发送在另一个 goroutine 接收之前都是阻塞的。因此在send()函数中,这一行将会被阻塞。
ch <- 1
直到在receive()函数中接收到值。
val := <-ch
我们还在主函数中设置了一个超时,以允许发送和接收函数都完成。如果在主函数结束时没有超时,程序将退出,两个 goroutine 可能无法调度。
为了说明发送时的阻塞,让我们在send()函数中向通道ch发送值后添加一个日志,并在从ch接收值之前在receive()函数中设置一个超时。
package main
import (
"fmt"
"time"
)
func main() {
ch := make(chan int)
go send(ch)
go receive(ch)
time.Sleep(time.Second * 2)
}
func send(ch chan int) {
ch <- 1
fmt.Println("Sending value to channel complete")
}
func receive(ch chan int) {
time.Sleep(time.Second * 1)
fmt.Println("Timeout finished")
_ = <-ch
return
}
输出
Timeout finished
Sending value to channel complete
日志
Timeout finished
将始终位于之前
Sending value to channel complete
尝试在接收函数中更改不同的超时值。你会发现上述顺序始终存在。这表明,在无缓冲通道上发送时会阻塞,直到在其他 goroutine 上发生接收。接收通道也是阻塞的,除非有另一个 goroutine 向该通道发送数据。
为了说明接收时的阻塞,让我们在 receive()函数中接收值后添加一个日志,并在 send()函数中发送值之前设置一个超时。
package main
import (
"fmt"
"time"
)
func main() {
ch := make(chan int)
go send(ch)
go receive(ch)
time.Sleep(time.Second * 2)
}
func send(ch chan int) {
time.Sleep(time.Second * 1)
fmt.Println("Timeout finished")
ch <- 1
}
func receive(ch chan int) {
val := <-ch
fmt.Printf("Receiving Value from channel finished. Value received: %d\n", val)
}
输出
Timeout finished
Receiving Value from channel finished. Value received: 1
在上面的程序中,我们在发送到通道之前添加了一个超时。
日志
Timeout finished
将始终在之前。
Receiving Value from channel finished. Value received: 1
尝试在 send()函数中更改不同的超时值。你将注意到上述顺序始终存在。这说明在无缓冲通道上的接收会被阻塞,直到其他 goroutine 在该通道上进行发送。
我们也可以在主函数中接收这个值。
package main
import (
"fmt"
)
func main() {
ch := make(chan int)
fmt.Println("Sending value to channel start")
go send(ch)
val := <-ch
fmt.Printf("Receiving Value from channel finished. Value received: %d\n", val)
}
func send(ch chan int) {
ch <- 1
}
输出
Sending value to channel start
Receiving Value from channel finished. Value received: 1
目前为止,我们已经看到无缓冲通道的例子。无缓冲通道没有任何存储,因此,对于无缓冲通道。
-
除非有其他 goroutine 来接收,否则在通道上发送会被阻塞。
-
接收会被阻塞,直到另一侧有其他 goroutine 发送。
在 Go 中,你也可以创建一个缓冲通道。缓冲通道有一定的容量来保存数据,因此,对于缓冲通道:
-
只有在缓冲区满时,发送到缓冲通道才会被阻塞。
-
接收仅在通道为空时被阻塞。
这是使用 make 函数创建缓冲通道的语法。
a = make(chan <type>, capacity)</type>
第二个参数指定通道的容量。无缓冲通道的容量为零。这就是为什么如果没有接收者,发送会被阻塞;如果没有发送者,接收会被阻塞。
让我们看一个缓冲通道的程序。
package main
import (
"fmt"
)
func main() {
ch := make(chan int, 1)
ch <- 1
fmt.Println("Sending value to channnel complete")
val := <-ch
fmt.Printf("Receiving Value from channel finished. Value received: %d\n", val)
}
在上面的程序中,我们创建了一个长度为 1 的缓冲通道,如此。
ch := make(chan int, 1)
我们在主 goroutine 中发送一个值并接收同一个值。这是可能的,因为如果缓冲通道没有满,发送到缓冲通道不会被阻塞。因此,下面的行对于缓冲通道不会阻塞。
ch <- 1
该通道的容量为一。因此,向通道发送数据不会被阻塞,值会存储在通道的缓冲区中。因此,在同一个 goroutine 中发送和接收仅对缓冲通道有效。让我们深入探讨上述提到的两个重要点。
-
当通道已满时,发送到通道会被阻塞。
-
当通道为空时,接收会被阻塞。
让我们看看每个程序。
当通道满时,发送被阻塞
package main
import (
"fmt"
)
func main() {
ch := make(chan int, 1)
ch <- 1
ch <- 1
fmt.Println("Sending value to channnel complete")
val := <-ch
fmt.Printf("Receiving Value from channel finished. Value received: %d\n", val)
}
输出
fatal error: all goroutines are asleep - deadlock!
在上面的程序中,我们创建了一个容量为一的通道。之后,我们向通道发送一个值,然后再向通道发送另一个值。
ch <- 1
ch <- 1
发送到通道的第二个请求被阻塞,因为缓冲区已满,因此导致了死锁情况,因为程序无法继续,这就是为什么你会看到输出为。
fatal error: all goroutines are asleep - deadlock!
当通道为空时,接收被阻塞
package main
import (
"fmt"
)
func main() {
ch := make(chan int, 1)
ch <- 1
fmt.Println("Sending value to channnel complete")
val := <-ch
val = <-ch
fmt.Printf("Receiving Value from channel finished. Value received: %d\n", val)
}
输出
fatal error: all goroutines are asleep - deadlock!
在上面的程序中,我们创建了一个容量为一的通道,之后我们向通道发送一个值,然后从通道接收一个值。接着,我们尝试第二次接收通道中的值,结果导致了死锁,因为程序无法继续,因为通道为空,无法接收。这就是为什么你会看到输出为。
fatal error: all goroutines are asleep - deadlock!
这说明如果通道缓冲区为空,接收将被阻塞。
通道方向
到目前为止,我们已经看到双向通道,既可以发送又可以接收数据。在 golang 中,也可以创建单向通道。可以创建一个只能发送数据的通道,也可以创建一个只能接收数据的通道。这由通道的箭头方向决定。
- 只能发送数据的通道。
这是这种通道的语法
chan<- int
- 只能发送数据的通道
这是这种通道的语法
<-chan in
现在的问题是,为什么你想创建一个只能发送数据或只能接收数据的通道。这在将通道传递给函数时非常有用,因为我们希望限制函数只能发送数据或接收数据。
有多种方法可以将通道作为函数参数传递。通道的箭头方向指定数据流的方向。
-
chan :双向通道(可读可写)
-
chan <- :仅向通道写入
-
<- chan :仅从通道读取(输入通道)
仅发送通道
- 这种通道的签名只能发送数据,传递给函数作为参数时会如下所示。
func process(ch chan<- int){ //doSomething }
- 尝试从这样的通道接收数据将出现以下错误。
invalid operation: <-ch (receive from send-only type chan<- int)
尝试取消注释代码中的以下行以查看上述错误
s := <-ch
代码:
package main
import "fmt"
func main() {
ch := make(chan int, 3)
process(ch)
fmt.Println(<-ch)
}
func process(ch chan<- int) {
ch <- 2
//s := <-ch
}
输出: 2
仅接收通道
- 这种通道的签名只能接收数据,传递给函数作为参数时会如下所示。
func process(ch <-chan int){ //doSomething }
- 尝试向这样的通道发送数据将出现以下错误。
invalid operation: ch <- 2 (send to receive-only type <-chan int)
尝试取消注释代码中的以下行以查看上述错误
ch <- 2
代码:
package main
import "fmt"
func main() {
ch := make(chan int, 3)
ch <- 2
process(ch)
fmt.Println()
}
func process(ch <-chan int) {
s := <-ch
fmt.Println(s)
//ch <- 2
}
输出: 2
使用 cap()函数获取通道容量
缓冲通道的容量是该通道可以容纳的元素数量。容量指通道的缓冲区大小。创建通道时可以在使用 make 函数时指定通道的容量。第二个参数是容量。
无缓冲通道的容量始终为零
package main
import "fmt"
func main() {
ch := make(chan int, 3)
fmt.Printf("Capacity: %d\n", cap(ch))
}
输出
Capacity: 3
在上述程序中,我们在 make 函数中指定了容量为 3
make(chan int, 3)
使用 len()函数获取通道长度
内置的len()函数可用于获取通道的长度。通道的长度是通道中已存在的元素数量。因此,长度实际上表示通道缓冲区中排队的元素数量。通道的长度始终小于或等于通道的容量。
无缓冲通道的长度始终为零
package main
import "fmt"
func main() {
ch := make(chan int, 3)
ch <- 5
fmt.Printf("Len: %d\n", len(ch))
ch <- 6
fmt.Printf("Len: %d\n", len(ch))
ch <- 7
fmt.Printf("Len: %d\n", len(ch))
}
输出
Len: 1
Len: 2
Len: 3
在上述代码中,首先创建了一个容量为 3 的通道。之后,我们不断向通道发送一些值。正如你从输出中注意到的那样,每次发送操作后,通道的长度增加 1,因为长度表示通道缓冲区中的项目数量。
通道的关闭操作
关闭是一个内置函数,可用于关闭通道。关闭通道意味着无法再向通道发送数据。通道通常在所有数据已发送且没有更多数据可发送时关闭。让我们看看一个程序。
package main
import (
"fmt"
"time"
)
func main() {
ch := make(chan int)
go sum(ch, 3)
ch <- 2
ch <- 2
ch <- 2
close(ch)
time.Sleep(time.Second * 1)
}
func sum(ch chan int, len int) {
sum := 0
for i := 0; i < len; i++ {
sum += <-ch
}
fmt.Printf("Sum: %d\n", sum)
}
输出
Sum: 6
在上面的程序中,我们创建了一个通道。然后我们在一个 goroutine 中调用sum函数。在主函数中,我们向通道发送 3 个值,之后关闭通道,表示无法再向通道发送值。sum函数使用 for 循环遍历通道并计算总值。
在关闭的通道上发送将导致恐慌。
请看下面的程序。
package main
func main() {
ch := make(chan int)
close(ch)
ch <- 2
}
输出
panic: send on closed channel
关闭已经关闭的通道也会导致恐慌。
在接收一个通道时,我们也可以使用一个额外的变量来确定通道是否已关闭。下面是相应的语法。
val,ok <- ch
ok 的值将是
-
如果通道未关闭则为真。
-
如果通道已关闭则为假。
package main
import (
"fmt"
)
func main() {
ch := make(chan int, 1)
ch <- 2
val, ok := <-ch
fmt.Printf("Val: %d OK: %t\n", val, ok)
close(ch)
val, ok = <-ch
fmt.Printf("Val: %d OK: %t\n", val, ok)
}
输出
Val: 2 OK: true
Val: 0 OK: false
在上面的程序中创建了一个容量为 1 的通道。然后我们向通道发送了一个值。第一次接收中的ok变量为真,因为通道未关闭。第二次接收中的 ok 变量为假,因为通道已关闭。
通道上的范围循环
可以使用范围循环从通道接收数据,直到其关闭。
package main
import (
"fmt"
"time"
)
func main() {
ch := make(chan int, 3)
ch <- 2
ch <- 2
ch <- 2
close(ch)
go sum(ch)
time.Sleep(time.Second * 1)
}
func sum(ch chan int) {
sum := 0
for val := range ch {
sum += val
}
fmt.Printf("Sum: %d\n", sum)
}
输出
Sum: 6
在上面的程序中,我们创建了一个通道。在主函数中向通道发送了三个值,之后关闭了通道。然后我们调用求和函数,并将通道传递给该函数。在求和函数中,我们对通道进行了范围循环。在遍历完通道中的所有值后,范围循环将退出,因为通道已关闭。
现在脑海中浮现的问题是,如果在主函数中不关闭通道,会发生什么。尝试注释掉关闭通道的那一行。然后运行程序。它也会输出死锁,因为范围循环在求和函数中将永远不会完成。
fatal error: all goroutines are asleep - deadlock!
零通道
通道的零值为零。因此,仅声明一个通道会创建一个默认的零通道,因为通道的零值为零。让我们看看一个演示程序。
package main
import "fmt"
func main() {
var a chan int
fmt.Print("Default zero value of channel: ")
fmt.Println(a)
}
输出
nil
关于零通道需要注意的一些要点。
-
向零通道发送将永远阻塞。
-
从零通道接收将永远阻塞。
-
关闭零通道会导致恐慌。
摘要表
到目前为止,我们已经看到了 5 个关于通道的操作。
-
发送
-
接收
-
关闭
-
长度
-
容量
让我们看看一个摘要表,展示不同类型通道上每个操作的结果。
命令 | 无缓冲通道(未关闭且非零) | 缓冲通道(未关闭且非零) | 已关闭通道 | 零通道 |
---|---|---|---|---|
发送 | 如果没有对应接收者则阻塞,否则成功 | 如果通道已满则阻塞,否则成功 | 恐慌 | 永远阻塞 |
接收 | 如果没有相应的发送者则阻塞,否则成功 | 如果通道为空则阻塞,否则成功 | 如果通道为空则接收数据类型的默认值,否则接收实际值 | 永远阻塞 |
关闭 | 成功 | 成功 | 恐慌 | 恐慌 |
长度 | 0 | 通道缓冲区中排队的元素数量 | -0 如果是无缓冲通道-如果是有缓冲通道则为缓冲区中排队的元素数量 | 0 |
容量 | 0 | 通道缓冲区的大小 | -0 如果是无缓冲通道-如果是有缓冲通道则为缓冲区的大小 | 0 |
结论
这就是关于 golang 中通道的所有内容。希望你喜欢这篇文章。请在评论中分享反馈/改进/错误。
下一篇教程 – 选择语句
上一篇教程 – 协程
Go (Golang) 中的字符常量
目录
-
概述
-
示例
-
类型字符常量
-
未类型命名字符常量
-
未类型未命名字符常量
-
概述
在 Go 中,字符常量表示为单引号之间的值。请参考这篇文章以更好地理解 Golang 中的字符。
同样,为了更好地理解 Golang 中的字符常量,理解 Go 中的类型和未类型常量是很重要的。请参考这篇文章了解更多 – golangbyexample.com/typed-untyped-constant-golang
/
一旦你阅读完这篇文章,你将会明白常量可以用三种方式声明
-
类型常量
-
未类型未命名常量
-
未类型命名常量
字符的情况也是如此。让我们看一个程序来理解它
示例
下面是一个说明字符常量的程序。
package main
import "fmt"
func main() {
type myChar int32
//Typed character constant
const aa int32 = 'a'
var uu = aa
fmt.Println("Untyped unnamed character constant")
fmt.Printf("uu: Type: %T Value: %v\n\n", uu, uu)
//Below line will raise a compilation error
//var vv myBool = aa
//Untyped named character constant
const bb = 'a'
var ww myChar = bb
var xx = bb
fmt.Println("Untyped named character constant")
fmt.Printf("ww: Type: %T Value: %v\n", ww, ww)
fmt.Printf("xx: Type: %T Value: %v\n\n", xx, xx)
//Untyped unnamed character constant
var yy myChar = 'a'
var zz = 'a'
fmt.Println("Untyped unnamed character constant")
fmt.Printf("yy: Type: %T Value: %v\n", yy, yy)
fmt.Printf("zz: Type: %T Value: %v\n", zz, zz)
}
输出:
Untyped unnamed character constant
uu: Type: int32 Value: 97
Untyped named character constant
ww: Type: main.myChar Value: 97
xx: Type: int32 Value: 97
Untyped unnamed character constant
yy: Type: main.myChar Value: 97
zz: Type: int32 Value: 97
在上述程序中,我们创建了一个新类型myChar
type myChar int32
上述程序还展示了
-
类型字符常量
-
未类型未命名字符常量
-
未类型命名字符常量
让我们理解它们的每一个及其行为
类型字符常量
定义如下
const aa int32 = 'a'
请注意,上述代码中的以下行将导致编译错误。这是因为变量aa的类型为int32。因此,下面的行将导致编译错误,因为它无法赋值给类型为myChar的变量。
var v myChar = aa
但是类型字符串常量可以赋值给使用var关键字创建的变量,如下所示
var uu = aa
未类型命名字符常量
定义如下
const bb = 'a'
未类型命名字符串常量可以赋值给类型为myChar的变量以及使用var关键字创建的变量,因为它是未类型的,所以常量的类型将根据赋值给它的变量类型决定。
var ww myChar = bb
var xx = bb
未类型未命名字符常量
它如下所示
'a'
未类型未命名字符串常量可以赋值给类型为myChar的变量以及使用var关键字创建的变量,因为它是未类型的,所以常量的类型将根据赋值给它的变量类型决定。
var yy myChar = 'a'
var zz = 'a'
Go 语言中的字符(Golang)
目录
-
概述
-
代码示例
-
注意事项
概述
Golang 没有任何‘char’数据类型。因此
-
byte用于表示 ASCII 字符。byte 是 uint8 的别名,因此为 8 位或 1 字节,可以表示所有从 0 到 255 的 ASCII 字符
-
rune用于表示所有 UNICODE 字符,包括所有存在的字符。rune 是int32的别名,能够表示所有 UNICODE 字符。其大小为 4 字节。
-
一个长度为一的string也可以隐式表示一个字符。一个字符字符串的大小将取决于该字符的编码。对于 utf-8 编码,它将在 1-4 字节之间
要声明byte或rune,我们使用单引号。在声明byte时,必须指定类型。如果不指定类型,则默认类型为rune。
要声明一个string,我们使用双引号或反引号。双引号字符串支持转义字符,而反引号字符串是原始字面字符串,不支持任何转义。
代码示例
请参见下面的程序。它显示
-
表示字符‘a‘的 byte
-
表示英镑符号‘£‘的 rune
-
一个字符微符号‘µ’的字符串
package main
import (
"fmt"
"reflect"
"unsafe"
)
func main() {
//If you don't specify type here
var b byte = 'a'
fmt.Println("Priting Byte:")
//Print Size, Type and Character
fmt.Printf("Size: %d\nType: %s\nCharacter: %c\n", unsafe.Sizeof(b), reflect.TypeOf(b), b)
r := '£'
fmt.Println("\nPriting Rune:")
//Print Size, Type, CodePoint and Character
fmt.Printf("Size: %d\nType: %s\nUnicode CodePoint: %U\nCharacter: %c\n", unsafe.Sizeof(r), reflect.TypeOf(r), r, r)
s := "µ" //Micro sign
fmt.Println("\nPriting String:")
fmt.Printf("Size: %d\nType: %s\nCharacter: %s\n", unsafe.Sizeof(s), reflect.TypeOf(s), s)
}
输出:
Priting Byte:
Size: 1
Type: uint8
Character: a
Priting Rune:
Size: 4
Type: int32
Unicode CodePoint: U+00A3
Character: £
Priting String:
Size: 16
Type: string
Character: µ
注意事项
- 用非 ASCII 字符声明 byte 会导致编译器错误,如下所示。我尝试用一个对应代码为 285 的字符
constant 285 overflows byte
- 在初始化byte或rune时,只能在单引号内声明一个字符。当尝试在单引号中添加两个字符时,将生成如下编译器警告
invalid character literal (more than one character)
检查当前登录用户在 Linux 上所属的所有组。
在 Linux 上,groups命令可以用来查看用户属于哪些组。
只需在终端中输入groups
。以下是我在 Mac 机器上的输出。
staff everyone localaccounts _appserverusr admin
检查 Go (Golang) 中是否设置了环境变量
os.LookupEnv 函数可用于检查特定环境变量是否已设置。它返回一个布尔值,如果给定的环境变量已设置,则为 true,否则为 false。让我们来看一个工作代码:
package main
import (
"fmt"
"log"
"os"
)
func main() {
//Set env a to b
err := os.Setenv("a", "x")
if err != nil {
log.Fatal(err)
}
val, present := os.LookupEnv("a")
fmt.Printf("a env variable present: %t\n", present)
fmt.Println(val)
val, present = os.LookupEnv("b")
fmt.Printf("b env variable present: %t\n", present)
}
输出:
a env variable present: true
x
b env variable present: false
在 Go (Golang) 中检查文件或目录是否存在
来源:
golangbyexample.com/check-if-file-or-directory-exists-go/
os.Stat 和 os.IsNotExist() 可用于检查特定文件或目录是否存在。
目录
** 文件存在
- 文件夹存在
**文件存在 **
package main
import (
"log"
"os"
)
func main() {
fileinfo, err := os.Stat("temp.txt")
if os.IsNotExist(err) {
log.Fatal("File does not exist.")
}
log.Println(fileinfo)
}
文件夹存在
package main
import (
"log"
"os"
)
func main() {
folderInfo, err := os.Stat("temp")
if os.IsNotExist(err) {
log.Fatal("Folder does not exist.")
}
log.Println(folderInfo)
}
检查给定的链表在 Go(Golang)中是否有循环。
目录
-
概述
-
程序
概述
目标是判断给定的链表是否有循环。如果链表中的最后一个节点指向前面的某个节点,则存在循环。
示例
上述链表有循环。以下是我们可以遵循的方法。
-
有两个指针,一个是慢指针,另一个是快指针。两者最初都指向头节点。
-
现在将慢指针移动 1 个节点,将快指针移动 2 个节点。
slow := slow.Next
fast := fast.Next.Next
- 如果慢指针和快指针在任何时刻相同,则链表有循环。
程序
这是相应的程序。
package main
import "fmt"
func main() {
first := initList()
ele4 := first.AddFront(4)
first.AddFront(3)
ele2 := first.AddFront(2)
first.AddFront(1)
//Create cycle
ele4.Next = ele2
output := hasCycle(first.Head)
fmt.Println(output)
}
type ListNode struct {
Val int
Next *ListNode
}
type SingleList struct {
Len int
Head *ListNode
}
func (s *SingleList) AddFront(num int) *ListNode {
ele := &ListNode{
Val: num,
}
if s.Head == nil {
s.Head = ele
} else {
ele.Next = s.Head
s.Head = ele
}
s.Len++
return ele
}
func initList() *SingleList {
return &SingleList{}
}
func hasCycle(head *ListNode) bool {
if head == nil || head.Next == nil {
return false
}
hasCycle := false
slow := head
fast := head
for slow != nil && fast != nil && fast.Next != nil {
slow = slow.Next
fast = fast.Next.Next
if slow == fast {
hasCycle = true
break
}
}
return hasCycle
}
输出
true
注意: 请查看我们的 Golang 高级教程。本系列教程详尽,我们尝试覆盖所有概念并提供示例。本教程适合希望获得专业知识和对 Golang 有深入理解的人 – Golang 高级教程
如果你有兴趣了解所有设计模式如何在 Golang 中实现。如果是,那么这篇文章适合你 – 所有设计模式 Golang
检查给定的树是否为二叉搜索树(BST)在 Go (Golang)中
目录
-
概述
-
程序
概述
我们可以使用以下策略来判断给定的树是否为 BST。
-
对于给定的当前节点,如果左子树和右子树都是 BST
-
左子树中的最大值小于当前节点值。
-
右子树中的最小值大于当前节点值。
这是相应的程序。
程序
package main
import (
"fmt"
"math"
)
type TreeNode struct {
Val int
Left *TreeNode
Right *TreeNode
}
func isValidBST(root *TreeNode) bool {
if root == nil {
return true
}
isValid, _, _ := isValidBSTUtil(root)
return isValid
}
func isValidBSTUtil(node *TreeNode) (bool, int, int) {
if node.Left == nil && node.Right == nil {
return true, node.Val, node.Val
}
min := node.Val
max := node.Val
isValidLeft := true
var leftMin, leftMax int
if node.Left != nil {
isValidLeft, leftMin, leftMax = isValidBSTUtil(node.Left)
if !isValidLeft {
return false, 0, 0
}
if node.Val <= leftMax {
return false, 0, 0
}
min = leftMin
}
isValidRight := true
var rightMin, rightMax int
if node.Right != nil {
isValidRight, rightMin, rightMax = isValidBSTUtil(node.Right)
if !isValidRight {
return false, 0, 0
}
if node.Val >= rightMin {
return false, 0, 0
}
max = rightMax
}
return true, min, max
}
func minOfFour(a, b, c, d int) int {
return int(math.Min(float64(a), math.Min(float64(b), math.Min(float64(c), float64(d)))))
}
func maxOfFour(a, b, c, d int) int {
return int(math.Max(float64(a), math.Max(float64(b), math.Max(float64(c), float64(d)))))
}
func main() {
root := TreeNode{Val: 2}
root.Left = &TreeNode{Val: 1}
root.Right = &TreeNode{Val: 3}
isValidBST := isValidBST(&root)
fmt.Println(isValidBST)
}
输出
true
注意: 请查看我们的 Golang 高级教程。本系列教程内容详尽,我们尽力覆盖所有概念并提供示例。本教程适合那些希望深入了解 Golang 并获得专业知识的人—— Golang 高级教程
如果你有兴趣了解如何在 Golang 中实现所有设计模式。如果是,那么这篇文章适合你—— 所有设计模式 Golang
在 Go(Golang)中检查链表是否是循环链表
![链表 - 是否是循环链表图像](https://gitee.com/OpenDocCN/geekdoc-golang-zh/raw/master/docs/go-exam-2023/img/fd1244a69df50e7bcb299f396b6f796a.png)
目录
-
概述
-
程序
概述
检查链表是否是循环链表。如果所有节点以循环的形式连接,则链表是循环的。
程序
在下面的程序中,我们首先创建一个链表。然后检查它是否是循环链表。它会先打印 false。之后,我们将链表转换为循环链表。然后再次检查它是否是循环链表。现在打印 true。
package main
import "fmt"
type node struct {
data string
next *node
}
type singlyLinkedList struct {
len int
head *node
}
func initList() *singlyLinkedList {
return &singlyLinkedList{}
}
func (s *singlyLinkedList) AddFront(data string) {
node := &node{
data: data,
}
if s.head == nil {
s.head = node
} else {
node.next = s.head
s.head = node
}
s.len++
return
}
func (s *singlyLinkedList) Traverse() error {
if s.head == nil {
return fmt.Errorf("TraverseError: List is empty")
}
current := s.head
for current != nil {
fmt.Println(current.data)
current = current.next
}
return nil
}
//Function to convert singly linked list to circular linked list
func (s *singlyLinkedList) ToCircular() {
current := s.head
for current.next != nil {
current = current.next
}
current.next = s.head
}
func (s *singlyLinkedList) IsCircular() bool {
if s.head == nil {
return true
}
current := s.head.next
for current.next != nil && current != s.head {
current = current.next
}
return current == s.head
}
func main() {
singleList := initList()
fmt.Printf("AddFront: D\n")
singleList.AddFront("D")
fmt.Printf("AddFront: C\n")
singleList.AddFront("C")
fmt.Printf("AddFront: B\n")
singleList.AddFront("B")
fmt.Printf("AddFront: A\n")
singleList.AddFront("A")
err := singleList.Traverse()
if err != nil {
fmt.Println(err.Error())
}
isCircular := singleList.IsCircular()
fmt.Printf("Before: Is Circular: %t\n", isCircular)
fmt.Printf("Size: %d\n", singleList.len)
singleList.ToCircular()
isCircular = singleList.IsCircular()
fmt.Printf("After: Is Circular: %t\n", isCircular)
}
输出
AddFront: D
AddFront: C
AddFront: B
AddFront: A
A
B
C
D
Before: Is Circular: false
Size: 4
After: Is Circular: true
在 Go(Golang)中检查一个数字是否是回文。
目录
-
概述
-
第一种解决方案 – 反转数字
-
第二种解决方案 – 使用递归
概述
例如,下面的数字是回文。
1
121
12321
9
0
下面的数字不是回文。
-121
1211
我们有两种解决方案来判断一个数字是否是回文。
-
反转数字。如果反转后的数字等于原始数字,则该数字是回文。
-
另一种方法是使用递归并传入数字的指针。在递归树向下移动时,将数字除以 10。当向上移动递归树时,将指针处的值除以 10。在递归树的任何步骤中,当它们相遇时,原始数字的最后一位将是第一位,指针处数字的第一位将是最后一位。我们可以比较这两者以检查它们是否相等。这个检查在每次相遇时都会进行。
第一种解决方案 – 反转数字
以下是相应的程序
package main
import (
"fmt"
"math"
)
func main() {
output := isPalindrome(121)
fmt.Println(output)
output = isPalindrome(12)
fmt.Println(output)
output = isPalindrome(1234)
fmt.Println(output)
output = isPalindrome(12321)
fmt.Println(output)
output = isPalindrome(-101)
fmt.Println(output)
}
func isPalindrome(x int) bool {
if x < 0 {
return false
}
if x < 10 {
return true
}
xReversed := reverse(x)
return xReversed == x
}
func reverse(x int) int {
sign := "positive"
if x >= 0 {
sign = "positive"
} else {
sign = "negative"
}
x = int(math.Abs(float64(x)))
var reversedDigit int
for x > 0 {
lastDigit := x % 10
reversedDigit = reversedDigit*10 + lastDigit
x = x / 10
}
if sign == "negative" {
reversedDigit = reversedDigit * -1
}
return reversedDigit
}
输出
true
false
false
true
false
第二种解决方案 – 使用递归
以下是相应的程序
package main
import "fmt"
func main() {
a := 121
output := isPalindrome(a, &a)
fmt.Println(output)
a = 12
output = isPalindrome(a, &a)
fmt.Println(output)
a = 1234
output = isPalindrome(a, &a)
fmt.Println(output)
a = 12321
output = isPalindrome(a, &a)
fmt.Println(output)
a = -121
output = isPalindrome(-a, &a)
fmt.Println(output)
}
func isPalindrome(x int, dup *int) bool {
if x < 0 {
return false
}
if x < 10 {
return true
}
palin := isPalindrome(x/10, dup)
*dup = *dup / 10
lastDigit := x % 10
if palin && *dup%10 == lastDigit {
return true
}
return false
}
输出
true
false
false
true
false
```*
<!--yml
类别:未分类
日期:2024-10-13 06:15:37
-->
# 在 Go (Golang)中检查一个数字是负数还是正数
> 来源:[`golangbyexample.com/num-positive-negative-go/`](https://golangbyexample.com/num-positive-negative-go/)
目录
+ 概述
+ 代码
# **概述**
**math**包提供了一个**Signbit**方法,可以用来检查给定的数字是负数还是正数。
+ 对于负数,它返回 true
+ 对于正数,它返回 false
以下是该函数的签名。它接收一个浮点数作为输入,并返回一个布尔值
```go
func Signbit(x float64) bool
代码
package main
import (
"fmt"
"math"
)
func main() {
//Will return false for positive
res := math.Signbit(4)
fmt.Println(res)
//Will return true for negative
res = math.Signbit(-4)
fmt.Println(res)
//Will return false for zerp
res = math.Signbit(0)
fmt.Println(res)
//Will return false for positive float
res = math.Signbit(-0)
fmt.Println(res)
}
输出:
false
true
false
false
检查 Go (Golang) 中 HTTP 请求是否存在特定 header。
目录
- 概述
概述
以下是 header 在 Go 中表示的格式。
type Header map[string][]string
所以在内部,header 是一个键值映射。
- 键以规范形式表示。规范形式意味着第一个字符和连字符后面的任何字符都是大写字母。其余字符都是小写字母。规范形式的示例包括:
Content-Type
Accept-Encoding
- 值表示为字符串数组。为什么是字符串数组?因为在请求中使用相同键的两个 header 和不同值是完全可以的。两个值将被收集到数组中。
例如,如果在传入请求中有以下 headers:
content-type: applcation/json
foo: bar1
foo: bar2
然后在服务器上,header 将如下所示:
map[string][]string {
"Content-Type": {"application/json"},
"Foo": {"bar1" , "bar2"}
}
由于 header 是一个映射,我们可以利用映射的特性检查某个特定键是否存在。以下是检查映射中键是否存在的格式。
val, ok := mapName[key]
这里有两种情况
-
如果键存在,val 变量将是映射中键的值,而 ok 变量将为 true。
-
如果键不存在,val 变量将是值类型的默认零值,而 ok 变量将为 false。
让我们来看一个例子。
package main
import (
"fmt"
"net/http"
)
func main() {
handler := http.HandlerFunc(handleRequest)
http.Handle("/example", handler)
http.ListenAndServe(":8080", nil)
}
func handleRequest(w http.ResponseWriter, r *http.Request) {
headers := r.Header
val, ok := headers["Content-Type"]
if ok {
fmt.Printf("Content-Type key header is present with value %s\n", val)
} else {
fmt.Println("Content-Type key header is not present")
}
val, ok = headers["someKey"]
if ok {
fmt.Printf("someKey key header is present with value %s\n", val)
} else {
fmt.Println("someKey key header is not present")
}
}
在上述程序中,我们启动了一个监听端口 8080 的服务器。我们还在该端点定义了一个 URL。运行此服务器并进行以下 API 调用。
curl -v -X POST http://localhost:8080/example -H "content-type: application/json"
运行此 API 后,请检查终端中的输出。它将输出结果。你可以检查输出。content-type header 存在,因此输出其值。someKey header 不存在,因此打印未找到。此外,请注意,在访问 header 映射时,我们需要以规范形式输入键。例如,即使 curl 中提供的 header 是:
content-type
但是在代码中访问 header 映射时,我们使用的键是规范形式 Content-Type。
val, ok := headers["Content-Type"]
如果我们只想检查一个键是否存在而不需要值,则可以用空标识符即“_”替代值。
_, ok = employeeSalary["Sam"]
检查字符串在 Go (Golang)中是否包含另一个字符串
目录
-
概述
-
代码:
概述
在 Golang 中,字符串是 UTF-8 编码的。GO 的strings包提供了一个Contains方法,可以用来检查特定字符串是否是另一个字符串的子字符串。
以下是函数的签名
func Contains(s, substr string) bool
如你所见,Compare 函数的返回值是一个布尔值。这个值将是
-
true 是substr在s中存在
-
false 是substr在s中不存在
代码:
package main
import (
"fmt"
"strings"
)
func main() {
present := strings.Contains("abc", "ab")
fmt.Println(present)
present = strings.Contains("abc", "xyz")
fmt.Println(present)
}
输出:
true
false
```*
<!--yml
类别:未分类
日期:2024-10-13 06:52:44
-->
# 检查字符串在 Go (Golang) 中是否包含单个或多个空格
> 来源:[`golangbyexample.com/string-whitespace-golang/`](https://golangbyexample.com/string-whitespace-golang/)
目录
+ 概述
+ 程序
# **概述**
可以使用简单的正则表达式来检查字符串在 Go 中是否包含单个或多个空格。
这是相同的程序
# **程序**
```go
package main
import (
"fmt"
"regexp"
)
func main() {
//Single whitespace
sampleWord := "Good morning"
isWhitespacePresent := regexp.MustCompile(`\s`).MatchString(sampleWord)
fmt.Println(isWhitespacePresent)
//Multiple Whitespace
sampleWord = "Good morning"
isMultipleWhitespacePresent := regexp.MustCompile(`\s*`).MatchString(sampleWord)
fmt.Println(isMultipleWhitespacePresent)
}
输出
true
true
注意: 请查看我们的 Golang 高级教程。该系列教程内容详尽,我们尝试用例子覆盖所有概念。本教程适合那些希望获得专业知识和对 Golang 有深入理解的人— Golang 高级教程
如果你有兴趣了解所有设计模式如何在 Golang 中实现。如果是的话,那么这篇文章就是为你准备的—
注意: 请查看我们的系统设计教程系列 系统设计问题
检查字符串是否以某个后缀结尾(Go 语言)
目录
-
概述**
-
代码:
概述
在 Go 中,字符串是 UTF-8 编码的。Go 的 strings 包提供了一个 HasSuffix 方法,可以用来检查字符串是否以某个特定后缀结尾
以下是该函数的签名
func HasSuffix(s, prefix string) bool
让我们来看一下工作代码
代码:
package main
import (
"fmt"
"strings"
)
func main() {
//Input string contains the suffix
res := strings.HasSuffix("abcdef", "ef")
fmt.Println(res)
//Input string doesn't contain the suffix
res = strings.HasPrefix("abcdef", "fg")
fmt.Println(res)
}
输出:
true
false
```*
<!--yml
类别:未分类
日期:2024-10-13 06:09:51
-->
# 在 Go (Golang) 中检查字符串是否为数字
> 来源:[`golangbyexample.com/check-if-string-is-number-golang/`](https://golangbyexample.com/check-if-string-is-number-golang/)
**strconv.Atoi()** 函数可用于检查字符串是否为数字。如果传入的字符串不是数字,此函数将返回错误。该函数将数字解析为基数 10。
[`golang.org/pkg/strconv/#Atoi`](https://golang.org/pkg/strconv/#Atoi)
函数的签名是
```go
func Atoi(s string) (int, error)
有效代码:
package main
import (
"fmt"
"strconv"
)
func main() {
x := "1234"
val, err := strconv.Atoi(x)
if err != nil {
fmt.Printf("Supplied value %s is not a number\n", x)
} else {
fmt.Printf("Supplied value %s is a number with value %d\n", x, val)
}
y := "123b"
val, err = strconv.Atoi(y)
if err != nil {
fmt.Printf("Supplied value %s is not a number\n", y)
} else {
fmt.Printf("Supplied value %s is a number with value %d\n", y, val)
}
}
输出:
Supplied value 1234 is a number with value 1234
Supplied value 123b is not a number
在 Go(Golang)中检查 IP 地址是 IPV4 还是 IPV6
-
IPV4 地址是 4 字节字符串,每个字节用点(‘.’)分隔
-
IPV6 地址由 8 组 4 个十六进制数字组成,每组用冒号(‘:’)分隔
以下代码
-
如果给定的 IP 地址无效,则打印无效
-
否则打印该 IP 地址是 ipV4 还是 ipV6
代码:
package main
import (
"fmt"
"net"
)
func main() {
validIPV4 := "10.40.210.253"
checkIPAddressType(validIPV4)
invalidIPV4 := "1000.40.210.253"
checkIPAddressType(invalidIPV4)
valiIPV6 := "2001:0db8:85a3:0000:0000:8a2e:0370:7334"
checkIPAddressType(valiIPV6)
invalidIPV6 := "2001:0db8:85a3:0000:0000:8a2e:0370:7334:3445"
checkIPAddressType(invalidIPV6)
}
func checkIPAddressType(ip string) {
if net.ParseIP(ip) == nil {
fmt.Printf("Invalid IP Address: %s\n", ip)
return
}
for i := 0; i < len(ip); i++ {
switch ip[i] {
case '.':
fmt.Printf("Given IP Address %s is IPV4 type\n", ip)
return
case ':':
fmt.Printf("Given IP Address %s is IPV6 type\n", ip)
return
}
}
}
输出:
Given IP Address 10.40.210.253 is IPV4 type
Invalid IP Address: 1000.40.210.253
Given IP Address 2001:0db8:85a3:0000:0000:8a2e:0370:7334 is IPV6 type
Invalid IP Address: 2001:0db8:85a3:0000:0000:8a2e:0370:7334:3445
检查一个项是否存在于 Go (Golang) 的切片中
让我们看看一个工作代码
package main
import "fmt"
func main() {
source := []string{"san", "man", "tan"}
result := find(source, "san")
fmt.Printf("Item san found: %t\n", result)
result = find(source, "can")
fmt.Printf("Item san found: %t\n", result)
}
func find(source []string, value string) bool {
for _, item := range source {
if item == value {
return true
}
}
return false
}
输出:
Item san found: true
Item can found: false
检查文件是否是目录在 Go (Golang)
请查看下面的代码以了解一个文件是否是文件或是目录
-
如果 temp 是一个文件,输出将是 = “temp 是一个文件”
-
如果 temp 是一个目录,输出将是 = “temp 是一个目录”
package main
import (
"fmt"
"log"
"os"
)
var (
fileInfo *os.FileInfo
err error
)
func main() {
info, err := os.Stat("temp")
if os.IsNotExist(err) {
log.Fatal("File does not exist.")
}
if info.IsDir() {
fmt.Println("temp is a directory")
} else {
fmt.Println("temp is a file")
}
}
检查 N 及其双倍是否存在于 Go (Golang)中
目录
-
概述
-
程序
概述
给定一个数组。目标是找出是否存在任何数字,其双倍也存在。
示例 1
Input: [8,5,4,3]
Output: true
Explanation: 4 and 8
示例 2
Input: [1,3,7,9]
Output: false
Explanation: There exists no number for which its double exist
这个思路是使用一个映射。对于每个元素,我们将检查并返回 true 如果
-
如果它的双倍存在于映射中
-
如果数字是偶数,那么如果它的一半存在于映射中
程序
下面是相同程序
package main
import "fmt"
func checkIfExist(arr []int) bool {
numMap := make(map[int]bool)
for i := 0; i < len(arr); i++ {
if numMap[arr[i]*2] {
return true
}
if arr[i]%2 == 0 && numMap[arr[i]/2] {
return true
}
numMap[arr[i]] = true
}
return false
}
func main() {
output := checkIfExist([]int{8, 5, 4, 3})
fmt.Println(output)
output = checkIfExist([]int{1, 3, 7, 9})
fmt.Println(output)
}
输出:
true
false
注意: 请查看我们的 Golang 高级教程。该系列教程详细且我们努力涵盖所有概念及示例。此教程适合希望获得 Golang 专业知识和扎实理解的读者 - Golang 高级教程
如果您有兴趣了解所有设计模式如何在 Golang 中实现。如果是,那么这篇文章适合您 - 所有设计模式 Golang
此外,您还可以查看我们的系统设计教程系列 - 系统设计教程系列