面试问题浓缩总结 面试问题浓缩总结
  • Go
  • Java
  • C/C++
  • JavaScript/HTML
  • MySQL
  • Redis
  • MongoDB
  • 操作系统
  • 计算机网络
  • spring全家桶
  • mybatis
  • 中间件
  • 软件相关
  • 系统相关
  • 算法
  • 数据结构
  • 设计模式
  • CMU硕士经典100题
  • 剑指offer
  • 重点手撕代码
  • 程序员面试金典
  • 3月
  • 4月
  • 智力题
  • 业务问题
  • 一些技术
  • 安全相关
APP下载 (opens new window)
GitHub (opens new window)
  • Go
  • Java
  • C/C++
  • JavaScript/HTML
  • MySQL
  • Redis
  • MongoDB
  • 操作系统
  • 计算机网络
  • spring全家桶
  • mybatis
  • 中间件
  • 软件相关
  • 系统相关
  • 算法
  • 数据结构
  • 设计模式
  • CMU硕士经典100题
  • 剑指offer
  • 重点手撕代码
  • 程序员面试金典
  • 3月
  • 4月
  • 智力题
  • 业务问题
  • 一些技术
  • 安全相关
APP下载 (opens new window)
GitHub (opens new window)
  • Go

    • 编译原理
    • 数据结构
    • 语言基础
    • 常用关键字
      • for和range
        • for循环的三种用法
        • 几个用法
        • for range里面修改数组后不会改变遍历次数
        • for range使用指针问题
        • for range清空数组的代码会进行优化
        • map会随机遍历
      • select
        • 简单例子
        • 特性
      • defer
        • 多个defer的执行顺序
        • return和defer之间的关系
        • defer表达式的返回值会被丢弃
      • panic和recover
        • 现象
      • make和new
      • switch
      • goto
      • delete
    • 并发编程
    • 内存管理
    • 元编程
    • 标准库
    • 其他
    • 面试问题
  • JAVA

  • C、C++语言

  • JavaScript和HTML

  • Android相关

  • 程序语言
  • Go
小游
2021-03-20
for和range
for循环的三种用法
几个用法
for range里面修改数组后不会改变遍历次数
for range使用指针问题
for range清空数组的代码会进行优化
map会随机遍历
select
简单例子
特性
defer
多个defer的执行顺序
return和defer之间的关系
defer表达式的返回值会被丢弃
panic和recover
现象
make和new
switch
goto
delete

常用关键字

# for和range

# for循环的三种用法

img

for range用于遍历切片,数组或者map

# 几个用法

  1. 使用 for range a {} 遍历数组和切片,不关心索引和数据的情况;
  2. 使用 for i := range a {} 遍历数组和切片,只关心索引的情况;
  3. 使用 for i, elem := range a {} 遍历数组和切片,关心索引和数据的情况;

# for range里面修改数组后不会改变遍历次数

func main() {
	arr := []int{1, 2, 3}
	for _, v := range arr {
		arr = append(arr, v)
	}
	fmt.Println(arr)
}
$ go run main.go
1 2 3 1 2 3
1
2
3
4
5
6
7
8
9

这里for代码只会执行3次,为什么?

因为我们在遍历时go会把切片赋值给一个新变量,同时通过len预先获取切片长度,所以不会改变循环次数

# for range使用指针问题

func main() {
	arr := []int{1, 2, 3}
	newArr := []*int{}
	for _, v := range arr {
		newArr = append(newArr, &v)
	}
	for _, v := range newArr {
		fmt.Println(*v)
	}
}
$ go run main.go
3 3 3
1
2
3
4
5
6
7
8
9
10
11
12

为什么会这样?

因为go遍历时会额外新建V2来存切片中的元素,而且每次循环时会重新覆盖这个值,所以结果会相同。正确的做法应该是使用 &arr[i] 替代 &v

# for range清空数组的代码会进行优化

func main() {
	arr := []int{1, 2, 3}
	for i, _ := range arr {
		arr[i] = 0
	}
}
1
2
3
4
5
6

依次遍历切片和哈希看起来是非常耗费性能的,因为数组、切片和哈希占用的内存空间都是连续的,go会对上面这种代码进行优化,会直接清空这片内存中的内容

# map会随机遍历

func main() {
	hash := map[string]int{
		"1": 1,
		"2": 2,
		"3": 3,
	}
	for k, v := range hash {
		println(k, v)
	}
}
1
2
3
4
5
6
7
8
9
10

这段代码,每次执行的结果都不一样,因为go会生成随机数,随机选择一个桶来作为起始地址进行遍历。

详细参考:Go 语言 for 和 range 的实现 | Go 语言设计与实现 (draveness.me) (opens new window)

# select

select 是操作系统中的系统调用,我们经常会使用 select、poll 和 epoll 等函数构建 I/O 多路复用模型提升程序的性能。Go 语言的 select 与操作系统中的 select 比较相似,同样用于监听IO多路操作

# 简单例子

func fibonacci(c, quit chan int) {
	x, y := 0, 1
	for {
		select {
		case c <- x:
			x, y = y, x+y
		case <-quit:
			fmt.Println("quit")
			return
		}
	}
}
1
2
3
4
5
6
7
8
9
10
11
12

上述控制结构会等待 c <- x 或者 <-quit 两个表达式中任意一个返回。无论哪一个表达式返回都会立刻执行 case 中的代码,当 select 中的两个 case 同时被触发时,会随机执行其中的一个。

# 特性

  1. 当 select 中的两个 case 同时被触发时,会随机执行其中的一个
  2. 当select 不加default时,就会阻塞当前 Goroutine 直到有一个case可以收发;当添加 default 时,就不会阻塞 Goroutine ,会自动选择一个可以收发的 Channel ,如果没有就执行default的内容。
  3. 如果 select 控制结构中包含 default 语句,有下面两种情况
    1. 当存在可以收发的 Channel 时,直接处理该 Channel 对应的 case;
    2. 当不存在可以收发的 Channel 时,执行 default 中的语句;

深入可以参考:Go 语言 select 的实现原理 | Go 语言设计与实现 (draveness.me) (opens new window)

# defer

defer 会在当前函数返回前执行传入的函数,它会经常被用于关闭文件描述符、关闭数据库连接以及解锁资源。

# 多个defer的执行顺序

func main() {
	for i := 0; i < 5; i++ {
		defer fmt.Println(i)
	}
}

$ go run main.go
4
3
2
1
0
1
2
3
4
5
6
7
8
9
10
11
12

我们可以看到,越到后面的defer会越先执行

深入可参考:理解 Go 语言 defer 关键字的原理 | Go 语言设计与实现 (draveness.me) (opens new window)

# return和defer之间的关系

Go 的函数返回值是通过堆栈返回的, return 语句不是原子操作,而是被拆成了两步.

  • 给返回值赋值 (rval)
  • 调用 defer 表达式
  • 返回给调用函数(ret)

下面这个例子就很好的说明了整个return执行的流程

package main
import "fmt"
func main() {
    fmt.Println(increase(1))
}
func increase(d int) (ret int) {
  defer func() {
    ret++
  }()
  return d
}
// 函数会返回2
1
2
3
4
5
6
7
8
9
10
11
12

# defer表达式的返回值会被丢弃

闭包与匿名函数.

  • 匿名函数:没有函数名的函数。
  • 闭包:可以使用另外一个函数作用域中的变量的函数。

在实际开发中,defer 的使用经常伴随着闭包与匿名函数的使用

package main
import "fmt"
func main() {
    for i := 0; i < 5; i++ {
        defer func() {
            fmt.Println(i)
        }()
    }
}
/**
5
5
5
5
5
之所以这样是因为,defer 表达式中的 i 是对 for 循环中 i 的引用。到最后,i 加到 5,故最后全部打印 5。
如果将 i 作为参数传入 defer 表达式中,在传入最初就会进行求值保存,只是没有执行延迟函数而已。
**/

/**比如下面这种情况**/

// 这个函数会返回1,因为defer执行的比return更晚,当return返回0时,此时执行defer,然后defer就会对result进行++操作
func f1() (result int) {
	defer func() {
		result++
	}()
	return 0
}

// 这个函数会返回5,同样是因为defer执行的更晚
func f2() (r int) {
    t := 5
   defer func() {
    // 这里并不会修改返回值,除非我们修改r
    t = t + 5
   }()
   return t
}
// 你也可以这样理解
func f() (r int) {
     t := 5
     r = t // 赋值指令
     func() {        // defer被插入到赋值与返回之间执行,这个例子中返回值r没被修改过
         t = t + 5
     }
     return        // 空的return指令
}

// 这个函数会返回1,因为defer里面的r其实是一个局部变量,++操作不会影响返回的值
func f3() (r int) {
	defer func(r int) {
		r = r + 5
	}(r)
	return 1
}
// 换成下面这个就会返回6了
func f3() (r int) {
	defer func() {
		r = r + 5
	}()
	return 1
}
// 也可以这样理解
func f() (r int) {
     r = 1  // 给返回值赋值
     func(r int) {        // 这里改的r是传值传进去的r,不会改变要返回的那个r值
          r = r + 5
     }(r)
     return        // 空的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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70

# panic和recover

  • panic 能够改变程序的控制流,调用 panic 后会立刻停止执行当前函数的剩余代码,并在当前 Goroutine 中递归执行调用方的 defer;
  • recover 可以中止 panic 造成的程序崩溃。它是一个只能在 defer 中发挥作用的函数,在其他作用域中调用不会发挥作用;

# 现象

  • panic 只会触发当前 Goroutine 的 defer;
  • recover 只有在 defer 中调用才会生效;
  • panic 允许在 defer 中嵌套多次调用;

深入参考:Go 语言 panic 和 recover 的原理 | Go 语言设计与实现 (draveness.me) (opens new window)

# make和new

  • make 的作用是初始化内置的数据结构,也就是我们在前面提到的切片、哈希表和 Channel2 (opens new window);
  • new 的作用是根据传入的类型分配一片内存空间并返回指向这片内存空间的指针3 (opens new window);

参考:Go 语言中的 make 和 new | Go 语言设计与实现 (draveness.me) (opens new window)

# switch

go的switch的类型判断部分的表达式是可加可不加的,如果不加就需要在case里面进行判断

switch {
      case grade == "A" :
         fmt.Printf("优秀!" )  
      case grade == "B", grade == "C" :
         fmt.Printf("良好" )  
      case grade == "D" :
         fmt.Printf("及格" )  
      case grade == "F":
         fmt.Printf("不及格" )
      default:
         fmt.Printf("差" );
   }
1
2
3
4
5
6
7
8
9
10
11
12

case里面可以有一个或者多个表达式

switch marks {
      case 90: grade = "A"
      case 80: grade = "B"
      case 50,60,70 : grade = "C"
      default: grade = "D"  
   }
1
2
3
4
5
6

case里是不需要用break的,因为go默认会加break。如果我们不想用break,那么可使用 fallthrough关键词来执行下面的case代码块

# goto

go是支持goto语句的,不过一般不推荐使用

package main

import "fmt"

func main() {
   /* 定义局部变量 */
   var a int = 10

   /* 循环 */
   LOOP: for a < 20 {
      if a == 15 {
         /* 跳过迭代 */
         a = a + 1
         goto LOOP
      }
      fmt.Printf("a的值为 : %d", a)
      a++  
   }  
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# delete

delete 关键字用于删除哈希表 map 中的元素, 参数为 map 和其对应的 key

编辑 (opens new window)
上次更新: 2021/03/27, 10:23:23
语言基础
并发编程

← 语言基础 并发编程→

Theme by Vdoing | Copyright © 2021-2025 小游
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式