“维权骑士”(http://rightknights.com)的版权保护计划,知乎专栏“网路行者”下的所有文章均为我本人(知乎ID:弈心)原创,未经允许不得转载。

如果你喜欢我的文章,请关注我的知乎专栏“网路行者”https://zhuanlan.zhihu.com/c_126268929, 里面有更多像本文一样深度讲解计算机网络技术的优质文章。


For循环基础

和Python相比,for循环在Golang中的使用有较大的区别。

首先,Go语言中标准的for循环是一种循环控制结构,其语法由initialization(初始化)、condition(条件)、post(结果)三部分组成,它们之间用分号;隔开,如下所示。

for initializatoin; condition; post {
     //statements...
}
  • initialization通常为赋值表达式,作用是给控制变量赋初值
  • condition通常为关系表达式或逻辑表达式,用来设定循环控制条件
  • post通常为赋值表达式,用来对控制变量增量或减量

下面举一个例子说明:

遍历整数1到10,将它们一一打印出来

package main

import "fmt"

func main() {
    for i := 1; i < 11; i++ {
        fmt.Println(i)
    }
}
  • 这里我们首先声明一个初始控制变量i,将整数1赋值给它(i := 1),这步即为initialization。
  • 然后通过关系表达式i < 11用来设定循环控制条件,即condition。如果循环控制条件为true,则执行循环体内的的语句,即这里的fmt.Println(i),系统会返回此时i的值,即1。
  • 然后执行post中的语句,即这里的i++,其意义为对变量i加1。
  • 因为循环控制条件为true,系统第二次执行for循环,此时初始空值变量i的值已由之前的1通过i++变成了2,2依然小于11,因此继续执行循环体内的语句,即fmt.Println(i),系统会返回此时i的值,即2。然后系统第三次执行for循环。。。
  • 如此循环下去后直到condition变为false时(即i >=11),循环终止。

运行代码效果如下图所示。

步长:通过for循环取整数10(包括10)以内的正奇数和正偶数

我们知道,上面遍历整数1到10的例子在Python中可以通过for循环配合range(1, 11)函数轻松实现,如下所示。

for i in range(1, 11):
    print (i)

我们同样也知道,Python中的range()函数自带步长参数,比如我们要取整数10以内的所有正奇数和正偶数的话可以这样实现。

#取整数10(包括10)以内的正奇数
for i in range(1, 11, 2):
    print (i)

#取整数10(包括10)以内的正偶数
for i in range(2, 11, 2):
    print (i)

但是Go语言中没有range()这个用来创造一组整数列表的函数,我们必须使用这种由initialization, condition, post三部分组成的循环控制结构来遍历一组整数,步长的话则可以通过调整post来实现,举例如下。

package main

import "fmt"

func main() {
    fmt.Println("取整数10(包括10)以内的正奇数")
    for i := 1; i < 11; i += 2 {
	fmt.Println(i)
    }
    fmt.Println("取整数10(包括10)以内的正偶数")
    for i := 2; i < 11; i += 2 {
	fmt.Println(i)
    }
}

这里我们通过将post从i++修改为i += 2后实现了步长的功能。

执行代码后效果如下图示所示。

For循环进阶

我们知道在Python中可以使用for语句来遍历一组可迭代的对象,可迭代的对象包括字符串、列表、集合、元组、字典等等,其语法也颇为简单,但是在Go中要针对字符串、数组或切片、map实现同样功能的话则稍微有点复杂,需要配合range语句实现,下面举例说明。

使用for + range来遍历一组切片

假设我们有一组切片,该切片的元素包含三个交换机的IP地址(字符串类型),如果我们想通过for循环将它们的值一一打出来的话,可以使用for语句配合range语句实现,代码如下。

package main

import "fmt"

func main() {
   switches := []string{"192.168.2.11", "192.168.2.12", "192.168.2.13"}
   for index, element := range switches {
       fmt.Println(index, element)
    }
}
  • 首先声明切片变量switches,该切片里包含三个用来表示交换机IP地址的元素,所有元素均为字符串。
  • 接下来使用for语句配合range语句来遍历切片变量switches,注意使用for + range来遍历任何可迭代的类型时默认都会返回两个值:索引号和切片元素,这里我分别使用index和element来表示它们(用户也可以自己定义,比如简写成i和v,表示index和value。或者写成a和b也可以,只是无意义罢了)。
  • 然后将索引号和具体的切片元素打印出来。

运行代码效果如下图所示。

注意红框部分即为索引号,这种将切片元素遍历出来并且附带索引号的形式是否让你想起了Python中的enumerate()函数?

如果不关心索引号,只想打印出切片里的元素的话,方法是将代表索引号的变量用下划线_表示即可,如下所示。

package main

import "fmt"

func main() {
   switches := []string{"192.168.2.11", "192.168.2.12", "192.168.2.13"}
   for _, element := range switches {
       fmt.Println(element)
    }
}
  • 这里我们将index替换成了下划线_,这样就可以省略索引号,只将切片元素打印出来。
  • 在Go语言中,下划线_又叫做空白标识符(blank identifier),它的作用是用来定义和使用未使用的变量,在Go中是一个很常见的知识点,后面的篇幅中还将多次提到它。

执行代码后效果如下图示所示。

如果只想打印出索引号的话,代码如下。

package main

import "fmt"

func main() {
   switches := []string{"192.168.2.11", "192.168.2.12", "192.168.2.13"}
   for index := range switches {
       fmt.Println(index)
    }
}
  • 如果只想打印出索引号的话,则直接将代表切片元素的变量拿掉即可,无需使用空白标识符替代。

执行代码后效果如下图示所示。

使用for + range来遍历字符串

我们同样可以使用for+range来遍历字符串,不过这里要注意一点:因为在遍历字符串将其内容一一打印出来的时候,我们实际打印的是字符(Go语言中字符和字符串是有区别的),在前面《网络工程师的Golang之路 -- Go数据类型(字符串)》中我们讲到了字符实际的类型为Rune,也就是int32,我们必须使用字符串格式化里的%c配合fmt.Printf()才能正确将字符串中的每一个字符打印出来,否则看到的将是该字符所对应的Rune的值(也就是int32),举例如下。

package main

import "fmt"

func main() {
   vendor := "Cisco"
   for i, v := range vendor {
       fmt.Println(i, v)
    }
}

这里我们尝试遍历字符串“Cisco”,结果在尝试一一打印字符"C","i","s","c","o"时因为没使用字符串格式化,所以返回的值为每个字符对应的Rune,也就是int32对应的整数。

正确的做法是使用fmt.Printf()来配合字符串格式化中的%c (%c代表字符)来将字符"C","i","s","c","o"一一打印出来。如下所示。

package main

import "fmt"

func main() {
   vendor := "Cisco"
   for i, v := range vendor {
       fmt.Printf("%d %c\n", i, v)
    }
}

而只打印索引号或者只打印字符的部分则留给读者自行尝试。

使用for + range来遍历map

使用for + range遍历map的方法同遍历切片和字符串大致一样,区别是遍历map时不再返回索引号,而是直接返回map中键值对的键(key)和值 (value),如下所示。

package main

import "fmt"

func main() {
    switch1 := map[string]string{
        "SN":      "12345abcde",
        "CPU":     "25.1",
        "version": "11.1",
        "port":    "48",
    }
    for key, value := range switch1 {
        fmt.Println(key, value)
    }
}
  • 这里我们用map[string]string{}创建了一个键和值均为字符串的map,总共有4组键值对,分别用来保存一台交换机的序列号、CPU用量、系统版本以及物理端口数量,然后将其赋值给变量switch1。
  • 随后我们使用for + range来遍历该map,其中key和value变量分别代表map中的键和值,也可以将这两个变量简写为k和v。

执行代码后效果如下图示所示。

如果只想打印出map中的键,代码如下。

package main

import "fmt"

func main() {
   switch1 := map[string]string{
       "SN":      "12345abcde",
       "CPU":     "25.1",
       "version": "11.1",
       "port":    "48",
   }
   for key := range switch1 {
        fmt.Println(key)
   }
}

如果只想打印出map中的值,代码如下。

package main

import "fmt"

func main() {
   switch1 := map[string]string{
       "SN":      "12345abcde",
       "CPU":     "25.1",
       "version": "11.1",
       "port":    "48",
   }
   for _, value := range switch1 {
        fmt.Println(key)
   }
}


While循环

和Python不同,Go语言中没有while语句,它的while循环实际是由for循环实现的,分为两种形式:

带条件(condition)的“while循环”

语法如下。

for condition {
    // statements
}

这里在for语句后只需要接一个条件即可实现类似于Python中的while循环,举例如下。

package main

import "fmt"

func main() {
    i := 0
    for i < 5 {
        fmt.Println(i)
        i++
    }
    fmt.Println(sum)
}
  • 这里我们声明一个变量i,将0赋值给它。
  • 通过带条件的“while循环”,如果i小于5,则将i的值打印出来,并将i的值加1。
  • 当i>=5时,“while循环”终止。

执行代码后效果如下图示所示。


不带条件的“while循环”

不带条件的“while循环”也就是我们在Python中理解的while True:,即无限循环,其语法如下。

for  {
    //statements
}

举例如下。

package main

import (
    "fmt"
    "time"
)

func main() {
    for {
        fmt.Println("这条信息将每隔一秒被打印出来一次,永久运行,直到用户手动终止")
        time.Sleep(time.Second)
    }
}
  • 这里我们引入了另外一个Go语言中内置的库time,它的用法和Python中的用法差不多,都是用来将程序休眠。
  • 因为不带条件的“while循环”实际就是我们理解的Python中的while true(无限循环),而又因为我们加入了time.Sleep(time.Second),这里fmt.Println()中的内容将会每隔一秒被永久不停地打印出来,直到我们用Ctrl + C键强制将程序终止。

执行代码后效果如下图示所示。

这里在打印8次fmt.Println()中的内容后,我们通过Ctrl + C键强行终止了程序。

当然我们不可能每次都使用Ctrl + C来手动终止一个无限循环,同Python一样,我们也可以在Go中使用break关键词来终止“while循环”。代码及演示如下。

package main

import (
    "fmt"
)

func main() {
    for {
        fmt.Println("这条信息只会被打印一次,因为有break")
        break
    }
}