目录
7.Printf(), Sprintf(), FprintF() 都是格式化输出,有什么不同?
8.go语言中,函数(function)和方法(method)的主要区别
9.defer 关键字的实现原理? defer 的作用和特点是什么?
1.与其他语言相比,使用 Go 有什么优势?
1. 高效的并发模型CSP
Go 语言内置了对并发编程的强大支持,通过 Goroutines(轻量级线程)和 Channels(用于通信的同步原语)实现了基于 CSP(Communicating Sequential Processes)模型的并发编程。
Go 的 Goroutines 轻量且高效,系统能够轻松创建和调度大量并发任务,而 Channels 提供了安全的同步和数据传递机制,避免了复杂的锁机制和线程管理,有助于构建清晰、解耦的并发程序。
这种模型使得编写高并发、高吞吐的服务器应用程序变得简单且高效。
2. 简洁的语法与清晰的编程模型
Go 语言设计简洁,语法接近自然语言,易于学习和阅读。它摒弃了复杂的类型系统、继承、虚函数等特性,转而采用结构体组合(Composition over Inheritance)、接口(Interfaces)和类型嵌入(Type Embedding)等简单而强大的工具来实现代码复用和抽象。这种设计使得 Go 代码结构清晰,易于理解和维护。此外,Go 的强制性代码风格(通过 gofmt
自动格式化)确保了团队间代码风格的一致性,降低了协作成本。
3. 快速编译与高性能执行
Go 是一种静态类型、编译型语言,编译速度快,生成的二进制文件小巧、无依赖,可以直接部署到目标环境。Go 编译器生成的代码通常具有优秀的运行时性能,接近 C/C++,同时避免了手动内存管理的复杂性。Go 的运行时(runtime)经过精心优化,尤其是在多核处理器上的并发性能表现出色,非常适合现代硬件架构。
4. 内置的垃圾回收机制
Go 语言内置了自动垃圾回收(GC)系统,程序员无需手动管理内存,大大降低了内存泄漏的风险和内存管理的复杂性。Go 的 GC 通过周期性标记-清除或三色标记法进行垃圾回收,虽然可能带来一定的延迟,但随着 Go 语言版本的演进,GC 性能不断提升,对大部分应用来说,其影响已降至可接受范围。
5. 丰富的标准库与活跃的社区
Go 语言拥有一个庞大且全面的标准库,覆盖了网络编程、加密、压缩、文本处理、数据序列化、操作系统交互等多个领域,为开发者提供了开箱即用的功能。此外,Go 社区活跃,有大量的高质量第三方库和框架,如用于 Web 开发的 Gin、Echo,数据库操作库 sqlx、gorm,以及众多工具和库支持微服务、云原生、DevOps 等场景。Go 语言得到了 Google、Cloudflare、Uber、Twitch 等众多知名公司的广泛应用,形成了深厚的生态系统。
6. 跨平台支持与良好可移植性
Go 语言编译的二进制文件无需依赖运行时环境或虚拟机,可以直接运行在多种操作系统(如 Linux、macOS、Windows)和架构(如 amd64、arm64)上。这种跨平台特性使得 Go 语言非常适合开发云原生应用、微服务、CLI 工具等需要在多种环境下部署的软件。
7. 易于部署与版本管理
Go 语言通过 go build
、go install
等工具简化了编译和部署流程,支持交叉编译,方便为不同平台生成对应二进制文件。Go 1.11 及更高版本引入了模块(Module)系统,提供了对依赖项的版本管理和包管理功能,使得大型项目和团队的依赖管理更为便捷和一致。
8. 强大的错误处理机制
Go 语言强制要求程序员显式处理错误,每个可能出错的操作都应返回一个 error
类型的值。这种设计促进了对错误的明确、及时处理,有助于编写健壮、可维护的代码。结合多返回值、panic/recover
机制以及自定义错误类型,Go 提供了一套完整的错误处理体系。
9. 泛型支持(Go 1.18+)
自 Go 1.18 版本起,Go 语言引入了泛型(Generics)特性,允许开发者编写适用于多种类型的通用代码,提高了代码复用性,减少了类型复制和转换的工作,同时保持了语言的简洁性和类型安全性。
综上所述,Go 语言以其高效的并发模型、简洁的语法、出色的性能、内置的垃圾回收、丰富的标准库、活跃的社区、良好的跨平台支持、易于部署的特性以及强大的错误处理机制,为开发者提供了构建高性能、易于维护的服务器端应用、网络服务、工具和微服务的优秀平台。泛型的加入进一步提升了 Go 的编程能力和代码复用水平。
2.什么是CSP理论
CSP 理论最初由计算机科学家 Tony Hoare 在1978年提出,其核心思想是通过独立的并发实体(processes)通过严格的通信机制来协同工作,而不是直接共享内存。
3.Go的基本结构
4.Golang的数据类型
基本数据类型
整数类型
int
: 默认的有符号整数类型,其大小取决于目标平台,一般为 32 或 64 位。int8
: 8 位有符号整数,取值范围为 -128 到 127。int16
: 16 位有符号整数,取值范围为 -32768 到 32767。int32
: 32 位有符号整数,取值范围为 -2147483648 到 2147483647。int64
: 64 位有符号整数,取值范围为 -9223372036854775808 到 9223372036854775807。
无符号整数类型
uint
: 默认的无符号整数类型,大小与int
相同,取决于平台。uint8
: 8 位无符号整数,取值范围为 0 到 255,也称为byte
。uint16
: 16 位无符号整数,取值范围为 0 到 65535。uint32
: 32 位无符号整数,取值范围为 0 到 4294967295。uint64
: 64 位无符号整数,取值范围为 0 到 18446744073709551615。
浮点数类型
float32
: 32 位浮点数,遵循 IEEE 754 标准,精度约为 6-7 位小数。float64
: 64 位浮点数,Go 语言中默认的浮点数类型,精度约为 15 位小数。
复数类型
complex64
: 由两个float32
组成的复数,表示实部和虚部。complex128
: 由两个float64
组成的复数,表示实部和虚部。
布尔类型
bool
: 表示逻辑值,仅有两个取值:true
和false
。
字符串类型
string
: 不可变的字符序列,由双引号"
或反引号`
包围,内部使用 UTF-8 编码。
派生数据类型
数组类型
[]T
: 固定长度的同类型元素序列,如[]int
表示整数数组。
切片类型
[]T
: 动态长度的同类型元素序列,与数组类似但更灵活,可以改变长度和容量。
结构体类型
struct { Field1 Type1; Field2 Type2; ... }
: 由零个或多个字段组成的数据结构,每个字段有自己的名称和类型。
指针类型
*T
: 指向类型T
的内存地址,通过&
运算符获取变量的地址,通过*
运算符访问指针所指向的值。
函数类型
func (arg1 Type1, arg2 Type2, ...) (ret1 Type1, ret2 Type2, ...)
: 表示具有特定参数列表和返回值列表的函数。
接口类型
interface { Method1(args); Method2(args); ... }
: 定义一组方法签名,任何实现了这些方法的类型都实现了该接口。
地图类型
map[K]V
: 关联数组或哈希表,存储键值对,键类型K
,值类型V
。
通道类型
chan T
: 用于 Goroutines 之间通信的通道,可以是带缓冲或无缓冲的,用于发送和接收类型为T
的值。
引用类型
Go 语言中的引用类型是指那些值为内存地址(指针)的类型。这些类型的变量并不直接存储数据本身,而是存储指向数据的指针。对引用类型的变量进行操作时,实际上是通过指针间接操作其指向的内存区域。Go 语言中的引用类型主要包括以下几种:
指针(Pointer)
- 指针类型变量直接存储一个内存地址,指向另一个变量。声明时使用
*
符号表示指针类型,如var p *int
表示p
是一个指向整数的指针。
切片(Slice)
- 切片是一个动态大小的、可增长的序列,其值由指向底层数组的指针、长度和容量组成。声明时使用
[]
符号,如var s []int
表示s
是一个整数切片。
映射(Map)
- 映射是一个键值对的集合,其值由指向散列表的指针和其他内部数据构成。声明时使用
map
关键字,如var m map[string]int
表示m
是一个键为字符串、值为整数的映射。
通道(Chan)
- 通道是 Go 语言中用于并发通信的类型,其值由指向内部数据结构(如环形缓冲区)的指针等组成。声明时使用
chan
关键字,如var ch chan int
表示ch
是一个传递整数的通道。
接口(Interface)
- 接口类型变量存储一个指向具体类型值的指针以及该值的实际类型信息。声明时使用
interface
关键字,如var i interface{}
表示i
是一个空接口类型变量。
引用类型的主要特点和注意事项包括:
内存共享:引用类型的变量通过指针共享底层数据,对一个变量的操作可能影响到其他变量。例如,多个切片可能共享同一底层数组,修改一个切片的元素会影响其他共享该数组的切片。
资源管理:对于引用类型,尤其是指针、切片和映射,需要关注其生命周期和何时释放资源。例如,使用完映射后应通过
delete
删除键值对或用 nil 赋值清空映射,以避免内存泄漏。并发安全:在并发环境中,对引用类型的访问和修改通常需要同步原语(如互斥锁
sync.Mutex
)来保证线程安全。特别是映射和通道,它们并非线程安全的,直接在多个 Goroutine 中并发读写可能会导致数据竞争或死锁。性能考量:由于引用类型涉及到间接寻址,访问速度理论上略低于直接存储数据的值类型。但在处理大量数据或需要动态调整数据结构大小时,引用类型(如切片、映射)因其灵活性和高效内存管理而更具优势。
总的来说,Go 语言中的引用类型提供了一种间接访问和操作数据的方式,它们在实现数据共享、内存管理和并发通信等方面扮演着重要角色。正确理解和使用引用类型是编写高效、健壮的 Go 语言程序的关键之一。
其他相关类型
rune
: 类型别名,等同于int32
,用于表示 Unicode 代码点(字符)。byte
: 类型别名,等同于uint8
,通常用于表示字节数据。uintptr
: 表示一个无类型指针,主要用于低级别编程或与 C 语言互操作。
这些数据类型构成了 Go 语言的基础,程序员可以根据实际需求选择合适的数据类型来构建程序。通过组合使用这些类型,可以实现复杂的数据结构、算法和系统设计。同时,Go 语言还提供了类型转换、类型断言、类型推断等机制,进一步增强了类型系统的灵活性和实用性。
5.Go 程序中的包是什么
在 Go 程序中,包(package)是组织和管理代码的基本单位。
包在文件顶部使用以下命令声明:
package <packagename>
使用以下方法导入和导出包以重用导出的函数或类型
import <packagename>
Golang 的标准包是
fmt
,其中包含格式化和打印功能,如Println()
6.Go支持什么形式的类型转换?将整数转换为浮点数
显式类型转换
使用类型转换操作符 T(x)
将表达式 x
显式转换为类型 T
。这是在需要将一个类型的值明确转换为另一种类型时使用的。
示例:将整数转换为浮点数:
var i int = 42
f := float64(i) // 显式将整数 i 转换为浮点数类型 float64
在这个例子中,i
是一个整数变量,通过 float64(i)
将其显式转换为 float64
类型的值,并赋给变量 f
。
隐式类型转换(类型提升)
在某些情况下,Go 语言会在编译期间自动进行类型转换,无需显式操作。这通常发生在赋值、算术运算、函数调用等场景中,只要目标类型能无损容纳源类型的所有值。例如,较小的整数类型(如 int8
、int16
)赋值给较大的整数类型(如 int
、int64
),或较小的浮点数类型(如 float32
)赋值给较大的浮点数类型(如 float64
)。
但是,需要注意的是,Go 语言并不支持隐式地将整数类型转换为浮点数类型,也不支持将浮点数类型转换为整数类型。对于这类转换,必须使用显式类型转换。
整数转换为浮点数
综上所述,要将整数转换为浮点数,需要使用显式类型转换,即使用 float64
(或 float32
)作为转换类型的操作符,将整数表达式放在括号内进行转换。例如:
var i int = 123
var f float64 = float64(i) // 将整数 i 转换为浮点数 f
这样,整数 i
的值就被转换为浮点数 f
,并存储在 f
变量中。这个过程在编译时是安全的,因为任何整数都可以无损地转换为浮点数表示。不过,要注意浮点数可能会有精度损失,尤其是在处理非常大或非常小的整数时。在实际编程中,应根据具体需求和精度要求谨慎使用类型转换。
7.Printf(), Sprintf(), FprintF() 都是格式化输出,有什么不同?
- Printf 是标准输出,一般是屏幕,也可以重定向。
- Sprintf()是把格式化字符串输出到指定的字符串中。
- Fprintf()是把格式化字符串输出到文件中。
8.go语言中,函数(function)和方法(method)的主要区别
java中的函数就是方法
go中的函数不属于任何类型和结构体,即没有接收者。而方法是有接收者的。
9.defer 关键字的实现原理? defer 的作用和特点是什么?
定义
在 Go 语言中,
defer
关键字用于延迟执行一个函数调用,直到包含该defer
语句的函数或方法返回时才执行。defer
语句的执行顺序遵循以下规则:
实现原理
Go1.14编译器会将defer函数直接插入到函数的尾部,无需链表和栈上参数拷贝,性能大幅提升。把defer函数在当前函数内展开直接调用,这种方式被称为open coded defer
作用和特点
-
后进先出(LIFO,Last In First Out):在同一个函数或方法中,多个
defer
语句的执行顺序与它们在代码中出现的顺序相反。也就是说,最近定义的defer
语句会最先执行,而最早定义的defer
语句会最后执行。 -
延迟执行:无论
defer
语句出现在函数或方法中的哪个位置,它指定的函数调用都会在函数或方法即将返回时(即所有的return语句执行之前)按上述顺序执行。 -
即使出现 panic:即使函数或方法内部发生了 panic,
defer
语句仍然会被执行,这使得defer
常用于资源清理、关闭文件、解锁互斥锁等需要确保在函数结束前完成的任务,即使在异常情况下也能得到妥善处理。但是defer定义在panic后面就不会执行了 -
执行时机:对于嵌套的函数调用,每个函数内部的
defer
语句会在相应函数返回时执行,与外部函数的defer
语句无关。也就是说,每个函数的defer
语句独立执行,互不影响。
面试题
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))
}
10.Go 语言当中是值传递还是引用传递?两者有什么区别
先说下结论: Go语言中所有的传参都是值传递(传值),都是一个副本,一个持贝。
参数如果是非引用类型(int、string, struct等这些),这样就在函数中就无法修改原内容数据。如果是引用类型(指针、map、slice, chan等这些),这样就可以修改原内容数据。
是否可以修改原内容数据,和传值、传引用没有必然的关系。在C++中,传引用肯定是可以修改原内容数据的,在Go语言里虽然只有传值,但是我们也可以修改原内容数据,因为参数可以是引用类型。(在Go中引用类型指向同一个变量的内存地址)
引用类型和引用传递是2个概念,切记!!!
什么是值传递?
将实参的值传递给形参,形参是实参的一份拷贝,实参和形参的内存地址不同。函数内对形参值内容的修改,是否会影响实参的值内容,取决于参数是否是引用类型。
什么是引用传递? 将实参的地址传递给形参,函数内对形参值内容修改,将会影响实参的值内容。Go语言是没有引用传递的。
11.go语言中,值接收者和指针接收者的区别
- 如果方法的接收者是指针类型,无论调用者是对象还是对象的指针,修改的都是对象本身,对调用者有影响
- 如果方法的接收者是值类型,无论调用者是对象还是对象的指针,修改的都是对象的副本,对调用者没有影响
- go的语法糖会隐式的帮我们创建对应的类型方法
使用指针类型的优点:
- 使用指针类型能修改调用者的值
- 使用指针类型能避免每次调用方法时复制该值,在遇到大型结构体时会更高效
12.Go语言中,返回局部变量的指针是否安全
一般来说,局部变量会在函数返回后消耗,因此被返回的引用就成了“无所指”的引用,程序会进入未知状态。
但在Go中是安全的,Go编译器将会对每个局部变量进行逃逸分析。如果发现局部变量的作用域超出该函数,则不会将内存分配在栈上,而是分配在堆上,因为不在栈区,即使释放函数也不会受影响。
package main
import "fmt"
func add(a int, b int) *int {
res := 0
res = a + b
return &res
}
func main() {
fmt.Println(add(1, 2))
}
// 函数add中的局部变量 res 发生了逃逸。res作为返回值在main函数中继续使用,res执行的内存不能分配在栈上,会随着函数结束而回收,只能分配在堆上。
PS C:\workspace\oslee\test\240605> go run -gcflags="-m -m -l" .\main.go
# command-line-arguments
./main.go:6:2: res escapes to heap:
./main.go:6:2: flow: ~r0 = &res:
./main.go:6:2: from &res (address-of) at ./main.go:8:9
./main.go:6:2: from return &res (return) at ./main.go:8:2
./main.go:6:2: moved to heap: res
./main.go:12:13: ... argument does not escape
0xc00000a0c8
13.Go 语言当中 new和make 的作用是什么?
make和new是内置函数,不是关键字。变量初始化,一般包括2步,变量声明+变最内存分配,var关键字就是用来声明变量的,new和make函数主要是用来分配内存的。
var声明值类型的变量时,系统会默认为他分配内存空间,并赋该类型的零值 比如布尔、数字、字符串、结构体。如果指针类型或者引用类型的变量,系统不会为它分配内存,默认就是nil 。此时如果你想直接使用,那么系统会抛异常,必须进行内存分配后,才能使用。
new和make两个内置函数,主要用来分配内存空间,有了内存,变量就能使用了,主要有以下区别:
- new函数用于为任意类型分配内存并初始化为零值,返回指向该内存的指针,适用于创建普通的值类型。
- make函数专门用于创建并初始化slice、map、chan这三种内建类型,返回的是这些类型的实例本身,而不是指针。
new
type MyStruct struct {
Field1 int
Field2 string
}
// 分配并初始化一个 MyStruct 类型的内存空间
s := new(MyStruct)
// s 是一个 *MyStruct 类型的指针,指向新分配的内存
fmt.Printf("Type of s: %T\n", s) // 输出:*main.MyStruct
// 通过指针 s 访问和修改 MyStruct 的字段
s.Field1 = 42
s.Field2 = "Hello"
fmt.Printf("MyStruct fields: Field1=%d, Field2=%s\n", s.Field1, s.Field2) // 输出:MyStruct fields: Field1=42, Field2=Hello
make
package main
import "fmt"
func main() {
// 创建一个长度为 5、容量为 5 的整数切片
slice := make([]int, 5)
fmt.Printf("slice %v size %d cap %d \n", slice, len(slice), cap(slice))
// 创建一个空的字符串到整数的映射
mp := make(map[string]int, 5)
fmt.Printf("mp %v size %d \n", mp, len(mp))
// 创建一个无缓冲的整数通道
channel := make(chan int, 5)
fmt.Printf("channel %v size %d cap %d \n", channel, len(channel), cap(channel))
}
12.什么是 Goroutine?你如何停止它?
1.什么是 Goroutine
Goroutine 是 Go 语言中实现并发编程的核心概念。它是一种轻量级的线程(或称协程),由 Go 运行时(runtime)管理,而非操作系统直接调度。相比于传统的操作系统的线程,它是一种用户态线程,具有更低的创建和上下文切换开销,使得编写高并发、高性能的应用程序变得更加容易和高效。
2.如何停止 Goroutine?
在 Go 语言中,没有直接的机制来强制停止一个正在运行的 Goroutine。然而,可以通过以下几种方式来实现对 Goroutine 的控制,使其在适当的时候自行退出。
使用 Channel 信号
- 创建一个带信号值(如布尔值)的 Channel,作为 Goroutine 终止的信号通道。
- Goroutine 在循环中监听(
select
语句)该 Channel,当接收到终止信号时,退出循环或执行清理逻辑,进而结束自身。- 主程序或其他控制逻辑通过向该 Channel 发送信号来触发 Goroutine 的停止。
package main
func main() {
stopChan := make(chan bool)
go func() {
for {
select {
case <-stopChan:
// 收到停止信号,退出循环
return
default:
// 正常工作逻辑...
}
}
}()
// 当需要停止 Goroutine 时
stopChan <- true
}
使用 Context
- 使用 context 包提供的 Context 类型作为 Goroutine 的生命周期上下文。
- 将 Context 作为参数传递给 Goroutine,并在其内部定期检查 Context.Done() 或通过 select 语句监听 Context 的关闭信号。
- 主程序或其他控制逻辑通过调用 Context.CancelFunc() 来触发上下文取消,进而通知 Goroutine 结束执行。
package main
import (
"context"
)
func main() {
ctx, cancel := context.WithCancel(context.Background())
go func(ctx context.Context) {
for {
select {
case <-ctx.Done():
// 收到上下文取消信号,退出循环
return
default:
// 正常工作逻辑...
}
}
}(ctx)
// 当需要停止 Goroutine 时
cancel()
}
使用WaitGroup
- 使用 sync.WaitGroup 来同步 Goroutine 的结束。当需要停止一组 Goroutine 时,递减 WaitGroup 的计数器而不是直接发送信号。
- Goroutine 在完成工作后调用 wg.Done(),而主程序使用 wg.Wait() 来阻塞直到所有 Goroutine 完成。
package main
import "sync"
func main() {
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
// Goroutine 工作逻辑...
}()
// 当需要停止 Goroutine(组)时
// 不直接停止,而是等待其自然完成
wg.Wait()
}
共享状态变量与 Mutex/RWMutex
- 通过共享一个标志变量(如布尔值)和互斥锁(
sync.Mutex
或sync.RWMutex
)来协调 Goroutine 的退出。- Goroutine 定期检查该标志变量(在锁保护下),当发现标志指示退出时,结束执行。
package main
import "sync"
var (
stopFlag bool
mu sync.Mutex
)
func main() {
go func() {
for {
mu.Lock()
if stopFlag {
mu.Unlock()
return
}
mu.Unlock()
// 正常工作逻辑...
}
}()
// 当需要停止 Goroutine 时
mu.Lock()
stopFlag = true
mu.Unlock()
}
总结来说,尽管 Go 语言本身不提供直接停止 Goroutine 的机制,但通过巧妙地利用 Channel、Context、WaitGroup 或共享状态变量,可以有效地控制 Goroutine 的生命周期,实现优雅的停止或适时的退出。选择哪种方式取决于具体的编程场景和需求。在实践中,Channel 和 Context 方法因其良好的抽象性和易用性而更为常用。
13.如何在运行时检查变量类型
1.类型断言
value := someInterfaceValue
if concreteValue, ok := value.(ConcreteType); ok {
// value 实际上是 ConcreteType 类型,现在可以使用 concreteValue
} else {
// value 不是 ConcreteType 类型
}
2.反射包 (reflect
)
import "reflect"
var myVar = 42
typ := reflect.TypeOf(myVar) // 获取类型信息
val := reflect.ValueOf(myVar) // 获取值的反射对象
// 检查基本类型
if typ.Kind() == reflect.Int {
fmt.Println("myVar is an int")
}
// 检查类型名
if typ.String() == "int" {
fmt.Println("myVar is of type 'int'")
}
3.类型开关 (switch
)
var someValue interface{} = ... // 可能是多种类型之一
switch actualValue := someValue.(type) {
case int:
fmt.Printf("someValue is an int with value %d\n", actualValue)
case string:
fmt.Printf("someValue is a string with value %s\n", actualValue)
case MyCustomType:
fmt.Printf("someValue is a MyCustomType with field X: %d\n", actualValue.X)
default:
fmt.Println("someValue has an unknown type")
}
14.Go 语言中 cap 函数可以作用于哪些内容?
1. 切片(Slice)
cap
函数最常用于获取切片([]T
)的容量(Capacity)。切片由三个部分组成:指向底层数组的指针、长度(Length)和容量(Capacity)。长度表示切片中有效元素的数量,而容量则是切片底层数组能够容纳的最大元素数量。使用cap
函数可以获取切片当前能够容纳多少个元素,而不考虑当前已有的元素个数:
s := make([]int, 5, 10) // 创建一个长度为 5、容量为 10 的切片
fmt.Println(cap(s)) // 输出:10
2. 通道(Chan)
cap
函数也可以用于获取通道(chan T
)的容量。对于有缓冲的通道,容量表示通道内部缓冲区能够存储的最大元素数量。无缓冲的通道(也称为同步通道)的容量为 0。通过cap
函数,可以了解通道能够暂存多少个元素,以便更好地管理并发通信:
ch := make(chan int, 3) // 创建一个容量为 3 的有缓冲通道
fmt.Println(cap(ch)) // 输出:3