
本文深入解析go语言中常见的“invalid indirect”错误,该错误通常发生在尝试对非指针类型(如函数)进行间接引用操作时。我们将以一个fizzbuzz程序的错误示例为切入点,详细阐述正确的函数调用方式,并在此基础上,提供一个完全符合go语言惯用法的fizzbuzz解决方案,涵盖文件i/o、错误处理、字符串构建及核心逻辑实现,旨在提升代码的健壮性和可读性。
在Go语言中,* 符号作为一元运算符时,其主要作用是对指针进行解引用(或称“间接引用”),以获取指针所指向的值。当编译器报告 invalid indirect of type func (int) string 这样的错误时,意味着你尝试对一个函数类型(func (int) string)使用了 * 运算符。
错误示例代码片段:
// ...
func WriteString(w *bufio.Writer) {
// 错误行:尝试对函数Fizzbuzz进行解引用
if n, err := w.WriteString(*Fizzbuzz); err != nil {
log.Fatalf("failed writing string: %s", err)
} else {
log.Printf("Wrote string in %d bytes", n)
}
}
// ...这里的 Fizzbuzz 是一个函数,其类型为 func (int) string。函数本身并不是一个指针,因此对其使用解引用操作符 * 是无效的,Go编译器会因此报错。
Go语言中函数的调用方式非常直接,只需在函数名后跟上括号 () 并传入所需参数即可。例如,如果 Fizzbuzz 函数接受一个 int 类型参数并返回一个 string,正确的调用方式应该是 Fizzbuzz(someIntArgument)。
立即学习“go语言免费学习笔记(深入)”;
正确的调用方式:
// Fizzbuzz 函数的签名是 func Fizzbuzz(N int) string
// 因此,调用它时需要传入一个int类型的参数
if n, err := w.WriteString(Fizzbuzz(someInteger)); err != nil {
// ...
}在上述错误示例中,WriteString 函数期望一个 string 类型参数。因此,我们应该调用 Fizzbuzz 函数并传入一个整数,然后将其返回的字符串传递给 w.WriteString。
FizzBuzz是一个经典的编程问题,要求根据输入的两个除数A和B,以及计数上限N,生成从1到N的序列。序列中,能被A整除的替换为'F',能被B整除的替换为'B',同时被A和B整除的替换为'FB',否则保留数字本身。
为了解决原始问题并使其符合Go语言的惯用法,我们需要进行以下改进:
原始的Fizzbuzz函数硬编码了3和5作为除数。根据问题描述,除数A和B应该从输入文件中读取。因此,我们需要一个更通用的函数来处理单个数字的FizzBuzz逻辑。
// getFizzBuzzOutput 根据给定的数字、除数A和除数B,返回对应的FizzBuzz字符串。
func getFizzBuzzOutput(num, divisorA, divisorB int) string {
divisibleByA := num%divisorA == 0
divisibleByB := num%divisorB == 0
switch {
case divisibleByA && divisibleByB:
return "FB"
case divisibleByA:
return "F"
case divisibleByB:
return "B"
default:
return fmt.Sprintf("%d", num)
}
}Go语言中,处理文件输入通常使用bufio.Scanner来逐行读取,这比bufio.Reader.ReadLine更简洁和健壮。
import (
"bufio"
"fmt"
"log"
"os"
"strconv"
"strings"
)
// processFile 读取指定路径的文件,处理每一行数据并生成FizzBuzz输出。
func processFile(filePath string) error {
file, err := os.Open(filePath)
if err != nil {
return fmt.Errorf("无法打开文件 %s: %w", filePath, err)
}
defer file.Close() // 确保文件在函数结束时关闭
scanner := bufio.NewScanner(file)
writer := bufio.NewWriter(os.Stdout) // 将结果写入标准输出
defer writer.Flush() // 确保所有缓冲数据被写入
for scanner.Scan() {
line := scanner.Text()
if line == "" { // 跳过空行
continue
}
parts := strings.Fields(line) // 按空格分割字符串
if len(parts) != 3 {
log.Printf("警告: 无效的输入行 '%s',期望3个数字,跳过。", line)
continue
}
// 解析A, B, N
divisorA, errA := strconv.Atoi(parts[0])
divisorB, errB := strconv.Atoi(parts[1])
countToN, errN := strconv.Atoi(parts[2])
if errA != nil || errB != nil || errN != nil {
log.Printf("警告: 无法解析输入行 '%s' 中的数字,跳过。错误: %v, %v, %v", line, errA, errB, errN)
continue
}
// 生成FizzBuzz序列并写入
generateAndWriteLine(writer, divisorA, divisorB, countToN)
}
if err := scanner.Err(); err != nil {
return fmt.Errorf("读取文件时发生错误: %w", err)
}
return nil
}为了高效地构建输出字符串,Go语言推荐使用strings.Builder,它能有效避免大量的字符串拼接操作带来的性能开销。
import (
// ... 其他导入
"strings"
)
// generateAndWriteLine 根据给定的参数生成FizzBuzz序列,并将其写入到bufio.Writer。
func generateAndWriteLine(w *bufio.Writer, divisorA, divisorB, countToN int) {
var sb strings.Builder // 使用 strings.Builder 高效构建字符串
for i := 1; i <= countToN; i++ {
sb.WriteString(getFizzBuzzOutput(i, divisorA, divisorB))
if i < countToN {
sb.WriteString(" ") // 除了最后一个元素,都添加空格
}
}
sb.WriteString("\n") // 每行结束后添加换行符
if _, err := w.WriteString(sb.String()); err != nil {
log.Fatalf("写入输出时发生错误: %v", err) // 写入错误通常是致命的
}
}main函数负责程序的入口点,处理命令行参数,并调用核心逻辑。
// main.go
package main
import (
"fmt"
"log"
"os"
)
func main() {
if len(os.Args) < 2 {
fmt.Println("用法: go run main.go <输入文件路径>")
os.Exit(1)
}
inputFilePath := os.Args[1]
if err := processFile(inputFilePath); err != nil {
log.Fatalf("处理文件失败: %v", err)
}
}将上述所有部分整合,形成一个完整且符合Go语言惯用法的FizzBuzz解决方案。
package main
import (
"bufio"
"fmt"
"log"
"os"
"strconv"
"strings"
)
// getFizzBuzzOutput 根据给定的数字、除数A和除数B,返回对应的FizzBuzz字符串。
func getFizzBuzzOutput(num, divisorA, divisorB int) string {
divisibleByA := num%divisorA == 0
divisibleByB := num%divisorB == 0
switch {
case divisibleByA && divisibleByB:
return "FB"
case divisibleByA:
return "F"
case divisibleByB:
return "B"
default:
return fmt.Sprintf("%d", num)
}
}
// generateAndWriteLine 根据给定的参数生成FizzBuzz序列,并将其写入到bufio.Writer。
func generateAndWriteLine(w *bufio.Writer, divisorA, divisorB, countToN int) {
var sb strings.Builder // 使用 strings.Builder 高效构建字符串
for i := 1; i <= countToN; i++ {
sb.WriteString(getFizzBuzzOutput(i, divisorA, divisorB))
if i < countToN {
sb.WriteString(" ") // 除了最后一个元素,都添加空格
}
}
sb.WriteString("\n") // 每行结束后添加换行符
if _, err := w.WriteString(sb.String()); err != nil {
log.Fatalf("写入输出时发生错误: %v", err) // 写入错误通常是致命的
}
}
// processFile 读取指定路径的文件,处理每一行数据并生成FizzBuzz输出。
func processFile(filePath string) error {
file, err := os.Open(filePath)
if err != nil {
return fmt.Errorf("无法打开文件 %s: %w", filePath, err)
}
defer file.Close() // 确保文件在函数结束时关闭
scanner := bufio.NewScanner(file)
writer := bufio.NewWriter(os.Stdout) // 将结果写入标准输出
defer writer.Flush() // 确保所有缓冲数据被写入
for scanner.Scan() {
line := scanner.Text()
if line == "" { // 跳过空行
continue
}
parts := strings.Fields(line) // 按空格分割字符串
if len(parts) != 3 {
log.Printf("警告: 无效的输入行 '%s',期望3个数字,跳过。", line)
continue
}
// 解析A, B, N
divisorA, errA := strconv.Atoi(parts[0])
divisorB, errB := strconv.Atoi(parts[1])
countToN, errN := strconv.Atoi(parts[2])
if errA != nil || errB != nil || errN != nil {
log.Printf("警告: 无法解析输入行 '%s' 中的数字,跳过。错误: %v, %v, %v", line, errA, errB, errN)
continue
}
// 生成FizzBuzz序列并写入
generateAndWriteLine(writer, divisorA, divisorB, countToN)
}
if err := scanner.Err(); err != nil {
return fmt.Errorf("读取文件时发生错误: %w", err)
}
return nil
}
func main() {
if len(os.Args) < 2 {
fmt.Println("用法: go run main.go <输入文件路径>")
os.Exit(1)
}
inputFilePath := os.Args[1]
if err := processFile(inputFilePath); err != nil {
log.Fatalf("处理文件失败: %v", err)
}
}
如何运行:
3 5 10 2 7 15
预期输出:
1 2 F 4 B F 7 8 F B 1 F 3 F 5 F B F 9 F 11 F 13 FB 15
以上就是Go语言中函数调用与无效间接引用错误解析及FizzBuzz实现优化的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号