Golang基础语法与特性(二) | 青训营笔记

141 阅读6分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第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
}