英文:
Perform an action on a collection of items
问题
在我的实际代码中,我正在使用encoding/xml
解析XML文档,并且我基本上有一堆嵌套的结构,形式如下 - 所有这些结构都可能出现多次,除了顶层的statements
元素:
statements
statement
opcode
args
pre
post
我对Go相当新手,显然我对interface{}
(空接口)的工作原理有误解:
.\stmtgen.go:58: 无法将print_name(类型为func(Statement))作为performAction的参数中类型为func(interface {})的类型使用
.\stmtgen.go:58: 无法将slist(类型为[]Statement)作为performAction的参数中类型为[]interface {}的类型使用
相关示例代码:
<!-- language: lang-golang -->
package main
import "fmt"
// 实际上是我的代码中的一个结构,但这足以进行演示。
type Opcode int
// 一个语句有一个名称,多个操作码可以使用此名称。
type Statement struct {
Name string
Opcodes []Opcode
}
// 打印语句名称。
func print_name(stmt Statement) {
fmt.Println(stmt.Name)
}
// 对集合中的每个项执行操作。
func performAction(action func(interface{}), v []interface{}) {
for i := range v {
action(v[i])
}
}
func main() {
slist := make([]Statement, 3)
slist[0] = Statement{"Statement 1"}
slist[1] = Statement{"Statement 2"}
slist[2] = Statement{"Statement 3"}
// 错误在这里
performAction(print_name, slist)
}
我必须为每种类型创建函数来打印值吗?
英文:
In my actual code, I'm parsing an XML document using encoding/xml
, and I basically have a bunch of nested structures of the following form — all of which may occur multiple times, except the top-level statements
element:
statements
statement
opcode
args
pre
post
I'm fairly new to Go, and I'm clearly misunderstanding how interface{}
(the empty interface) works:
.\stmtgen.go:58: cannot use print_name (type func(Statement)) as type func(interface {}) in argument to performAction
.\stmtgen.go:58: cannot use slist (type []Statement) as type []interface {} in argument to performAction
Relevant example code:
<!-- language: lang-golang -->
package main
import "fmt"
// Actually a structure in my code, but this suffices for demonstration.
type Opcode int
// A Statement has a Name and multiple Opcodes may use this Name.
type Statement struct {
Name string
Opcodes []Opcode
}
// Print the statement name.
func print_name(stmt Statement) {
fmt.Println(stmt.Name)
}
// Perform an action on each item of a collection.
func performAction(action func(interface{}), v []interface{}) {
for i := range v {
action(v[i])
}
}
func main() {
slist := make([]Statement, 3)
slist[0] = Statement{"Statement 1"}
slist[1] = Statement{"Statement 2"}
slist[2] = Statement{"Statement 3"}
//ERROR HERE
performAction(print_name, slist)
}
Must I create functions to print the values for every single type?
答案1
得分: 1
一个空的interface{}
可以包含任何值,并作为类型interface{}
传递。当你需要从中获取值时,可以执行类型断言,例如:
var anyValue interface{}
anyValue = "hello"
strValue := anyValue.(string)
如果anyValue
不是所断言的类型,则会引发恐慌。
类型断言还可以用于返回一个布尔值,以判断接口是否为该类型:
strValue, ok := anyValue.(string)
if ok {
// anyValue 包含一个字符串!
}
如果你不知道接口的类型,可以使用switch
语句来确定其类型,例如:
switch val := anyValue.(type) {
case string:
// anyValue 包含一个字符串
// val 是一个字符串
break
case int:
// anyValue 包含一个整数
// val 是一个整数
break
default:
// 未处理的接口类型
}
希望这样可以更清楚地理解空接口interface{}
的类型。
而声明了方法的接口interface{...}
是不同的,它们不能有成员(像结构体那样),只能有方法,并且它们的底层类型必须实现接口中声明的所有方法。你可以有一个名为actionPerformer
的接口(接口名称应该以“er”作为后缀,因为它们正在做某些事情):
type actionPerformer interface {
action(interface{})
}
实现了接口中所有方法的类型可以转换为该接口类型,然后如果在接口上调用其中一个方法,它将在底层类型上运行该方法。例如,如果Statement
结构体实现了action(interface{})
方法,那么Statement
结构体可以转换为actionPerformer
类型,如果在actionPerformer
上调用action(interface{})
函数,将运行Statement
结构体上的action
函数。因此,你可以有多个类型都有action(interface{})
方法,并且它们都可以转换为actionPerformer
,你可以在其上调用action
函数。
func (code Opcode) action(arg interface{}) {
fmt.Println(arg.(int) + int(code))
}
func (stmt Statement) action(arg interface{}) {
fmt.Println(arg.(string), stmt.Name)
}
stmt := Statement{"Statement 1", nil}
stmtActionPerformer := actionPerformer(stmt)
opcode := Opcode(5)
opcodeActionPerformer := actionPerformer(opcode)
stmtActionPerformer.action("hello") // 将打印 "hello" + 语句的名称
opcodeActionPerformer.action(2) // 将打印 7
类型断言仍然可以用于这些类型的接口,例如:
stmt := stmtActionPerformer.(Statement)
fmt.Println(stmt.Name)
这只是一个人为的例子,但是有了这个理解,你可能希望像这样使用接口来编写代码。请记住,在接口之间进行转换是昂贵的,因此应该谨慎使用,但是当使用正确时,它们是强大的工具。
对于你的例子,一个简单的printNames
函数比所有的接口转换更高效(请注意,在Go中,名称应该使用驼峰格式,而不是使用下划线):
func printNames(stmts []Statement) {
for _, stmt := range stmts {
fmt.Println(stmt.Name)
}
}
还可以定义一个StatementList
类型,并为其添加方法:
type StatementList []Statement
func (list StatementList) printNames() {
for _, stmt := range list {
fmt.Println(stmt.Name)
}
}
掌握这些知识后,使用Go语言会更有趣,希望对你有所帮助
英文:
An empty interface{}
can contain any value and passed around as the type interface{}
. when you need the value from it, you can perform a type assertion like this:
var anyValue interface{}
anyValue = "hello"
strValue := anyValue.(string)
If anyValue is not of the type being asserted then it will cause a panic
the type assertion can also be used to return a bool if the interface is of that type with a multiple return
strValue, ok := anyValue.(string)
if ok {
//anyValue contains a string!
}
If you dont know the type of the interface, you can use a switch to determine it's type like this:
switch val := anyValue.(type) {
case string:
// anyValue contains a string
// and val is a string
break
case int:
// anyValue contains an int
// and val is and int
break
default:
//unhandled interface type
}
Hopefully this makes the empty interface{} type clearer.
interfaces{...} which have methods declared in them are different, they can not have members (like structs can), only methods, and their underlying type must implement all the methods declared in the interface. You could have an interface actionPerformer (interface names should have the suffix "er" as they are doing something)
type actionPerformer interface {
action(interface{})
}
A type that implements all the methods in an interface can be cast to that interface type, then if you call one of those methods on the interface, it will run the method on the underlying type.
For example, if the Statement struct implements the action(interface{})
method, the Statement struct can be cast to an actionPerformer
type and if the action(interface{})
function is called on the actionPerformer
, the action function on the Statement struct is run. So you could have multiple types that all have the action(interface{})
method and they can all be cast to an actionPerformer
which you can call the action function on.
func (code Opcode) action(arg interface{}) {
fmt.Println(arg.(int) + int(code))
}
func (stmt Statement) action(arg interface{}) {
fmt.Println(arg.(string), stmt.Name)
}
stmt := Statement{"Statement 1", nil}
stmtActionPerformer := actionPerformer(stmt)
opcode := Opcode(5)
opcodeActionPerformer := actionPerformer(opcode)
stmtActionPerformer.action("hello") // will print "hello "+whatever the statements name is
opcodeActionPerformer.action(2) //will print be 7
Type assertions can still be used on these types of interface e.g.
stmt := stmtActionPerformer.(Statement)
fmt.Println(stmt.Name)
This is a contrived example, but with this in mind, you might want to write your code using interfaces like this.
Remember casting between interfaces is costly, so should be done sparingly, however they are a powerful tool when used correctly.
For your example, a simple printNames function would be much more efficient than all that interface casting (note that in golang, names should be in the CamelCase format, not using underscores)
func printNames(stmts []Statement) {
for _, stmt := range stmts {
fmt.Println(stmt.Name)
}
}
It might also be useful to have a type StatementList and add methods to it:
type StatementList []Statement
func (list StatementList) printNames() {
for _, stmt := range list {
fmt.Println(stmt.Name)
}
}
Getting the hang of this stuff make golang a lot more fun, hope this helps
答案2
得分: 0
你必须像参数类型一样精确声明performAction
函数的参数。
func performAction(action func(Statement), v []Statement) {
for i := range v {
action(v[i])
}
}
或者你可以在所有参数上使用interface{}
,然后根据需要进行类型转换。
func performAction(action interface{}, v interface{}) {
for _, each := range v.([]Statement) {
action.(func(Statement))(each)
}
}
- 类型为
[]Statement
的数据不能赋值给[]interface{}
- 类型为
func(Statement)
的数据也不能赋值给func(interface{})
使用interface{}
,然后将其转换为原始类型。
英文:
You have to declare the parameters of performAction
exactly same like the arguments type.
func performAction(action func(Statement), v []Statement) {
for i := range v {
action(v[i])
}
}
Or you could use interface{}
on all parameters instead. Then cast it according to the needs.
func performAction(action interface{}, v interface{}) {
for _, each := range v.([]Statement) {
action.(func(Statement))(each)
}
}
- data with type
[]Statement
cannot be assigned to[]interface{}
- also for type
func(Statement)
cannot be assigned tofunc(interface{})
Use interface{}
, then cast it to the original type.
答案3
得分: 0
这对我有效:
package main
import (
"fmt"
)
// 实际上是我代码中的一个结构体,但这足以进行演示。
type Opcode int
// 一个语句有一个名称,多个操作码可以使用这个名称。
type Statement struct {
Name string
Opcodes []Opcode
}
// 打印语句的名称。
func print_name(stmt interface{}) {
if s, ok := stmt.(Statement); !ok {
panic("typ err")
} else {
fmt.Println(s.Name)
}
}
// 对集合中的每个项执行操作。
func performAction(action func(interface{}), v []interface{}) {
for i := range v {
action(v[i])
}
}
func main() {
slist := make([]interface{}, 3)
slist[0] = Statement{"语句 1", nil}
slist[1] = Statement{"语句 2", nil}
slist[2] = Statement{"语句 3", nil}
performAction(print_name, slist)
/*输出:
语句 1
语句 2
语句 3
*/
}
希望对你有帮助!
英文:
this works for me:
package main
import (
"fmt"
)
// Actually a structure in my code, but this suffices for demonstration.
type Opcode int
// A Statement has a Name and multiple Opcodes may use this Name.
type Statement struct {
Name string
Opcodes []Opcode
}
// Print the statement name.
func print_name(stmt interface{}) {
if s, ok := stmt.(Statement); !ok {
panic("typ err")
} else {
fmt.Println(s.Name)
}
}
// Perform an action on each item of a collection.
func performAction(action func(interface{}), v []interface{}) {
for i := range v {
action(v[i])
}
}
func main() {
slist := make([]interface{}, 3)
slist[0] = Statement{"Statement 1", nil}
slist[1] = Statement{"Statement 2", nil}
slist[2] = Statement{"Statement 3", nil}
performAction(print_name, slist)
/*output:
Statement 1
Statement 2
Statement 3
*/
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论