后端小白的Go之旅2|青训营笔记

37 阅读7分钟

这是我参与「第五届青训营」笔记创作活动的第18天,本篇主要记录的内容如下:

  • 字符串操作
  • fmt打印方法
  • 对json数据的处理
  • 时间处理
  • 数字与字符串相互转换库strconv的方法
  • 进程信息处理
  • 小实践:猜字谜游戏

字符串操作

Go中内置了非常丰富的字符串操作方法,熟练使用这些方法能够提高字符串的处理效率,这些方法包含在内置strings库中。

初始化字符串 a := "hello"

  1. Contains 判断字符串中是否含有指定字符(串):
fmt.Println(strings.Contains(a, "ll")) //true
  1. Count 统计字符串中指定的字符(串)的个数:
fmt.Println(strings.Count(a, "l")) //2
fmt.Println(strings.Count(a, "ll")) //1
  1. HasPrefix 判断字符串是否以指定字符开头:
fmt.Println(strings.HasPrefix(a, "he")) //true
  1. HasSuffix 判断字符串是否以指定字符结尾:
fmt.Println(strings.HasSuffix(a, "llo")) //true
  1. Index 查找指定字符串的位置:
fmt.Println(strings.Index(a, "ll")) //2
  1. Joint 以指定连接符连接两个字符串:
fmt.Println(strings.Join([]string{"he", "llo"}, "-")) //he-llo
  1. Repeat 重复字符串:
fmt.Println(strings.Repeat(a, 2)) //hellohello
  1. Replace 替换字符串中的字符(串):
fmt.Println(strings.Replace(a, "e", "E", -1)) //hEllo 
//-1所在位置表示对字符串首n个匹配的字符(串)作用
fmt.Println(strings.Replace(a, "l", "L", 1)) //heLlo 
fmt.Println(strings.Replace(a, "l", "L", 2)) //heLLo 
  1. Split 以指定字符(存在于字符串中)为分隔符分割字符串,返回值为字符串数组
fmt.Println(strings.Split("a-b-c", "-"))  // [a b c]
  1. ToLower 大写转小写:
fmt.Println(strings.ToLower(a))   // hello
  1. ToUpper 小写转大写:
fmt.Println(strings.ToUpper(a))  // HELLO
  1. len() 返回字符串长度

fmt格式化打印方法

Go语言中打印变量时可以不指定变量类型,使用“%v”可以打印任意类型的变量

s := "hello"
n := 123
p := point{1, 2}
fmt.Println(s, n) // hello 123
fmt.Println(p)    // {1 2}
// 可用%v打印任意类型的变量
fmt.Printf("s=%v\n", s)  // s=hello
fmt.Printf("n=%v\n", n)  // n=123
fmt.Printf("p=%v\n", p)  // p={1 2}
fmt.Printf("p=%+v\n", p) // p={x:1 y:2} %+v得到详细结构
fmt.Printf("p=%#v\n", p) // p=main.point{x:1, y:2} %#v得到更加详细的结构

f := 3.141592653
fmt.Println(f)          // 3.141592653
fmt.Printf("%.2f\n", f) // 3.14

对json数据的处理

Go语言里的json操作很简单,对于已有的结构体,我们只需要保证每个字段的首字母为大写(即golang里的公开字段),然后此结构体就可以使json.Marshal() 进行序列化,然后其会变成一个byte数组(该类型数组通过使用string(数组名)打印出字符串,此字符串就是序列化之后的字符串),序列化后的字符串可以使用json.Unmarshal()反序列化到一个空的变量中。序列化之后的字符串的输出风格是每个首字母为大写,想要小写需要在该字段后加上一个tag:json:小写字段名, 综上操作的具体代码如下:

package main

import (
   "encoding/json"
   "fmt"
)

// 对于已有结构体,要保证每个字段的第一个字母是大写(即golang里的公开字段)即可
type userInfo struct {
   Name  string
   Age   int `json:"age"`
   Hobby []string
}

func main() {
   a := userInfo{Name: "wang", Age: 18, Hobby: []string{"Golang", "TypeScript"}}
   buf, err := json.Marshal(a) //用json.Marshal进行序列化
   if err != nil {
      panic(err)
   }
   fmt.Println(buf)         // [123 34 78 97...] 要用string()进行强制类型转化,不然会打印出16进制的编码
   fmt.Println(string(buf)) // {"Name":"wang","age":18,"Hobby":["Golang","TypeScript"]}

   buf, err = json.MarshalIndent(a, "", "\t")
   if err != nil {
      panic(err)
   }
   fmt.Println(string(buf))

   var b userInfo
   err = json.Unmarshal(buf, &b)
   if err != nil {
      panic(err)
   }
   fmt.Printf("%#v\n", b) // main.userInfo{Name:"wang", Age:18, Hobby:[]string{"Golang", "TypeScript"}}
}

时间处理

golang里最常用的时间处理是time.Now()快速获取当前时间。

time.Date()构造带时区的时间,然后可以用t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute()方式获取时间点信息。

.Sub()做时间减法获取时间段信息

time.Parse(layout string, value string),以指定模板解析成时间

time.Now().Unix()获取一个时间戳

综上操作的具体代码如下:

now := time.Now() //golang里最常用的时间处理
fmt.Println(now) // 2022-03-27 18:04:59.433297 +0800 CST m=+0.000087933
t := time.Date(2022, 3, 27, 1, 25, 36, 0, time.UTC)
t2 := time.Date(2022, 3, 27, 2, 30, 36, 0, time.UTC)
fmt.Println(t)                                                  // 2022-03-27 01:25:36 +0000 UTC
fmt.Println(t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute()) // 2022 March 27 1 25
fmt.Println(t.Format("2006-01-02 15:04:05"))                    // 2022-03-27 01:25:36
diff := t2.Sub(t)
fmt.Println(diff)                           // 1h5m0s
fmt.Println(diff.Minutes(), diff.Seconds()) // 65 3900
t3, err := time.Parse("2006-01-02 15:04:05", "2022-03-27 01:25:36")
if err != nil {
   panic(err)
}
fmt.Println(t3 == t)    // true
fmt.Println(now.Unix()) // 1648738080

数字与字符串相互转换库strconv的方法

golang里关于数字和字符串的相互转化的方法都在strconv包下 1.ParseFloat, ParseInt 解析一个字符串

f, _ := strconv.ParseFloat("1.234", 64)
fmt.Println(f) // 1.234

n, _ := strconv.ParseInt("111", 10, 64) //(字符串,进制,返回的精度)
fmt.Println(n)  //111   

n, _ = strconv.ParseInt("0x1000", 0, 64)// 0代表自动推测进制
fmt.Println(n) // 4096
  1. Atoi 快速把一个字符串转换为十进制
n2, _ := strconv.Atoi("123") //Atoi快速把十进制字符串转换为数字
fmt.Println(n2)              // 123
   //Itoa快速把十进制数字转换为字符串
n2, err := strconv.Atoi("AAA")
fmt.Println(n2, err) // 0 strconv.Atoi: parsing "AAA": invalid syntax

获取进程相关信息

使用以下方法前需要引入os库 1.Args 获取进程执行过程中的命令行参数 使用命令go run example/20-env/main.go a b c d直接执行一个go的源文件

fmt.Println(os.Args) // [/var/folders/8p/n34xxfnx38dg8bv_x8l62t_m0000gn/T/go-build3406981276/b001/exe/main a b c d]

输出的os.Args长度是5,第一个成员是二进制自身的路径加名字,这里是一个临时目录,后面才是a b c d 2.Getenv, Setenv 获取、写入环境变量

fmt.Println(os.Getenv("PATH")) // /usr/local/go/bin...
fmt.Println(os.Setenv("AA", "BB"))
  1. exec.Command 快速启动子进程,并且获取其输入输出
buf, err := exec.Command("grep", "127.0.0.1", "/etc/hosts").CombinedOutput()
if err != nil {
   panic(err)
}
fmt.Println(string(buf)) // 127.0.0.1       localhost

猜数游戏

游戏设计分为以下几步:

  • 生成随机数
  • 读取用户输入
  • 实现判断逻辑
  • 实现游戏循环

生成随机数

生成随机数需要先引入"math/rand"包,然后定义随机数的生成范围,只需定义一个最大数值即可。

package main

import (
   "fmt"
   "math/rand"
)

func main() {
   maxNum := 100
   secretNumber := rand.Intn(maxNum)
   fmt.Println("The secret number is ", secretNumber)
}

问题来了,当重复打印几次随机数时,我们发现每次生成的随机数都是一样的,这是因为我们在生成随机数时没有设置随机种子,而如python一样,如果设置的种子也是一个固定值,那么无论打印多少次也会产生一样的随机数,那么如何设置一个变化的种子呢?答案是使用时间戳。时间戳(timestamp),指格林威治时间1970年01月01日00时00分00秒(北京时间1970年01月01日08时00分00秒)起至现在的总秒数,通常是一个字符序列,唯一地标识某一刻的时间。因此,使用了时间戳后,因为每次打印都是在不同的时间启动的程序,那么每个种子都是唯一的。 更新后的程序如下:

package main

import (
   "fmt"
   "math/rand"
)

func main() {
   maxNum := 100
   rand.Seed(time.Now().UnixNano())//使用时间戳初始化种子
   secretNumber := rand.Intn(maxNum)
   fmt.Println("The secret number is ", secretNumber)
}

再次运行可以发现每次打印都输出不一样的结果

读取用户输入

该功能可以通过bufio或scanf的方法实现,其中scanf的方法的实现较为简洁。

bufio实现方式: 每个程序执行的时候都会打开几个文件,比如stdin stdout stderr等,stdin文件可以通过os.Stdin获取。直接操作这个文件不方便,我们就用bufio.NewReader把一个文件转换成reader变量。reader变量会有很多操作对一个流进行操作的操作,我们可以使用它的ReadString方法来读取一行。如果失败,我们会打印错误并退出。 ReadString 返回的结果包含尾随换行符,我们将其剥离并转换为数字。如果转换失败,我们也会打印错误并退出。具体实现如下:

fmt.Println("Please input your guess")
reader := bufio.NewReader(os.Stdin)
input, err := reader.ReadString('\n')
if err != nil {
   fmt.Println("An error occured while reading input. Please try again", err)
   return
}
input = strings.Trim(input, "\r\n")

guess, err := strconv.Atoi(input)
if err != nil {
   fmt.Println("Invalid input. Please enter an integer value")
   return
}
fmt.Println("You guess is", guess)

Scanf实现方式

var guess int
fmt.Scanf("%d", &guess)
fmt.Println("You guess is", guess)

实现逻辑判断

将用户输入的值与答案进行比较,对各个情况作出不同的提示。

if guess > secretNumber {
   fmt.Println("Your guess is bigger than the secret number. Please try again")
} else if guess < secretNumber {
   fmt.Println("Your guess is smaller than the secret number. Please try again")
} else {
   fmt.Println("Correct, you Legend!")
}

实现游戏循环

此步只需将读取用户输入和实现逻辑判断放入一个while循环里,并在猜对条件下加入break语句即可实现:

for {
   fmt.Scanf("%d", &guess)
   //input, err := reader.ReadString('\n')
   //if err != nil {
   // fmt.Println("An error occured while reading input. Please try again", err)
   // continue
   //}
   //input = strings.Trim(input, "\r\n")
   //
   //guess, err := strconv.Atoi(input)
   //if err != nil {
   // fmt.Println("Invalid input. Please enter an integer value")
   // continue
   //}
   fmt.Println("You guess is", guess)
   if guess > secretNumber {
      fmt.Println("Your guess is bigger than the secret number. Please try again")
   } else if guess < secretNumber {
      fmt.Println("Your guess is smaller than the secret number. Please try again")
   } else {
      fmt.Println("Correct, you Legend!")
      break
   }