文章目录
五. 运算 操作
逻辑与
0 && 0 =1
1 && 1 =1
1 && 0 =0
逻辑或
0 || 0 =0
0 || 1 =1
1 || 0 =1
1 || 1 =1
关系运算:结果也为true,false
算数运算符
+ ,- ,* ,/ ,%
自增
:++,只能用于变量,只能放在后面
自减
:–
赋值运算
== ,!= ,> , >= , < ,<=
结果为布尔值bool
位运算
也就是二进制运算
&
与运算,相同为1,不同为0
|
或运算,有1,为1
^
与或,不同为1
&^
按位清空,把都为1的位置清空
左移<< ,成2
右移 >>,除2
赋值运算
= , +=, -=, *=, /= , %=, ,|= ,&= ,^= , &^= , <<= , <<=
类型转换
- 不同类型在没有任何操作之前是
不能,运算的
- 由大往小转换,可能会出现溢出
强制类型转换
var A int =3
var B uint =4
fmt.Println(A+ int(B))
六. 流程控制
1 .条件语句if else
&&
左右都要满足
||
有一个满足
if 条件 {
} else if 条件{
} else{
}
var yes string
fmt.Scan(&yes)
if yes == "Y" || yes == "y"{
fmt.Println("ok")
} else{
fmt.Println("error")
}
2 . 选择语句switch
主要有两种用法
- 单值,对变量进行匹配
- 表达式,进行比较
- case后面 可以是
多个条件
- 穿透
fallthrough
,在case语句块后添加fallthrough会继续
执行下一个case
switch 变量 {
# 值,可以是多个
case 值 :
语句1
case 值 ;
语句2
default:
语句3
}
单值,匹配
var yes string
fmt.Scan(&yes)
switch yes {
case "y","Y":
fmt.Println("ok")
default:
fmt.Println("no")
}
表达式,进行比较
switch{
case A>=90:
fmt.Println("A")
case A>=80:
fmt.Println("A")
}
匹配类型
var x interface{} //x是空接口类型,可以接收任意类型
var y = 19.9
x = y
switch i := x.(type) {
case nil:
fmt.Printf("x的类型是%T\n", i)
case float64:
fmt.Printf("x的类型是%T\n", i)
default:
fmt.Println("未知类型")
}
switch 后可接函数
switch 后面可以接一个函数,只要保证 case 后的值类型与函数的返回值 一致即可
。
import "fmt"
// 判断一个同学是否有挂科记录的函数
// 返回值是布尔类型
func getResult(args ...int) bool {
for _, i := range args {
if i < 60 {
return false
}
}
return true
}
func main() {
chinese := 80
english := 50
math := 100
switch getResult(chinese, english, math) {
// case 后也必须 是布尔类型
case true:
fmt.Println("该同学所有成绩都合格")
case false:
fmt.Println("该同学有挂科记录")
}
}
switch 可不接表达式
switch 后可以不接任何变量、表达式、函数。
当不接任何东西时,switch - case 就相当于 if - elseif - else
score := 30
switch {
case score >= 95 && score <= 100:
fmt.Println("优秀")
case score >= 80:
fmt.Println("良好")
case score >= 60:
fmt.Println("合格")
case score >= 0:
fmt.Println("不合格")
default:
fmt.Println("输入有误...")
}
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")
}
}
-
如果有多个case都可以运行,select会
随机公平地选出一个执行
,其他不会执行。 -
如果没有可运行的case语句,且有
default
语句,那么就会执行default的动作。 -
如果没有可运行的case语句,且没有default语句,
select将阻塞
,直到某个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
3 . 循环语句 for
方式一
source :=0
//初始 条件 后置
for i:=1 ; i <=100 ; i++{
source +=i
}
fmt.Println(source)
方式二
source =0
i :=1
for i <=100 {
source +=i
i++
}
方式三
:死循环
var i int =0
for{
fmt.Println(i)
i++
}
方式四:字符串,数组,切片,映射,管道
遍历一个可迭代对象
,
for range
range 后可接数组、切片,字符串等
- range 会复制对象
返回两个值:索引和数据
,若你后面的代码用不到索引,需要使用 _ 表示 。
name :="长得帅"
//索引 i , 字符ch name字符串
for i,ch := range name{
fmt.Println(i,ch)
}
占位符 “_”
name :="长得帅"
//索引 i , 字符ch name字符串
for _,ch := range name{
fmt.Println(ch)
}
- 建议用引用类型,其
底层
数据不会被复制
package main
func main() {
s := []int{1, 2, 3, 4, 5}
for i, v := range s { // 复制 struct slice { pointer, len, cap }。
if i == 0 {
s = s[:3] // 对 slice 的修改,不会影响 range。
s[2] = 100 // 对底层数据的修改。
}
println(i, v)
}
}
//结果
0 1
1 2
2 100
3 4
4 5
4. 结束语句
-
continue
跳过本次循环 -
break
break lable
跳出循环,可以如下,跳出多层循环
END
for i :=1 ;i<=100 ;i++{
break END
}
- return用在
方法或函数
中,表示终止所在的方法或函数(method与function)
return在main函数中,表示终止main函数,终止程序
for i := 0; i <= 10; i++ {
if i == 5 {
return //直接退出main函数了
}
fmt.Printf("本次循环次数:%d\n", i)
}
//永远走不带这里了,第五次for循环时候,直接return了
fmt.Println("循环结束,走到了我")
5. 跳转goto
goto
标签:大写的英文字母,放在最左面
可以一次性跳出多层循环
goto 标签A
标签A:
用跳转实现for循环
goto语句与标签之间不能有变量声明,否则编译错误
.\main.go:7:7: goto flag jumps over declaration of say at .\main.go:8:6
6. defer 延迟语句
1. 延迟调用
在后面跟一个函数的调用,就能实现将这个 xxx 函数的调用延迟
到当前函数执行完后再执行。
defer xxx()
import "fmt"
func myfunc() {
fmt.Println("B")
}
func main() {
defer myfunc()
fmt.Println("A")
}
//输出如下
A
B
import "fmt"
func main() {
defer fmt.Println("B")
fmt.Println("A")
}
2. 即时求值的变量快照
使用 defer 只是延时调用函数,此时传递给函数里的变量,不应该受到后续程序的影响
。
比如这边的例子
import "fmt"
func main() {
name := "go"
defer fmt.Println(name) // 输出: go
name = "python"
fmt.Println(name) // 输出: python
}
// 输出
python
go
可见给 name 重新赋值为 python,后续调用 defer 的时候,仍然使用未重新赋值
的变量值,就好在 defer 这里,给所有的这是做了一个快照一样。
如果 defer 后面跟的是匿名函数
,情况会有所不同, defer 会取到最后的变量值
package main
import "fmt"
func main() {
name := "go"
defer func(){
fmt.Println(name) // 输出: python
}()
name = "python"
fmt.Println(name) // 输出: python
}
3. 多个defer 反序调用
当我们在一个函数里使用了 多个defer,那么这些defer 的执行函数是如何的呢?
import "fmt"
func main() {
name := "go"
defer fmt.Println(name) // 输出: go
name = "python"
defer fmt.Println(name) // 输出: python
name = "java"
fmt.Println(name) //输出:java
}
//输出
java
python
go
输出如下,可见 多个defer 是反序调用
的,有点类似栈
一样,后进先出。
4. defer 与 return 先后顺序
defer 和 return
到底是哪个先调用?
import "fmt"
var name string = "go"
func myfunc() string {
defer func() {
name = "python"
}()
fmt.Printf("myfunc 函数里的name:%s\n", name)
return name
}
func main() {
myname := myfunc()
fmt.Printf("main 函数里的name: %s\n", name)
fmt.Println("main 函数里的myname: ", myname)
}
//输出
myfunc 函数里的name:go
main 函数里的name: python
main 函数里的myname: go
第一行name 此时还是全局变量,值还是go
第二行也不难理解,在 defer 里改变了这个全局变量,此时name的值已经变成了 python
第三行, defer 是return 后才调用的
。所以在执行 defer 前,myname 已经被赋值成 go 了。
5. 为什么要有 defer?
把这个放在 defer 执行的函数放在 return 那里执行不就好了。
但是当一个函数里有多个 return
时,你得多调用好多次这个函数,代码就臃肿起来了。
没有 defer
func f() {
r := getResource() //0,获取资源
......
if ... {
r.release() //1,释放资源
return
}
......
if ... {
r.release() //2,释放资源
return
}
......
if ... {
r.release() //3,释放资源
return
}
......
r.release() //4,释放资源
return
}
用了 defer 后,代码就显得简单直接,不管你在何处 return,都会执行 defer 后的函数
。
func f() {
r := getResource() //0,获取资源
defer r.release() //1,释放资源
......
if ... {
...
return
}
......
if ... {
...
return
}
......
if ... {
...
return
}
......
return
}
7. 异常机制:panic 和 recover
-
panic:
抛
出异常,使程序崩溃 -
recover:
捕获
异常,恢复程序或做收尾工作
panic、recover 参数类型为interface{}
,因此可抛出任何类型对象。
revocer 调用后,抛出的 panic 将会在此处终结
,不会再外抛,但是 recover,并不能任意使用,它有强制要求,必须得在 defer 下
才能发挥用途。
-
利用recover处理panic指令,defer 必须放在 panic 之前定义,另外 recover 只有在 defer 调用的函数中才有效。否则当panic时,recover无法捕获到panic,无法防止panic扩散。
-
recover 处理异常后,逻辑并不会恢复到 panic 那个点去,函数跑到 defer 之后的那个点。
-
多个 defer 会形成 defer 栈,后定义的 defer 语句会被最先调用。
1. 触发panic
手动触发宕机,是非常简单的一件事,只需要调用 panic
这个内置函数
package main
func main() {
panic("crash")
}
运行后,直接报错宕机
$ go run main.go
go run main.go
panic: crash
goroutine 1 [running]:
main.main()
E:/Go-Code/main.go:4 +0x40
exit status 2
2. 捕获 panic用recover
发生了异常,有时候就得捕获
这就不得不引出另外一个内建函数 – recover
,它可以让程序在发生宕机后起生回生
。
但是 recover 的使用,有一个条件,就是它必须在 defer 函数中才能生效
,其他作用域下,它是不工作的。
import "fmt"
func set_data(x int) {
defer func() {
// recover() 可以将捕获到的panic信息打印
if err := recover(); err != nil {
fmt.Println(err)
}
}()
// 故意制造数组越界,触发 panic
var arr [10]int
arr[x] = 88
}
func main() {
set_data(20)
// 如果能执行到这句,说明panic被捕获了
// 后续的程序能继续运行
fmt.Println("everything is ok")
}
运行后
$ go run main.go
runtime error: index out of range [20] with length 10
everything is ok
通常来说,不应该对进入 panic 宕机的程序做任何处理,但有时,需要我们可以从宕机中恢复,至少我们可以在程序崩溃前,做一些操作,
当 web 服务器遇到不可预料的严重问题时,在崩溃前应该将所有的连接关闭,如果不做任何处理,会使得客户端一直处于等待状态,如果 web 服务器还在开发阶段,服务器甚至可以将异常信息反馈到客户端,帮助调试。
3. 无法跨协程
即使 panic 会导致整个程序退出,但在退出前,若有 defer 延迟函数,还是得执行完 defer
。
但是这个 defer 在多个协程之间是没有效果
,在子协程
里触发 panic
,只能触发自己协程内的 defer,而不
能调用 main 协程里的 defer 函数的。
import (
"fmt"
"time"
)
func main() {
// 这个 defer 并不会执行
defer fmt.Println("in main")
go func() {
defer println("in goroutine")
panic("")
}()
time.Sleep(2 * time.Second)
}
//输出如下
in goroutine
panic:
goroutine 6 [running]:
main.main.func1()
E:/Go-Code/main.go:12 +0x7b
created by main.main
E:/Go-Code/main.go:10 +0xbc
exit status 2
向已关闭的通道发送数据会引发panic
package main
import (
"fmt"
)
func main() {
defer func() {
if err := recover(); err != nil {
fmt.Println(err)
}
}()
var ch chan int = make(chan int, 10)
close(ch)
ch <- 1
}
//结果
send on closed channel
练习
将一个数从个位,十位,百位输出
思路
百位: n/100%10
十位: n/10%10
个位: n/1%10
代码
package main
import "fmt"
func main() {
var n,s int
fmt.Scan(&n)
if n>=999 {
s =4
} else if n>99{
s =3
}else if n>9{
s=2
}else {
s=1
}
for i:=1;i<=s;i++{
// l为10 *i
l :=1
for j:=2;j<=i;j++{
l *=10
}
fmt.Println(n/l%10)
}
}
多次登陆
package main
import "fmt"
func main() {
var name,pwd string
//限制:只能重复登录三次,
var logincache=3
for i :=1;i<=3;i++{
fmt.Println("请输入用户名")
fmt.Scanln(&name)
fmt.Println("请输入密码")
fmt.Scanln(&pwd)
if name == "qcq" && pwd == "qcq123" {
fmt.Println("欢迎来着王者荣耀!")
break
}else {
logincache--
fmt.Printf("输入错误,你还有%d次机会\n",logincache)
}
}
}
输出一个正方行
计算100以内所有奇数的和
func main() {
i :=1
num :=0
for i<=100{
num +=i
i+=2
}
fmt.Println(num)
}
给学生成绩评级
func main() {
var n int
fmt.Scanln(&n)
switch{
case n>=90:
fmt.Println("A")
case n>=80:
fmt.Println("B")
case n>60:
fmt.Println("C")
default:
fmt.Println("你太笨了")
}
}
打印成发口诀表
func main() {
for i :=1;i<=9;i++{
for j:=1;j<=i;j++{
fmt.Printf("%d*%d=%d\t",j,i,i*j)
}
fmt.Println()
}
}
猜数字,生成一个数,让用户猜五次
并且每次都给用户提示,大小,以及还有几次机会
可重新
挑战