Golang Go语言中为啥喜欢这样创建变量

发布于 1周前 作者 yibo5220 来自 Go语言

Golang Go语言中为啥喜欢这样创建变量

经常能看到类似 newRouter 这样的代码

type router struct {
	roots    map[string]*node
	handlers map[string]HandleFunc
}

func newRouter() *router {
	return &router{
		roots:    make(map[string]*node),
		handlers: make(map[string]HandlerFunc),
	}
}

func (r *router) AddRoute(method string, pattern string, handler HandleFunc) 
func (r *router) getRoute(method string, path string) (*node, map[string]string)

然后在其它地方这样调用

r := newRouter()
r.AddRoute()

为啥不直接这样呢?

var r router

代码都能看懂,但是不知道为啥喜欢这样用,或者这样用的好处是啥?

我能想到的是:

  1. 通过指针来保证代码块中访问的一致性
  2. 避免参数复制产生的内存消耗

还有其它的好处吗?

在其它项目中还会看到这样的代码

package gulu
 // ..........

// Result represents a common-used result struct. type Result struct { Code int json:"code" // return code Msg string json:"msg" // message Data interface{} json:"data" // data object }

// NewResult creates a result with Code=0, Msg="", Data=nil. func (*GuluRet) NewResult() *Result { return &Result{ Code: 0, Msg: “”, Data: nil, } }

package controller
// .... 
func addSymArticleAction(c *gin.Context) {
	result := gulu.Ret.NewResult()
	defer c.JSON( http.StatusOK, result)

	arg := map[string]interface{}{}
	if err := c.BindJSON(&arg); nil != err {
		result.Code = util.CodeErr
		result.Msg = "parses add article request failed"

		return
	}
    // ....
}

更多关于Golang Go语言中为啥喜欢这样创建变量的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html

44 回复

用来给属性赋默认值…

更多关于Golang Go语言中为啥喜欢这样创建变量的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


map 不赋值会 painc 啊

map 要初始化的,不然会报错

你举的例子应该没有这样实际运行过,上面说的 panic 问题主要是针对 map 之类的特殊类型

另外一个一些必须进行初始化操作因为没有初始化方法的原因需要通过这种方式实现,比如默认值为非空。

当然并不是所有的情况都是需要这样进行的,比如官方库提供的 sync.Mutex 也是直接使用的

这属于 go 的糟粕之一。
go 号称所有类型创建时就会默认初始化,但类型只要包含引用这个默认的初始化值基本上就没法用(像这里 map 被初始化成 nil 而不是一个空的 map )。
于是 go 在编译时不进行静态检查而是给你一个“脏的初始值”,你那样用编译时没问题但跑着跑着就炸了。

类似于构造函数,直接声明创建变量没问题,但是变量初始值怎么确定是问题。

个人理解:这里的 map 类似于 C++ 中的 unique_ptr,它可以自动 delete,但不能自动 new 。
感觉根本问题是没有构造函数,只能用一个普通函数模拟构造函数。

简单来说就是代替构造函数

过度解读一下,除了 map 初始化的问题,同时 New 的好处是为了方便以后扩展维护,指定默认值,执行其他初始化操作等,先把口子留着

指针空值问题,
总要有个初始化的构造函数吧

这是我不喜欢 go 的一个地方,没有构造函数,方法没有默认参数

Go 语言没有类,同样也没有构造器语言特性。

NewType 是 Go 语言的构造器命名惯例,等同于 Python init,PHP __construct 。

所以为啥不弄一个 new 之类的构造函数。。
go 开发团队:为了保持解析器简单 :D

那你能给出更好的解决方案吗?

没法强制指定构造函数确实是我不太喜欢的一点

这是一种类似工程规范的东西,普通的 NewXxx,有些会有多个初始变体会写成类似 NewXxxWithAaa 或 NewXxxFromAaa 之类的可读形式,隐藏掉没必要暴露的细节,无论对于调用者还是开发者,都可以提高可维护性。

  1. 对结构体所有的属性进行必要的初始化,避免 panic ;
    2. 方便做一些预处理,初始化函数可以传入参数,也可以返回错误,和 init 、__construct 还不太一样;

    另外 map 和 slice 底层定义都是结构体,一堆属性和指针的结合,区别在于 slice 的 append 函数可以自动初始化和扩容底层的数组结构,而 map 是直接 key-value 赋值的,不初始化底层结构,所以用 var 创建的 slice 一般可以正常使用,而 map 不行,会 panic,这个不算脏的初始值,也算不上是糟粕;

上面虽然基本都提到了,但是用更简单易懂的方法来说明的话,那就是因为 Go 语言的类( struct )不存在构造函数这种东西,导致创建类的时候做不到普通语言那样 MyClass(para1, para2, para3,…) 。
所以如果你想在创建类的时候初始化一些什么东西的时候,就只能用这种 NewXXXX() 的工厂函数来创建。总之这点我也用得很不爽,明明是面向对象,但用工厂函数写总给人一种自己在写面向过程一样

var r router 只是变量的声明而已, 对于初始化新的结构体我有时候会 new(router).Init(“aa”), Init()用来返回原结构体指针, 这样可以链式. 而且已经有 new 这个函数了, 其他类似的关键字会显得的有些多余
另外 gin*Context 的那个, 其实可以自己把 handler 封装一下, 把 if else 压缩在一个函数里面, handler 主体依旧可以符合带返回值函数的样子, 对于 gin 来说, 请求的处理是 steaming 的, 所以没有返回符合框架的设计

这是 Go 语言设计中比较差的地方。
所有类型都有默认值,但这个默认值不一定有用,甚至可能都不是合法值。
对于 slice,你 var s []int 是合法的,nil 和空 slice 是一回事,append 和 len 也能正确处理 nil 的情况,这时候体验很好。
但对于 map,你 var m map[int]int 就是个非法值,不论是存是取都会炸,这时候 nil 和空 map 就不是一回事了。

根本原因是,每个类型都有自己的初始化方式,它自己最清楚该怎么初始化自己,这就是构造函数的作用。类型可以不定义构造函数,这时候编译器才需要接管类型的初始化工作,给类型一个默认的零值。如果类型自己定义的初始化的方式,编译器就不要越俎代庖了。同时,构造函数是初始化对象的唯一、统一的方法,类型的作者可以由此控制新的对象必定处于合法的初始状态。
但 Go 语言说,没有构造函数了,所有类型都有默认的零值。但有的类型,零值是合法的初始值,但其他类型就未必了。比如这个类型里有指向堆内存的指针,需要在用之前分配内存,nil 肯定不中用了;有的类型,
不过,这也同时取决于类型的设计者有没有考虑到这点,有没有把零值也纳入到合法值的范畴。但这就比较麻烦了,难道要每个函数都要在开头判断一下传进来的是不是 nil,如果是再做个初始化?这可太蠢了,不如直接在文档里规定不允许用 var,必须用 NewXXX 新建对象。

go 开发团队喜欢简洁,导致 go 有很多特有的写法,适应了很久才习惯

var name Type 不能叫声明,应该叫零值初始化,确实分配了内存也赋予了零值。

默认的构造函数没有什么用,C++里面要删除各种默认的构造函数的情况还少么?这个真的不是什么黑点,构造函数本质上跟现在这样的静态函数没有什么区别。

主要是因为没有构造方法吧

楼里都在说 constructor 的问题,这个不是 constructor 的问题,而是 go 自作聪明搞得所有变量都默认初始化。
正确的做法是编译器静态检查访问所有对未初始化变量的访问并在编译时报错,而不是给个不正确的初始值然后运行时爆炸。
这种默认初始化值还有一些其他问题,比如 int 传个 0 进来你不知道它是 uninitialized 还是就是 0 。

go 的变量初始化就是申请内存,然后 memset 为 0 。如果需要额外的初始化就用一个函数来进行初始化工作,思路很清晰。这一块倒是贯彻了 python 里面的 Explicit is better than implicit 。隐式的构造函数看着很方便,但有时候反而带来了无谓的开销。

是我不严谨了, 因为 nil 我更习惯叫声明

也许是因为检查各个变量是否被初始化会让编译期检查变得复杂且缓慢吧,Go 有不少设计就是为了确保编译流程简单且迅速。

我写了 2 年多 Go 了,还是对某些语法很排斥。
特别是类型转换,有时会被气爆炸!
uint32 不能隐式转成 uint64

不知道 LS 一段人在说什么。。。。。。。

这个东西叫做 类型断言。。。。。。,很多现代静态语言都有这个功能,比如 Rust 。

就是初始化的问题,go 没构造函数其实影响不大

这只是一个 null safety 问题,至于需要确认是否初始化有其他方式可以确定。

在其他语言里也不是什么很特殊的东西。

https://kotlinlang.org/docs/reference/null-safety.html
https://dart.dev/null-safety/understanding-null-safety

关于 null safety,谷歌开发者有一篇文章关于这个问题( Dart ):

https://mp.weixin.qq.com/s/rgVJn928fyGunNO5kDKnSA

但是如果允许 nil 这个特殊类型之后,如果库出现了问题,在没有异常处理的 Go 语言里没处理好可能程序动不动就崩了…

你这里 struct 的 field 都导出了,要是有没导出的咋办?
这个用法基本上和 typedef 一个未声明结构的指针差不多
只能从函数返回,实际上无法实例化

这不就是构造函数吗

连零值的概念都不懂,就别满嘴糟粕糟粕了。map 的零值就是 nil,为啥?如果我只是想声明一个 map 变量,在用现有的 map 赋值,那声明的时候就初始化一个空的干嘛?多余!

这就是构造函数啊,难道你们构造东西的时候不写点逻辑进去?

还有,把创建的地方收在一起后期改造也好改啊, 要不然自己去初始化,项目里边 100 多个地方都 create,你想改个默认值都不能改。

唉。。。

真的忍不住吐槽, 没有基本代码素养的人太多了。。。一个现代的语言动不动被吐槽糟粕。 真正的糟粕是 js 这种历史包袱一大堆的垃圾好吗

你觉得创建出来就是 nil,别人也可以觉得创建出来就得能用,不能是 nil 。
问题就在于它明明可以在编译时检查避免访问未初始化变量,却偏要自作聪明的默认给一个“零值”作为初始值,这个“零值”有时候是想要的有时候不是。
访问 undefined 变量在脚本语言中是很普遍的一类问题,go 作为静态编译型语言完全可以编译时就避免这类问题却不这么做,所以说是糟粕之一。

我没有说构造函数有问题啊,你到底看没看我写的什么。
我说的是变量默认初始化的问题,换一个正常点的语言楼主这种做法编译时就会得到错误“uninitialized variable”,而 go 的做法使其能够通过编译并在运行时崩溃。

嘛,如果你要说 nil 一无是处那就过分了,https://github.com/golang/go/wiki/CodeReviewComments#declaring-empty-slices,我一直觉得把其它语言的编程习惯生搬硬套到一门新语言这件事就很奇怪,有这个喷的时间不如去查一下为什么要有这个东西

<iframe src="https://www.youtube.com/embed/ynoY2xz-F8s" class="embedded_video" allowfullscreen="" type="text/html" id="ytplayer" frameborder="0"></iframe>

噫 为啥 YouTube 的链接被吃了(

<iframe src="https://www.youtube.com/embed/ynoY2xz-F8s" class="embedded_video" allowfullscreen="" type="text/html" id="ytplayer" frameborder="0"></iframe>

在Golang(Go语言)中,变量的创建方式有其独特之处,这主要源于Go语言的设计理念:简洁、明确和高效。以下是Go语言中常见变量创建方式受欢迎的几个原因:

  1. 简洁性:Go语言鼓励使用短变量声明(:=)来创建局部变量,这种方式简洁明了,无需显式指定变量类型,编译器会根据右侧表达式的类型自动推断。这不仅减少了代码量,还提高了可读性。

  2. 明确性:虽然短变量声明简化了代码,但它只能在函数内部使用,这有助于限制变量的作用域,使代码更加模块化,易于理解和维护。同时,这种限制也避免了全局变量滥用带来的潜在问题。

  3. 高效性:Go语言的编译器在编译阶段会进行类型检查和优化,确保代码在运行时既安全又高效。短变量声明作为类型推断的一部分,有助于编译器更好地进行这些优化。

  4. 一致性:在Go语言中,无论是基本类型、复合类型还是接口类型的变量,都可以通过短变量声明来创建,这保持了代码的一致性,降低了学习成本。

综上所述,Go语言中这种独特的变量创建方式,既体现了其简洁、明确的设计理念,又保证了代码的高效性和一致性。这种设计使得Go语言成为了一种易于上手、易于维护且性能卓越的编程语言,受到了广大开发者的喜爱。

回到顶部