Go语言里面的各种疑难杂症

  • 什么是闭包?闭包有什么缺陷?
func AddUpper() func(int)int{
  var n int =10
  return func (x int) int{
    n = n+x
    return n
  }
}

func main(){
  f := AddUpper()
  fmt.Println(f(1))//11
  fmt.Println(f(2))//13
  fmt.Println(f(3))//16
}

(1) AddUpper是一个函数,返回的数据类型是func (int) int
(2)闭包可以这么理解:返回一个匿名函数,但是这个匿名函数引用到了函数外的n,因此这个匿名函数就和n形成一个整体,构成闭包。
(3)也可以理解为:闭包是类,函数是操作,n是字段。函数和它使用到的n构成闭包。
(4)当我们反复调用f函数时,因为n是初始化一次,因此每调用一次就进行一次累计。
(5)搞清楚闭包的关键,就是分析出返回的函数它使用到哪些变量。
缺陷:闭包的缺陷大家可以搜索下,有一堆的缺陷,什么可读性差、性能问题等等。但是,我个人认为最为重要的一点是匿名函数和函数外的变量生命周期不一致这点导致的问题。如果匿名函数的生命周期比外部变量长,那么外部变量便无法被及时回收,导致内存泄漏;反之,如果匿名函数的生命周期比外部变量短,则会出现一个有意思的现象,匿名函数所引用到的外部变量值不一致。

  • 什么情况下会出现栈溢出?
    (1)递归调用层数过深
    (2)无限循环
    (3)大量的函数调用嵌套:在某些情况下,如果函数调用层次过深,每次函数调用都会在栈上分配一些临时变量,当调用层次太多,栈可能会耗尽。
    (4)大规模的局部变量分配:如果一个函数内部有大量的局部变量需要分配,且这些变量占用控件比较大,也有可能导致栈溢出。

  • 什么是不定参数?调用方法时,不定参数可以传入0个值吗?方法内部怎么使用不定参数?
    不定参数是指函数或方法的参数数量是不确定的,并且可以接受任意数量的参数。在Go语言中,可以使用"…“语法来表示不定参数。
    在函数或方法的参数列表中,使用”…"来指定不定参数的类型。例如,func sum(numbers ...int)表示sum函数接受任意数量的int类型参数。
    在调用方法时,不定参数可以传入0个值。如果不定参数传入了0个值,那么在方法内部,不定参数的长度为0,即切片的长度为0。
    对于不定参数,在方法内部可以像操作普通切片一样使用。可以对不定参数进行迭代、切片操作,也可以将其传递给需要切片参数的函数或方法。
    以下是一个示例:

func sum(numbers ...int) int {
 total := 0
    for _, num := range numbers  
          total += num
       return total
}  
func main(){
    fmt.Println(sum(1, 2, 3))        // 输出:6
    fmt.Println(sum(4, 5, 6, 7, 8))  // 输出:30
    fmt.Println(sum())               // 输出:0,传入0个值
}   

在sum函数内部,不定参数numbers会作为一个int类型的切片。使用range关键字迭代不定参数,对每个元素进行累加操作。当传入0个值时,不定参数切片的长度为0,返回0。

  • 什么是defer?解释下defer的运作机制?
    在Go语言中,defer用于注册延迟执行的函数(或方法)调用,即在函数结束之前执行某个操作。defer语句会将要执行的函数压入一个栈中,当外围函数执行完毕时,栈中的函数按照先进后出的顺序执行。
    defer语句使用关键字"defer"加上要延迟执行的函数调用语句。可以在函数内部的任意位置使用defer语句,多个defer语句的执行顺序与声明顺序相反。
    以下是一个示例:
func main() {
 defer fmt.Println("1")
    defer fmt.Println("2")
    defer fmt.Println("3")
    fmt.Println("Hello")
}  

输出结果为:

Hello
3
2
1

在上面的示例中,"Hello"会先被打印出来,然后依次执行defer语句中的函数调用,按照逆序打印数字1、2、3。

defer通常用于释放资源、关闭打开的文件、解锁互斥锁等操作。当函数包含多个defer语句时,可以确保资源的释放和清理操作会在函数结束时执行,无论函数是正常返回还是发生异常。

需要注意的是,defer延迟执行的函数调用参数在defer语句出现时就会求值,而不是在函数返回时才求值。因此,在使用defer时要注意传递参数的值是否符合预期。
至于defer的实现机制:大家可以参考这篇博客:https://blog.csdn.net/weixin_41663412/article/details/104914850

  • 一个方法内部的defer能不能超过8个?
    可以,只不过超过8个,就不能使用开放编码机制。

  • defer内部能不能修改返回值?怎么改?
    在Go语言中,defer语句是在函数返回之前执行的。虽然可以在defer语句中修改函数内部局部变量的值,但是无法直接修改函数的返回值。
    但是,如果函数的返回值是通过命名返回值(named return values)方式定义的,那么可以在defer语句中修改返回值。命名返回值允许我们在函数体中为返回值赋值,并且可以在函数体中的任意位置随时修改返回值。
    下面是一个示例代码:

package main

import "fmt"

func example() (result int) {
	defer func() {
		result = 100 // 在defer语句中修改返回值
	}()

	result = 10 // 正常的返回值赋值

	return result // 返回值为修改后的值
}

func main() {
	fmt.Println(example()) // 输出结果为100
}

在上述示例中,我们在defer语句中修改了返回值result的值,并且最终的返回结果被修改为了100。这是因为defer语句会在函数返回之前执行,所以最后的返回值会受到defer语句的影响。

  • 数组和切片有什么区别?
    数组和切片是Go语言中常用的数据结构,它们有以下几点区别:
  1. 长度固定 vs. 长度可变:数组的长度是固定的,定义时需要指定长度,并且无法修改。而切片的长度是可变的,可以根据需要进行动态扩容或裁剪。

  2. 数据存储方式:数组的元素在内存中是连续存储的,而切片则是一个对数组的引用,并包含了指向底层数组的指针、长度和容量等信息。

  3. 传递方式:通过值传递 vs. 通过引用传递:在函数间传递数组时,实际上是进行值传递,即将整个数组的副本传递给函数。而传递切片时,只是将切片的引用(指针)传递给函数,因此不会复制整个切片的内容,函数内部对切片进行的修改会反映到原始切片中。

  4. 使用方式:数组通常用于表示具有固定长度的集合,而切片则更适合处理动态变化的集合。切片提供了丰富的内置函数,如追加元素、裁剪切片、复制切片等,这些操作在数组上通常无法直接进行。

总的来说,数组适合用于长度固定,不需要频繁修改的情况;而切片则更具灵活性,适合处理长度变化、需要频繁操作的集合。
Go语言里面的各种疑难杂症_第1张图片

  • 切片怎么扩容?
    Go语言里面的各种疑难杂症_第2张图片
    扩容系数:当容量小于256(新版,旧版为1024)的时候,2倍扩容;否则按照1.25倍扩容。
    一开始是2倍扩容主要是为了提高内存的利用率和减少扩容的次数。当容器中的元素数量超过当前容量时,需要对容器进行扩容。通过将容量扩大到原来的2倍,可以在一定程度上减少扩容次数,因为每次扩容都需要重新分配更大的内存空间,并将原有数据复制到新的内存中,这个过程是比较耗时的。
    而后面改为1.25倍扩容是为了避免在容器元素数量较大时,扩容过程占用过多的内存空间。当容器的元素数量较大时,每次扩容后容量增加的幅度相对较小,这样可以更加灵活地利用内存,并且减少不必要的内存浪费。
    综上所述,一开始是2倍扩容可以提高内存利用率和减少扩容次数,后面改为1.25倍扩容可以减少内存占用和内存浪费。

切片的扩容与缩容:https://blog.csdn.net/qq_20867981/article/details/86467539

你可能感兴趣的:(go,golang,开发语言,后端)