详解Go语言中的屏蔽现象
在刚开始学习Go语言的过程中,难免会遇到一些问题,尤其是从其他语言转向Go开发的人员,面对语法及其内部实现的差异,在使用Go开发时也避免不了会踩“坑”。本文主要针对Go设计中的屏蔽现象进行详细的说明,我主要从变量屏蔽和方法屏蔽的角度去分析Go中的屏蔽现象。
程序实体的作用域
在开始分析变量的屏蔽之前,我们先来理解一下Go语言中的作用域,Go语言的作用域决定了一个程序实体可以被访问的范围。在Go语言的组织架构中,程序实体被层层嵌套的代码块包裹,正是这些代码块决定了程序实体的访问权限,Go 语言中程序实体的访问权限主要体现三种形式:包级别私有,模块级别私有(internal)以及公开的访问权限。包级别私有和模块级别私有对应的是代码包代码块,而公开的访问权限则是对应全域代码块。然而更细粒度的权限控制代码块还体现在函数,循环体内等。在Go语言层面其实就是依据代码块对程序实体作用域进行的定义。
变量的屏蔽现象
根据对作用域的理解,程序实体的访问权限由代码块控制,变量也属于程序实体, 嵌套的代码块导致变量出现被屏蔽的现象。
package main
import "fmt"
var name = struct{}{}
func main() {
name := "vitamin "
{
name := 0
fmt.Println(name)
}
fmt.Println(name)
}
点击运行
main包中的name变量被main函数中的同名变量“屏蔽”,而main函数中的name变量又被子代码块内部的声明的同名变量所“屏蔽”,而且这里的同名变量类型可以不同。为什么会出现这种“屏蔽”现象?其实这也暗示了Go语言变量的查找过程,当然这个过程并不限制于变量,而是适用于所有程序实体。
程序实体总是优先查找当前所在的代码块,如果当前代码块内查找不到该程序实体的定义则会根据嵌套关系直接向嵌套该代码块的内部查找,循环往复,直到在当前代码块所属的包内查找,如果包内仍然找不到该变量的定义,程序就会出现编译错误。
但是在这里有一个特殊情况:通过import .的方式静态导入的代码包,会将导入的代码包内公开的程序实体当作是当前源码文件的程序实体。这样在当前源码文件内查找不到程序实体的定义后,会在该代码包中查找,若仍然查到不到,才会去该源码文件所在的代码包中去查找。
方法的屏蔽现象
在Go语言中方法是定义在某个自定义数据类型(不能是接口类型)上的特殊函数,那在方法的定义上会不会出现和变量一样的“屏蔽”现象呢,让我们以结构体为例看几个例子
在结构体内定义同名的字段和方法
type Superhero struct{
FightWay string
}
func(c Superhero) FightWay()string{
return c.FightWay
}
func main() {
s:=Superhero{}
fmt.Println(s.FightWay)
}
点击运行
运行代码后我们会看到这样的编译错误: "type Superhero has both field and method named FightWay",这也直接的说明了在结构体内部是不允许出现这种定义方式的,所以也根本不存在“屏蔽”的现象。
在结构体内定义同名但不同签名的方法
在某些编程语言中,这种定义方式可能叫做“重载”,是实现面向对象中多态的一种表现形式,那么在Go语言中又会是怎么样呢?
type Superhero struct{
FightWay string
}
func(c Superhero) FightWay()string{
return c.FightWay
}
func(c *Superhero) FightWay(way string){
c.FightWay = way
}
func main() {
s:=Superhero{}
fmt.Println(s.FightWay())
}
点击运行
运行代码后编译出现错误: "type Superhero has both field and method named FightWay",看来这种定义方式在Go语言中也是不允许的,所以也不存在“屏蔽”的现象。
在含内嵌类型的结构体和被内嵌类型结构体之中定义同名的属性
在Go语言中并不存在继承的概念,而是以一种更加灵活地方式:组合类型(内嵌类型),来实现对多个结构体之间的组合,并将内嵌类型的属性和方法嫁接到了组合类型,避免了继承这种关系导致结构体之间的强依赖和繁琐的多重继承问题。
type Superhero struct {
FightWay string
}
type Ironman struct {
FightWay string
Superhero
}
func main() {
i:=Ironman{ FightWay: "Steel Armor", Superhero:Superhero{ "The Avengers" }}
fmt.Println(i.FightWay)
}
点击运行
由输出结果可以看到组合的结构体(Ironman)内的 FightWay 属性“屏蔽”了被内嵌类型(Superhero)的同名属性
在含内嵌类型的结构体和被内嵌类型结构体之中定义同名的属性和方法
type Superhero struct {
FightWay string
}
type Ironman struct {
Superhero
}
func(i Ironman )FightWay()string{
return i.Superhero.FightWay
}
func main() {
i:=Ironman{ Superhero:Superhero{ "The Avengers" }}
fmt.Println(i.FightWay)
}
点击运行
运行程序后我们发现编译报错: “Println arg i.FightWay is a func value, not called”,这也间接的说明了组合类型(Ironman)中定义的方法 FightWay()string “屏蔽”了内嵌类型(Superhero)中的同名属性,,反过来我们尝试一下
type Superhero struct {
}
func(s Superhero)FightWay()string{
return "The Avengers"
}
type Ironman struct {
FightWay string
Superhero
}
func main() {
i:=Ironman{ FightWay:"Steel Armor" }
fmt.Println(i.FightWay())
}
点击运行
运行代码后我们同样会看到编译错误: "cannot call non-function i.FightWay (type string)",这也从反面论证了在组合类型和内嵌类型中声明同名的属性和方法之间的互相“屏蔽“现象。
在含内嵌类型的结构体和被内嵌类型结构体之中定义同名但不同签名的方法
type Superhero struct { }
func(s Superhero)FightWay()string{
return "The Avengers"
}
type Ironman struct {
Name string
Superhero
}
func(i *Ironman) FightWay(name string){
i.Name=name
}
func main() {
i:=Ironman{ Name:"Iron Man" }
i.FightWay()
fmt.Println(i.Name)
}
点击运行
运行代码后我们发现编译器报错: "not enough arguments in call to i.FightWay",在组合类型和被内嵌类型之间定义的同名不同签名的方法同样存在“屏蔽”现象。
上面我们演示的都是看上去不是在同一层面的结构,那么对于多个内嵌类型处于同一层面,这时会不会发生变量或者是方法的屏蔽现象呢
在同一层面的内嵌类型的结构体内定义同名的属性
type Superhero struct {
Name string
}
type Skill struct{
Name string
}
type Ironman struct {
Superhero
Skill
}
func main() {
i:=Ironman{ Superhero:Superhero{"Iron Man"},Skill:Skill{ Name:"Steel Armor" } }
fmt.Println(i.Name)
}
点击运行
当我们运行代码后,会发现编译器输出这样一句话:“ambiguous selector i.Name”,这是由于组合类型就是把内嵌类型内的变量嫁接到组合类型,此时在组合类型内存在两个一样的属性,当属性被调用的时候,编译器也不知道该调用哪一个,跟内嵌类型在组合类型内出现的顺序没有关系。当然编译不通过也就不会涉及“屏蔽”现象了。
在同一层面的内嵌类型的结构体内定义同名的变量和方法
type Superhero struct {
Name string
}
type Skill struct{}
func(s Skill) Name()string{
return "Hero`s Skill"
}
type Ironman struct {
Superhero
Skill
}
func main() {
i:=Ironman{ Superhero:Superhero{"Iron Man"} }
fmt.Println(i.Name)
}
点击运行
当我们运行代码之后,也是会出现编译错误:“ambiguous selector i.Name” 。接下来我们在看看方法会不会也出现类似的编译错误
在同一层面的内嵌类型的结构体内定义同名但不同签名的方法
type Superhero struct { }
func(s Superhero) Name()string{
return "Super Hero "
}
type Skill struct{}
func(s Skill) Name()(string,error){
return "Hero`s Skill",nil
}
type Ironman struct {
Superhero
Skill
}
func main() {
i:=Ironman{ }
fmt.Println(i.Name())
}
点击运行
运行代码后,诶!还是编译错误:“ambiguous selector i.Name”。
其实,无论组合类型中内嵌了多少个其他类型又或者是这些内嵌类型又内嵌了其他内嵌类型,出于最深层次的属性或者是方法被上层同名属性或方法“屏蔽”的概率就越高;当处于同一层面的多个内嵌类型之间含有同性的属性或者是方法时,从错误信息也可得知,编译器不知如何选择被调用的属性或者方法,从而引发编译错误,当然这也很容易理解。
好了,上面我们针对嵌入的类型都是非指针类型,如果在结构内嵌入的是指针类型又会是怎么样的呢?
在结构体内嵌入某个类型的指针类型
当我们在组合类型内嵌入某个自定义类型的指针类型时,仍然像上述那样出现各种“屏蔽”和编译不通过的现象。但是他们之间的区分在于值类型方法和指针类型的方法对应实现接口的是不相同的(不含实现0个的情况)。
如何访问被屏蔽的信息
对于访问被“屏蔽”的属性或者是方法,我们可以通过使用组合类型实例+“.”+内嵌类型的类型名称+“.”+被“屏蔽”的属性或方法的链式编程的形式访问到
type Superhero struct {
FightWay string
}
type Ironman struct {
FightWay string
Superhero
}
func main() {
i:=Ironman{ FightWay: "Steel Armor", Superhero:Superhero{ "The Avengers" }}
fmt.Println(i.Superhero.FightWay)
}
点击运行
运行代码后,我们会看到结果:“The Avengers”,而这个值正是被组合类型同名属性“屏蔽”掉属性的值。
屏蔽可以带来哪些好处
虽然Go语言设计形式允许存在“屏蔽”现象,在“屏蔽”的同时也给我们带来了一些好处,下面我们来谈谈:
组合类型的方法可以对内嵌类型的同名方法进行包装,扩展,这也是在面向对象开发过程中常用的一种手法。
type Task struct{}
func(t Task) Do(){
fmt.Println("击败灭霸,救回队友")
}
type Ironman struct {
Task
}
func(r Ironman) Do(){
fmt.Println("提供成熟的技术方案")
r.Task.Do()
fmt.Println("在战斗中负责打响指,自我牺牲")
}
func main() {
i:=Ironman{ }
i.Do()
}
点击运行
在这场终局之战中,整个联盟的任务就是击败灭霸,救回队友,而钢铁侠的任务除了基本任务外,还负责成熟的技术支持和在战斗中自我牺牲。
总结
- 在相同的代码包不同作用域下的同名变量或则是方法之间存在屏蔽现象
- 在相同结构内定义同名的属性或是方法不存在屏蔽现象,编译不通过
- 在内嵌类型和被内嵌类型之间的定义同名的属性或者是方法存在屏蔽现象
- 在同一平面的内嵌类型之间定义同名的方法或者是属性不存在屏蔽现象,编译不通过
本文只是对一些常见的“屏蔽”现象进行简单的说明和演示,让在初学Go语言的过程中对这种现象有一个初步的了解,碰见该现象时知道是怎么回事,从而采取措施避过这种现象或者是从现象中获益。
注:本文所编写的例子均为做演示使用,可能存在逻辑不通或拼写错误的情况,还望见谅。
详解Go语言中的屏蔽现象的更多相关文章
- 详解 Go 语言中的 time.Duration 类型
swardsman详解 Go 语言中的 time.Duration 类型swardsman · 2018-03-17 23:10:54 · 5448 次点击 · 预计阅读时间 5 分钟 · 31分钟之 ...
- 详解Python编程中基本的数学计算使用
详解Python编程中基本的数学计算使用 在Python中,对数的规定比较简单,基本在小学数学水平即可理解. 那么,做为零基础学习这,也就从计算小学数学题目开始吧.因为从这里开始,数学的基础知识列位肯 ...
- 详解go语言的array和slice 【二】
上一篇已经讲解过,array和slice的一些基本用法,使用array和slice时需要注意的地方,特别是slice需要注意的地方比较多.上一篇的最后讲解到创建新的slice时使用第三个索引来限制sl ...
- zz详解深度学习中的Normalization,BN/LN/WN
详解深度学习中的Normalization,BN/LN/WN 讲得是相当之透彻清晰了 深度神经网络模型训练之难众所周知,其中一个重要的现象就是 Internal Covariate Shift. Ba ...
- 详解Go语言调度循环源码实现
转载请声明出处哦~,本篇文章发布于luozhiyun的博客: https://www.luozhiyun.com/archives/448 本文使用的go的源码15.7 概述 提到"调度&q ...
- 详解 $_SERVER 函数中QUERY_STRING和REQUEST_URI区别
详解 $_SERVER 函数中QUERY_STRING和REQUEST_URI区别 http://blog.sina.com.cn/s/blog_686999de0100jgda.html 实例: ...
- 详解jquery插件中(function ( $, window, document, undefined )的作用。
1.(function(window,undefined){})(window); Q:(function(window,undefined){})(window);中为什么要将window和unde ...
- [转载]详解网络传输中的三张表,MAC地址表、ARP缓存表以及路由表
[转载]详解网络传输中的三张表,MAC地址表.ARP缓存表以及路由表 虽然学过了计算机网络,但是这部分还是有点乱.正好在网上看到了一篇文章,讲的很透彻,转载过来康康. 本文出自 "邓奇的Bl ...
- 详解WebService开发中四个常见问题(2)
详解WebService开发中四个常见问题(2) WebService开发中经常会碰到诸如WebService与方法重载.循环引用.数据被穿该等等问题.本文会给大家一些很好的解决方法. AD:WO ...
随机推荐
- 查询相应id下的数据
---恢复内容开始--- u方法这样的:带不起模板引擎 <a href="{:U('Del/wzxg','','')}/{$vo.id}">修改</a> 这 ...
- 关于Cocos2d-x中addchild和removeChild方法的参数的解析
一.addchild virtual void addchild( Node * child , int localZOrder , int tag )添加一个子节点到容器中,有Z轴顺序和一个标记. ...
- dlib VS2013 face关键点检测与对齐
http://blog.csdn.net/hust_bochu_xuchao/article/details/53906223 http://blog.csdn.net/xiamentingtao/a ...
- am335x hid-multitouch.c
am335x在使用电容屏,需要加载hid-multitouch.ko模块 由下面文件生成 kernel/drivers/hid/hid-multitouch.c 内核中编译模块 make module ...
- ubuntu下使用VI编辑文件必知的常用命令
进入vi的命令 vi filename :打开或新建文件,并将光标置于第一行首 vi +n filename :打开文件,并将光标置于第n行首 vi + filename :打开文件,并将光标置于最后 ...
- RTC终于tm的通了
ITDS(1316336566) 2014-1-16 10:34:36我们板子上用的是pcf8563默认没使用这个,用图形界面选择下这个完以后,在配置下就这两步骤ITDS(1316336566) 2 ...
- (转)引用---FFMPEG解码过程
视频播放过程 首先简单介绍以下视频文件的相关知识.我们平时看到的视频文件有许多格式,比如 avi, mkv, rmvb, mov, mp4等等,这些被称为容器(Container), 不同的容器格式规 ...
- 一个牛人给Java初学者的建议
学习Java的同学注意了!!! 学习过程中遇到什么问题或者想获取学习资源的话,欢迎加入Java学习交流群,群号码:618528494 我们一起学Java! 给初学者之一:浅谈Java及应用学java ...
- apk 反编译工具的使用
在学习android 开发的时候,我们经常回尝试使用到别人的apk,希望能了解别人怎么编写的代码,于是想要一个能实现其反编译的软件,将软件反编译出来,查看其代码. 工具/原料 反编译软件dex2jar ...
- require() 方法讲解
require.config({ paths:{ "jquery":"jquery.min", "underscore":"und ...