Big Digits—Two-Dimensional Slices

bigdigits程序(bigdigits/bigdigits.go)从命令行读取一个数字,并且向命令行输出相同的"big"数字。

robin@Robin bigdigits
$ ./bigdigits 2015

 222     000     1   55555
2   2   0   0   11   5
   2   0     0   1   5
  2    0     0   1    555
 2     0     0   1       5
2       0   0    1   5   5
22222    000    111   555

bigdigits 程序需要导入4个package

import (
    "fmt"
    "log"
    "os"
    "path/filepath"
)

fmt package提供了格式化文本以及读取格式化后的文本函数。log package 提供了日志记录函数。 os package提供了独立于操作系统的变量和函数,包括os.Args([]string slice of strings)用于保存命令输入的参数。 path包中的filepath package 提供了操作文件名和路径的函数(可跨平台).

我们需要一个二维的数据(a slice of sliecs of strings). 以下是我们创建的二维字符串slice. 数字0说明了每个数字在命令行窗口对应行的输出.

var bigDigits = [][]string{
    {"  000  ",
     " 0   0 ",
     "0     0",
     "0     0",
     "0     0",
     " 0   0 ",
     "  000  "},
    {" 1 ", "11 ", " 1 ", " 1 ", " 1 ", " 1 ", "111"},
    {" 222 ", "2   2", "   2 ", "  2  ", " 2   ", "2    ", "22222"},
    {" 333 ", "3   3", "    3", "  33 ", "    3", "3   3", " 333 "},
    {"   4  ", "  44  ", " 4 4  ", "4  4  ", "444444", "   4  ","   4  "},
    {"55555", "5    ", "5    ", " 555 ", "    5", "5   5", " 555 "},
    {" 666 ", "6    ", "6    ", "6666 ", "6   6", "6   6", " 666 "},
    {"77777", "    7", "   7 ", "  7  ", " 7   ", "7    ", "7    "},
    {" 888 ", "8   8", "8   8", " 888 ", "8   8", "8   8", " 888 "},
    {" 9999", "9   9", "9   9", " 9999", "    9", "    9", "    9"},
}

代码

package main

import (
    "fmt"
    "log"
    "os"
    "path/filepath"
)

func main() {
    if len(os.Args) == 1 { ➊
        fmt.Printf("usage: %s <whole-number>\n", filepath.Base(os.Args[0]))
        //usage: bigdigits.exe <whole-number>
        os.Exit(1)
    }

    stringOfDigits := os.Args[1]
    for row := range bigDigits[0] { ➋
        line := ""
        for column := range stringOfDigits { ➌
            digit := stringOfDigits[column] - '0'
            if 0 <= digit && digit <= 9 {
                line += bigDigits[digit][row] + "  "
            } else {
                log.Fatal("invalid whole number")
            }
        }
        fmt.Println(line)
    }
}

var bigDigits = [][]string{
    {"  000  ",
     " 0   0 ",
     "0     0",
     "0     0",
     "0     0",
     " 0   0 ",
     "  000  "},
    {" 1 ", "11 ", " 1 ", " 1 ", " 1 ", " 1 ", "111"},
    {" 222 ", "2   2", "   2 ", "  2  ", " 2   ", "2    ", "22222"},
    {" 333 ", "3   3", "    3", "  33 ", "    3", "3   3", " 333 "},
    {"   4  ", "  44  ", " 4 4  ", "4  4  ", "444444", "   4  ","   4  "},
    {"55555", "5    ", "5    ", " 555 ", "    5", "5   5", " 555 "},
    {" 666 ", "6    ", "6    ", "6666 ", "6   6", "6   6", " 666 "},
    {"77777", "    7", "   7 ", "  7  ", " 7   ", "7    ", "7    "},
    {" 888 ", "8   8", "8   8", " 888 ", "8   8", "8   8", " 888 "},
    {" 9999", "9   9", "9   9", " 9999", "    9", "    9", "    9"},
}

程序首先检查是否有输入命令行参数(调用程序后面跟的参数), 如果没有,则log(os.Args)将为1(os.Args[0]保存的是程序的名字, 整个slice的长度至少为1)如第一条语句(➊)所示。在这个例子中,我们通过使用fmt.Printf()函数输出格式化的信息。 它接受 % 占位符,这跟C/C++的printf()函数相似。

path/filepath package提供了路操作函数——比如, filepath.Base()函数返回给定路径的文件基本名(i.e., 文件名). 在输出完使用信息后, 程序使用了os.Exit()函数,并且返回1给操作系统,终止此程序。 在类Unix的系统上,返回0表示成功,非0表示error。

filepath.Base()函数的使用,演示了Go非常好的一个特点:当一个package被导入后。不管它是在顶级或者逻辑上在另一个包中(e.g., path/filepath)。 我们都只需要使用最后的组件(e.g., filepath). 也可以为pacakge给出本地名字,以避免命名冲突。

如果命令行中至少有一个参数,则我们将第一个参数复制给stringOfDigits变量(字符串)。 为了将用户输入的数字转换为big数字,我们必须bigDigits slice 中的每一行, 这样才能产生输出中的行数。换言之,每一行的输出都代表所有数字的最上面部分,然后在输出第二行。 bigDigits中的数字拥有相同的行数,所以我们只需要读取第一个0的行数。

Go的for loop有多种语法,用于不同的目的。在这里,我们使用了 for .. range loops 它反回每一个元素的索引位置。也可以使用以下的语法。

for row := 0; row < len(bigDigits[0]); row++ {
    line := ""
    for column := 0; column < len(stringOfDigits); column++ {
        ...

这种语法跟C, C++ and JAVA类似,可for ... range 语法相比来说更简短方便。

每一行的遍历我们都是设置line为空字符。然后我们在遍历stringOfDigits字符串中的每一列(i.e., the characters)。 Go字符串保存的是UTF-8的字节。所以有可能一个字符由两个或者多个字节组成。 但在这里我们不用担心, 数字0,1,...9都是单个字节。跟7-bit的ASCII一样。

当我们在字符串中使用index索引指定位置时,它返回的是这个位置的字节(byte). (byte类型是uint8的同义词). 所以我们将字符串中检索到的字节,减去数字0的字节,得到的就是它代表的数字。在UTF-8(and ASCII)中, '0'字符的字符码(character) 48. 字符'1'的字符码是 49, 以此类推。 所以,如果我们的字符是 '3'(code point 51), 我们可以通过 '3' - '0'(i.e., 51 - 48),它的结果是一个整型(byte) 3。

如果digit(of type byte)在数字范围内,则我们将它添加到变量line后。

如果digit没有在有效的范围内,则调用log.Fatal()函数,输出一个错误信息到os.Stderr。错误信息包含date, time,和一个error. message. 如果没有明确的指明日志输出的目的地。 则调用os.Exit(1)终此程序。我们为什么没有在第一条语句中使用(➊). 是因为我们想要打印出程序的使用信息,而不需要使用到date和time.