当前位置:   article > 正文

Golang面试题——基础知识_go 有方向的 channel 不可以被关闭

go 有方向的 channel 不可以被关闭

Golang基础面试题

1、defer相关

defer的执行顺序后进先出(LIFO),在函数结束前执行

  • defer在return之后执行,但在函数退出之前,defer可以修改返回值。
  • defer 的参数是一个函数,该函数最先被调用。
  • panic 需要等 defer 执行结束后才会执行。
func DeferFunc1(i int) (t int) {
	t = i
	defer func() {
		t += 3
	}()
	return t
}
func DeferFunc2(i int) int {
	t := i
	defer func() {
		t += 3
	}()
	return t
}
func DeferFunc3(i int) (t int) {
	defer func() {
		t += i
	}()
	return 2
}
func main() {
	println(DeferFunc1(1))
	println(DeferFunc2(1))
	println(DeferFunc3(1))
}

// 输出
4
1
3
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

  DeferFunc1中,有名返回(指定返回值命名func test() (t int)),执行 return 语句时,并不会再创建临时变量保存,defer 语句修改了t,即对返回值产生了影响,所以返回4。

  DeferFunc2中,无名返回(返回值没有指定命名),执行Return语句后,Go会创建一个临时变量保存返回值,defer 语句修改的是 t,而不是临时变量,所以返回1。

  DeferFunc3中,有名返回,执行 return 语句时,把2赋值给t,defer 语句再执行t+1,所以返回3。

2、for range

  range 循环会复用地址,&stu实际上一致指向同一个指针,最终该指针的值为遍历的最后一个struct的值拷贝。(for循环复用局部变量i)

func main() {
	m := make(map[string]*student)
	stus := []student{
		{Name: "zhou", Age: 24},
		{Name: "li", Age: 23},
		{Name: "wang", Age: 22},
	}
	// 错误写法
	for _, stu := range stus {
		fmt.Printf("%p\n", &stu)
		m[stu.Name] = &stu
	}
	for k, v := range m {
		fmt.Println(k, "=>", v.Name)
	}

	// 正确
	for i := 0; i < len(stus); i++ {
		m[stus[i].Name] = &stus[i]
	}
	for k, v := range m {
		fmt.Println(k, "=>", v.Name)
	}
}

// 输出
0xc000008078
0xc000008078
0xc000008078
zhou => wang
li => wang  
wang => wang
zhou => zhou
li => li    
wang => wang
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35

for 和 for range有什么区别?

  • 使用场景不同
      for可以遍历array和slice、遍历key为整型递增的map、遍历string
      for range可以完成所有for可以做的事情,却能做到for不能做的,包括遍历key为string类型的map并同时获取key和value、遍历channel。

  • 实现不同
      for可以获取到的是被循环对象的元素本身,可以对其进行修改;
      for range使用值拷贝的方式代替被遍历的元素本身,是一个值拷贝,而不是元素本身。

func main() {
	five := []string{"Annie", "Betty", "Charley", "Doug", "Edward"}

	for _, v := range five {
		five = five[:2]
		fmt.Printf("v[%s]\n", v)
	}
	fmt.Println(five)
}
//v[Annie]
//v[Betty]  
//v[Charley]
//v[Doug]   
//v[Edward]
//[Annie Betty]
//循环内的切片值会缩减为2,但循环将在切片值的自身副本上进行操作。这允许循环使用原始长度进行迭代而没有任何问题,因为后备数组仍然是完整的。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

3、go的并发

  i++不是原子操作,会出现并发问题。Go 提供了一个检测并发访问共享资源是否有问题的工具, race 参数:go run -race main.go

  第一个go协程中i是外部for的一个变量,地址不变化,每次指向的都是一样的地址,遍历完成后,最终i=10。

  第二个go协程中i是函数参数,会发生值拷贝,地址会变,内部每次指向新的地址。

func main() {
	var wg sync.WaitGroup
	wg.Add(10) 
	
	// 错误
	for i := 0; i < 10; i++ {
		go func() {
			fmt.Println(i)
			wg.Done()
		}()
	}
	
	// 正确
	for i := 0; i < 10; i++ {
		go func(i int) {
			fmt.Println(i)
			wg.Done()
		}(i)
	}
	wg.Wait()
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

4、select随机性

  select会随机选择一个可用通道做收发操作。 所以代码是有肯触发异常,也有可能不会。select可以在多个chan间等待执行,有三点原则:

  • select 中只要有一个case能return,则立刻执行。
  • 当如果同一时间有多个case均能return则伪随机方式抽取任意一个执行。
  • 如果没有一个case能return则可以执行”default”块。
func main() {
    runtime.GOMAXPROCS(1)
    int_chan := make(chan int, 1)
    string_chan := make(chan string, 1)
    int_chan <- 1
    string_chan <- "hello"
    select {
    case value := <-int_chan:
        fmt.Println(value)
    case value := <-string_chan:
        panic(value)
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

5、make默认值和append

  make初始化是有默认值的,默认值为0。append函数执行完后,返回的是一个全新的 slice,并且对传入的 slice 并不影响。

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

func main() {
    s := []int{5}
    s = append(s, 7)
    s = append(s, 9)
    x := append(s, 11)
    y := append(s, 12)
    fmt.Println(s, x, y)
}
//[5 7 9] [5 7 9 12] [5 7 9 12]
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

6、结构体比较

  结构体是可以比较的,但前提是结构体成员字段全部可以比较,并且结构体成员字段类型、个数、顺序也需要相同,当结构体成员全部相等时,两个结构体相等。

  特别注意的点,如果结构体成员字段的顺序不相同,那么结构体也是不可以比较的。如果结构体成员字段中有不可以比较的类型,如map、slice、function 等,那么结构体也是不可以比较的。

func main() {
	sn1 := struct {
		age  int
		name string
	}{age: 11, name: "Zhang San"}
	sn2 := struct {
		age  int
		name string
	}{age: 11, name: "Zhang San"}

	fmt.Println(sn1 == sn2) // 输出 true


	sn3 := struct {
		name string
		age  int
	}{age: 11, name: "Zhang San"}

	fmt.Println(sn1 == sn3)
	// 错误提示:Invalid operation: sn1 == sn3 (mismatched types struct {...} and struct {...})
	

	sn4 := struct {
		name string
		age  int
		grade map[string]int
	}{age: 11, name: "Zhang San"}
	sn5 := struct {
		name string
		age  int
		grade map[string]int
	}{age: 11, name: "Zhang San"}

	fmt.Println(sn4 == sn5)
	// 错误提示:Invalid operation: sn4 == sn5 (the operator == is not defined on struct {...})
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36

7、type

  编译失败,因为type只能使用在interface类型。

//错误
func main(key int) {
	switch key.(type) {
	case int:
		println("int")
	case interface{}:
		println("interface")
	default:
		println("unknown")
	}
}
//正确
func main(key interface{}) {
	switch t := key.(type) {
	case int:
		println("int")
	case float64:
		println("float64")
	case string:
		println("string")
	default:
		println("unknown")
	}
	return
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

8、iota

  iota是go语言的常量计数器,只能在常量的表达式中使用,只能用于int类型的常量。​ 使用iota能简化定义,在定义枚举时很有用。

  • iota在const关键字出现时将被重置为0
  • const中每新增一行常量声明将使iota计数一次(iota可理解为const语句块中的行索引)
  • 可以通过下划线跳过不想要的值
  • 可以表示左移和右移动
  • iota以行位单位,同一行的iota数值一样
  • iota可以实现插队,中间有别的常量会打断iota的递增,要想实现插队则在别的变量后面在加入iota
const (
	m = iota // m = 0
	_
	_
	n 		 // n = 3
)
const (
	y = 2 >> iota // y = 2,y右移0位
	x             // x = 1,x右移1位
)
const (
	_ = iota
	l = 1 << iota // l = 2,l左移1位
	h             // h = 4,h左移2位
)
const (
	_  = iota
	KB = 1 << (10 * iota) // KB = 1024,表示1向左移动10*1位
	MB                    // MB = 1024*1024,表示向左移动10*2位
)
const (
	t, r = iota + 1, iota + 2 // t = 1,r = 2 (iota = 0)
	v, u                      // v = 2,u = 3 (iota = 1)
	p, q                      // p = 3,q = 4 (iota = 2)
)
const (
	frist   = iota // frist = 0
	sencond = 5
	thr     = iota // thrid = 2
	last           // last = 3
)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

9、常量

  常量不同于变量的在运行期分配内存,常量通常会被编译器在预处理阶段直接展开,作为指令数据使用,无法获取常量的地址。

package main
const cl  = 100

var bl    = 123

func main()  {
    println(&bl,bl)
    println(&cl,cl)
}
// Cannot take the address of 'cl'
// 编译错误,无法获取常量的地址
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

  如果是批量声明的常量,除了第一个外其它的常量右边的初始化表达式都可以省略,如果省略初始化表达式则表示使用前面常量的初始化表达式,对应的常量类型也是一样的。

const (
    a = 1
    b
    c = 2
    d
)
fmt.Println(a, b, c, d) // "1 1 2 2"
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

10、有方向的channel不可被关闭

data := make(chan int)
data := make(chan<- int)    // 定义单向信道,定义只写数据的信道,<-指向chan
data := make(<-chan int)    // 定义单向信道,定义只读数据的信道
只写通道:chan<- T
只读通道:<-chan T
func main(){    
    data := make(chan int)
    go func(out chan<- int){
        time.Sleep(2* time.Second)
        out <- 1
    }(data)
    <- data
    fmt.Println("Receive data, first")
    go func(out <-chan int){
        time.Sleep(2 * time.Second)
        <-out
        fmt.Println("Receive data, Second")
        os.Exit(0)
    }(data)
    data <- 2
    for {
        time.Sleep(1 * time.Second)
    }   
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Cpp五条/article/detail/89140
推荐阅读
相关标签
  

闽ICP备14008679号