2

普通结构体

1.public

type Flower struct {
    Name string
    Color string
    Category string
}

外部包可以直接引用和赋值
2.private

type leaf struct {
    count int
    size int
}

外部包不能直接引用
需要定义public函数提供操作,例如

func (l *leaf) SetColor(color string) {
    l.color = color
}

3.public结构体里面有private

type Flower struct {
    name string
    color string
    category string
}

这种情况下,只能通过public的函数或者方法来操作内部的private属性
4.private结构体里面有public属性

type leaf struct {
    color string
    size int
    Count int
}

这种也是允许的,例如:

func NewLeaf(color string, size, count int) *leaf{
    var l = leaf{color, size, count}
    return &l
}

在另一个包中引用这些结构体

func main() {
    leaf := plant.NewLeaf("green", 5, 10)
    fmt.Println(leaf)
    leaf.Count = 5
    fmt.Println(leaf)
}

输出:

&{green 5 10}
&{green 5 5}

很少看到这么使用

嵌套结构体

type Leaf struct {
    Color string
    Size int
    Count int
}

type Flower struct {
    Leaf Leaf
    Name string
    Color string
    Category string
}

访问方式:

func main() {
    f := plant.Flower{}
    f.Category = "Roseceae"
    f.Name = "Rose"
    f.Color = "Red"
    f.Leaf.Color = "green"
    f.Leaf.Count = 5
    f.Leaf.Size = 10

    fmt.Println(f)
}

这种嵌套,在访问内部结构的成员时,必须显示的指向,如:
f.Leaf.Color

嵌套匿名结构体

type Leaf struct {
    Color string
    Size int
    Count int
}

type Flower struct {
    Leaf
    Name string
    Color string
    Category string
}

访问方式:

func main() {
    f := plant.Flower{}
    f.Category = "Roseceae"
    f.Name = "Rose"
    f.Color = "Red"
    f.Count = 5
    f.Size = 10
    f.Leaf.Color = "green"

    fmt.Println(f)
}

如上可以看出,有两种访问方式:
a.直接由外部结构体访问内部结构体成员,跟访问外部结构体内部自己的成员一样

f.Count = 5
f.Size = 10

b.外部结构体指向内部结构体,再访问其内部成员

f.Leaf.Color = "green"

注意:在内外结构体有成员名称相同时,如果访问嵌套结构体的成员,必须按照b方式访问,否则是直接访问的外层结构体成员变量。

补充:
这种匿名嵌套的结构体初始化的时候,必须一层层初始化,例如:

f := &Flower{
   Name:"Rose",
   Color:"Red",
   Category:"Roseceae",
   Size:10,
   Count:5,
}

这种初始化会报错,找不到成员Size和Count
正确的初始化方法:

l := Leaf{
   Size:  10,
   Count: 5,
   Color: "Green",
}
f := &Flower{
   Name:     "Rose",
   Color:    "Red",
   Category: "Roseceae",
   Leaf:     l,
}

或者:

f := &Flower{
   Name:     "Rose",
   Color:    "Red",
   Category: "Roseceae",
   Leaf: Leaf{
      Size:  10,
      Count: 5,
      Color: "Green",
   },
}

结构体中嵌套接口

以golang内置的包context为例

type Context interface {
    Deadline() (deadline time.Time, ok bool)
    Done() <-chan struct{}
    Err() error
    Value(key interface{}) interface{}
}

// A canceler is a context type that can be canceled directly. The
// implementations are *cancelCtx and *timerCtx.
type canceler interface {
    cancel(removeFromParent bool, err error)
    Done() <-chan struct{}
}

// A cancelCtx can be canceled. When canceled, it also cancels any children
// that implement canceler.
type cancelCtx struct {
    Context

    mu       sync.Mutex            // protects following fields
    done     chan struct{}         // created lazily, closed by first cancel call
    children map[canceler]struct{} // set to nil by the first cancel call
    err      error                 // set to non-nil by the first cancel call
}

这种嵌套,可以当成继承来理解,
首先Context接口有四个方法,全部被cancelCtx这个结构体继承了,然后重写了其中三个方法

func (c *cancelCtx) Value(key interface{}) interface{} {
    if key == &cancelCtxKey {
        return c
    }
    return c.Context.Value(key)
}

func (c *cancelCtx) Done() <-chan struct{} {
    c.mu.Lock()
    if c.done == nil {
        c.done = make(chan struct{})
    }
    d := c.done
    c.mu.Unlock()
    return d
}

func (c *cancelCtx) Err() error {
    c.mu.Lock()
    err := c.err
    c.mu.Unlock()
    return err
}

同时,cancelCtx又实现了canceler这个接口定义的方法,所以cancelCtx又可以当成canceler接口用:


// cancel closes c.done, cancels each of c's children, and, if
// removeFromParent is true, removes c from its parent's children.
func (c *cancelCtx) cancel(removeFromParent bool, err error) {
    //具体实现见源码
}

func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
    c := newCancelCtx(parent)
    propagateCancel(parent, &c)
    return &c, func() { c.cancel(true, Canceled) }
}

// newCancelCtx returns an initialized cancelCtx.
func newCancelCtx(parent Context) cancelCtx {
    return cancelCtx{Context: parent}
}


// propagateCancel arranges for child to be canceled when parent is.
func propagateCancel(parent Context, child canceler) {
    //具体实现见源码
}

从WithCancel中可以看到,执行c := newCancelCtx(parent)之后,c其实是cancelCtx类型,再调用propagateCancel时,第二个参数是canceler接口类型,直接取c地址传入,然后&c就当canceler接口用了,最后return时,又将&c当Context接口返回了

Context包代码不多,可以自己研究源码,认识会更深刻。

json结构体

JSON:
Json结构体定义的时候,成员名称和结构体名称必须可导出,即必须以大写开头,如:

type Os struct {
    Arch     string
    Major    string
    Minor    string
    Name     string
    Platform string
    Version  string
}

但是,这样在序列化成json字符串的时候,会以成员名称作为json字符串中的名称:

func main() {
    linux := Os{
        Arch: "x86_64",
        Major: "7",
        Minor: "7",
        Name: "CentOS Linux",
        Version: "7.7",
    }
    js, err := json.Marshal(linux)
    if err != nil {
        return
    }
    fmt.Println(string(js))
}

输出:

{"Arch":"x86_64","Major":"7","Minor":"7","Name":"CentOS Linux","Platform":"","Version":"7.7"}

有些情况下,我们的json字符串想全部以小写开头,则可以用如下方法指定输出时的字符串名称:

type Os struct {
    Arch     string `json:"arch"`
    Major    string `json:"major"`
    Minor    string `json:"minor"`
    Name     string `json:"name"`
    Platform string `json:"platform"`
    Version  string `json:"version"`
}

可以直接用内置的接口进行序列化处理:

func main() {
    linux := Os{
        Arch: "x86_64",
        Major: "7",
        Minor: "7",
        Name: "CentOS Linux",
        Platform: "centos",
        Version: "7.7",
    }
    js, err := json.Marshal(linux)
    if err != nil {
        return
    }
    fmt.Println(string(js))
}

输出:

{"arch":"x86_64","major":"7","minor":"7","name":"CentOS Linux","platform":"centos","version":"7.7"}

如果某个字段没有赋值,例如Platform:

func main() {
    linux := Os{
        Arch: "x86_64",
        Major: "7",
        Minor: "7",
        Name: "CentOS Linux",
        Version: "7.7",
    }
    js, err := json.Marshal(linux)
    if err != nil {
        return
    }
    fmt.Println(string(js))
}

输出:

{"arch":"x86_64","major":"7","minor":"7","name":"CentOS Linux","platform":"","version":"7.7"}

可以看到json字符串中仍然生成了platform这个字段,如果想对于某些不赋值的字段在生成json串的时候忽略掉,则可以在定义结构体的时候,使用omitempty,定义如下:

type Os struct {
    Arch     string `json:"arch"`
    Major    string `json:"major"`
    Minor    string `json:"minor"`
    Name     string `json:"name"`
    Platform string `json:"platform,omitempty"`
    Version  string `json:"version"`
}

func main() {
    linux := Os{
        Arch: "x86_64",
        Major: "7",
        Minor: "7",
        Name: "CentOS Linux",
        Version: "7.7",
    }
    js, err := json.Marshal(linux)
    if err != nil {
        return
    }
    fmt.Println(string(js))
}

输出:

{"arch":"x86_64","major":"7","minor":"7","name":"CentOS Linux","version":"7.7"}

注意注意注意

`json:"platform,omitempty"`

这个字符串中不能包含空格,否则无效
例如结构体中定义为:

Platform string `json: "platform,omitempty"`

这种json:后面跟空格,则不能指定json序列化时候显示的成员名称,默认是结构体成员名,即Platform

{"arch":"x86_64","major":"7","minor":"7","name":"CentOS Linux","Platform":"","version":"7.7"}

xml结构体

1.普通XML

type logcollect struct {
    XMLName   xml.Name `xml:"localfile"`
    Logformat string  `xml:"log_format"`
    Location  string  `xml:"location"`
}

func main() {
    lc := logcollect{Logformat: "syslog", Location: "/home/log/*.log"}

    output, err := xml.MarshalIndent(lc, "", "  ")
    if err != nil {
        return
    }
    fmt.Println(string(output))
}

输出

<localfile>
  <log_format>syslog</log_format>
  <location>/home/log/*.log</location>
</localfile>

2.嵌套xml
如果log_format或者location并不是简单的string类型,或者带一些属性,可以单独定义,如下:

type logformat struct {
    XMLName    xml.Name `xml:"log_format"`
    Id         int      `xml:"id,attr"`
    Log_format string   `xml:",chardata"`
}

type location struct {
    XMLName  xml.Name `xml:"location"`
    Length   int      `xml:"length,attr"`
    Location string   `xml:",chardata"`
}

type logcollect struct {
    XMLName   xml.Name `xml:"localfile"`
    Logformat logformat
    Location  location
}

序列化:

func main() {
    lf := logformat{Log_format: "syslog", Id: 1000}
    loc := location{Location: "/home/log/*.log", Length: 64}
    lc := logcollect{Logformat: lf, Location: loc}

    output, err := xml.MarshalIndent(lc, "", "  ")
    if err != nil {
        return
    }
    fmt.Println(string(output))
}

输出

<localfile>
  <log_format id="1000">syslog</log_format>
  <location length="64">/home/log/*.log</location>
</localfile>

3.动态设置XML的标签名
如上面定义的xml标签名字,都是在XMLName后面用xml:方式来指定的,如果想要程序中动态指定,
可以用如下方式设置,以logformat的标签名为例:

type logformat struct {
    XMLName    xml.Name
    Id         int      `xml:"id,attr"`
    Log_format string   `xml:",chardata"`
}

type location struct {
    XMLName  xml.Name `xml:"location"`
    Length   int      `xml:"length,attr"`
    Location string   `xml:",chardata"`
}

type logcollect struct {
    XMLName   xml.Name `xml:"localfile"`
    Logformat logformat
    Location  location
}

序列化:

func main() {
    lf := logformat{XMLName: xml.Name{Local:"LOGFORMAT"}, Log_format: "syslog", Id: 1000}
    loc := location{Location: "/home/log/*.log", Length: 64}
    lc := logcollect{Logformat: lf, Location: loc}

    output, err := xml.MarshalIndent(lc, "", "  ")
    if err != nil {
        return
    }
    fmt.Println(string(output))
}

输出:

<localfile>
  <LOGFORMAT id="1000">syslog</LOGFORMAT>
  <location length="64">/home/log/*.log</location>
</localfile>

可以看到标签名为LOGFORMAT了
注意:如果结构体定义中未指定标签名,代码中也没有赋值,则会以结构体名称作为标签名,同时首字母大写
4.动态设置XML的属性名
同样的也可以动态修改属性名称,如果属性在定义结构体的时候不确定,可以用如下方式定义:

type logformat struct {
    XMLName    xml.Name `xml:"log_format"`
    XMLAttr    xml.Attr `xml:"id,attr"`
    Log_format string   `xml:",chardata"`
}

type location struct {
    XMLName  xml.Name `xml:"location"`
    Length   int      `xml:"length,attr"`
    Location string   `xml:",chardata"`
}

type logcollect struct {
    XMLName   xml.Name `xml:"localfile"`
    Logformat logformat
    Location  location
}

func main() {
    lf := logformat{
        Log_format: "syslog",
        XMLAttr:
            xml.Attr{
                Name:xml.Name{
                    Local:"ID",
                },
                Value:"100",
            },
    }
    loc := location{Location: "/home/log/*.log", Length: 64}
    lc := logcollect{Logformat: lf, Location: loc}

    output, err := xml.MarshalIndent(lc, "", "  ")
    if err != nil {
        return
    }
    fmt.Println(string(output))
}

输出:

<localfile>
  <log_format ID="100">syslog</log_format>
  <location length="64">/home/log/*.log</location>
</localfile>

如果有多个属性,可以将其定义为数组

type logformat struct {
    XMLName    xml.Name `xml:"log_format"`
    XMLAttr    []xml.Attr `xml:"id,attr"`
    Log_format string   `xml:",chardata"`
}

laughbug
275 声望2 粉丝

Quitters never win and winners never quit.