go语言错题集(坑)【三】_type *shopi.shopclient is pointer to interface, no-程序员宅基地

技术标签: go  

系列相关:

go语言错题集(坑)【一】

go语言错题集(坑)【二】

go语言错题集(坑)【三】

目录

 

不要对Go并发函数的执行时机做任何假设

假设T类型的方法上接收器既有T类型的,又有*T指针类型的,那么就不可以在不能寻址的T值上调用*T接收器的方法

一个包含nil指针的接口不是nil接口

将map转化为json字符串的时候,json字符串中的顺序和map赋值顺序无关

Json反序列化数字到interface{}类型的值中,默认解析为float64类型

即使在有多个变量、且有的变量存在有的变量不存在、且这些变量共同赋值的情况下,也不可以使用:=来给全局变量赋值

*interface 是一个指向interface的指针类型,而不是interface类型


不要对Go并发函数的执行时机做任何假设

请看下列的列子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import (
	"fmt"
	"runtime"
	"time"
)

func main(){
	names := []string{"lily", "yoyo", "cersei", "rose", "annei"}
	for _, name := range names{
		go func(){
			fmt.Println(name)
		}()
	}
	runtime.GOMAXPROCS(1)
	runtime.Gosched()
}

请问输出什么?

答案:

1
2
3
4
5
annei
annei
annei
annei
annei

为什么呢?是不是有点诧异?
输出的都是“annei”,而“annei”又是“names”的最后一个元素,那么也就是说程序打印出了最后一个元素的值,而name对于匿名函数来讲又是一个外部的值。因此,我们可以做一个推断:虽然每次循环都启用了一个协程,但是这些协程都是引用了外部的变量,当协程创建完毕,再执行打印动作的时候,name的值已经不知道变为啥了,因为主函数协程也在跑,大家并行,但是在此由于names数组长度太小,当协程创建完毕后,主函数循环早已结束,所以,打印出来的都是遍历的names最后的那一个元素“annei”。
如何证实以上的推断呢?
其实很简单,每次循环结束后,停顿一段时间,等待协程打印当前的name便可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import (
	"fmt"
	"runtime"
	"time"
)

func main(){
	names := []string{"lily", "yoyo", "cersei", "rose", "annei"}
	for _, name := range names{
		go func(){
			fmt.Println(name)
		}()
		time.Sleep(time.Second)
	}
	runtime.GOMAXPROCS(1)
	runtime.Gosched()
}

打印结果:

1
2
3
4
5
lily
yoyo
cersei
rose
annei

以上我们得出一个结论,不要对“go函数”的执行时机做任何的假设,除非你确实能做出让这种假设成为绝对事实的保证。

假设T类型的方法上接收器既有T类型的,又有*T指针类型的,那么就不可以在不能寻址的T值上调用*T接收器的方法

请看代码,试问能正常编译通过吗?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import (
	"fmt"
)
type Lili struct{
	Name string
}

func (Lili *Lili) fmtPointer(){
	fmt.Println("poniter")
}

func (Lili Lili) fmtReference(){
	fmt.Println("reference")
}


func main(){
	li := Lili{}
	li.fmtPointer()
}

答案:

1
能正常编译通过,并输出"poniter"

感觉有点诧异,请接着看以下的代码,试问能编译通过?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import (
	"fmt"
)
type Lili struct{
	Name string
}

func (Lili *Lili) fmtPointer(){
	fmt.Println("poniter")
}

func (Lili Lili) fmtReference(){
	fmt.Println("reference")
}


func main(){
	Lili{}.fmtPointer()
}

答案:

1
2
3
不能编译通过。
“cannot call pointer method on Lili literal”
“cannot take the address of Lili literal”

是不是有点奇怪?这是为什么呢?其实在第一个代码示例中,main主函数中的“li”是一个变量,li的虽然是类型Lili,但是li是可以寻址的,&li的类型是*Lili,因此可以调用*Lili的方法。

一个包含nil指针的接口不是nil接口

请看下列代码,试问返回什么

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import (
	"bytes"
	"fmt"
	"io"
)

const debug = true

func main(){
	var buf *bytes.Buffer
	if debug{
		buf = new(bytes.Buffer)
	}
	f(buf)
}
func f(out io.Writer){

	if out != nil{
		fmt.Println("surprise!")
	}
}

答案是输出:surprise。
ok,让我们吧debug开关关掉,及debug的值变为false。那么输出什么呢?是不是什么都不输出?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import (
	"bytes"
	"fmt"
	"io"
)

const debug = false

func main(){
	var buf *bytes.Buffer
	if debug{
		buf = new(bytes.Buffer)
	}
	f(buf)
}
func f(out io.Writer){

	if out != nil{
		fmt.Println("surprise!")
	}
}

答案是:依然输出surprise。

这是为什么呢?
这就牵扯到一个概念了,是关于接口值的。概念上讲一个接口的值分为两部分:一部分是类型,一部分是类型对应的值,他们分别叫:动态类型和动态值。类型系统是针对编译型语言的,类型是编译期的概念,因此类型不是一个值。
在上述代码中,给f函数的out参数赋了一个*bytes.Buffer的空指针,所以out的动态值是nil。然而它的动态类型是bytes.Buffer,意思是:“A non-nil interface containing a nil pointer”,所以“out!=nil”的结果依然是true。
但是,对于直接的``
bytes.Buffer``类型的判空不会出现此问题。

1
2
3
4
5
6
7
8
9
10
11
import (
	"bytes"
	"fmt"
)

func main(){
	var buf *bytes.Buffer
	if buf == nil{
		fmt.Println("right")
	}
}

还是输出: right
只有 接口指针 传入函数的接口参数时,才会出现以上的坑。
修改起来也很方便,把*bytes.Buffer改为io.Writer就好了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import (
	"bytes"
	"fmt"
	"io"
)
const debug = false
func main(){
	var buf  io.Writer //原来是var buf *bytes.Buffer
	if debug{
		buf = new(bytes.Buffer)
	}
	f(buf)
}
func f(out io.Writer){
	if out != nil{
		fmt.Println("surprise!")
	}
}

将map转化为json字符串的时候,json字符串中的顺序和map赋值顺序无关

请看下列代码,请问输出什么?若为json字符串,则json字符串中key的顺序是什么?

1
2
3
4
5
6
7
8
9
10
func main() {
	params := make(map[string]string)

	params["id"] = "1"
	params["id1"] = "3"
	params["controller"] = "sections"

	data, _ := json.Marshal(params)
	fmt.Println(string(data))
}

答案:输出{"controller":"sections","id":"1","id1":"3"}
利用Golang自带的json转换包转换,会将map中key的顺序改为字母顺序,而不是map的赋值顺序。map这个结构哪怕利用for range遍历的时候,其中的key也是无序的,可以理解为map就是个无序的结构,和php中的array要区分开来

Json反序列化数字到interface{}类型的值中,默认解析为float64类型

请看以下程序,程序想要输出json数据中整型id加上3的值,请问程序会报错吗?

1
2
3
4
5
6
7
8
9
10
11
12
func main(){
	jsonStr := `{"id":1058,"name":"RyuGou"}`
	var jsonData map[string]interface{}
	json.Unmarshal([]byte(jsonStr), &jsonData)

	sum :=  jsonData["id"].(int) + 3
	fmt.Println(sum)
}

``` 
答案是会报错,输出结果为:

panic: interface conversion: interface {} is float64, not int

1
使用 Golang 解析 JSON  格式数据时,若以 interface{} 接收数据,则会按照下列规则进行解析:

bool, for JSON booleans

float64, for JSON numbers

string, for JSON strings

[]interface{}, for JSON arrays

map[string]interface{}, for JSON objects

nil for JSON null

1
2
3
4
5
6
7
8
9
10
11
应该改为:
```go
func main(){
	jsonStr := `{"id":1058,"name":"RyuGou"}`
	var jsonData map[string]interface{}
	json.Unmarshal([]byte(jsonStr), &jsonData)

	sum :=  int(jsonData["id"].(float64)) + 3
	fmt.Println(sum)
}

即使在有多个变量、且有的变量存在有的变量不存在、且这些变量共同赋值的情况下,也不可以使用:=来给全局变量赋值

:=往往是用来声明局部变量的,在多个变量赋值且有的值存在的情况下,:=也可以用来赋值使用,例如:

1
2
msgStr := "hello wolrd"
msgStr, err := "hello", errors.New("xxx")//err并不存在

但是,假如全局变量也使用类似的方式赋值,就会出现问题,请看下列代码,试问能编译通过吗?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var varTest string

func test(){
	varTest, err := function()
	fmt.Println(err.Error())
}

func function()(string, error){
	return "hello world", errors.New("error")
}


func main(){
	test()
}

答案是:通不过。输出:

1
varTest declared and not used

但是如果改成如下代码,就可以通过:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var varTest string

func test(){
	err := errors.New("error")
	varTest, err = function()
	fmt.Println(err.Error())
}

func function()(string, error){
	return "hello world", errors.New("error")
}


func main(){
	test()
}

输出:

1
error

这是什么原因呢?
答案其实很简单,在test方法中,如果使用varTest, err := function()这种方式的话,相当于在函数中又定义了一个和全局变量varTest名字相同的局部变量,而这个局部变量又没有使用,所以会编译不通过。

*interface 是一个指向interface的指针类型,而不是interface类型

请问以下代码,能编译通过吗?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import (
	"fmt"
)

type Father interface {
	Hello()
}


type Child struct {
	Name string
}

func (s Child)Hello()  {

}

func main(){
	var buf  Child
	buf = Child{}
	f(&buf)
}
func f(out *Father){
	if out != nil{
		fmt.Println("surprise!")
	}
}

答案是:不能编译通过。输出:

1
*Father is pointer to interface, not interface

注意了:接口类型的变量可以被赋值为实现接口的结构体的实例,但是并不能代表接口的指针可以被赋值为实现接口的结构体的指针实例。即:

1
var buf Father = Child{}

是对的,但是

1
var buf *Father = new(Child)

却是不对的。应该改为:

1
2
var buf Father = Child{}
var pointer *Father = &buf

要想让问题最开始的代码编译通过要将以上代码修改为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import (
	"fmt"
)

type Father interface {
	Hello()
}


type Child struct {
	Name string
}

func (s Child)Hello()  {

}

func main(){
	var buf  Father
	buf = Child{}
	f(&buf)
}
func f(out *Father){
	if out != nil{
		fmt.Println("surprise!")
	}
}

 

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/sinat_38068807/article/details/104012833

智能推荐

ehcache作为分布式缓存的研究-程序员宅基地

文章浏览阅读59次。ehcache支持两种拓扑结构,一种是Distributed Caching,另一种是Replicated CachingDistributed Caching这和一般意义上的分布式缓存非常类似,这一类型的缓存是有client-server之分的,application通过client向server端请求缓存数据,在server端,数据是散列到多个节点上的。具体而准确的拓扑结构见下图(该图正是..._ehcache 分布式模式 docker

ECCV 2022 | 视频插帧中的实时中间流估计-程序员宅基地

文章浏览阅读710次。概述视频插帧算法旨在视频的相邻帧之间生成若干个中间帧。它被广泛用于视频处理,多媒体播放器和显示设备上。本文提出了一种基于实时中间流估计的视频插帧算法 RIFE,包括一个端到端的高效的中间流估计网络 IFNet,以及基于特权蒸馏的光流监督框架。RIFE 支持在两帧之间的任意时刻点插帧,在多个数据集上达到了最先进的性能且不依赖于任何的预训练模型。相比目前流行的 SuperSlomo 和 DAIN 技术..._光流逆转

SCI论文写作常用表达整理_sci写作comments-程序员宅基地

文章浏览阅读3.6k次,点赞6次,收藏23次。目录解释为什么你的主题X是重要的概述X研究的过去和现在的历史(没有直接参考文献)描述X研究可能的未来指示知识上的差距和可能存在的局限性说明论文的目的及其贡献(Abstract,Introduction)解释你所在领域的关键术语解释你将如何在论文中使用术语和缩写给出论文的结构,包括什么与不包括什么全面介绍从过去到现在的文献回顾过去的文献回顾随后的和最..._sci写作comments

字节跳动云原生大数据平台运维管理实践_云原生在大数据平台的实践应用-程序员宅基地

文章浏览阅读1.2k次。字节跳动过去几年在支撑自身业务的过程中积累了很多大数据领域的引擎工具,目前也在探索将这些引擎工具的能力进行标准化、产品化的输出。组件繁多:大数据领域完成一项工作需要很多组件配合。比如分布式大数据存储及各种任务执行引擎:Flink、Spark 及各种 ETL 的 OLAP 工具和调度 ETL 的任务调度工具,还有支撑工具引擎的运行日志监控系统和项目用户权限的辅助系统等;部署复杂:这些系统的组件繁多,相互配合也非常复杂,导致部署变得困难。比如部署一套完整的生产环境,可能会涉及到多个依赖和配置管理。_云原生在大数据平台的实践应用

PFMEA详解结构分析——Sun FMEA软件_pfmea分析软件-程序员宅基地

文章浏览阅读220次。同样的道理,PFMEA也需要事先策划范围,一般的指导思想是,那些有安全或法律法规影响的、创新程度比较大的、可靠性要求比较高的部分需要重点关注,因为这些地方要么容易发生问题,要么一旦发生问题,影响就特别巨大。结构分析在表格软件中则是以不同列的形式表达不同层次的结构元素,中间列是关注元素,即过程,它的左边列是该过程所属的流程,而右边列是该过程的工作元素。因此,需要仔细地审查生产的价值流从而避免漏失。需要注意的是,FMEA是活的文件,它将一直伴随着产品和过程,为它们保驾护航,将产品和过程的风险保持在合理的范围。_pfmea分析软件

Maven的下载安装配置教程(详细图文)_maven下载-程序员宅基地

文章浏览阅读10w+次,点赞271次,收藏583次。目录一、简单了解一下什么是Maven二、maven的下载三、maven的安装四、maven的环境变量配置五、setting文件配置六、开发工具配置Maven一、简单了解一下什么是MavenMaven就是一款帮助程序员构建项目的工具,我们只需要告诉Maven需要哪些Jar 包,它会帮助我们下载所有的Jar,极大提升开发效率。1.Maven翻译为“专家“, ”内行”的意思,是著名Apache公司下基于Java开发的开源项目。2.Maven项目对象模型(POM)是一.._maven下载

随便推点

linux常用命令-curl命令详解(超详细)_linux curl命令详解-程序员宅基地

文章浏览阅读4.1k次,点赞6次,收藏13次。本文介绍了 `curl` 命令的常用选项和参数,以及示例用法。`curl` 是一个功能强大的命令行传输工具,用于发送请求和下载文件。常用选项包括保存文件、发送 POST 请求、自定义头部信息、指定请求方法、身份验证、跟随重定向、忽略 SSL 证书验证、静默模式和详细模式等。示例用法涵盖了下载文件、发送 POST 请求、附加头部信息、指定请求方法、身份验证、跟随重定向、忽略 SSL 证书验证、静默模式和详细模式等。这些选项和参数可以根据具体需求和场景进行灵活运用。_linux curl命令详解

golang微服务框架go-zero系列-2:在go-zero中使用jwt-token鉴权实践-程序员宅基地

文章浏览阅读2.6k次。阅读本文前你需要阅读金光灿灿的Gorm V2+适合创业的golang微服务框架go-zero实战创建项目生成go.mod文件以如下指令创建项目mkdir jwttokencd jwttokengo mod init jwttoken定义user.api本文设计API如下描述格式方法参数返回是否需要鉴权用户登录/open/authorizationpostmobile:手机号,passwd:密码,code:图片验证码id:用户ID,token:用户t

深度学习框架-Keras:特点、架构、应用和未来发展趋势_keras框架-程序员宅基地

文章浏览阅读1.5k次。深度学习是一种新兴的技术,已经在许多领域中得到广泛的应用,如计算机视觉、自然语言处理、语音识别等。Keras是深度学习的一种重要框架,它具有许多优点,如简单易用、模块化、多后端支持等。Keras核心是Keras的主要库,它提供了一些高级API,如Sequential和Functional API,可以方便地构建和训练神经网络模型。语音识别是另一个深度学习的重要领域,Keras可以在语音识别中得到广泛应用。Keras是一种简单易用的深度学习框架,它提供了一些高级API,可以方便地构建和训练神经网络模型。_keras框架

点滴学习Linux --- Vim 代码块缩进快捷键_linux代码缩进快捷键-程序员宅基地

文章浏览阅读6k次,点赞4次,收藏3次。使用vim进行代码编写时,我们进行需要对代码进行对其。本人之前采用的都是笨方法,一行一行的对其,这样不但效率低,而且还可能出现错误,那vim中有没有自带代码缩紧功能呢?答案是肯定的。1.使用Shift +V 选择你要缩进的行2.按下‘=’即可是不是很简单?_linux代码缩进快捷键

Docker Review - Docker 概念 & 入门篇_review docker tool-程序员宅基地

文章浏览阅读1.7w次。文章目录概述What's Docker ?概述使用Docker技术可以帮助企业快速水平扩展服务,从而到达弹性部署业务的能力。在云服务概念兴起之后,Docker的使用场景和范围进一步发展,如今在微服务架构越来越流行的情况下,微服务+Docker的完美组合,更加方便微服务架构运维部署落地。What’s Docker ?..._review docker tool

java计算机毕业设计基于安卓Android的谷惠农产品线上销售APP-ssm_农产品销售app设计与实现-程序员宅基地

文章浏览阅读88次。本app设计的现状和趋势,从需求、结构、数据库等方面的设计到app的实现,分别为前后端实现。本app根据现实情况来选择一种可行的开发方案,借助java编程语言和MySQL数据库等实现app的全部功能,接下来对系统进行测试,测试系统是否有漏洞和测试用户权限来完善app,最终app完成达到相关标准。谷惠农产品线上销售APP能够通过互联网得到广泛的、全面的宣传,让尽可能多的用户了解和熟知谷惠农产品线上销售APP的便捷高效,不仅为群众提供了服务,而且也推广了自己,让更多的群众了解自己。数据库:mysql 5.7;_农产品销售app设计与实现