admin管理员组

文章数量:1579389

1、go基础

一、面向对象

1.1 多态

在 Go 语言中,多态是通过接口来实现的:

type AnimalSounder interface {
    MakeDNA()
}
​
func MakeSomeDNA(animalSounder AnimalSounder) {     // 参数是AnimalSounder接口类型
    animalSounder.MakeDNA()
}
type AnimalSounder interface {
    MakeDNA()
}
​
func MakeSomeDNA(animalSounder AnimalSounder) {
    animalSounder.MakeDNA()
}
​
func (c *Cat) MakeDNA() {
    fmt.Println("煎鱼是煎鱼")
}
​
func (c *Dog) MakeDNA() {
    fmt.Println("煎鱼其实不是煎鱼")
}
​
func main() {
    MakeSomeDNA(&Cat{})
    MakeSomeDNA(&Dog{})
}

二、与其他语言对比

1.1、go语言与java有什么区别

语法和风格:

  • Go语言的语法相对简洁,清晰易读,而是使用结构体和接口
  • Java类和继承复杂

并发模型:

  • Go语言轻量级线程和通信,并发更简单和高效。
  • Java也有并发支持,但它使用线程和锁的模型,相对而言可能更复杂。

内存管理:

  • Go语言具有垃圾回收机制,开发者无需手动管理内存。减少内存泄漏和提高开发效率。
  • Java同样具有垃圾回收,但在某些情况下,可能需要更多的调优来处理大规模的、高性能的应用程序。

性能:

  • golang比java快,
  • go原生的编译性能生成的二进制文件相对较小
  • java通常需要再java虚拟机JVM上运行

生态系统:

  • 相对于一些其他主流语言,Go语言的第三方库数量可能相对较少。虽然Go社区在不断发展,但某些领域的库可能仍不如其他语言那样丰富。

错误处理

  • 未使用到的会报错
  • Go语言使用显式的错误处理机制,即通过返回值来传递错误。有时这会导致代码中充斥着处理错误的代码块,使得代码显得较为冗长。

go适合做什么

  • 服务端开发
  • 分布式系统,微服务
  • 网络编程
  • 区块链开发
  • 内存KV数据库,例如boltDB、levelDB
  • 云平台

三、符号类型

1.1、制表符

\t        一个制表符
\r 回车(与\n区别:从当前行最前面开始覆盖)

1.2、格式化输出

%t bool
%b 二进制
%o 八进制fmt.Printf("%o\n", 255) // 输出:377
%O fmt.Printf("%O\n", 255) // 输出:0o377 (Go 1.13+)
%x 十六进制表示,使用 a-f
%X 十六进制表示,使用 A-F
%s fmt.Printf("%s\n", "Hello, world!") // 输出:Hello, world!
%q fmt.Printf("%q\n", "Hello, world!") // 输出:"Hello, world!"
%e 科学计数法,如 -1234.456e+78
%E 科学计数法,如 -1234.456E+78
%p 指针地址,表示为十六进制,并加上前缀 0x
%v 按值的默认格式输出
%+v fmt.Printf("%+v\n", struct{ X int }{X: 1}) // 输出:{X:1}
%#v fmt.Printf("%q\n", "Hello, world!") // 输出:"Hello, world!"/ 输出:struct { X int }{X:1}
%T 输出类型

1.3、四种声明方式

var(声明变量), const(声明常量), type(声明类型) ,func(声明函数)。

 1.4、整数型

rune处理中文、日文或者其他复合字符时,汉字3个字节

1.5、浮点

float32会出现小数后的尾数丢失

1.6、值类型

  • int,float,bool,string,数组和结构体
  • 变量直接存储值,内存通常在栈中分配

1.7、引用类型

  • 变量存储的是一个地址,这个地址对应的空间才真正存储数据(值),内存通常在堆上分配,当没有任何变量引用这个地址时,该地址对应的数据空间就成为一个垃圾,由Gc来回收。

1.8、iota

示例 :自动计数

const (
            n1 = iota //0
            n2        //1
            n3        //2
            n4        //3
        )

 使用_跳过某些值

    const (
            n1 = iota //0
            n2        //1
            _
            n4        //3
        )

iota声明中间插队

    const (
            n1 = iota //0
            n2 = 100  //100
            n3 = iota //2
            n4        //3
        )
    const n5 = iota //0

多个iota定义在一行 

    const (
            a, b = iota + 1, iota + 2 //1,2
            c, d                      //2,3
            e, f                      //3,4
        )

 1.9、结构体

  • 结构体所有字段在内存中是连续的

1.10、反射

  •  返回数据时大多数用的是json,而结构体中结构体在其他包,引用时首字母需要大写,现在就需要自定返回`json:"name"`就可以将当前字段别名化
type person struct[
Name string `json:"name"`
}

 结构体之间转换 

两个结构体里字段要完全一样,赋值方式A(b)不可以a = b

package main

import "fmt"

type A struct {
	a int
}
type B struct {
	a int
}

func main() {
	var a A
	var b B
	b.a = 1
	a = A(b)
	fmt.Println(a)
}

1.11、字符串

  • 字符串的内容不能在初始化后被修改,但string底层是[]byte,转成[]byte可以进行修改

转成字符串

strconv.FormatInt(int,10)

strconv.Itoa(int)

整数转为10进制字符串
strconv.FormatFloat(float,’f‘,10,64) 浮点数,格式,保留小数位数10,float64
strconv.FormatBool(bool) bool转string

字符串转出

  •         strconv.ParseBool("true")
  •         strconv.ParseInt("99",10,0)  注string,10进制,第3位  0:int, 8 :int8,   16 : int16……
  •         strconv.ParseUint("99",10,0)
  •         strconv.ParseFloat("") string,大小

1.12、Go 语言中不同的类型如何比较是否相等?

  • string,int,float interface 等可以通过 reflect.DeepEqual 和等于号进行比较
  •  slice,struct,map 使用 reflect.DeepEqual 来检测是否相等

1.13、Go中 uintptr和 unsafe.Pointer 的区别?

  • uintptr 用于指针和整数之间的转换,而 unsafe.Pointer 则用于不同类型的指针之间的转换,它们都是不安全的操作,需要谨慎使用。

1.14、context

  • Context 的数据结构包含 Deadline,Done,Err,Value,
  • Deadline 方法返回一个 time.Time,表示当前 Context 应该结束的时间,ok 则表示有结束时间,
  • Done 方法当 Context 被某一个操作进行了取消或者超时时候返回的一个 close 的 channel,告诉给 context 相关的函数要停止当前工作然后返回了,
  • Err 表示 context 被取消的原因,
  • Value 方法表示 context 实现共享数据存储的地方,
  • 协程安全的。

四、应用知识

1.1、golang 中 make 和 new 的区别?

  • 共同点:给变量分配内存
  • 不同点:make:函数主要用于创建切片,map和通道
  • new和make都在堆上分配内存

func new(Type) *Type

    new:返回指向新分配的零值的指针,主要用于创建值类型(如结构体、数组等)的实例,但不会对这些实例进行初始化。

1.2、for range 的时候它的地址会发生变化么?

  • for a,b := range c 遍历中, a 和 b 在内存中只会存在一份,每次循环时遍历到的数据都是以值覆盖的方式赋给 a 和 b,a,b 的内存地址始终不变。由于有这个特性,
  • for 循环里面如果开协程,不要直接把 a 或者 b 的地址传给协程。在每次循环时,创建一个临时变量。

1.3、golang 中解析 tag 是怎么实现的?反射原理是什么?

  • 反射机制允许在运行时检查类型信息、获取和修改变量的值、调用方法等。反射的基本思想是在运行时检查变量的类型信息
  • 反射的核心是reflect包,其中的TypeValue类型分别提供了类型信息和值信息。

1.4、init函数与main区别

相同点:
        两个函数在定义时不能有任何的参数和返回值,且Go程序自动调用。
    不同点:
        init可以应用于任意包中,且可以重复定义多个。
        main函数只能用于main包中,且只能定义一个。
  • 同一个go文件的init()调用顺序是从上到下的。
  • 同一个package中不同文件是按文件名字符串比较“从小到大”顺序调用各文件中的init()函数。
  • 不同的包,如果不相互依赖的话,按照main包中"先import的后调用"的顺序调用其包中的init(),如果package存在依赖,则先调用最早被依赖的package中的init()
  • 执行流程:引入文件的变量定义->本含税变量定义->init->main

1.5、go defer底层多个 defer 的顺序,defer 在什么时机会修改返回值?

  • 顺序:首先return,其次return value,最后defer。defer可以修改函数最终返回值,多个 defer 调用顺序是 先进后出,底层通过链表的形式维护了延迟函数的调用顺序,每次插入_defer 实例,均插入到链表的头部
  • 修改时机:有名返回值或者函数返回指针
  • 作用:defer延迟函数,释放资源,如释放锁,关闭文件,关闭链接;捕获panic
  • 注:defer函数紧跟在资源打开后面,否则defer可能得不到执行,导致内存泄露。

1.6、defer捕获panic

func main() {
	defer func() {
		if r := recover(); r != nil {
			fmt.Println("捕获到 panic:", r)
		}
	}()
	panic("hhhhhh")

}

1.7、go出现panic的场景

  • 数组/切片越界

  • 空指针调用。比如访问一个 nil 结构体指针的成员

  • 过早关闭 HTTP 响应体

  • 除以 0

  • 向已经关闭的 channel 发送消息

  • 重复关闭 channel

  • 关闭未初始化的 channel

  • 未初始化 map。注意访问 map 不存在的 key 不会 panic,而是返回 map 类型对应的零值,但是不能直接赋值

  • 跨协程的 panic 处理

  • sync 计数为负数。

  • 类型断言不匹配。var a interface{} = 1; fmt.Println(a.(string)) 会 panic,建议用 s,ok := a.(string)

1.8、Go 多返回值怎么实现的?

  • 函数的返回值被打包成一个结构体(tuple)并作为单一的返回值从函数中返回。这种方法允许Go语言在不牺牲性能的情况下支持多返回值。

1.9、GoRoot 和 GoPath 有什么用

  • GOROOT用于指定Go语言的安装目录
  • GOPATH用于指定你的工作空间,包括你自己的项目以及第三方包的下载和安装位置。

1.10、编译相关的命令

  • go build 编辑会将所需的库文件包含在.exe中
  • go run 如果需要在其他机器上执行,需要另一台机器搭建go的相同环境
  • go install 用于编译并安装指定的代码包及它们的依赖包

1.11、执行流程:

  • .go文件  -->  (go build编译)  -->  生成可执行文件.exe  -->  结束

1.12、go install 个go get区别

  • go install 主要用于构建和安装本地的包或程序,而 go get 主要用于从远程代码仓库拉取并安装包或程序。
  • go install 不会自动解析和安装依赖,它只会构建和安装指定的包或程序。而 go get 会自动解析和安装依赖的包。
  • go install 可以在本地构建和安装包,无需联网,适用于无需下载依赖的情况。而 go get 需要从远程代码仓库下载代码,需要联网。

1.13、入一个go的工程,有些依赖找不到,该怎么办

  • go mod tidy 更新项目的依赖
  • go get 更新依赖
  • go clean -modcache 清除缓存

 1.14、闭包

package main

import "fmt"

func main() {
	f := test()
	fmt.Println(f(1))//11
	fmt.Println(f(2))//13
}
func test() func(x int) int {
	var n int = 10
	return func(x int)int {
		n = n + x
		return n
	}
}

1.15、类型转换和断言的区别

  • 目的:类型转换用于值的类型转换,而类型断言用于检查和获取接口值的实际类型。

  • 语法:类型转换使用括号和目标类型,而类型断言使用特殊的语法 x.(T)x, ok := y.(T)

  • 失败处理:类型转换可能会导致编译错误或运行时错误,而类型断言在失败时可以通过布尔值 ok 进行安全检查。

1.16、算法复杂度

五、切片

1.1、slice

  • copy:相当于覆盖
  • append:在原数组后面添加数据 

1.2、数组和切片的区别

  1. 长度:

    • 数组的长度是固定的,在声明时需要指定长度,并且不能改变。
    • 切片的长度是可变的,可以根据需要动态增长或缩减。
  2. 声明方式:

    • 数组的声明方式为 [长度]类型,例如 [3]int 表示包含 3 个整数的数组。
    • 切片的声明方式为 []类型,例如 []int 表示一个整数切片。
  3. 初始化:

    • 数组可以通过初始化列表进行初始化,例如 [3]int{1, 2, 3}
    • 切片通常使用 make() 函数或者直接声明并初始化来进行初始化,例如 make([]int, 3) 或者 []int{1, 2, 3}
  4. 传递方式:

    • 数组在函数调用时会进行值拷贝,即传递的是数组的副本。
    • 切片在函数调用时传递的是切片的引用,即底层共享相同的底层数组。
  5. 长度和容量:

    • 切片除了长度外,还有一个容量(Capacity)的概念。长度表示切片当前包含的元素个数,而容量则表示底层数组从切片开始位置到底层数组末尾的元素个数。
    • 使用内置的 len()cap() 函数可以分别获取切片的长度和容量。
  6. 操作:

    • 数组是一个连续的内存块,因此支持常量时间的索引访问和迭代操作。
    • 切片支持动态增长和缩减、追加、拷贝等操作,因为切片底层是一个指向数组的指针、长度和容量的组合。

1.3、Go 的 slice 底层数据结构和一些特性

  • 切片底层是一个指向数组的指针、长度和容量的组合
  • len 表示切片长度,cap 表示切片容量。
  • 当原容量不够,则 slice 先扩容,扩容之后 slice 得到新的 slice,将元素追加进新的 slice,返回新的 slice。
  • `slice` 的长度小于 1024,它的容量翻倍;如果长度大于等于 1024,它的容量增加 25%
type slice struct{
    ptr *[2]int
    len int
    cap int
}

1.4、从数组中取一个相同大小的slice有成本吗?

  • [:]从切片中取数组并不会有明显的额外成本,它只是创建了一个新的切片对象,其长度和容量与原始切片相同。这个操作的时间复杂度是 O(1)。
  • 用切片语法从数组中取一个切片时,实际上并没有进行底层数组的复制,而是创建了一个新的切片对象,该切片对象与原始数组共享相同的底层数组

1.5、切片是否线程安全,如何保证安全

  • 不安全
  • 使用互斥锁
  • 使用通道

1.6、切片是否会自动进行内存释放?为什么?

  • 不进行内存的分配和释放
  • 数组的存储是由 Go 的垃圾回收器进行管理的。当一个对象(包括底层数组)不再被引用时,垃圾回收器将释放其占用的内存。

1.7、切片如何避免切片引起的内存泄漏

  • 垃圾回收机制,开发者相对不太容易发生严重的内存泄漏问题。然而,通过良好的代码实践,可以更进一步减少不必要的内存占用,确保程序的性能和稳定性。
  • 避免循环引用:确保切片没有形成循环引用,即使切片中的元素不再需要,也能及时释放内存。

六、map

1.1、map操作

  • delete(myMap, "Bob")   删除

1.2、map 使用注意的点,是否并发安全?

  • map (key)必须是可比较的类型。这包括基本数据类型(如整数、浮点数、字符串、布尔值)和某些复合类型(如指针、数组、结构体)
  • 如果想在 map 中使用结构体作为键,你需要确保结构体的字段都是可比较的类型。换句话说,结构体中的字段不能包含切片、映射或函数等不可比较的类型。
  • 想要保证遍历map时元素有序,可以使用辅助的数据结构,例如orderedmap(有序的是按照ascc从小到大排序)
  • 要先初始化,否则panic
  • 不安全,确保安全可以采取:互斥锁
  • sync.Map: Go语言提供了`sync`包中的`Map`类型,它是一种并发安全

本文标签: Golang