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()正常。

Go基础系列:struct的导出和暴露问题的更多相关文章

  1. C#基础系列——小话泛型

    前言:前面两章介绍了C#的两个常用技术:C#基础系列——反射笔记 和 C#基础系列——Attribute特性使用 .这一章来总结下C#泛型技术的使用.据博主的使用经历,觉得泛型也是为了重用而生的,并且 ...

  2. C#基础系列——异步编程初探:async和await

    前言:前面有篇从应用层面上面介绍了下多线程的几种用法,有博友就说到了async, await等新语法.确实,没有异步的多线程是单调的.乏味的,async和await是出现在C#5.0之后,它的出现给了 ...

  3. 你所不知道的ASP.NET Core MVC/WebApi基础系列(一)

    前言 最近发表的EF Core貌似有点多,可别误以为我只专攻EF Core哦,私下有时间也是一直在看ASP.NET Core的内容,所以后续会穿插讲EF Core和ASP.NET Core,别认为你会 ...

  4. JVM基础系列第15讲:JDK性能监控命令

    查看虚拟机进程:jps 命令 jps 命令可以列出所有的 Java 进程.如果 jps 不加任何参数,可以列出 Java 程序的进程 ID 以及 Main 函数短名称,如下所示. $ jps 6540 ...

  5. Spring基础系列--AOP织入逻辑跟踪

    原创作品,可以转载,但是请标注出处地址:https://www.cnblogs.com/V1haoge/p/9619910.html 其实在之前的源码解读里面,关于织入的部分并没有说清楚,那些前置.后 ...

  6. Spring基础系列-AOP源码分析

    原创作品,可以转载,但是请标注出处地址:https://www.cnblogs.com/V1haoge/p/9560803.html 一.概述 Spring的两大特性:IOC和AOP. AOP是面向切 ...

  7. Spring基础系列-Spring事务不生效的问题与循环依赖问题

    原创作品,可以转载,但是请标注出处地址:https://www.cnblogs.com/V1haoge/p/9476550.html 一.提出问题 不知道你是否遇到过这样的情况,在ssm框架中开发we ...

  8. mybatis基础系列(二)——基础语法、别名、输入映射、输出映射

    增删改查 mapper根节点及其子节点 mybatis框架需要读取映射文件创建会话工厂,映射文件是以<mapper>作为根节点,在根节点中支持9个元素,分别为insert.update.d ...

  9. c#基础系列3---深入理解ref 和out

    "大菜":源于自己刚踏入猿途混沌时起,自我感觉不是一般的菜,因而得名"大菜",于自身共勉. 扩展阅读 c#基础系列1---深入理解 值类型和引用类型 c#基础系 ...

随机推荐

  1. Unity自动切割动画

    最近在开发项目时,需要处理大量的动画,于是就网上查找资料,然后写了这么编辑器工具: 就是在模型导入时,根据配置文件自动切割动画. 首先我们需要封装两个类:一个模型类和一个动画类 public clas ...

  2. 基础项目构建,引入web模块,完成一个简单的RESTful API 转载来自翟永超

    简介 在您第一次接触和学习Spring框架的时候,是否因为其繁杂的配置而退却了?在你第n次使用Spring框架的时候,是否觉得一堆反复粘贴的配置有一些厌烦?那么您就不妨来试试使用Spring Boot ...

  3. springboot 不同环境切换不同的配置文件

    开发的流程是本地>测试>预发布>正式,所以不同的环境,肯定是不同的配置文件,所以我们需要针对不同的环境做不同的配置切换. 下面我们来说说 springboot 是怎么来切换的: 1. ...

  4. Node.js中实现套接字服务

    后端服务的一个重要的部分是通过套接字进行通信的能力. 套接字允许一个进程通过一个IP地址和端口与另一个进程通信 同一个服务器上的两个不同进程的进程间通信(IPC)或者访问一个完全不同 的服务器上运行的 ...

  5. hadoop配置笔记

    接上回,hadoop的配置文件都在下载的压缩包目录中的etc/hadoop/中 hadoop-env.sh有个地方配置java_home 其他常用的设置文件有: core-site.xml yarn- ...

  6. #224 Profile Lookup (for in & if )

    我们有一个对象数组,里面存储着通讯录. 函数 lookUp 有两个预定义参数:firstName值和prop属性 . 函数将会检查通讯录中是否存在一个与传入的 firstName 相同的联系人.如果存 ...

  7. @ResponseBody 返回乱码 的解决办法

    1:最快的  最简单的办法是  在Ajax请求脸面指定头信息Accept属性,StringHttpMessageConverter默认iso-8859-1编码,但是会根据请求头信息指定的编码格式来转换 ...

  8. orcale mysql基本的分页查询法

    orcale分页查询sql语句: SELECT * FROM ( SELECT A.*, ROWNUM RN FROM (SELECT * FROM TABLE_NAME) A WHERE ROWNU ...

  9. SpringDataJPA

    看着自己弟弟在成都聚全家之力盘一套房, 看着自己二哥,在成都也为车贷房贷奔波劳累,身心俱惫, 生活不易啊,这个社会环境下,就像从数据库拿数据一样,只拿我们想要的,或许会活的滋润很多吧. 最近的这个项目 ...

  10. python爬虫学习之查询IP地址对应的归属地

    话不多说,直接上代码吧. import requests def getIpAddr(url): response = requests.get(url) response.encoding=resp ...