详解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 ...
随机推荐
- node-webkit读取json文件
1.原理 node-webkit包含了node.js,node.js提供了处理json数据文件的方法,通过node.js提供的方法,我们可以比较方便地读取json文件. 2.示例 这里我们读取的文件是 ...
- s:debug标签的错误ConcurrentModificationException
搭建SSH的时候页面上加入<s:debug>标签后台出现 严重: Servlet.service() for servlet jsp threw exception java.util.C ...
- 12款优秀 jQuery Ajax 分页插件和教程
12款优秀 jQuery Ajax 分页插件和教程 在这篇文章中,我为大家收集了12个基于 jQuery 框架的 Ajax 分页插件,这些插件都提供了详细的使用教程和演示.Ajax 技术的出现使得 W ...
- Python之多进程
1.Pool的用法 #!/usr/bin/env python # -*- coding: utf-8 -*- ''' @author: Shiyu Huang @contact: huangsy13 ...
- free 和delete 把指针怎么啦?
别看 free 和 delete 的名字恶狠狠的(尤其是 delete),它们只是把指针所指的内存给 释放掉,但并没有把指针本身干掉. 发现指针 p 被 free 以后其地址仍然不变(非 NULL), ...
- 最简单的基于FFMPEG+SDL的音频播放器 ver2 (採用SDL2.0)
===================================================== 最简单的基于FFmpeg的音频播放器系列文章列表: <最简单的基于FFMPEG+SDL ...
- 【Java面试题】57 short s1 = 1; s1 = s1 + 1;有什么错? short s1 = 1; s1 += 1;有什么错?
Java规范有这样的规则 [ 1.高位转低位需要强制转换 2.低位转高位自动转. ] short s1 = 1; s1 = s1 + 1;有什么错? 答: i 是int 型 s1 short型 通 ...
- squid2.7安装与配置
CleverCode近期研究了一下squid的安装与配置. 如今总结一下.分享给大家. 1 简单介绍 代理server英文全称是Proxy Server,其功能就是代理网络用户去取得网络信息. Squ ...
- 第五章 使用 SqlSession(MyBatis)
在 MyBatis 中,你可以使用 SqlSessionFactory 来创建 SqlSession.一旦你获得一个 session 之后,你可以使用它来执行映射语句,提交或回滚连接,最后,当不再需要 ...
- WPF: 把引用的dll移动到自定义路径
需求: 有A.exe和B.exe, 都引用了 C.dll, output路径都是 W:\Debug. A和B都添加了对C的引用,正常情况下C会被复制到 output 里面. C这样子的dll很多,不想 ...