Go基础-运算符 & 流程控制 | 青训营笔记

133 阅读8分钟

这是我参与「第三届青训营 -后端场」笔记创作活动的第2篇笔记

image.png

1. Go语言运算符

运算符用于在程序运行时执行数学或逻辑运算。

1.1. 算术运算符

运算符描述
+相加
-相减
*相乘
/相除
%求余
++自增
--自减

1.2. 关系运算符

运算符描述
==检查两个值是否相等,如果相等返回 True 否则返回 False。
!=检查两个值是否不相等,如果不相等返回 True 否则返回 False。
检查左边值是否大于右边值,如果是返回 True 否则返回 False。
<检查左边值是否小于右边值,如果是返回 True 否则返回 False。
>=检查左边值是否大于等于右边值,如果是返回 True 否则返回 False。
<=检查左边值是否小于等于右边值,如果是返回 True 否则返回 False。

1.3. 逻辑运算符

运算符描述
&&逻辑 AND 运算符。 如果两边的操作数都是 True,则条件 True,否则为 False。
逻辑 OR 运算符。 如果两边的操作数有一个 True,则条件 True,否则为 False。
!逻辑 NOT 运算符。 如果条件为 True,则逻辑 NOT 条件 False,否则为 True。

1.4. 位运算符

运算符描述
&按位与运算符"&"是双目运算符。 其功能是参与运算的两数各对应的二进位相与。
按位或运算符""是双目运算符。 其功能是参与运算的两数各对应的二进位相或
按位异或运算符"^"是双目运算符。 其功能是参与运算的两数各对应的二进位相异或,当两对应的二进位相异时,结果为1。
<<左移运算符"<<"是双目运算符。左移n位就是乘以2的n次方。 其功能把"<<"左边的运算数的各二进位全部左移若干位,由"<<"右边的数指定移动的位数,高位丢弃,低位补0。
>>右移运算符">>"是双目运算符。右移n位就是除以2的n次方。 其功能是把">>"左边的运算数的各二进位全部右移若干位,">>"右边的数指定移动的位数。

1.5. 赋值运算符

运算符描述
=简单的赋值运算符,将一个表达式的值赋给一个左值
+=相加后再赋值
-=相减后再赋值
*=相乘后再赋值
/=相除后再赋值
%=求余后再赋值
<<=左移后赋值
>>=右移后赋值
&=按位与后赋值
^=按位异或后赋值
=按位或后赋值

1.6. 其他运算符

运算符描述
&返回变量存储地址
*指针变量

1.7. 优先级

image.png

2. Go语言流程控制

程序的流程控制结构一共有三种:顺序结构,选择结构,循环结构。Go 语言的常用流程控制有 if 和 for,而 switch 和 goto 主要是为了简化代码、降低重复代码而生的结构,属于扩展类的流程控制。

2.1. Go语言分支结构

Go 语言提供了以下几种条件判断语句:

语句示例
if语句if 语句由一个布尔表达式后紧跟一个或多个语句组成。
if...else语句if 语句后可以使用可选的 else 语句, else 语句中的表达式在布尔表达式为 false 时执行。
if嵌套语句可以在 if 或 else if 语句中嵌入一个或多个 if 或 else if 语句。
switch语句switch 语句用于基于不同条件执行不同动作。
select语句select 语句类似于 switch 语句,但是 select 会随机执行一个可运行的case。如果没有case可运行,它将阻塞,直到有case可运行。

注:Go 没有三目运算符,所以不支持 ? : 形式的条件判断。

2.1.1. If 语句

  1. 普通 if 语句

if 语句示例:

if 布尔表达式1 {
   /* 在布尔表达式1为 true 时执行 */
} else if 布尔表达式2{
   /* 在布尔表达式1为 false ,布尔表达式2为true时执行 */
} else{
   /* 在上面两个布尔表达式都为false时,执行*/
}
  1. if 语句特殊形式

if 还有一种特殊的形式,可以在 if 表达式之前添加一个执行语句,再根据变量值进行判断,示例如下或者:

if statement; condition {  
    
}
if num := 10; num % 2 == 0 { 
    fmt.Println(num,"is even") 
}  else {
    fmt.Println(num,"is odd")
}
// 或者
if err := Connect(); err != nil {
    fmt.Println(err)
    return
}

注:num 定义在 if 里,那么只能够在该if..else语句块中使用,否则编译器会报错。Connect 是一个带有返回值的函数,err:=Connect() 是一个语句,执行 Connect 后,将错误保存到 err 变量中。err != nil才是 if 的判断表达式,当 err 不为空时,打印错误并返回。这种写法可以将返回值与判断放在一行进行处理,而且返回值的作用范围被限制在 if、else 语句组合中。 拓展:

在编程中,变量的作用范围越小,所造成的问题可能性越小,每一个变量代表一个状态,有状态的地方,状态就会被修改,函数的局部变量只会影响一个函数的执行,但全局变量可能会影响所有代码的执行状态,因此限制变量的作用范围可以提高代码的稳定性。

2.1.2. Switch 语句

  1. switch 语句

switch 是一个条件语句,它根据表达式结果并将其与可能匹配的列表进行比较,并根据匹配执行代码块,每一个 case 分支都是唯一的,从上直下逐一测试,直到匹配为止,匹配项后面也不需要再加break。Go里面 switch 默认相当于每个case最后带有break,匹配成功后不会自动向下执行其他case,而是跳出整个switch, 但是可以使用fallthrough强制执行后面的case代码。还可以在case中同时测试多个可能符合条件的值,使用逗号分割它们。

switch 表达式 {
    case 匹配条件1:
        ...
    case 匹配条件2:
        ...
    default:
        ...
}

表达式的结果可以是任何类型,而 匹配条件1 和 匹配条件2 则需要是同类型的任意值。类型不被局限于常量或整数,但必须是相同的类型;或者最终结果为相同类型的表达式。


import "fmt"

func main() {
   /* 定义局部变量 */
   var grade string = "B"
   var marks int = 90

   switch marks {
      case 90: grade = "A"
      case 80: grade = "B"
      case 50,60,70 : grade = "C"
      default: grade = "D"  
   }

   switch {
      case grade == "A" :
         fmt.Printf("优秀!\n" )    
      case grade == "B", grade == "C" :
         fmt.Printf("良好\n" )      
      case grade == "D" :
         fmt.Printf("及格\n" )      
      case grade == "F":
         fmt.Printf("不及格\n" )
      default:
         fmt.Printf("差\n" );
   }
   fmt.Printf("你的等级是 %s\n", grade );      
}
  1. switch 的其他形式

switch 后的表达式是可选的,可以省略。如果该表达式被省略,则被认为是switch true。

func main() {  
    num := 75
    switch { // expression is omitted
    case num >= 0 && num <= 50:
        fmt.Println("num is greater than 0 and less than 50")
    case num >= 51 && num <= 100:
        fmt.Println("num is greater than 51 and less than 100")
    case num >= 101:
        fmt.Println("num is greater than 100")
    }
}
//num is greater than 51 and less than 100

在 switch 后,表达式前还可以多一条初始化语句:

func main() {
	//多一条初始化语句
	switch lan := "golang"; lan {
	case "java":
		fmt.Println("java语言...")
	case "c":
		fmt.Println("c语言...")
	case "python":
		fmt.Println("python语言...")
	case "golang":
		fmt.Println("go语言...")
	default:
		fmt.Println("HelloWorld!")
	}
}
  1. Type Switch

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

Type Switch 语法格式如下:

switch x.(type){
    case type:
       statement(s);      
    case type:
       statement(s); 
    /* 你可以定义任意个数的case */
    default: /* 可选 */
       statement(s);
}

示例:

package main

import "fmt"

func main() {
   var x interface{}
     
   switch i := x.(type) {
      case nil:  
         fmt.Printf(" x 的类型 :%T",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("未知型")    
   }  
}
// x 的类型 :<nil>
  1. fallthrough

因为Go语言默认在每个 case 语句后自动添加 break,正常情况下只会执行一个 case 语句,便不会往下执行,Go提供了 fallthrough语句,可以强制执行下一条 case 语句,忽略表达式结果,无论该 case 语句的结果是否为true 都会执行该 case 语句。

package main

import "fmt"

func main() {

    switch {
    case false:
            fmt.Println("1.case 条件语句为 false")
            fallthrough
    case true:
            fmt.Println("2.case 条件语句为 true")
            fallthrough
    case false:
            fmt.Println("3.case 条件语句为 false")
            fallthrough
    case true:
            fmt.Println("4.case 条件语句为 true")
    case false:
            fmt.Println("5.case 条件语句为 false")
            fallthrough
    default:
            fmt.Println("6.默认 case")
    }
}
//2.case 条件语句为 true
//3.case 条件语句为 false
//4.case 条件语句为 true 

从输出结果可以看出:switch 从第一个判断表达式为 true 的 case 开始执行,如果 case 带有 fallthrough,程序会继续执行下一条 case,且它不会去判断下一个 case 的表达式是否为 true。

  1. switch 支持多条件匹配,但是 case 后的常量值不能重复。
  2. 不同 case 之间不需要添加 break 分隔,默认只会执行一个 case。
  3. 如果想要执行多个 case,可在 case 语句后添加 fallthrough 关键字,强制执行下一条 case 语句。

2.1.3. Select 语句

select 语句类似于 switch 语句,但是 select 会随机执行一个可运行的 case,如果没有 case 可运行,它将阻塞,直到有 case 可运行。

package main

import "fmt"

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")
   }    
}
//no communication
  • 每个case都必须是一个通信
  • 所有channel表达式都会被求值
  • 所有被发送的表达式都会被求值
  • 如果任意某个通信可以进行,它就执行;其他被忽略。
  • 如果有多个case都可以运行,Select会随机公平地选出一个执行。其他不会执行。
  • 否则:如果有default子句,则执行该语句。如果没有default字句,select将阻塞,直到某个通信可以运行;Go不会重新对channel或值进行求值。 注:Select 语句是 Go 的特有分支语句,其中涉及 Go 的通道通信等内容,初学者可跳过这部分,后续学习了相关的知识后再回头进行学习。

2.2. Go语言循环结构

2.2.1. for循环

  1. 循环处理语句

Go中只有唯一的循环语句:for语句,没有while语句do...while语句

Go 语言提供了以下几种类型循环处理语句:

循环类型描述
for 循环重复执行语句块
循环嵌套在 for 循环中嵌套一个或多个 for 循环
  1. 循环控制语句

循环控制语句可以控制循环体内语句的执行过程。GO 语言支持以下几种循环控制语句:

控制语句描述
break 语句经常用于中断当前 for 循环或跳出 switch 语句
continue 语句跳过当前循环的剩余语句,然后继续进行下一轮循环。
goto 语句将控制转移到被标记的语句。
  1. for 循环语法结构
for init; condition; post { 
    //循环体
}
  • init: 一般为赋值表达式,给控制变量赋初值
  • condition: 关系表达式或逻辑表达式,循环控制条件
  • post: 一般为赋值表达式,给控制变量增量或减量 注:所有的三个组成部分,即init、condition和post都是可选的。

其他形式:

for condition { 
    //循环体
}

这种形式下,与 while非常相似,init语句可以放到for循环外部,post语句在循环体中。 示例:

// 普通形式
sum := 0
for i := 0; i < 10; i++ {
    sum += i
}
// 省略init语句
sum := 0
i := 0
for ; i < 10; i++ {
    sum += i
}
// 省略init和post语句
sum := 0
i := 0
for i < 10 {
    sum += i
    i++
}
// 省略init和condition语句
sum := 0
i := 0
for ; ; i++ {
    if(i < 10)
    sum += i
}
  1. 无限循环

如果循环中条件语句永远不为 false 则会进行无限循环,我们可以通过 for 循环语句中只设置一个条件表达式或者省略全部语句来执行无限循环:

func main() {
    for true  {
        fmt.Printf("无限循环...\n");
    }
}
// 或
func main() {
    for {
        fmt.Printf("无限循环...\n");
    }
}

使用循环语句时,需要注意以下几点:

  • 左花括号{必须与 for处于同一行。
  • Go语言中的 for 循环与 C 语言一样,都允许在循环条件中定义和初始化变量,唯一的区别是,Go语言不支持以逗号为间隔的多个赋值语句,必须使用平行赋值的方式来初始化多个变量。
  • Go语言的 for 循环同样支持 continuebreak 来控制循环,但是它提供了一个更高级的 break,可以选择中断哪一个循环,如下例:
for j := 0; j < 5; j++ {
    for i := 0; i < 10; i++ {
        if i > 5 {
            break JLoop
        }
        fmt.Println(i)
    }
}
JLoop:
// ...

2.2.2. for...range循环(键值循环)

for...range循环是Go语言特有的一种的迭代结构,在许多情况下都非常有用,for...range 可以遍历数组、切片、字符串、map 及通道(channel),for...range 语法上类似于其它语言中的 foreach语句,一般形式为:

for key, value := range collection {
    ...
}

注:value 始终为集合中对应索引的值拷贝,因此它一般只具有只读性质,对它所做的任何修改都不会影响到集合中原有的值。

2.2.5. Go语言break语句

Go 语言的 break 语句,有与其他编程语言类似的功能,但不同的是,Go 语言的 break 语句还可以选择性中断指定循环,而不是默认只中断break所在那层循环。

package main
import "fmt"
func main() {

loop:
	// 使用 break语句,终止到指定循环
	for i := 0; i < 3; i++ {
		for j := 0; j < 20; j++{
			if j >= 2{
				break loop
			}
			package main

import "fmt"

func main() {
	//使用goto语句,跳出循环
	for i := 0; i < 5; i++ {
		for j := 0; j < 4; j++ {
			fmt.Println("i =", i, "j =", j)
			if j == 2 {
				goto over
			}
		}
	}
over:
	fmt.Println("Over")
}
		}
	}
	fmt.Println("Over")
}

image.png

2.2.6. goto语句

goto语句可以无条件地跳转到程序中指定的位置,在GO语言中,实现循环的跳转除了使用break和continue以外,还可以使用goto语句。当然,goto语句不仅可以使用在循环中,还可以使用在代码的任何地方。

goto 语句使用最多的场景就是程序的错误处理,也就是当程序出错时,统一跳转到相应的标签处,统一处理错误。语法格式:

...
goto lable
...
label:
   //do something

使用goto需要在后面加上 label ,可以直接将代码跳转到 label 的地方执行。

示例:使用goto跳出循环

package main

import "fmt"

func main() {
	//使用goto语句,跳出循环
	for i := 0; i < 5; i++ {
		for j := 0; j < 4; j++ {
			fmt.Println("i =", i, "j =", j)
			if j == 2 {
				goto over
			}
		}
	}
over:
	fmt.Println("Over")
}

image.png

注:goto语句更多的是使用在程序的错误处理,跳转到错误处理代码块对错误进行统一的处理。