在这篇文章中,我们来学习Go语言的内置函数(built-in)。

什么是Go语言内置函数呢?

简单地理解就是指Go内置的不需要以包名为前缀就可以直接访问的函数:

//非内置函数
import "fmt"
fmt.Println("test")
//内置函数
println("test")

按目前Go最新的版本(1.20.5),Go语言总共有15个内置函数,分别为:append,new,make,len,cap,copy,delete,panic,recover,close,complex,real,imag,print,println。

append

append函数用于将一个或多个元素添加到切片(slice)的末尾,其函数签名为:

func append(slice []Type, elems ...Type) []Type

append函数第一个参数slice是一个切片,elems表示切片的元素,其数据类型与切片元素的数据类型必须相同:

s0 := []int{12}

// 添加一个元素 s1 = []int{1, 2, 3}
s1 := append(s0, 3)   

 // 添加多个元素 s2 = []int{1, 2, 3, 4, 5, 6}
s2 := append(s1, 456)

// 将切片添加到另一个切片 s3 = []int{1, 2, 3, 4, 5, 6 , 1 , 2}
s3 := append(s2, s0...) 

 // 截取不同切片再合并 s4 = []int{[4 5 6 3 4 5 6 1 2]}
s4 := append(s3[3:6], s3[2:]...)

//空接口切片
var t []interface{}
//t == []interface{}{100, 6.8, "test"}
t = append(t, 1006.8"test")   

var b []byte
// 将字符串添加字节数组 b = []byte{'j', 'u', 's','t','','g','o'}
b = append(b, "just go"...) 

append函数往切片添加元素时,如果切片的容量还没满,那么返回的新切片与源切片指向同一个底层数组:

package main

import "fmt"

func main() {

 //创建一个容量为2,长度为0的切片
 s1 := make([]int02)

 //向s1添加两个元素
 s2 := append(s1, 34)

 fmt.Printf("s1的地址:%p,容量:%d,长度:%dn", s1, cap(s1), len(s1))
 fmt.Printf("s2的地址:%p,容量:%d,长度:%dn", s2, cap(s2), len(s2))
}

上面代码的运行结果如下所示,可以看出两个切片指向的底层数组地址是一致的:

s1的地址:0xc0000140a0,容量:2,长度:0
s2的地址:0xc0000140a0,容量:2,长度:2

如果切片的容量已经耗尽,那么append函数会为切片重新分配一个更大容量的底层数组,并将数据复制过去,此时返回的切片则指向新分配的数组:

package main

import "fmt"

func main() {
 //创建一个容量为2,长度为0的切片
 s3 := make([]int02)
 //向s4添加两个元素
 s4 := append(s3, 1234)

 fmt.Printf("s3的地址:%p,容量:%d,长度:%dn", s3, cap(s3), len(s3))
 fmt.Printf("s4的地址:%p,容量:%d,长度:%dn", s4, cap(s4), len(s4))
}

上面代码的运行结果如下,可以看到,向一个容量只有2的切片添加4个元素时,append函数会自动扩容:

s3的地址:0xc0000140c0,容量:2,长度:0
s4的地址:0xc000020080,容量:4,长度:4

new

new函数用于为指定的类型分配内存,并返回一个对应内存的指针,其函数签名如下:

func new(Type) *Type

使用new分配的一般是普通数据类型(如:int,float32等)或复合类型(比如array,struct):

i := new(int)
*i = 10
fmt.Println(*i) //10

type User struct{
    ID int
    Name string
}

// 下面使用new创建结构体的语句等同于 u := &User{}
u := new(User) 

make

make函数用于创建并初始化slice、channel和map这样的引用类型,其函数签名如下:

func make(t Type, size ...IntegerType) Type

与new函数相似,make的第一个参数是所要定义的数据类型,而与new函数不同的是,make函数并不是返回一个指针类型,这是因为slice,channel和map是引用类型:

m := make(map[string]string)

当创建切片时,必须传入第二个参数来指定切片的长度:

s := make([]int,2)

也可以传入第三个参数来指定义切片的容量,如果不传第三个参数,则容量与长度相等:

//容量:5,长度:2
s1 := make([]int,2,5)
//未指定容量,因此容量与长度都等3
s2 := make([]string,3)

如果是创建map类型时,那么就不需要传入第二个与第三个参数了:

m := make(map[string]string)

创建channel对象时,make函数的第二个参数用于指定channel缓冲区的大小,如果没有传入,就表示创建了一个没有缓冲区的channel:

//无缓冲区
c1 := make(chan int)
//缓冲区大小:2
c := make(chan int,2)

len与cap

len函数用于获得指定类型的长度,其函数签名如下:

func len(v Type) int

len函数只能用于获得数组go数组,数组指针,切片,channel以及字符串这五种类型的长度。

如果是数组或者数组指针,len函数返回的是数组元素数量:

a := [5]int{12345}

p := &a

fmt.Printf("数组长度:%d,指针数组长度:%dn"len(a), len(p))

对于channel类型,len函数返回channel当前缓冲区元素的个数:

package main

import (
 "fmt"
 "sync"
)

func main() {
  //容量为10
 ch := make(chan int10)
 var w sync.WaitGroup
 w.Add(1)
 go func(ch chan int) {
  ch <- 1
  ch <- 2
  ch <- 3
  close(ch)
  w.Done()
 }(ch)
 w.Wait()
 fmt.Println(len(ch))//3
}

对于切片,len函数返回则返回切片元素的数量:

//长度为2,容量为10的切片
s := make([]string,2,10)
fmt.Println("切片的长度:%d",len(s))//2

对于字符串,len函数返回的是字符串的字节数,而不是字符串的字符数,因为len把字符串当作字节数组来处理:

fmt.Println(len("学习Go语言"))//14
fmt.Println(len([]byte("学习Go语言"))) //14

cap函数用于获得指定类型的容量,其函数的签名如下:

func cap(v Type) int

对于数组或者数组指针,容量与长度是相等的,因此cap函数与len函数的返回值是相等:

0 <= len(s) == cap(s)

而对于切片与channel类型,则返回其容量,并且容量大于长度,切片与channel的容量与长度的关系为:

0 <= len(s) <= cap(s)

cap函数用法示例:

a := [5]int{1,2,3,4,5}
s := make([]int,2 10)

fmt.Printf("数组的长度:%d,数组的容量:%dn",len(a),cap(a))
fmt.Printf("切片的长度:%d,切片的容量:%dn",len(s),cap(s))

copy

copy函数用于将源切片src复制到目标切片dst中,并返回复制元素的个数,其函数签名如下:

func copy(dst, src []Type) int

源切片与目标切片的数据类型必须相同,且目标切片必须有对应的容量可以存储复制过来的元素:

package main

import "fmt"

func main() {

 var s1 = make([]int1)
 var s2 = []int{123}
 i := copy(s1, s2)

 fmt.Printf("复制了:%d个元素n", i)
 fmt.Println(s1)
}

上述程序的运行结果为:

复制了:1个元素
[1]

因为目标切片的容量为1,因此只复制了一个元素。

delete

delete函数用于根据指定的key删除对应map类型的元素,其函数签名如下:

func delete(m map[Type]Type1, key Type)

当map为nil或者要删除的key不存在时,调用delete并不会引发什么错误:

package main

import "fmt"

func main() {

 var bookstore = map[string]float32{
  "Go入门":     100,
  "Go Web编程"200,
 }

 delete(bookstore, "Go入门")
 //删除不存在的key,并不会报错
 delete(bookstore, "编程思想")

 fmt.Println(bookstore)
}

panic与recover

在Go语言,如果触发了panic而没有及时捕获,那么就意味着程序崩溃,有很多错误会触发panic,比如**数组访问越界(index out of bounds)或者除以0(division by zero)**等。

除了系统自动触发的panic,我们也可以使用panice()函数手动触发panic,panic()函数签名如下:

func panic(v any)

panic()函数可以接收一个任意类型的值,甚至可以传入nil,如果上层有异常捕获,则这个值最终会被上层捕获:

panic(nil)

当然,如果给panic()传入nil,当上层捕获异常时,会很奇怪,明明有异常,为什么得到的信息是nil,所以在后续的版本中,Go如果将nil作为panic()函数的值go数组,同样会触发panic。

recover函数与defer语句结合,是捕获panic的唯一手段,recover函数的返回值就是传给panic()函数的值:

package main

import "fmt"

func myFunc(){
    panic("触发panic")
}

func main() {

 protect(myFunc)
}

func protect(f func()) {
 defer func() {
  msg := recover()
  fmt.Println(msg)
 }()
 f()
}

close

close函数用于关闭channel,不过close只能关闭双向channel与用于发送数据的channel,向一个只用于接收数据的channel发送数据触发panic:

//双向
var ch1 chan int
//用于发送数据
var ch2 chan<- int
//用于接收数据
var ch3 <-chan int

close(ch1)
close(ch2)
close(ch3) //报错

complex,real,imag

complex函数用于构建一个复数,其作用与直接声明一个complex128类型相同:

package main

import "fmt"

func main() {

 var c1 complex128 = 2 + 10i
 var c2 = complex(210)

 if c1 == c2 {
  fmt.Println("c1与c2相等")
 }
}

real函数用于获得复数的实部,imag函数用于获得复数的虚部:

fmt.Println(real(c1)) // 2
fmt.Println(imag(c1)) // 10

print与println

print与println函数用于将所有参数向控制台输出,两者唯一的区别在于println()会在每个参数中间加一个空格,并且最后会换行:

print(1,2,3,4//1234
println(1,2,3,4,5//1 2 3 4 

不过,一般很少使用这两个函数,因为标准库fmt包同样可以输出,并且功能更加强大。

小结

好了,至此我们就学完了15个Go语言的内置函数,其实,还未发布的Go1.21版本新增了max,min和clear3个内置函数,以后的文章中我们再来介绍这几个函数的用法吧。

限 时 特 惠: 本站每日持续更新海量各大内部创业教程,一年会员只需98元,全站资源免费下载 点击查看详情
站 长 微 信: muyang-0410