admin管理员组文章数量:1579437
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
包,其中的Type
和Value
类型分别提供了类型信息和值信息。
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、数组和切片的区别
-
长度:
- 数组的长度是固定的,在声明时需要指定长度,并且不能改变。
- 切片的长度是可变的,可以根据需要动态增长或缩减。
-
声明方式:
- 数组的声明方式为
[长度]类型
,例如[3]int
表示包含 3 个整数的数组。 - 切片的声明方式为
[]类型
,例如[]int
表示一个整数切片。
- 数组的声明方式为
-
初始化:
- 数组可以通过初始化列表进行初始化,例如
[3]int{1, 2, 3}
。 - 切片通常使用
make()
函数或者直接声明并初始化来进行初始化,例如make([]int, 3)
或者[]int{1, 2, 3}
。
- 数组可以通过初始化列表进行初始化,例如
-
传递方式:
- 数组在函数调用时会进行值拷贝,即传递的是数组的副本。
- 切片在函数调用时传递的是切片的引用,即底层共享相同的底层数组。
-
长度和容量:
- 切片除了长度外,还有一个容量(Capacity)的概念。长度表示切片当前包含的元素个数,而容量则表示底层数组从切片开始位置到底层数组末尾的元素个数。
- 使用内置的
len()
和cap()
函数可以分别获取切片的长度和容量。
-
操作:
- 数组是一个连续的内存块,因此支持常量时间的索引访问和迭代操作。
- 切片支持动态增长和缩减、追加、拷贝等操作,因为切片底层是一个指向数组的指针、长度和容量的组合。
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
版权声明:本文标题:golang学习 内容由热心网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:https://www.elefans.com/xitong/1727854996a1133920.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论