在刚开始学习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语言中的屏蔽现象的更多相关文章

  1. 详解 Go 语言中的 time.Duration 类型

    swardsman详解 Go 语言中的 time.Duration 类型swardsman · 2018-03-17 23:10:54 · 5448 次点击 · 预计阅读时间 5 分钟 · 31分钟之 ...

  2. 详解Python编程中基本的数学计算使用

    详解Python编程中基本的数学计算使用 在Python中,对数的规定比较简单,基本在小学数学水平即可理解. 那么,做为零基础学习这,也就从计算小学数学题目开始吧.因为从这里开始,数学的基础知识列位肯 ...

  3. 详解go语言的array和slice 【二】

    上一篇已经讲解过,array和slice的一些基本用法,使用array和slice时需要注意的地方,特别是slice需要注意的地方比较多.上一篇的最后讲解到创建新的slice时使用第三个索引来限制sl ...

  4. zz详解深度学习中的Normalization,BN/LN/WN

    详解深度学习中的Normalization,BN/LN/WN 讲得是相当之透彻清晰了 深度神经网络模型训练之难众所周知,其中一个重要的现象就是 Internal Covariate Shift. Ba ...

  5. 详解Go语言调度循环源码实现

    转载请声明出处哦~,本篇文章发布于luozhiyun的博客: https://www.luozhiyun.com/archives/448 本文使用的go的源码15.7 概述 提到"调度&q ...

  6. 详解 $_SERVER 函数中QUERY_STRING和REQUEST_URI区别

    详解 $_SERVER 函数中QUERY_STRING和REQUEST_URI区别 http://blog.sina.com.cn/s/blog_686999de0100jgda.html   实例: ...

  7. 详解jquery插件中(function ( $, window, document, undefined )的作用。

    1.(function(window,undefined){})(window); Q:(function(window,undefined){})(window);中为什么要将window和unde ...

  8. [转载]详解网络传输中的三张表,MAC地址表、ARP缓存表以及路由表

    [转载]详解网络传输中的三张表,MAC地址表.ARP缓存表以及路由表 虽然学过了计算机网络,但是这部分还是有点乱.正好在网上看到了一篇文章,讲的很透彻,转载过来康康. 本文出自 "邓奇的Bl ...

  9. 详解WebService开发中四个常见问题(2)

    详解WebService开发中四个常见问题(2)   WebService开发中经常会碰到诸如WebService与方法重载.循环引用.数据被穿该等等问题.本文会给大家一些很好的解决方法. AD:WO ...

随机推荐

  1. js学习笔记29----拖拽

    原理:先计算鼠标与拖拽目标的左侧距离 跟 上面距离,再计算拖动后的位置. 示例代码: <!DOCTYPE html> <html lang="en"> &l ...

  2. GDB十分钟教程 (链接)

    未联系作者,只能放个链接了. 十分赞的gdb教程. GDB十分钟教程

  3. 利用kseq.h parse fasta/fastq 文件

    在分析中经常需要统计fasta/fastq文件的序列数和碱基数, 但是没有找到一些专门做这件事的小工具,可能是这个功能太简单了: 之前用自己写的perl的脚本统计这些信息, 当fastq文件非常大时, ...

  4. perl 面向对象编程

    今天看到一个perl面向对象编程的例子,充分体现了如何对数据进行封装: 自己模仿写一个读取配置文件的例子, 配置文件的内容如下 samtools_binary = /usr/bin/samtools ...

  5. 【Java NIO的深入研究2】RandomAccessFile的使用

    RandomAccessFile RandomAccessFile是用来访问那些保存数据记录的文件的,你就可以用seek( )方法来访问记录,并进行读写了.这些记录的大小不必相同:但是其大小和位置必须 ...

  6. opengl 模板测试 glStencilOp glStencilFunc

    下面来设置蒙板缓存和蒙板测试. 首先我们启用蒙板测试,这样就可以修改蒙板缓存中的值. 下面我们来解释蒙板测试函数的含义: 当你使用glEnable(GL_STENCIL_TEST)启用蒙板测试之后,蒙 ...

  7. Android 下使用 JSON 实现 HTTP 请求

    不得不说,JSON 格式的确是非常美妙的,速度快而且简化了很多操作在 Android 下,Android SDK 已经为我们封装好了整个与 JSON 有关的操作,使用非常方便 以下就是一个标准的 JS ...

  8. mysql数据库,如何在登录mysql之后执行操作系统上的SQL脚本?

    需求描述: 通过mysql客户端登录到mysql数据库,如何执行操作系统上的SQL脚本文件呢? 操作过程: 1.编写测试脚本文件 [mysql@redhat6 scripts]$ cat SeCoun ...

  9. 制作SD卡启动自己编译的uboot.bin

    README for FriendlyARM Tiny4412 -----------------------------------------------------1. Build uboot ...

  10. cocos2d-x 2.2 创建项目

    楼主用的是2.2版本号 曾经的版本号是要在vs中加入模版 建立项目 但新版本号更新后使用python建立项目 最好是python2.7以上 找到create_project.py文件所在路径  too ...