【问题标题】:How to check value in nested pointer如何检查嵌套指针中的值
【发布时间】:2018-01-28 06:50:45
【问题描述】:

如何轻松检查嵌套指针?

type inner1 struct {
    value string
}

type inner2 struct {
    value *inner1
}

type outter struct {
    value *inner2
}

我有这样的数据:

o := &outter{
    value: &inner2{
        value: &inner1{
            value: "I need this data",
        },
    },
} 

如果我想从中获取数据,我需要检查 nil 指针。

func printValue(o *outter) (string, bool) {
    if o.value != nil {
        v1 := o.value
        if v1.value != nil {
            v2 := v1.value
            return v2.value, true
        }
    }
    return "", false
}

是的,我可以做到。但在我的真实场景中,这个嵌套指针部分要长得多。我会更喜欢另一种方式。

我已经检查了这个答案,Test for nil values in golang nested stucts。但我需要替代方案。

我怎样才能做到这一点。有什么更高效更好的选择吗?

【问题讨论】:

  • 这些指针的底层类型是否非常不同?你真的需要 10 种不同的类型吗?
  • 是的。它们因不同的目的而不同。我需要所有这些。没办法

标签: pointers go nested panic


【解决方案1】:

恐慌和恢复是针对特殊情况的。检查指针是否为nil 通常不是,所以你应该远离它。使用if 语句检查指针是否为nil 比使用recover() 引入延迟函数要干净得多。尤其是这个recover() 将停止所有其他类型的恐慌,即使是那些由于试图取消引用nil 指针之外的其他原因而导致的恐慌。 (使用deferrecover() 也比使用if 进行简单的nil 检查要慢。)

如果始终要求数据是非nil 值,则应考虑不使用指针。如果数据总是需要非nil,但由于某种原因你需要使用指针,那么如果任何指针仍然是nil,这可能是让你的代码恐慌的有效情况。

如果它可能是nil,那么你必须检查nil 的情况并适当地处理它。你必须明确这一点,Go 不会帮你省略这个检查。

为了避免在每个地方都检查nil,实用函数或方法是合理的。请注意,即使接收者是nil,也可以调用方法,这在这种情况下可能很有用。

例如,您可以附加以下方法:

func (i *inner1) Get() (string, bool) {
    if i == nil {
        return "", false
    }
    return i.value, true
}

func (i *inner2) Get() (string, bool) {
    if i == nil {
        return "", false
    }
    return i.value.Get()
}

func (o *outter) Get() (string, bool) {
    if o == nil {
        return "", false
    }
    return o.value.Get()
}

请注意,每个Get() 方法都需要检查一个指针,无论数据结构有多复杂。

测试它:

o := &outter{
    value: &inner2{
        value: &inner1{
            value: "I need this data",
        },
    },
}
fmt.Println(o.Get())

o.value.value = nil
fmt.Println(o.Get())

o.value = nil
fmt.Println(o.Get())

o = nil
fmt.Println(o.Get())

输出(在Go Playground上试试):

I need this data true
 false
 false
 false

上述解决方案隐藏outter 的内部结构,这对于使用outter 的用户很有用(如果outter 的内部结构发生变化,则不需要更新客户端,只需outter.Get()方法)。

类似的方法是添加只返回接收者结构字段的方法:

func (i *inner1) Value() (string, bool) {
    if i == nil {
        return "", false
    }
    return i.value, true
}

func (i *inner2) Inner1() *inner1 {
    if i == nil {
        return nil
    }
    return i.value
}

func (o *outter) Inner2() *inner2 {
    if o == nil {
        return nil
    }
    return o.value
}

这种方法要求客户了解outter 的内部结构,但同样在使用时不需要任何nil 检查:

o := &outter{
    value: &inner2{
        value: &inner1{
            value: "I need this data",
        },
    },
}
fmt.Println(o.Inner2().Inner1().Value())

o.value.value = nil
fmt.Println(o.Inner2().Inner1().Value())

o.value = nil
fmt.Println(o.Inner2().Inner1().Value())

o = nil
fmt.Println(o.Inner2().Inner1().Value())

输出是一样的。在Go Playground 上试试这个。

【讨论】:

  • @aerokite 检查有关示例方法的添加。无论数据结构的复杂程度如何,每种方法都需要一个 nil 检查。
  • 似乎没有简单的方法。为此检查需要冗余代码。
  • @aerokite 您必须为每个指针至少一次某处检查nil那个你不能省略。您所要做的就是把这张支票放在哪里。我的示例包含nil 指针,每个指针只检查一次。所以没有多余的。
  • 我在我的问题中做了同样的事情,对吧?逻辑上
  • 感谢@icza 的宝贵时间。它的帮助
【解决方案2】:

我可以使用恐慌恢复方法。这将解决我的问题。但这对我来说似乎很老套。

func printValue(o *outter) (string, bool) {
    defer func() (string, bool) {
        if r := recover(); r != nil {
            return "", false
        }
        return "", false
    }()
    return o.value.value.value, true
}

【讨论】:

    【解决方案3】:

    没有简单的方法。您可能会采取恢复方式,但这不是 IMO 惯用的方式,您应该检查是否没有发现其他不相关的错误。

    我更喜欢单个if 而不是多个。我不认为下面的代码丑陋或冗长。

    func printValue(o *outter) (string, bool) {
        if o.value != nil and o.value.value != nil and o.value.value.value != nil {
            return *o.value.value.value, true
        }
        return "", false
    }
    

    【讨论】:

    • 你是对的。但如果嵌套长度为 10,我需要大量检查。我正在寻找替代方案。
    【解决方案4】:

    使用反射

    func getFieldByName(v interface{}, fields string, sep string) interface{} {
        r := reflect.ValueOf(v)
        s := strings.Split(fields, sep)
        for _, field := range s {
            r = reflect.Indirect(r)
            if r.IsValid() {
                r = reflect.Indirect(r).FieldByName(field)
            } else {
                return nil
            }
        }
        if r.IsValid() {
            return r.Interface()
        }
        return nil
    }
    

    使用恐慌恢复

    type safeGetFn func() interface{}
    
    func safeGet(f safeGetFn, d interface{}) (ret interface{}) {
        defer func() interface{} {
            if r := recover(); r != nil {
                ret = d
            }
            return ret
        }()
        return f()
    }
    

    反射示例:https://play.golang.org/p/m0_zQqJm7MY

    恐慌恢复示例:https://play.golang.org/p/PNPPBXCvHxJ

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-03-17
      • 2012-05-08
      • 2020-02-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-03-18
      相关资源
      最近更新 更多