go

基础语法

Posted by 云起 on 2022-09-18
Estimated Reading Time 8 Minutes
Words 1.9k In Total
Viewed Times

基础语法

条件语句

if

不支持三目运算

1
2
3
if a >2 {
fmt.Println(a)
}
1
2
3
4
5
6
7
if n := "abc"; x > 0 {     // 初始化语句未必就是定义变量, 如 println("init") 也是可以的。
println(n[2])
} else if x < 0 { // 注意 else if 和 else 左大括号位置。
println(n[1])
} else {
println(n[0])
}

switch

Golang switch 分支表达式可以是任意类型,不限于常量。可省略 break,默认自动终止。

1
2
3
4
5
6
7
8
switch var1 {
case val1:
...
case val2:
...
default:
...
}

Type Switch

switch 语句还可以被用于 type-switch 来判断某个 interface 变量中实际存储的变量类型。

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
func main() {
var x interface{}
//写法一:
switch i := x.(type) { // 带初始化语句
case nil:
fmt.Printf(" x 的类型 :%T\r\n", i)
case int:
fmt.Printf("x 是 int 型")
case float64:
fmt.Printf("x 是 float64 型")
case func(int) float64:
fmt.Printf("x 是 func(int) 型")
case bool, string:
fmt.Printf("x 是 bool 或 string 型")
default:
fmt.Printf("未知型")
}
//写法二
var j = 0
switch j {
case 0:
case 1:
fmt.Println("1")
case 2:
fmt.Println("2")
default:
fmt.Println("def")
}
//写法三
var k = 0
switch k {
case 0:
println("fallthrough")//println会输出乱序
fallthrough
/*
Go的switch非常灵活,表达式不必是常量或整数,执行的过程从上至下,直到找到匹配项;
而如果switch没有表达式,它会匹配true。
Go里面switch默认相当于每个case最后带有break,
匹配成功后不会自动向下执行其他case,而是跳出整个switch,
但是可以使用fallthrough强制执行后面的case代码。
*/
case 1:
fmt.Println("1")
case 2:
fmt.Println("2")
default:
fmt.Println("def")
}
//写法四
var n = 0
switch { //省略条件表达式,可当 if...else if...else
case n > 0 && n < 10:
fmt.Println("i > 0 and i < 10")
case n > 10 && n < 20:
fmt.Println("i > 10 and i < 20")
default:
fmt.Println("def")
}
}

select

select 是Go中的一个控制结构,类似于用于通信的switch语句。每个case必须是一个通信操作,要么是发送要么是接收。
select 随机执行一个可运行的case。如果没有case可运行,它将阻塞,直到有case可运行。一个默认的子句应该总是可运行的。

1
2
3
4
5
6
7
8
9
select {
case communication clause :
statement(s);
case communication clause :
statement(s);
/* 你可以定义任意数量的 case */
default : /* 可选 */
statement(s);
}
  • ​ 每个case都必须是一个通信
  • ​ 每个case语句里必须是一个IO操作
  • ​ select中的default子句总是可运行的。
  • ​ 所有channel表达式都会被求值
  • ​ 所有被发送的表达式都会被求值
  • ​ 如果任意某个通信可以进行,它就执行;其他被忽略。
  • ​ 如果有多个case都可以运行,Select会随机公平地选出一个执行。其他不会执行。
  • ​ 否则:
  • ​ 如果有default子句,则执行该语句。
  • ​ 如果没有default字句,select将阻塞,直到某个通信可以运行;Go不会重新对channel或值进行求值。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
func main() {
var c1, c2, c3 chan int
var i1, i2 int
select {
case i1 = <-c1:
fmt.Printf("received ", i1, " from c1\n")
case c2 <- i2:
fmt.Printf("sent ", i2, " to c2\n")
case i3, ok := (<-c3): // same as: i3, ok := <-c3
if ok {
fmt.Printf("received ", i3, " from c3\n")
} else {
fmt.Printf("c3 is closed\n")
}
default:
fmt.Printf("no communication\n")
}
}
1
2
3
4
5
6
7
8
9
10
11
select { //不停的在这里检测
case <-chanl : //检测有没有数据可以读
//如果chanl成功读取到数据,则进行该case处理语句
case chan2 <- 1 : //检测有没有可以写
//如果成功向chan2写入数据,则进行该case处理语句


//假如没有default,那么在以上两个条件都不成立的情况下,就会在此阻塞//一般default会不写在里面,select中的default子句总是可运行的,因为会很消耗CPU资源
default:
//如果以上都没有符合条件,那么则进行default处理流程
}

典型用法

超时判断
1
2
3
4
5
6
7
8
9
10
11
//比如在下面的场景中,使用全局resChan来接受response,如果时间超过3S,resChan中还没有数据返回,则第二条case将执行
var resChan = make(chan int)
// do request
func test() {
select {
case data := <-resChan:
doData(data)
case <-time.After(time.Second * 3):
fmt.Println("request time out")
}
}
退出
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//主线程(协程)中如下:
var shouldQuit=make(chan struct{})
fun main(){
{
//loop
}
//...out of the loop
select {
case <-c.shouldQuit:
cleanUp()
return
default:
}
//...
}

//再另外一个协程中,如果运行遇到非法操作或不可处理的错误,就向shouldQuit发送数据通知程序停止运行
close(shouldQuit)
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
func produce(id int, ch chan string, quitSub chan int){
for i:=0;i< 10;i++{
ch <- fmt.Sprintf("msg:%d:%d",id,i)
}
quitSub <- id
}

func main() {
c1 := make(chan string, 1) //定义两个有缓冲的通道,容量为1
c2 := make(chan string, 1)
quitForSelect := make(chan int, 2) //用于通知退出for select循环
quitTag := []int{} //用于读取和存储quitForSlect通道的两个值,保证两个product都写入完毕

quitMain := make(chan bool) //阻塞main gorouting,等待for select 处理完成

go produce(1, c1, quitForSelect)
go produce(2, c2, quitForSelect)

go func(){
for { //使用select来等待这两个通道的值,然后输出
select {
case msg1 := <- c1:
fmt.Println(msg1)
case msg2 := <- c2:
fmt.Println(msg2)
case q := <- quitForSelect:
fmt.Println("got quit tag for Gorouting id:", q)
quitTag = append(quitTag,q)
if len(quitTag) == 2{
fmt.Println("end to quit Main ")
quitMain <- true
}
}
}
}()

<-quitMain
close(c2)
close(c1)
fmt.Println("exit from main")
}

判断channel是否阻塞

1
2
3
4
5
6
7
8
9
//在某些情况下是存在不希望channel缓存满了的需求的,可以用如下方法判断
ch := make (chan int, 5)
//...
data:=0
select {
case ch <- data:
default:
//做相应操作,比如丢弃data。视需求而定
}

循环语句

for

  • ​ for init; condition; post { }

  • ​ for condition { }

  • ​ for { }

  • ​ init: 一般为赋值表达式,给控制变量赋初值;

  • ​ condition: 关系表达式或逻辑表达式,循环控制条件;

  • ​ post: 一般为赋值表达式,给控制变量增量或减量。

  • ​ for语句执行过程如下:

    • ​ ①先对表达式 init 赋初值;

    • ​ ②判别赋值表达式 init 是否满足给定 condition 条件,

      ​ 若其值为真,满足循环条件,则执行循环体内语句,然后执行 post,进入第二次循环,再 判别 condition;

      ​ 否则判断 condition 的值为假,不满足条件,就终止for循环,执行循环体外语句。

1
2
3
4
5
6
7
8
9
10
11
12
13
for i, n := 0, len(s); i < n; i++ { // 常见的 for 循环,支持初始化语句。
println(s[i])
}

n := len(s)
for n > 0 { // 替代 while (n > 0) {}
n--
println(s[n]) // 替代 for (; n > 0;) {}
}

for { // 替代 while (true) {}
println(s) // 替代 for (;;) {}
}
1
2
3
for i, n := 0, length(s); i < n; i++ {     // 避免多次调用 length 函数。
println(i, s[i])
}
1
2
3
for true  {
fmt.Printf("这是无限循环。\n");
}

range

for 循环的 range 格式可以对 slice、map、数组、字符串等进行迭代循环。格式如下:

1
2
3
for key, value := range oldMap {
newMap[key] = value
}

range 会复制对象.

即会进行值拷贝,遍历的为拷贝的副本,此时更改值类型的值,遍历时也是原值,不会发生改变

但是引用类型仍然可以修改底层数据

Goto、Break、Continue

1
2
3
4
1.三个语句都可以配合标签(label)使用
2.标签名区分大小写,定以后若不使用会造成编译错误
3.continuebreak配合标签(label)可用于多层循环跳出
4.goto是调整执行位置,与continuebreak配合标签(label)的结果并不相同

If you like this blog or find it useful for you, you are welcome to comment on it. You are also welcome to share this blog, so that more people can participate in it. If the images used in the blog infringe your copyright, please contact the author to delete them. Thank you !