GO语言基础
1. 与其他语言相比,GO语言有什么优势?
Go或Golang是一种由Google开发的现代静态类型编程语言,与其他编程语言相比具有以下优势:
并发性:Go的设计考虑到了并发性,使得编写可以同时处理多个任务的程序变得容易。这使得它非常适合性能和可扩展性非常重要的大型复杂系统。
简单性:Go有一个干净、直接的语法和一小组关键字,使其易于学习和理解,特别是对于刚接触编程的开发人员。
性能:Go编译成机器代码,这使得它比许多其他高级编程语言更快、更高效。它还内置了一个垃圾收集器,可以自动释放不再使用的内存,从而使开发人员从手动内存管理中解放出来。
静态类型化:Go是静态类型化的,这意味着变量具有在编译时已知的固定类型。这有助于在开发过程的早期发现错误,从而更容易维护和调试代码。
标准库:Go有一个强大的标准库,它提供了广泛的功能,从网络和I/O到加密和压缩,使构建复杂应用程序变得容易。
跨平台:Go是跨平台的,可以在Windows、Mac和Linux系统上使用,因此它是一种通用且可移植的语言,可以用于各种项目。
2. GO使用的数据类型有哪些?
基础类形:uint8、uint16、uint32、uint64、int8、int16、int32、int64、float32、float64、complex64、complex128、byte、rune(int32)、uint、int、uintptr(无符号整型,存放指针)
派生类型:指针类型(Pointer)、数组类型、结构化类型(struct)、Channel(线程安全)、函数类型、切片类型、接口类型(interface)、Map 类型
3. GO的类型转换和类型推断是什么?
类型转换:
不同类型+-*计算的时候编译器会把不同类型转换成相同类型进行计算
隐式转换的优先级为: 整数(int) <符文数(rune)<浮点数(float)<复数(Imag)
类型推断:
golang使用特殊的操作符":="用于变量的类型推断,且其只能作用于函数或方法体内部。
编译器的执行过程为:词法(token)解析->语法(syntax)分析->抽象语法树(ast)构建->类型检查->生成中间代码->代码优化->生成机器码。
类型推断发生于前四个阶段,即词法(token)解析->语法(syntax)分析->抽象语法树(ast)构建->类型检查。
4. GO语言中Channel有什么特点。读写一个已关闭的Channel会发生什么?
先进先出的队列、有缓冲和无缓冲两种、并且是线程安全的
特点:
1.从channel读数据,如果channel缓冲区为空或者没有缓冲区,当前goroutine会被阻塞。
2.向channel写数据,如果channel缓冲区已满或者没有缓冲区,当前goroutine会被阻塞。
3.被阻塞的goroutine将会挂在channel的等待队列中
4.因读阻塞的goroutine会被向channel写入数据的goroutine唤醒。
5.因写阻塞的goroutine会被从channel读数据的goroutine唤醒。
写关闭异常,读关闭空零、如果channel已经关闭但有数据还是可以读取到
5. Go语言中New和Make有什么区别?简单讲讲他们的用法。
make还建立了一个内置复合类型的新实例并对其进行初始化,而new只是分配内存并返回一个指向它的指针。
new 的作用是初始化一个指向类型的指针(*T)(内存分配初始化值为0),不常用
make 的作用是为 slice,map 或 chan 初始化并返回引用(T),无可替代
6. 如何理解GO语言中的值传递。“GO不存在指针或者引用传递”应该如何理解。
go在穿参时全都是值传递,创建该参数的副本并将其存储在函数的本地作用域中。对函数内的参数所做的任何更改都不会反映在函数外的原始参数中。哪怕传递的是指针类型、也会先copy指针再进行传递。
但我们可以通过指针副本去修改原始参数的数据。
7. GO语言中数组和切片有什么区别?
数组:
具有固定的大小,一旦创建了数组,其大小就不能更改。
通过索引到数组中来访问。
按值传递。将数组作为参数传递给函数时,将创建整个数组的副本。
切片:
是动态大小、灵活的阵列。
通过索引切片来访问。
通过引用传递。当您将切片作为参数传递给函数时,将创建对原始切片的引用,而不是副本。
对于大多数用例来说更有效,因为它们在传递给函数时不需要复制整个数组,并且可以动态调整大小。
type slice struct {
array unsafe.Pointer
len int
cap int
}
8. 切片是如何扩容的。有哪些规则?
1. 如果切片的容量小于 1024 个元素,于是扩容的时候就翻倍增加容量。
2. 一旦元素个数超过 1024 个元素,那么增长因子就变成 1.25 ,
即每次增加原来容量的四分之一。
3. 原数组还有容量可以扩容(实际容量没有填充完),这种情况下,
扩容以后的数组还是指向原来的数组。原来数组的容量已经达到了最大值,
再想扩容, Go 默认会先开一片内存区域,把原来的值拷贝过来,
然后再执行 append() 操作。
扩容源码:
growslice(){
...
newcap := old.cap
doublecap := newcap + newcap
if cap > doublecap {
newcap = cap
} else {
if old.len < 1024 {
newcap = doublecap
} else {
for 0 < newcap && newcap < cap {
newcap += newcap / 4
}
if newcap <= 0 {
newcap = cap
}
}
}
...
switch{
case et.size == sys.PtrSize:
lenmem = uintptr(old.len) * sys.PtrSize
newlenmem = uintptr(cap) * sys.PtrSize
capmem = roundupsize(uintptr(newcap) * sys.PtrSize)
overflow = uintptr(newcap) > maxAlloc/sys.PtrSize
newcap = int(capmem / sys.PtrSize)
}
...
}
func roundupsize(size uintptr) uintptr {
if size < _MaxSmallSize {
if size <= smallSizeMax-8 {
return uintptr(class_to_size[size_to_class8[(size+smallSizeDiv-1)/smallSizeDiv]])
} else {
return uintptr(class_to_size[size_to_class128[(size-smallSizeMax+largeSizeDiv-1)/largeSizeDiv]])
}
}
if size+_PageSize < size {
return size
}
return alignUp(size, _PageSize)
}
9. GO语言的异常处理是如何实现的。panic和recover的实现。
panic会停止程序的正常执行,并开始堆栈的展开,直到所有goroutine都停止,程序终止。
“Recover”是一个内置函数,可与紧急情况结合使用,以捕获紧急情况的值,并将控制返回到程序的正常执行。它经常在defer语句中使用,以捕捉恐慌并以可控的方式处理,例如记录错误或优雅地关闭程序。
恐慌是通过调用“Panic”函数触发的,并提供一个值作为参数。程序执行停止并生成回溯,展开调用堆栈,直到找到用“recover”声明的函数。recover函数检索传递给死机的值并停止死机。
10. GO语言的错误处理。
错误处理通常通过返回一个error类型的值进行处理。当函数返回一个非nil的error类型值,表示出现了错误。这种方法称为错误值模式。在Go语言中,error是一个内建类型,它是一个接口类型,可以表示任何错误信息。
11. Defer在使用中有哪些需要注意的地方?
1. 是一个“栈”的关系,也就是先进后出。
2. 关键字 defer 用于注册延迟调用。
3. 这些调用直到 return 后才被执。因此,可以用来做资源清理。
4. defer 语句中的变量,在defer声明时就决定了。
5. defer 最大的功能是 panic 后依然有效。
6. 循环中使用defer语句时,语句只会在循环结束时才执行。
defer常用来:关闭文件句柄、锁资源释放、数据库连接释放
12. GO语言的逃逸分析是什么。哪些情况会出现逃逸。
逃逸分析(escape analysis):
分析一个对象是放栈上还是放在堆上,当发现变量的作用域没有跑出函数范围(不管是不是动态new出来的),
就可以在栈上,反之则必须分配在堆。
防止当前变量被回收后,栈上其它指向的指针成为空指针、区分使用堆栈内存减轻GC压力、观察逃逸分析优化堆内存的开销提高程序运行速度。
Go 语言的逃逸分析遵循以下两个不变性:
指向栈对象的指针不能存在于堆中;
指向栈对象的指针不能在栈对象回收后存活;
go build -gcflags '-m' .