0
点赞
收藏
分享

微信扫一扫

Golang 【basic_leaming】常用函数 C


阅读目录

  • ​​基础​​
  • ​​如何高效地拼接字符串​​
  • ​​1 "+"​​
  • ​​2 fmt.Sprintf​​
  • ​​3 strings.Builder​​
  • ​​4 bytes.Buffer​​
  • ​​5 strings.join​​
  • ​​性能比较:​​
  • ​​5种拼接方法的实例代码​​
  • ​​什么是 rune 类型​​
  • ​​支持默认参数或可选参数吗?​​
  • ​​如何交换 2 个变量的值?​​
  • ​​Go 语言 tag 的用处?​​
  • ​​如何获取一个结构体的所有tag?​​
  • ​​如何判断 2 个字符串切片(slice) 是相等的?​​
  • ​​结构体打印时,%v 和 %+v 的区别​​
  • ​​Go 语言中如何表示枚举值(enums)?​​
  • ​​空 struct{} 的用途​​
  • ​​go 里面的 int 和 int32 是同一个概念吗?​​
  • ​​实现原理​​
  • ​​init() 函数是什么时候执行的?​​
  • ​​如何知道一个对象是分配在栈上还是堆上?​​
  • ​​2 个 interface 可以比较吗 ?​​
  • ​​2 个 nil 可能不相等吗?​​

基础

如何高效地拼接字符串

拼接字符串的方式有:

  • ​+​
  • fmt.Sprintf
  • strings.Builder
  • bytes.Buffer
  • strings.Join

1 “+”

使用+操作符进行拼接时,会对字符串进行遍历,计算并开辟一个新的空间来存储原来的两个字符串。

2 fmt.Sprintf

由于采用了接口参数,必须要用反射获取值,因此有性能损耗。

3 strings.Builder

用 WriteString() 进行拼接,内部实现是指针+切片,同时String()返回拼接后的字符串,它是直接把 []byte 转换为string,从而避免变量拷贝。

4 bytes.Buffer

bytes.Buffer是一个一个缓冲byte类型的缓冲器,这个缓冲器里存放着都是byte,

bytes.buffer底层也是一个[]byte切片。

5 strings.join

strings.join 也是基于strings.builder来实现的,并且可以自定义分隔符,在join方法内调用了b.Grow(n)方法,这个是进行初步的容量分配,而前面计算的n的长度就是我们要拼接的slice的长度,因为我们传入切片长度固定,所以提前进行容量分配可以减少内存分配,很高效。

性能比较:

​strings.Join ≈ strings.Builder > bytes.Buffer > "+" > fmt.Sprintf​

5种拼接方法的实例代码

package main

import (
"bytes"
"fmt"
"strings"
)

func main() {
a := []string{"a", "b", "c"}
//方式1:+
ret := a[0] + a[1] + a[2]

//方式2:fmt.Sprintf
ret := fmt.Sprintf("%s%s%s", a[0], a[1], a[2])

//方式3:strings.Builder
var sb strings.Builder
sb.WriteString(a[0])
sb.WriteString(a[1])
sb.WriteString(a[2])
ret := sb.String()

//方式4:bytes.Buffer
buf := new(bytes.Buffer)
buf.Write(a[0])
buf.Write(a[1])
buf.Write(a[2])
ret := buf.String()

//方式5:strings.Join
ret := strings.Join(a, "")
}

什么是 rune 类型

ASCII 码只需要 7 bit 就可以完整地表示,但只能表示英文字母在内的128个字符,为了表示世界上大部分的文字系统,发明了 Unicode, 它是ASCII的超集,包含世界上书写系统中存在的所有字符,并为每个代码分配一个标准编号(称为Unicode CodePoint),在 Go 语言中称之为 rune,是 int32 类型的别名。

Go 语言中,字符串的底层表示是 byte (8 bit) 序列,而非 rune (32 bit) 序列。

sample := "我爱GO"
runeSamp := []rune(sample)
runeSamp[0] = '你'
fmt.Println(string(runeSamp)) // "你爱GO"
fmt.Println(len(runeSamp)) // 4

一个汉字 = 2字节

1字节(Byte)=8字位=8个二进制数

1字位(bit)=1个二进制数

1B=8b

1KB=1024B

1MB=1024KB

1GB=1024MB

b称为字位、B称为字节、KB称为千字节、MB称为兆字节、GB称为吉字节。

支持默认参数或可选参数吗?

不支持。但是可以利用结构体参数,或者…传入参数切片数组。

// 这个函数可以传入任意数量的整型参数
func sum(nums ...int) {
total := 0
for _, num := range nums {
total += num
}
fmt.Println(total)
}

如何交换 2 个变量的值?

  • 变量 ​​a,b = b,a;​
  • 指针 ​​*a,*b = *b, *a​

Go 语言 tag 的用处?

tag 可以为结构体成员提供属性。

常见的:

  • json:序列化或反序列化时字段的名称
  • db: sqlx 模块中对应的数据库字段名
  • form: gin框架中对应的前端的数据字段名
  • binding: 搭配 form 使用, 默认如果没查找到结构体中的某个字段则不报错值为空, binding为 required 代表没找到返回错误给前端。

如何获取一个结构体的所有tag?

利用反射:

package main

import (
"fmt"
"reflect"
)

type Author struct {
Name int `json:Name`
Publications []string `json:Publication,omitempty`
}

func main() {

t := reflect.TypeOf(Author{})

for i := 0; i < t.NumField(); i++ {
name := t.Field(i).Name
s, _ := t.FieldByName(name)
fmt.Println(name, s.Tag)
}

}

PS E:\TEXT\test_go\test\case> go run .\case.go
Name json:Name
Publications json:Publication,omitempty
PS E:\TEXT\test_go\test\case>

上述例子中,reflect.TypeOf方法获取对象的类型,之后NumField()获取结构体成员的数量。

通过 ​​Field(i)​​​ 获取第 ​​i​​​ 个成员的名字。
再通过其 Tag 方法获得标签。

如何判断 2 个字符串切片(slice) 是相等的?

这个是是顺序一致的比较,如果需要不一致的比较需要转成 map 类型。

​reflect.DeepEqual() ​​, 但反射非常影响性能。

结构体打印时,%v 和 %+v 的区别

​%v​​ 输出结构体各成员的值;

​%+v​​ 输出结构体各成员的名称和值;

​%#v​​ 输出结构体名称和结构体各成员的名称和值;

Go 语言中如何表示枚举值(enums)?

在常量中用 iota 可以表示枚举。
iota从0开始。

const (
B = 1 << (10 * iota)
KiB
MiB
GiB
TiB
PiB
EiB
)

空 struct{} 的用途

用 map 模拟一个 set,那么就要把值置为 struct{},struct{} 本身不占任何空间,可以避免任何多余的内存分配。

package main

import "fmt"

type Set map[string]struct{}

func main() {
set := make(Set)

for _, item := range []string{"A", "A", "B", "C"} {
set[item] = struct{}{}
}

fmt.Println(len(set)) // 3

if _, ok := set["A"]; ok {
fmt.Println("A exists") // A exists
}
}

有时候给通道发送一个空结构体, ​​channel<-struct{}{}​​ ,也是节省了空间。

package main

func main() {
ch := make(chan struct{}, 1)
go func() {
<-ch
// do something
}()
ch <- struct{}{}
// ...
}

仅有方法的结构体:​​type Lamp struct{}​

go 里面的 int 和 int32 是同一个概念吗?

不是一个概念!千万不能混淆。

go语言中的 int 的大小是和操作系统位数相关的,如果是32位操作系统,int类型的大小就是4字节。如果是64位操作系统,int类型的大小就是8个字节。

除此之外uint也与操作系统有关。

int8占1个字节,int16占2个字节,int32占4个字节,int64占8个字节。

实现原理

init() 函数是什么时候执行的?

简答: 在 main 函数之前执行。

详细:
init() 函数是go初始化的一部分,由runtime初始化每个导入的包,初始化不是按照从上到下的导入顺序,而是按照解析的依赖关系,没有依赖的包最先初始化。

每个包首先初始化包作用域的常量和变量(常量优先于变量),然后执行包的init()函数。

同一个包,甚至是同一个源文件可以有多个init()函数。

init()函数没有入参和返回值,不能被其他函数调用,同一个包内多个init()函数的执行顺序不作保证。

执行顺序:​​import –> const –> var –>init()–>main()​

一个文件可以有多个init()函数!

如何知道一个对象是分配在栈上还是堆上?

Go和C++不同,Go局部变量会进行逃逸分析。

如果变量离开作用域后没有被引用,则优先分配到栈上,否则分配到堆上。

那么如何判断是否发生了逃逸呢?

​go build -gcflags '-m -m -l' xxx.go​

关于逃逸的可能情况:

变量大小不确定,变量类型不确定,变量分配的内存超过用户栈最大值,暴露给了外部指针。

2 个 interface 可以比较吗 ?

Go 语言中,interface 的内部实现包含了 2 个字段,类型 T 和值 V,interface 可以使用 ​​==​​​ 或 ​​!=​​ 比较。

2 个 interface 相等有以下 2 种情况:

1、两个 interface 均等于 nil(此时 V 和 T 都处于 unset 状态)
2、类型 T 相同,且对应的值 V 相等。

看下面的例子:

package main

import "fmt"

type Stu struct {
Name string
}

type StuInt interface{}

func main() {
var stu1, stu2 StuInt = &Stu{"Tom"}, &Stu{"Tom"}
var stu3, stu4 StuInt = Stu{"Tom"}, Stu{"Tom"}
fmt.Println(stu1 == stu2) // false
fmt.Println(stu3 == stu4) // true
}

stu1 和 stu2 对应的类型是 ​​*Stu​​,值是 Stu 结构体的地址,两个地址不同,因此结果为 false。

stu3 和 stu4 对应的类型是 Stu,值是 Stu 结构体,且各字段相等,因此结果为 true。

2 个 nil 可能不相等吗?

可能不等。interface 在运行时绑定值,只有值为 nil 接口值才为 nil,但是与指针的 nil 不相等。

举个例子:

package main

import "fmt"

func main() {
var p *int = nil
var i interface{} = nil
if p == i {
fmt.Println("Equal")
}
}

两者并不相同。
总结:两个 nil 只有在类型相同时才相等。


举报

相关推荐

0 条评论