GO语言基础

157 阅读8分钟

GO语言基础

1. 与其他语言相比,GO语言有什么优势?

Go或Golang是一种由Google开发的现代静态类型编程语言,与其他编程语言相比具有以下优势:
并发性:Go的设计考虑到了并发性,使得编写可以同时处理多个任务的程序变得容易。这使得它非常适合性能和可扩展性非常重要的大型复杂系统。
简单性:Go有一个干净、直接的语法和一小组关键字,使其易于学习和理解,特别是对于刚接触编程的开发人员。
性能:Go编译成机器代码,这使得它比许多其他高级编程语言更快、更高效。它还内置了一个垃圾收集器,可以自动释放不再使用的内存,从而使开发人员从手动内存管理中解放出来。
静态类型化:Go是静态类型化的,这意味着变量具有在编译时已知的固定类型。这有助于在开发过程的早期发现错误,从而更容易维护和调试代码。
标准库:Go有一个强大的标准库,它提供了广泛的功能,从网络和I/O到加密和压缩,使构建复杂应用程序变得容易。
跨平台:Go是跨平台的,可以在Windows、Mac和Linux系统上使用,因此它是一种通用且可移植的语言,可以用于各种项目。

2. GO使用的数据类型有哪些?

基础类形:uint8uint16uint32uint64int8int16int32int64float32float64complex64complex128byterune(int32)、uintintuintptr(无符号整型,存放指针)
派生类型:指针类型(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,mapchan 初始化并返回引用(T),无可替代

6. 如何理解GO语言中的值传递。“GO不存在指针或者引用传递”应该如何理解。

go在穿参时全都是值传递,创建该参数的副本并将其存储在函数的本地作用域中。对函数内的参数所做的任何更改都不会反映在函数外的原始参数中。哪怕传递的是指针类型、也会先copy指针再进行传递。
但我们可以通过指针副本去修改原始参数的数据。

7. GO语言中数组和切片有什么区别?

数组:
    具有固定的大小,一旦创建了数组,其大小就不能更改。
    通过索引到数组中来访问。
    按值传递。将数组作为参数传递给函数时,将创建整个数组的副本。
切片:
    是动态大小、灵活的阵列。
    通过索引切片来访问。
    通过引用传递。当您将切片作为参数传递给函数时,将创建对原始切片的引用,而不是副本。
    对于大多数用例来说更有效,因为它们在传递给函数时不需要复制整个数组,并且可以动态调整大小。
type slice struct {
    array unsafe.Pointer
    len   int               // 长度
    cap   int               // 当前切片容量  cap >= len
}

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{
        //sys.PtrSize机器本机字长的大小,即 32 位系统上为4,64 位系统上为8
        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 {
       // class_to_size 0, 8, 16, 32, 48, 64...32768
       // size_to_class8 0-31
       // size_to_class128 31-66
      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类型的值进行处理。当函数返回一个非nilerror类型值,表示出现了错误。这种方法称为错误值模式。在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' ./*.go
常见引发内存逃逸的情况:
    1. 送指针或带有指针的值到 channel 中
    2. 在一个切片上存储指针或带指针的值
    3. slice 的背后数组被重新分配了,因为 append 时可能会超出其容量(cap)
    4. 在 interface 类型上调用方法
    5. 创建大对象超出栈空间
    6. 将局部变量作为返回值
    7. 将局部变量作为goroutine参数