Golang中结构体指针成员的用法解析

Golang中结构体指针成员的用法解析 我注意到有些人在结构体中使用指针类型的成员,想知道与普通类型相比这样做有什么优势。(他们在库中使用了这种方式,该库将以XML格式将数据发送到其他微服务。)

type Employee struct{

Name *string
DepName  *string

}
6 回复

谢谢

更多关于Golang中结构体指针成员的用法解析的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


是的,这就是原因……如果为 nil,那么 XML 编组就不会取值。

感谢。其背后的真正原因可能是 XML 的 Marshal 方法不会考虑值是否为 nil。

func main() {
    fmt.Println("hello world")
}

我怀疑该API会区分空字符串和nil字符串。也许nil字符串会从XML中省略,而空字符串则是一个空属性(例如 <Employee Name=""/><Employee /> 的对比)。

我不知道在您的具体情况下为什么有人会这样做。但区别应该是显而易见的:

package main

import "fmt"

type Foo struct {
	a *string
	b string
}

func (foo Foo) String() string {
	return fmt.Sprintf("Foo(a=%s, b=%s)", *foo.a, foo.b)
}

func main() {
	a := "a-orig"
	b := "b-orig"

	// Creat two instances.
	foo := Foo{a: &a, b: b}
	fmt.Println(foo)

	bar := Foo{a: &a, b: b}
	fmt.Println(bar)

	// Now we change `a` and `b`.
	a = "a-changed"
	b = "b-changed"

	// But only the change to `a` has effect in the structs.
	fmt.Println(foo)
	fmt.Println(bar)
}

输出:

Foo(a=a-orig, b=b-orig)
Foo(a=a-orig, b=b-orig)
Foo(a=a-changed, b=b-orig)
Foo(a=a-changed, b=b-orig)

参见 https://play.golang.org/p/ZL-4nBksGN1

在Golang中使用结构体指针成员主要有以下优势:

  1. 区分零值和未设置值:指针可以区分字段是否被显式设置(包括设置为nil),而值类型无法区分零值和未设置状态
  2. 内存效率:当字段可能为nil或需要共享数据时,指针可以避免不必要的内存分配
  3. 数据共享:多个结构体实例可以共享同一个底层数据

示例代码:

package main

import (
    "encoding/xml"
    "fmt"
)

type Employee struct {
    ID       int     `xml:"id"`
    Name     *string `xml:"name,omitempty"`
    DepName  *string `xml:"department,omitempty"`
    Salary   *int    `xml:"salary,omitempty"`
}

func main() {
    // 场景1:区分未设置字段和零值
    emp1 := Employee{ID: 1}
    // Name为nil,表示未设置
    // 如果Name是string类型,则无法区分空字符串是未设置还是显式设置为空
    
    // 场景2:XML序列化时的omitempty行为
    name := "John"
    emp2 := Employee{
        ID:      2,
        Name:    &name,
        DepName: nil, // 不会被序列化
    }
    
    // XML序列化
    data, _ := xml.MarshalIndent(emp2, "", "  ")
    fmt.Println(string(data))
    // 输出:
    // <Employee>
    //   <id>2</id>
    //   <name>John</name>
    // </Employee>
    
    // 场景3:共享数据
    sharedDep := "Engineering"
    emp3 := Employee{ID: 3, DepName: &sharedDep}
    emp4 := Employee{ID: 4, DepName: &sharedDep}
    
    // 修改共享数据
    *emp3.DepName = "R&D"
    fmt.Println(*emp3.DepName) // R&D
    fmt.Println(*emp4.DepName) // R&D
    
    // 场景4:处理可选字段
    processEmployee(&emp1)
}

func processEmployee(emp *Employee) {
    if emp.Name != nil {
        fmt.Printf("Employee name: %s\n", *emp.Name)
    } else {
        fmt.Println("Name not provided")
    }
    
    // 安全访问指针成员
    if emp.DepName != nil && *emp.DepName != "" {
        fmt.Printf("Department: %s\n", *emp.DepName)
    }
}

在XML序列化场景中,指针配合omitempty标签特别有用:

type Config struct {
    Host     *string `xml:"host,omitempty"`
    Port     *int    `xml:"port,omitempty"`
    Timeout  *int    `xml:"timeout,omitempty"`
}

func main() {
    // 只设置部分字段
    host := "localhost"
    config := Config{
        Host: &host,
        // Port和Timeout保持nil,不会被序列化
    }
    
    data, _ := xml.MarshalIndent(config, "", "  ")
    fmt.Println(string(data))
    // 输出:
    // <Config>
    //   <host>localhost</host>
    // </Config>
}

需要注意指针成员的nil检查:

func updateEmployee(emp *Employee) {
    // 错误:可能panic
    // fmt.Println(*emp.Name)
    
    // 正确:先检查nil
    if emp.Name != nil {
        fmt.Println(*emp.Name)
    }
    
    // 使用辅助函数
    name := getStringValue(emp.Name)
    fmt.Println(name)
}

func getStringValue(s *string) string {
    if s != nil {
        return *s
    }
    return ""
}
回到顶部