• Go基础系列:struct的导出和暴露问题


    struct的导出和暴露问题

    关于struct的导出

    struct的属性是否被导出,也遵循大小写的原则:首字母大写的被导出,首字母小写的不被导出。

    所以:

    1. 如果struct名称首字母是小写的,这个struct不会被导出。连同它里面的字段也不会导出,即使有首字母大写的字段名
    2. 如果struct名称首字母大写,则struct会被导出,但只会导出它内部首字母大写的字段,那些小写首字母的字段不会被导出

    也就是说,struct的导出情况是混合的。

    但并非绝对如此,如果struct嵌套了,那么即使被嵌套在内部的struct名称首字母小写,也能访问到它里面首字母大写的字段

    例如:

    type animal struct{
        name string
        Speak string
    }
    type Horse struct {
        animal
        sound string
    }
    

    Horse中嵌套的animal是小写字母开头的,但Horse是能被导出的,所以能在其它包中使用Horse struct,其他包也能访问到animal中的Speak属性。

    很多时候,Horse这个名字是不安全的,因为这表示导出Horse这个struct给其他包,也就是将Horse给暴露出去了,外界可以直接打开Horse这个"黑匣子"。

    但如果不将Horse导出,如何能在其它包构建出Horse实例?见下文。

    不要暴露struct

    很多时候,不应该将某包(如包abc)中的struct(如animal)直接暴露给其它包,暴露意味着打开了那个"黑匣子",所以struct会以小写字母开头,不将其导出。

    这时在外界其它包中构建包abc的animal,就没法直接通过以下几种方式实现:

    • var xxx abc.animal
    • new(abc.animal)
    • &abc.animal{...}
    • abc.animal{...}

    例如,下面的是错误的:

    // abc/abc.go文件内容:
    package abc
    
    type animal struct{
        name string
        Speak string
    }
    
    // test.go内容:
    package main
    
    import "./abc"
    
    func main() {
        // 全都错误
    	var t1 abc.animal
    	t2 := new(abc.animal)
    	t3 := &abc.animal{}
    	t4 := abc.animal{}
    }
    

    那么如何在外界构建隐藏起来的struct实例?这时可以在abc包中写一个可导出的函数,通过这个函数来构建struct实例。例如:

    // abc/abc.go文件内容:
    package abc
    
    type animal struct{
        name string
        Speak string
    }
    
    func NewAnimal() *animal{
        a := new(animal)
        return a
    }
    
    
    // test.go内容:
    package main
    
    import (
    	"fmt"
    	"./abc"
    )
    
    func main() {
    	t1 := abc.NewAnimal()
    //	t1.name = "haha"    // 无法访问name属性
    	t1.Speak = "hhhh"
    	fmt.Println(t1.Speak)
    }
    

    上面的代码一切正常,在main包中可以通过NewAnimal()构建出abc包中未导出的animal struct。注意,上面NewAnimal()中是使用new()函数构造实例的,它返回的是实例的指针,至于如何构造实例,完全可以根据自己的需求,但对于struct类型来说,一般都是使用指针的,也就是完全可以将new()通用化。

    由于animal中的name字段是不导出的字段,所以在外界即便是通过NewAnimal()构建出了animal实例,也无法访问该实例的name属性,所以没法为name字段赋值。换句话说,name属性永远是初始化的0值。

    因此,为了让构建实例时自定义name属性,需要在构造方法NewAnimal()上指定设置给name属性的参数。修改NewAnimal()函数:

    func NewAnimal(name string) *animal{
        a := new(animal)
        a.name = name
        return a
    }
    

    然后在其它包中构建animal实例:

    t1 := abc.NewAnimal("longshuai")
    

    虽然其它包中构建的animal实例已经具备了name属性,但还是无法访问该实例的name属性。所以,在abc包中继续写一个可导出的方法,该方法用于获取实例的name属性:

    // abc/abc.go中添加:
    func (a *animal) GetName() string {
        return a.name
    }
    

    于是外界包中可以通过这个导出的方法获取实例的name属性:

    t1 := abc.NewAnimal("longshuai")
    fmt.Println(t1.GetName())
    

    实际上,上面NewAnimal()构造对象时,可以不用传递name参数,而是像GetName()一样,写一个专门的可导出方法来设置实例的name属性。改写abc/abc.go中的代码:

    func NewAnimal() *animal{
        a := new(animal)
        return a
    }
    func (a *animal) SetName(name string){
        a.name = name
    }
    

    现在,abc/abc.go中的animal struct就完全对外隐藏了。

    但需要注意的是,上面的setter类方法SetName()不能同时被2个或多个线程修改,否则值被覆盖,出现线程安全问题,可以使用sync包或者goroutine和channel来解决这个问题。

    嵌套struct中的方法导出问题

    当内部struct嵌套进外部struct时,内部struct的方法也会被嵌套,也就是说外部struct拥有了内部struct的方法。

    但是需要注意方法的首字母大小写问题。由于内、外struct在同一包内,所以直接在该包内构建外部struct实例,外部struct实例是可以直接访问内部struct的所有方法的。但如果在其它包内构建外部struct实例,该实例将无法访问内部struct中首字母小写的方法

    以下是在同一个包内测试,外部实例可以直接调用内部struct的方法:

    package main
    
    import (
    	"fmt"
    )
    
    type person struct {
    	name string
    	age  int
    }
    
    // 未导出方法
    func (p *person) speak() {
    	fmt.Println("speak in person")
    }
    
    // 导出的方法
    func (p *person) Sing() {
    	fmt.Println("Sing in person")
    }
    
    // Admin exported
    type Admin struct {
    	person
    	salary int
    }
    
    func main() {
        a := new(Admin)
        a.speak()  // 正常输出
        a.Sing()   // 正常输出
    }
    

    执行结果时a.speak()a.Sing()都正常输出。

    以下是不同包内测试,struct定义在abc/abc.go文件中,main在test.go中,它们的目录结构如下:

    $ tree .
    .
    ├── abc
    │   └── abc.go
    ├── test.go
    

    abc/abc.go的内容为:

    package abc
    
    import "fmt"
    
    // 未导出的person
    type person struct {
    	name string
    	age  int
    }
    
    // 未导出的方法
    func (p *person) speak() {
    	fmt.Println("speak in person")
    }
    
    // 导出的方法
    func (p *person) Sing() {
    	fmt.Println("Sing in person")
    }
    
    // Admin exported
    type Admin struct {
    	person
    	salary int
    }
    

    test.go的内容为:

    package main
    
    import "./abc"
    
    func main() {
    	a := new(abc.Admin)
    
    	// 下面报错
    //	a.speak()
    
    	// 下面正常
    	a.Sing()
    }
    

    执行结果是,a.speak()报错,但a.Sing()正常。

  • 相关阅读:
    16.检查是否为BST
    15.输出单层结点
    14.高度最小的BST
    *13.有向路径检查
    12.二叉树平衡检查
    11.双栈排序
    10.回文链表
    9.链式A+B
    8.链表分割
    7.访问单个节点的删除
  • 原文地址:https://www.cnblogs.com/f-ck-need-u/p/9887233.html
Copyright © 2020-2023  润新知