GO语言工程实践课后作业—猜字谜| 豆包MarsCode AI刷题

81 阅读6分钟

使用 fmt.Scanf 简化 Go 代码分析

在 Go 语言中,标准库提供了多种方法来从用户获取输入,其中 fmt.Scanf 是一种非常有效且简洁的方式来获取格式化的输入。在这篇文章中,我们将详细分析如何通过使用 fmt.Scanf 简化一个数字猜测游戏的代码,并解释它相对于其他输入方法的优势。

1. 问题背景与原始代码分析

首先,我们从原始代码开始。原始代码实现了一个简单的猜数字游戏,用户需要猜测一个由程序随机生成的数字。用户通过标准输入输入他们的猜测,程序根据猜测的大小给出提示,直到猜中为止。

原始代码使用了 bufio.Reader 来读取输入,并通过 strconv.Atoi 将字符串转换为整数。虽然这种方法是有效的,但它存在以下缺点:

  • 冗余的错误处理:在读取输入时,需要额外处理输入的错误,例如将字符串转换为整数时可能发生的转换错误。
  • 复杂的代码结构:由于需要处理用户输入的字符串并手动转换,代码变得较为冗长和复杂。
  • 性能开销:每次用户输入时都要通过 strconv.Atoi 将字符串转换为整数,这在性能要求较高的应用中可能会成为瓶颈。

因此,通过使用 fmt.Scanf,我们能够简化代码,避免冗余的输入处理,提高程序的可读性和执行效率。

2. fmt.Scanf 的优点

在 Go 语言中,fmt 包提供了 ScanScanfScanln 等函数来从标准输入中读取数据。它们的主要特点是能够自动处理不同类型的数据输入,并且能够直接根据指定的格式将输入转换为对应的数据类型。与 bufio.Readerstrconv.Atoi 方法相比,fmt.Scanf 具有以下优点:

  • 简洁性fmt.Scanf 将输入读取和数据类型转换合并为一个步骤,避免了手动读取输入并转换数据类型的繁琐过程。通过在格式字符串中指定输入的类型,fmt.Scanf 自动进行类型转换。
  • 错误处理fmt.Scanf 内置了对错误的处理。如果用户输入不符合指定格式,它会自动返回错误,程序可以根据这个错误做出响应。这使得输入验证变得更加简单。
  • 灵活性fmt.Scanf 支持格式化字符串,能够读取多种类型的输入(如整数、浮点数、字符串等)。在需要读取不同类型的数据时,fmt.Scanf 的格式化功能非常有用。

3. 代码优化:使用 fmt.Scanf

3.1 简化输入处理

原始代码中的输入处理部分采用了 bufio.NewReader 读取输入字符串,并通过 strconv.Atoi 将字符串转换为整数。这个过程涉及两个步骤,且每次输入时都需要额外的错误检查。使用 fmt.Scanf,我们可以将这两个步骤合并为一个步骤,从而使代码更加简洁和直观。

例如,在原始代码中,用户输入的字符串首先被读取并转换为整数:

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)
    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
}

在这段代码中,用户输入的字符串首先通过 reader.ReadString 被读取,然后通过 strconv.Atoi 将其转换为整数。如果转换失败,则返回错误并提示用户重新输入。

而使用 fmt.Scanf 后,我们可以直接将输入读取并转换为整数,代码变得更加简洁:

var guess int
_, err := fmt.Scanf("%d", &guess)
if err != nil {
    fmt.Println("Invalid input. Please enter an integer.")
    fmt.Scanln() // Discard invalid input
    continue
}

在这个版本中,fmt.Scanf("%d", &guess) 会直接读取一个整数并存储在 guess 变量中。如果输入无效,fmt.Scanf 会返回错误,我们可以提示用户重新输入,并通过 fmt.Scanln() 清空无效输入。

3.2 代码结构的优化

通过将输入和类型转换的操作简化为单一的 fmt.Scanf 调用,我们的代码结构变得更加清晰。去除了多余的 bufio.Readerstrconv.Atoi,代码可读性得到了显著提升。而且,由于 fmt.Scanf 自动处理了错误情况,我们也不需要在每次输入时手动检查输入的有效性,减少了代码的复杂度。

3.3 日志记录功能

另外,我们还增加了日志记录功能。在每次用户进行猜测时,程序会将用户的猜测数字和猜测时间记录到文件 guess_log.txt 中。这可以帮助我们跟踪游戏过程中的每一次猜测,也为调试和分析提供了数据支持。

文件的打开和写入如下所示:

file, err := os.OpenFile("guess_log.txt", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0666)
if err != nil {
    fmt.Println("Error opening file:", err)
    return
}
defer file.Close()

// 记录猜测
file.WriteString(fmt.Sprintf("Guess: %d at %s\n", guess, time.Now().Format(time.RFC1123)))

通过这种方式,用户的每次输入都会被记录,并带有时间戳。日志文件可以帮助我们分析游戏的历史记录,也有助于日后的程序维护和性能优化。

3.4 改进后的代码

最终,我们的代码经过优化后变得更加简洁、直观,并且具备了错误处理和日志记录功能。完整的优化代码如下:

package main

import (
    "fmt"
    "math/rand"
    "os"
    "time"
)

func main() {
    maxNum := 100
    rand.Seed(time.Now().UnixNano())
    secretNumber := rand.Intn(maxNum)

    fmt.Println("Please input your guess (an integer):")

    // 打开文件记录路径
    file, err := os.OpenFile("guess_log.txt", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0666)
    if err != nil {
        fmt.Println("Error opening file:", err)
        return
    }
    defer file.Close()

    // 循环直到用户猜对为止
    for {
        var guess int
        _, err := fmt.Scanf("%d", &guess)
        if err != nil {
            fmt.Println("Invalid input. Please enter an integer.")
            fmt.Scanln() // Discard invalid input
            continue
        }

        // 输出猜测结果
        fmt.Printf("Your guess is %d\n", guess)

        // 记录猜测信息到文件
        file.WriteString(fmt.Sprintf("Guess: %d at %s\n", guess, time.Now().Format(time.RFC1123)))

        // 判断用户输入
        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
        }
    }
}

运行示例:

  1. 用户输入整数进行猜测。
  2. 每次输入都会被记录到文件,并且游戏会反馈输入结果。
  3. 用户最终猜中时,游戏结束并打印 "Correct, you Legend!"。

guess_log.txt输出内容

Guess: 15 at Sat, 30 Nov 2024 04:40:37 UTC
Guess: 30 at Sat, 30 Nov 2024 04:40:42 UTC
Guess: 60 at Sat, 30 Nov 2024 04:40:45 UTC
Guess: 80 at Sat, 30 Nov 2024 04:40:48 UTC
Guess: 100 at Sat, 30 Nov 2024 04:40:51 UTC
Guess: 90 at Sat, 30 Nov 2024 04:40:53 UTC
Guess: 85 at Sat, 30 Nov 2024 04:40:56 UTC

4. 总结与反思

使用 fmt.Scanf 的主要优势体现在简化了代码结构,避免了冗余的错误处理和类型转换操作。在实际开发中,简洁的代码不仅能够提升可读性,还能减少潜在的错误和维护成本。通过自动处理输入转换和错误检查,fmt.Scanf 是处理用户输入的理想选择,尤其适用于简单的输入需求。

然而,值得注意的是,fmt.Scanf 对于复杂的输入验证和转换可能不如手动处理灵活。在一些需要特殊输入处理或验证的场景中,可能仍然需要手动读取输入并进行更细致的错误检查。对于本案例来说,fmt.Scanf 完全满足了需求,简化了代码并提升了开发效率。