Golang如何遍历接口中的对象列表并检测特定值

Golang如何遍历接口中的对象列表并检测特定值 你好, 我遇到了一些麻烦。我正在学习加州大学欧文分校在Coursera上开设的第二门专门针对Golang的课程,目前正在学习接口和方法。

我正在处理三种不同类型的动物“对象”,它们分别来自各自的结构体,并且都实现了一个接口。我认为我已经接近成功了,以下是我目前的代码:

package main

import (
	"bufio"
	"fmt"
	"log"
	"os"
	"strings"
)

/*
编写一个程序,允许用户创建一组动物并获取这些动物的信息。
每只动物都有一个名字,可以是牛、鸟或蛇中的一种。通过每个命令,用户既可以创建
三种类型之一的新动物,也可以请求查询他/她已经创建的动物的信息。
每只动物都有一个由用户定义的唯一名称。请注意,用户可以定义选定类型的动物,
但动物类型仅限于牛、鸟或蛇。下表包含了三种
动物类型及其相关数据。

动物	食物		移动方式		叫声
牛		草		行走			哞
鸟		蠕虫		飞翔			叽喳
蛇		老鼠		滑行			嘶嘶

你的程序应该向用户显示一个提示符“>”,表示用户可以输入请求。
你的程序应该每次接受用户的一个命令,打印出响应,并在新的一行打印出新的提示符。
你的程序应该在这个循环中无限继续。用户的每个命令必须是“newanimal”命令或“query”命令。

每个“newanimal”命令必须是一行包含三个字符串。第一个字符串是“newanimal”。
第二个字符串是一个任意字符串,它将作为新动物的名称。
第三个字符串是新动物的类型,可以是“cow”、“bird”或“snake”。
你的程序应该通过创建新动物并在屏幕上打印“Created it!”来处理每个newanimal命令。

每个“query”命令必须是一行包含3个字符串。第一个字符串是“query”。
第二个字符串是动物的名称。第三个字符串是请求的
关于动物的信息名称,可以是“eat”、“move”或“speak”。你的程序应该通过打印出请求的数据来处理每个查询命令。

定义一个名为Animal的接口类型,它描述动物的方法。具体来说,Animal接口应包含
方法Eat()、Move()和Speak(),这些方法不接收参数且不返回值。Eat()方法应打印动物的食物,
Move()方法应打印动物的移动方式,Speak()方法应打印动物的叫声。
定义三种类型Cow、Bird和Snake。对于这三种类型中的每一种,定义方法Eat()、Move()和Speak()
以便类型Cow、Bird和Snake都满足Animal接口。当用户创建动物时,创建相应
类型的对象。当用户发出查询命令时,你的程序应调用适当的方法。
*/

func main() {

	inputReader := bufio.NewReader(os.Stdin)

	for {
		fmt.Println("欢迎来到动物公园,您可以在这里创建和查询动物")
		fmt.Println("首先,您可以执行以下任一类型的查询:")
		fmt.Println("要为数据库创建动物,您需要使用以下语法:")
		fmt.Println("newanimal (动物名称) (动物类型)")
		fmt.Println("创建动物后,您可以通过以下方式查询动物:")
		fmt.Println("query (动物名称) (动物行为)")
		fmt.Print(">")

		userQuery, err := inputReader.ReadString('\n')
		if err != nil {
			log.Fatal(err)
		}

		userQuery = strings.Trim(userQuery, "\n")
		sliceOfQuery := strings.Split(userQuery, " ")
		if len(sliceOfQuery) > 3 || len(sliceOfQuery) < 3 {
			fmt.Println("无效查询!")
		}
		determinant := sliceOfQuery[0]
		sliceOfAnimals := []animal{cow{}, snake{}, bird{}}

		switch determinant {
		case "newanimal":
			if sliceOfQuery[2] == "cow" {
				sliceOfAnimals = append(sliceOfAnimals, cow{name: sliceOfQuery[1]})
				fmt.Println("创建成功!")
			} else if sliceOfQuery[2] == "snake" {
				sliceOfAnimals = append(sliceOfAnimals, snake{name: sliceOfQuery[1]})
				fmt.Println("创建成功!")
			} else if sliceOfQuery[2] == "bird" {
				sliceOfAnimals = append(sliceOfAnimals, bird{name: sliceOfQuery[1]})
				fmt.Println("创建成功!")
			} else {
				fmt.Println("无效查询!")
			}
		case "query":
			fmt.Println(sliceOfQuery[0])
			fmt.Println(sliceOfQuery[1])
			fmt.Println(sliceOfQuery[2])
			for _, animal := range sliceOfAnimals {
				if animal.(cow).name == sliceOfQuery[1] {
					if sliceOfQuery[2] == "move" {
						animal.move()
					} else if sliceOfQuery[2] == "eat" {
						animal.eat()
					} else if sliceOfQuery[2] == "speak" {
						animal.speak()
					}
				} else if animal.(bird).name == sliceOfQuery[1] {
					if sliceOfQuery[2] == "move" {
						animal.move()
					} else if sliceOfQuery[2] == "eat" {
						animal.eat()
					} else if sliceOfQuery[2] == "speak" {
						animal.speak()
					}

				} else if animal.(snake).name == sliceOfQuery[1] {
					if sliceOfQuery[2] == "move" {
						animal.move()
					} else if sliceOfQuery[2] == "eat" {
						animal.eat()
					} else if sliceOfQuery[2] == "speak" {
						animal.speak()
					}

				} else {
					fmt.Println("未找到该名称的动物!")
				}
			}

		default:
			fmt.Println("无效的用户查询!")

		}
	}

}

type animal interface {
	eat()
	move()
	speak()
}

type cow struct{ name string }
type snake struct{ name string }
type bird struct{ name string }

func (c cow) eat() {
	fmt.Printf("%s 吃草\n", c.name)
}

func (c cow) move() {
	fmt.Printf("%s 行走\n", c.name)
}

func (c cow) speak() {
	fmt.Printf("%s 哞哞叫\n", c.name)
}

func (s snake) eat() {
	fmt.Printf("%s 吃老鼠\n", s.name)
}

func (s snake) move() {
	fmt.Printf("%s 滑行\n", s.name)
}

func (s snake) speak() {
	fmt.Printf("%s 嘶嘶叫\n", s.name)
}

func (b bird) eat() {
	fmt.Printf("%s 吃蠕虫\n", b.name)
}

func (b bird) move() {
	fmt.Printf("%s 飞翔\n", b.name)
}

func (b bird) speak() {
	fmt.Printf("%s 叽叽喳喳叫\n", b.name)
}

出于某种原因,当我尝试通过‘newanimal’查询命令添加一个新创建的对象时,我总是得到“无效查询”而不是“创建成功!”。以下是我编译并运行代码的一些示例输出:

$ go run animalinterface.go
欢迎来到动物公园,您可以在这里创建和查询动物
首先,您可以执行以下任一类型的查询:
要为数据库创建动物,您需要使用以下语法:
newanimal (动物名称) (动物类型)
创建动物后,您可以通过以下方式查询动物:
query (动物名称) (动物行为)
>newanimal john cow
无效查询!
欢迎来到动物公园,您可以在这里创建和查询动物
首先,您可以执行以下任一类型的查询:
要为数据库创建动物,您需要使用以下语法:
newanimal (动物名称) (动物类型)
创建动物后,您可以通过以下方式查询动物:
query (动物名称) (动物行为)
>newanimal bob snake
无效查询!
欢迎来到动物公园,您可以在这里创建和查询动物首先,您可以执行以下任一类型的查询:
要为数据库创建动物,您需要使用以下语法:newanimal (动物名称) (动物类型)
创建动物后,您可以通过以下方式查询动物:query (动物名称) (动物行为)>newanimal tweety bird
无效查询!欢迎来到动物公园,您可以在这里创建和查询动物
首先,您可以执行以下任一类型的查询:要为数据库创建动物,您需要使用以下语法:newanimal (动物名称) (动物类型)
创建动物后,您可以通过以下方式查询动物:query (动物名称) (动物行为)>query tweety eatpanic: interface conversion: main.animal is main.cow, not main.birdgoroutine 1 [running]:
main.main()        C:/Users/iokhamaf/Documents/Coursera/Module4/animalinterface.go:96 +0xf72exit status 2

在查询接口动物切片时,我遇到了一个运行时错误,提示我接口转换的恐慌信息。有什么建议吗?谢谢!


更多关于Golang如何遍历接口中的对象列表并检测特定值的实战教程也可以访问 https://www.itying.com/category-94-b0.html

4 回复

userQuery = userQuery[:len(userQuery)-2]

你能解释一下这行代码的用途吗?

更多关于Golang如何遍历接口中的对象列表并检测特定值的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


下一行代码读取输入,包括字符串末尾的 \n 字符。

		userQuery, err := inputReader.ReadString('\n')

而你提到的那一行代码会移除最后一个字符。在这个例子中,移除的是 \n

		userQuery = userQuery[:len(userQuery)-2] // by Saya

GonzaSaya:

for _, animal := range sliceOfAnimals {
    if animal.getName() == sliceOfQuery[1] { // by Saya
        if sliceOfQuery[2] == "move" {
            animal.move()
        } else if sliceOfQuery[2] == "eat" {
            animal.eat()
        } else if sliceOfQuery[2] == "speak" {
            animal.speak()
        }
    } else {
        fmt.Println("Animal name not found!")
    }
}

谢谢,我现在完全明白它是如何工作的了。我的实现大概有95%是正确的。感谢你的帮助。

你的代码存在几个关键问题。首先,sliceOfAnimals 在每次循环迭代中都被重置,导致之前创建的动物丢失。其次,在查询时使用了类型断言 animal.(cow).name,这会在接口实际存储其他类型时引发 panic。以下是修正后的代码:

package main

import (
	"bufio"
	"fmt"
	"log"
	"os"
	"strings"
)

func main() {
	inputReader := bufio.NewReader(os.Stdin)
	animals := make(map[string]animal)

	for {
		fmt.Print("> ")
		userQuery, err := inputReader.ReadString('\n')
		if err != nil {
			log.Fatal(err)
		}

		userQuery = strings.TrimSpace(userQuery)
		sliceOfQuery := strings.Split(userQuery, " ")
		if len(sliceOfQuery) != 3 {
			fmt.Println("无效查询!")
			continue
		}

		command := sliceOfQuery[0]
		name := sliceOfQuery[1]
		arg := sliceOfQuery[2]

		switch command {
		case "newanimal":
			switch arg {
			case "cow":
				animals[name] = cow{name: name}
				fmt.Println("创建成功!")
			case "snake":
				animals[name] = snake{name: name}
				fmt.Println("创建成功!")
			case "bird":
				animals[name] = bird{name: name}
				fmt.Println("创建成功!")
			default:
				fmt.Println("无效的动物类型!")
			}

		case "query":
			animal, exists := animals[name]
			if !exists {
				fmt.Println("未找到该名称的动物!")
				continue
			}

			switch arg {
			case "eat":
				animal.eat()
			case "move":
				animal.move()
			case "speak":
				animal.speak()
			default:
				fmt.Println("无效的行为查询!")
			}

		default:
			fmt.Println("无效的命令!")
		}
	}
}

type animal interface {
	eat()
	move()
	speak()
}

type cow struct{ name string }
type snake struct{ name string }
type bird struct{ name string }

func (c cow) eat()   { fmt.Println("草") }
func (c cow) move()  { fmt.Println("行走") }
func (c cow) speak() { fmt.Println("哞") }

func (s snake) eat()   { fmt.Println("老鼠") }
func (s snake) move()  { fmt.Println("滑行") }
func (s snake) speak() { fmt.Println("嘶嘶") }

func (b bird) eat()   { fmt.Println("蠕虫") }
func (b bird) move()  { fmt.Println("飞翔") }
func (b bird) speak() { fmt.Println("叽喳") }

主要修改:

  1. 使用 map[string]animal 来持久化存储动物,键为动物名称
  2. 移除了每次循环重置的切片,避免数据丢失
  3. 使用 strings.TrimSpace() 替代 strings.Trim() 处理换行符和空格
  4. 查询时直接通过名称从 map 中获取动物,无需遍历和类型断言
  5. 简化了输出格式以符合题目要求

示例运行:

> newanimal john cow
创建成功!
> newanimal bob snake
创建成功!
> query john eat
草
> query bob move
滑行
回到顶部