zoukankan      html  css  js  c++  java
  • Go-常见的面试题(一)

    文章转载地址:https://juejin.im/entry/5971bed66fb9a06bb21adf15

    1、写出下面代码的输出

    package main
    
    import "fmt"
    
    func main() {
    	defer_all()
    	panic("触发异常")
    }
    
    func defer_all()  {
    	defer func() {
    		fmt.Println("打印前")
    	}()
    	defer func() {
    		fmt.Println("打印中")
    	}()
    	defer func() {
    		fmt.Println("打印后")
    	}()
    }
    

      解析:这道题主要考察的是对 defer 的理解,defer 主要是延迟函数,延迟到调用者函数执行 return 命令之前,

    多个 defer 之前按照先进后出的顺序执行,所以,这道题中,在 panic 触发时结束函数运行,在 return 之前依次打

    印:打印后、打印中、打印前。最后 runtime 运行时抛出打印 panic 异常信息,panic 需要 defer 结束后才会向上传

            需要注意的是,函数的 return value 不是原子操作,而是在编译器中被分解成两部分:返回值和return,而我们

    知道 defer 是在 return 之前执行的,所以可以在 defer 函数中修改返回值,如下示例:

    package main
    
    import (
    	"fmt"
    )
    
    func main() {
    	fmt.Println(doubleScore(0))    //0
    	fmt.Println(doubleScore(20.0)) //40
    	fmt.Println(doubleScore(50.0)) //50
    }
    func doubleScore(source float32) (score float32) {
    	defer func() {
    		if score < 1 || score >= 100 {
    			//将影响返回值
    			score = source
    		}
    	}()
    	score = source * 2
    	return
    
    	//或者
    	//return source * 2
    }
    

    2. 下面的代码输出什么?

    package main
    
    import "fmt"
    
    func calc(index string, a, b int) int {
    	ret := a + b
    	fmt.Println(index, a, b, ret)
    	return ret
    }
    
    func main()  {
    	a := 1
    	b := 2
    	defer calc("1", a, calc("10", a, b))
    	a = 0
    	defer calc("2", a, calc("20", a, b))
    	b = 1
    }
    

     解析:

       程序在执行到第三行的时候,会先执行 calc 函数的 b 参数,即:calc("10",a,b),输出:10,1,2,3 得到值 3,然后因为

    defer 定义的函数是延迟函数故 calc("1",1,3) 会被延迟执行

       程序执行到第五行的时候,同样先执行 calc("20",a,b) 输出:20,0,2,2 得到值 2,同样将 calc("2",0,2) 延迟执行

       程序执行到末尾的时候,按照栈先进后出的方式依次执行:calc("2",0,2),calc("1",1,3),则就依次输出:2,0,2,2、

    1,1,3,4

    3.请写出以下输出内容

    func main() {
    	s := make([]int, 5)
    	s = append(s,1,2,3)
    	fmt.Println(s)
    }

      解析:

         使用 make 初始化 slice,第二个参数代表的是 slice 的长度,slice 还有第三个参数表示容量,这里没有指定容量表示创建一个

    满容的切片,使用 len()、cap() 函数获取切片的 长度,初始化后切片的长度和容量都是 5,使用 append 追加三个元素使得切片的

    长度大于原有的容量,此时切片的容量扩大一倍,变成 10,因此输出的结果为:

    [0 0 0 0 0 1 2 3]
    

    3. 下面的代码能正常编译吗?

    package main
    
    import (
    	"fmt"
    )
    
    type People interface {
    	Speak(string) string
    }
    
    type Stduent struct{}
    
    func (stu *Stduent) Speak(think string) (talk string) {
    	if think == "bitch" {
    		talk = "You are a good boy"
    	} else {
    		talk = "hi"
    	}
    	return
    }
    
    func main() {
    	var peo People = Stduent{}
    	think := "bitch"
    	fmt.Println(peo.Speak(think))
    }
    

      运行打印结果:

    # command-line-arguments
    .main.go:23:6: cannot use Stduent literal (type Stduent) as type People in assignment:
            Stduent does not implement People (Speak method has pointer receiver)
    

      从上面的输出信息可以看出 Student 没有实现 People 这个接口

           解析:

           我们来看一下语言规范里面定义的规则,这些规则用来说明一个类型的值或指针是否实现了该接口:

           1.类型 *T 的可调用方法集包含接收者为 *T 或 T 的所有方法集

              这条规则说的是如果我们用来调用接口方法的变量是一个指针类型,那么方法的接收者可以是值类型也可以是指针类型,

    现在看一下我们的例子显然是不符合规则的,var peo People = Student{} 是一个值类型

           2. 类型 T 的可调用方法集包含接收者为 T 的所有方法集

             这条规则说的是如果我们用来调用接口方法的变量是一个值类型,那么方法的接收者必须要是值类型才可以被调用,看一下

    我们的例子,方法的接收者是指针类型

            上面的代码可以这样修改:

            1.var peo People = &Student{}

            2.将方法的接收者改成值类型

            可以参考这篇文章:https://github.com/Unknwon/gcblog/blob/master/content/26-methods-interfaces-and-embedded-types-in-golang.md

    4. 下面的代码是死循环吗?

    func main() {
    	v := []int{1, 2, 3}
    	for i := range v {
    		v = append(v, i)
    	}
    }
    

      解析:

           直接运行上面的代码我们会发现上面的程序不会出现死循环,能够正常结束。 

           我们来看一下切片的 for range ,它的底层代码是:

    //   for_temp := range
    //   len_temp := len(for_temp)
    //   for index_temp = 0; index_temp < len_temp; index_temp++ {
    //           value_temp = for_temp[index_temp]
    //           index = index_temp
    //           value = value_temp
    //           original body
    //   }
    

      从底层代码中我们可以看到在遍历 slice 之前会先计算 slice 的长度作为循环次数,循环体中,每次循环会先获取

    元素值,如果 for-range 中接收 index,value 则会对 index,value 进行一次赋值

           由于循环开始前循环次数已经确定了,所以循环过程中新添加的元素没办法遍历到 

           参考文章:https://my.oschina.net/renhc/blog/2396058

     5.下面的代码有什么问题吗?

    slice := []int{0, 1, 2, 3}
    myMap := make(map[int]*int)
    
    for index, value := range slice {
    	myMap[index] = &value
    }
    fmt.Println("=====new map=====")
    for k, v := range myMap {
    	fmt.Printf("%d => %d
    ", k, *v)
    }
    

      运行打印输出结果:

    =====new map=====
    3 => 3
    0 => 3
    1 => 3
    2 => 3
    

      结果完全一样,都是最后一次遍历的值。通过第 4 道题目对切片 for-range 的底层代码可知,遍历后

    的值赋值给 value,在我们的例子中,会把 value 的地址保存到 myMap 中,这里的 value 是一个全局变量,

    &value 取得是这个全局变量的地址,所以最后输出的结果都是一样的并且是最后一个值,相当于如下代码:

            // for_temp := range
    	// len_temp := len(for_temp)
    	// for index_temp = 0;index_temp < len_temp;index_temp++ {
    	//     value_temp = for_temp[index_temp]
    	//     index = index_temp
    	//     value = value_temp
    	//     myMap[index] = &value
    	//     original body
    	// }

           注意:这里必须是保存指针才会有问题,如果直接保存的是 value,不会有问题

           总结:通过 for-range 遍历切片,首先,计算遍历的次数(切片的长度);每次遍历,都会把当前遍历到的值

    存放到一个全局变量中

           参考文章:https://juejin.im/entry/5bd7d1ac51882541b558f0b8

           go range 内部实现(Dave 大神):https://garbagecollected.org/2017/02/22/go-range-loop-internals/

  • 相关阅读:
    【Wyn Enterprise BI知识库】 什么是商业智能 ZT
    Wyn BI的机会在哪里:越靠近消费者的行业,比如零售、文娱和金融,信息化投入越大 ZT
    客户化软件时代的前夜 ZT
    在“非软件企业”开发软件的困局 ZT
    行业观察报告:从SAAS困局看行业趋势 ZT
    超级干货 :一文读懂数据可视化 ZT
    传统BI还是自助式BI---BI与数据分析 ZT
    【BI学习笔记】在Linux上安装Wyn Enterprise商业智能报表服务器
    MAMP 配置: Mac with OSX 10.8 + (Mac + Apache + MySQL + Php)
    Emule Xtreme Kid eD2K 设置
  • 原文地址:https://www.cnblogs.com/leeyongbard/p/10462859.html
Copyright © 2011-2022 走看看