这是我参与「第五届青训营 」伴学笔记创作活动的第2天
1.Go的基础语法(续)
(1). Go的运算符
Golang的运算符与其他高级语言相同,有算术运算符、赋值运算符、关系运算符、逻辑运算符、位运算符、其他运算符。分别是+-*/%<>=++--、!、||、&&、|、&、^、>> <<等。
值得注意的是,Golang不允许自增/自减运算符作为作为赋值语句的值。
i=1
tmp:= i++ //报错
(2). Go的条件语句
Golang中条件语句指定一个或多个条件,通过测试条件是否为 true 来决定是否执行指定语句,并在条件为 false 情况执行其他语句,对于Golang中的条件语句,大括号不可以省略。
if语句(嵌套):
var tmp int = 3
if tmp >=1 {
fmt.Printf("tmp大于等于1\n" )
if tmp >=5
fmt.Printf("tmp大于等于5\n")
}
if...else if...else 语句:
var tmp int = 3
if tmp >0 {
fmt.Printf("tmp大于0\n" )
}
else if tmp == 0{
fmt.Printf("tmp等于0\n" )
}
else {
fmt.Printf("tmp小于0\n" )
}
- Golang的Switch语句的case并不需要break来结束,这与C++、Java不同
- 如果需要控制执行的特定case,可以使用break来根据条件控制结束
- Switch语句中的default是默认执行,不论位置总是最后执行
switch语句:
var tmp int = 3
switch tmp {
case 1:
fmt.Println("tmp的值为1")
case 2:
fmt.Println("tmp的值为2")
case 3,4:
fmt.Println("tmp的值为3或4")
default:
fmt.Println("tmp的值不在范围之内")
}
//还可以写为:
switch {
case tmp==1:
fmt.Println("tmp的值为1")
case tmp==2:
fmt.Println("tmp的值为2")
case tmp==3,tmp==4:
fmt.Println("tmp的值为3或4")
default:
fmt.Println("tmp的值不在范围之内")
}
另外,Golang中的switch 语句还可以被用于 type-switch 来判断某个interface变量中实际存储的变量类型。以下程序的执行结果为"tmp类型为nil":
package main
import "fmt"
func main() {
var tmp interface{}
switch tp := tmp.(type) {
case nil:
fmt.Printf("tmp类型为nil")
case bool:
fmt.Printf("tmp类型为bool" )
default:
fmt.Printf("tmp类型未知")
}
}
这里还要说明一个Golang的关键字fallthrough,这个关键字是用来强制执行语句的,若在每一条case最后加上这个关键字,不论是否满足下一个case的条件,强制执行下一条case的内容。
case 3:
fmt.Println("输出3")
fallthrough
case 4:
fmt.Println("输出4") //强制执行
除了if和switch语句,Golang中还有select语句,其机制类似于switch,只能用于case为Channel通道操作,当某个通道完成后即可执行操作,如果有多个已经准备完毕,为了防止饥饿,随机选择一个可以执行的Channel;当没有准备完毕的通道时,首先执行default,若无defalut则会等待直到有一个可以执行的通道为止。
以下是使用协程来对定义好的通道操作并从中获取数据,这个过程是存在阻塞的,因此会按照顺序输出1和2:
package main
import (
"fmt"
"time"
)
func main() {
channel1 := make(chan string)
channel2 := make(chan string)
go func() {
time.Sleep(4 * time.Second)
channel1 <- "1"
}()
go func() {
time.Sleep(5 * time.Second)
channel2 <- "2"
}()
for i := 0; i < 2; i++ {
select {
case m1 := <- channel1:
fmt.Println("received", m1)
case m2 := <- channel2:
fmt.Println("received", m2)
}
}
}
以下是非阻塞式获取数据的方式:不断从通道中获取数据,当两个通道都没有可用数据时执行default的内容
go func() {
for {
channel1 <- "1"
}
}()
go func() {
for {
channel2 <- "2"
}
}()
for {
select {
case msg1 := <-channel1:
fmt.Println(msg1)
case msg2 := <-channel2:
fmt.Println(msg2)
default:
fmt.Println("Nothing can be proccessed")
}
}
(3). Go的循环语句
Golang和大多数高级程序设计语言类似,都有循环语句的语法。和C++、Java不同的是,Go中只有for循环而没有while循环,对于Golang中的循环语句,大括号不可以省略。其基本语法可以按照以下几种归纳:
最基本的for循环,可以和C++中的语法进行对比理解:
for i:=1; i<=10; i++ { } 等价于C++的 for(int i=1; i<=10; i++){ }
for i<=10 { } 等价于C++的 while(i<=10){ }
for ; i<=10; { } 等价于C++的 for(; i<=10; ){ }
for { }或 for true { } 等价于C++的 for(; ; ){ } 和 while(true) {}
另外,类似于Java的增强for循环和Python的range,对 slice、map、数组、字符串等进行迭代循环,格式如下:
for key, value := range map1 {
map2[key] = value
}
for key := range map1 {
if(key == 3) fmt.Println("map1中存在键为3的元素")
}
for _,value := range map1 {
if(value == 2) fmt.Println("map1中存在值为2的元素")
}
在Golang中自然也就允许for循环嵌套出现:
for i:=1;i<=n;i++ {
for j:=2;j<=i;j++ {
//语句
}
}
既然存在循环,那么循环控制语句也是必要的,Golang中也有break、goto、continue语句。
首先是break语句,在Go中,break语句的语法还支持使用Label标签进行跳出:
label1:
for i := 1; i <= 2; i++ {
fmt.Printf("i: %d\n", i)
for j := 2; j <= 4; j++ {
fmt.Printf("j: %d\n", j)
if(j == 3) {
break label1 //当j==3直接跳出所有循环,如果没有标签则还会执行外层循环
}
}
}
输出:
带label1:
i: 1
j: 2
j: 3
不带label1:
i: 1
j: 2
j: 3
i: 2
j: 2
j: 3
continue语句和break类似,不过并不会结束循环,而是继续,加上标签同样可以省去一些执行步骤。
label1:
for i := 1; i <= 2; i++ {
fmt.Printf("i: %d\n", i)
for j := 2; j <= 3; j++ {
fmt.Printf("j: %d\n", j)
continue label1 //直接跳出该for循环执行外层循环,如果没有标签则还会执行内层循环
fmt.Println("mark")//不带标签和普通循环有什么不同?括号体内continue之后的语句不执行
}
}
输出:
带label1:
i: 1
j: 2
i: 2
j: 2
不label1:
i: 1
j: 2
j: 3
i: 2
j: 2
j: 3
在软件工程中,goto语句会破坏程序的结构化程度,因此不推荐对它不合理的使用;goto也可以达到类似于continue的效果:
a := 1
label: for a < 5 {
if a == 3 {
a++ //不会输出3
goto label
}
fmt.Println(a)
a++
}
(4). Go的函数
Golang的函数由函数标识、函数名、函数参数、返回值(可省略,类似于void)和函数体组成。
值传递不会改变传入参数的值,引用传递类似于C++的址传递,会改变传入参数的值。
如果函数的实参包括引用类型,如指针,slice(切片)、map、function、channel等类型,实参可能会由于函数的间接引用被修改。
func name(par1 int, par2 float64, ...) (ret_type1, ret_type2, ...) {
//函数内容
}
//值传递交换函数
func swap(x, y string) {
var tmp string
tmp = x
x = y
y = tmp
}
//引用传递交换函数,调用为 swap(&x, &y)
func swap(x *string, y *string) {
var tmp string
tmp = *x
*x = *y
*y = tmp
}
函数声明:
func calNumber(value int) float64 //函数签名,可能该函数由其他语言实现
type another_name func(x int) int //使用类似于C++typedef的语法,为匿名函数的签名定义别名
借此,Golang还可以实现回调函数:
type another_name func(val int) int
func main() {
testCallBack(1, callBack)
}
func testCallBack(x int, f another_name) {
f(x) //传入的参数x作为参数传入another_name这个函数中
}
func callBack(x int) int {
fmt.Printf("x: %d\n", x)
return x
}
//以上等价于
type another_name func(val int) int
func main() {
testCallBack(1, func(x int) int {
fmt.Printf("x: %d\n", x)
return x
})
}
//或
type another_name func(val int) int
func main() {
callBack := func(x int) int {
fmt.Printf("x: %d\n", x)
return x
}
testCallBack(1, callBack)
}
函数还可以实现闭包,利用匿名函数,它是一个内联语句或表达式,可直接使用函数内的变量: 以下内容借鉴于菜鸟教程:
package main
import "fmt"
func add(x1, x2 int) func(int, int) (int, int, int) {
//add的返回值是(有两个int参数,返回值为三个int参数)的函数
i := 0
return func(x3, x4 int) (int, int, int) {
i += 1
return i, x1 + x2, x3 + x4
}
}
func main() {
add_func := add(1, 2) //add_func是一个函数, i初始值为0,x1=1,x2=2
fmt.Println(add_func(4, 5)) //i值为1, x1=1,x2=2, x3=4, x4=5
fmt.Println(add_func(1, 3)) //i值为2, x1=1,x2=2, x3=1, x4=3
fmt.Println(add_func(2, 2)) //i值为3, x1=1,x2=2, x3=2, x4=2
/**
1 3 9
2 3 4
3 3 4
*/
}
//省略部分参数的情况:
package main
import "fmt"
func getSequence() func() int {
i := 0
return func() int {
i += 1
return i
}
}
func main() {
nextNumber := getSequence()//i=0
nextNumber1 := getSequence()//i=0
fmt.Println(nextNumber())//1
fmt.Println(nextNumber())//2
fmt.Println(nextNumber())//3
fmt.Println(nextNumber1())//1
fmt.Println(nextNumber())//4
fmt.Println(nextNumber1())//2
}
Golang中并没有类,利用结构体可以实现面向对象语法,其中类的“方法”,可以按照如下方式实现:
type ClassA struct {
value float64
}
func (c ClassA) getCal() float64 { //方法
return c.value * c.value
}
func main() {
var class1 ClassA
class1.value = 3
fmt.Println(class1.getCal()) //9
}