struct的属性是否被导出,也遵循大小写的原则:首字母大写的被导出,首字母小写的不被导出。安全
因此:函数
也就是说,struct的导出状况是混合的。测试
但并不是绝对如此,若是struct嵌套了,那么即便被嵌套在内部的struct名称首字母小写,也能访问到它里面首字母大写的字段。线程
例如:指针
type animal struct{ name string Speak string } type Horse struct { animal sound string }
Horse中嵌套的animal是小写字母开头的,但Horse是能被导出的,因此能在其它包中使用Horse struct,其余包也能访问到animal中的Speak属性。code
不少时候,Horse这个名字是不安全的,由于这表示导出Horse这个struct给其余包,也就是将Horse给暴露出去了,外界能够直接打开Horse这个"黑匣子"。对象
但若是不将Horse导出,如何能在其它包构建出Horse实例?见下文。string
不少时候,不该该将某包(如包abc)中的struct(如animal)直接暴露给其它包,暴露意味着打开了那个"黑匣子",因此struct会以小写字母开头,不将其导出。test
这时在外界其它包中构建包abc的animal,就无法直接经过如下几种方式实现:import
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的方法:
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()
正常。