Golang:交换两个数字的接口

huangapple go评论71阅读模式
英文:

Golang : interface to swap two numbers

问题

我可以帮你翻译代码部分。以下是翻译好的代码:

package main

import "fmt"

type num struct {
	value interface{}
}

type numbers struct {
	b *num
	c *num
}

func (a *num) SwapNum(var1, var2 interface{}) {
	temp := var1
	var1 = var2
	var2 = temp
}

func main() {
	a := 1
	b := 2
	c := 3.5
	d := 5.5

	SwapNum(a, b)
	fmt.Println(a, b) // 2 1
	SwapNum(c, d)
	fmt.Println(c, d) // 5.5 3.5
}

请注意,你需要将SwapNum函数改为SwapNum(a *num, var1, var2 interface{}),并在main函数中使用a.SwapNum来调用该函数。此外,你还需要在main函数中创建一个num类型的变量,然后将其传递给SwapNum函数。这样才能正确地使用接口来交换两个输入数字。

英文:

I want to swap two numbers using interface but the interface concept is so confusing to me.

http://play.golang.org/p/qhwyxMRj-c

This is the code and playground. How do I use interface and swap two input numbers? Do I need to define two structures?

type num struct {
	value interface{}
}

type numbers struct {
	b *num
	c *num
}

func (a *num) SwapNum(var1, var2 interface{}) {
	var a num

	temp := var1
	var1 = var2
	var2 = temp
}

func main() {
	a := 1
	b := 2
	c := 3.5
	d := 5.5

	SwapNum(a, b)
	fmt.Println(a, b) // 2 1
	SwapNum(c, d)
	fmt.Println(c, d) // 5.5 3.5
}

答案1

得分: 16

首先,interface{}类型是一个接受所有值的类型,因为它是一个带有空方法集的接口,而每种类型都可以满足该接口。例如,int类型没有任何方法,interface{}类型也没有。

要编写一个交换两个变量值的方法,首先需要确保这些变量是可修改的。传递给函数的值总是被复制的(除了引用类型,如切片和映射,但这不是我们目前关注的问题)。通过使用指向变量的指针,可以实现可修改的参数。

因此,根据这个知识,可以这样定义SwapNum

func SwapNum(a interface{}, b interface{})

现在,SwapNum是一个接受任意类型的两个参数的函数。
不能写成:

func SwapNum(a *interface{}, b *interface{})

因为这只接受*interface{}类型的参数,而不是任意类型的参数。
(可以在这里尝试一下)

所以我们有了函数签名,唯一剩下的就是交换值了。

func SwapNum(a interface{}, b interface{}) {
    *a, *b = *b, *a
}

不,这样是不会起作用的。使用interface{}时,我们必须进行运行时类型断言,以检查我们是否做对了。因此,代码必须使用reflect包进行扩展。如果你对反射不熟悉,可以参考这篇文章

基本上,我们需要这个函数:

func SwapNum(a interface{}, b interface{}) {
    ra := reflect.ValueOf(a).Elem()
    rb := reflect.ValueOf(b).Elem()
    tmp := ra.Interface()
    
    ra.Set(rb)
    rb.Set(reflect.ValueOf(tmp))
}

这段代码使用reflect.ValueOf()ab进行反射,以便我们可以检查它们。在同一行中,我们假设我们得到了指针值,并通过调用.Elem()对它们进行解引用。

这基本上等同于ra := *arb := *b
之后,我们通过使用.Interface()请求值并进行赋值(实际上是进行了复制),从而复制了*a

最后,我们使用ra.Set(rb)a的值设置为b的值,这相当于*a = *b,然后将之前存储在临时变量中的b赋值给a。为此,我们需要将tmp转换回自身的反射,以便可以使用rb.Set()(它接受一个reflect.Value作为参数)。

我们能做得更好吗?

是的!我们可以使代码更加类型安全,或者更好地说,通过使用reflect.MakeFunc使Swap的定义类型安全。在文档中(点击链接)有一个非常类似于你尝试的示例。基本上,你可以使用反射来填充函数原型的内容。由于你提供了函数的原型(签名),编译器可以检查类型,而当值被缩小为interface{}时,编译器无法检查类型。

示例用法:

var intSwap func(*int, *int)
a, b := 1, 0
makeSwap(&intSwap)
intSwap(&a, &b)
// 现在a的值为0,b的值为1

这背后的代码是:

swap := func(in []reflect.Value) []reflect.Value {
    ra := in[0].Elem()
    rb := in[1].Elem()
    tmp := ra.Interface()

    ra.Set(rb)
    rb.Set(reflect.ValueOf(tmp))

    return nil
}

makeSwap := func(fptr interface{}) {
    fn := reflect.ValueOf(fptr).Elem()
    v := reflect.MakeFunc(fn.Type(), swap)
    fn.Set(v)
}

swap函数的代码基本上与SwapNum的代码相同。makeSwap与文档中使用的代码相同,其中解释得很清楚。

**免责声明:**上述代码对给定的内容和值做了很多假设。通常需要检查,例如,给定的值是否实际上是指针值等等。出于清晰起见,我省略了这些检查。

英文:

First of all, the interface{} type is simply a type which accepts all values as it is an interface with an empty method set and every type can satisfy that. int for example does not have any methods, neither does interface{}.

For a method which swaps the values of two variables you first need to make sure these variables are actually modifiable. Values passed to a function are always copied (except reference types like slices and maps but that is not our concern at the moment). You can achieve modifiable parameter by using a pointer to the variable.

So with that knowledge you can go on and define SwapNum like this:

func SwapNum(a interface{}, b interface{})

Now SwapNum is a function that accepts two parameters of any type.
You can't write

func SwapNum(a *interface{}, b *interface{})

as this would only accept parameters of type *interface{} and not just any type.
(Try it for yourself here).

So we have a signature, the only thing left is swapping the values.

func SwapNum(a interface{}, b interface{}) {
    *a, *b = *b, *a
}

No, this will not work that way. By using interface{} we must do runtime type assertions to check whether we're doing the right thing or not. So the code must be expanded using the reflect package. This article might get you started if you don't know about reflection.

Basically we will need this function:

func SwapNum(a interface{}, b interface{}) {
	ra := reflect.ValueOf(a).Elem()
	rb := reflect.ValueOf(b).Elem()
	tmp := ra.Interface()
	
	ra.Set(rb)
	rb.Set(reflect.ValueOf(tmp))
}

This code makes a reflection of a and b using reflect.ValueOf() so that we can
inspect it. In the same line we're assuming that we've got pointer values and dereference
them by calling .Elem() on them.

This basically translates to ra := *a and rb := *b.
After that, we're making a copy of *a by requesting the value using .Interface()
and assigning it (effectively making a copy).

Finally, we set the value of a to b with [ra.Set(rb)]5, which translates to *a = *b
and then assigning b to a, which we stored in the temp. variable before. For this,
we need to convert tmp back to a reflection of itself so that rb.Set() can be used
(it takes a reflect.Value as parameter).

Can we do better?

Yes! We can make the code more type safe, or better, make the definition of Swap type safe
by using reflect.MakeFunc. In the doc (follow the link) is an example which is very
like what you're trying. Essentially you can fill a function prototype with content
by using reflection. As you supplied the prototype (the signature) of the function the
compiler can check the types, which it can't when the value is reduced to interface{}.

Example usage:

var intSwap func(*int, *int)
a,b := 1, 0
makeSwap(&intSwap)
intSwap(&a, &b)
// a is now 0, b is now 1

The code behind this:

swap := func(in []reflect.Value) []reflect.Value {
	ra := in[0].Elem()
	rb := in[1].Elem()
	tmp := ra.Interface()

	ra.Set(rb)
	rb.Set(reflect.ValueOf(tmp))

	return nil
}

makeSwap := func(fptr interface{}) {
	fn := reflect.ValueOf(fptr).Elem()
	v := reflect.MakeFunc(fn.Type(), swap)
	fn.Set(v)
}

The code of swap is basically the same as that of SwapNum. makeSwap is the same
as the one used in the docs where it is explained pretty well.

Disclaimer: The code above makes a lot of assumptions about what is given and
what the values look like. Normally you need to check, for example, that the given
values to SwapNum actually are pointer values and so forth. I left that out for
reasons of clarity.

huangapple
  • 本文由 发表于 2013年10月11日 00:54:15
  • 转载请务必保留本文链接:https://go.coder-hub.com/19301742.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定