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

2020-08-03 11:52:18 +08:00
 Ansen

经常能看到类似 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
	}
    // ....
}
6953 次点击
所在节点    Go 编程语言
43 条回复
Trim21
2020-08-03 11:54:13 +08:00
用来给属性赋默认值…
ahmcsxcc
2020-08-03 11:56:07 +08:00
map 不赋值会 painc 啊
coderxy
2020-08-03 11:58:49 +08:00
map 要初始化的,不然会报错
janxin
2020-08-03 12:05:14 +08:00
你举的例子应该没有这样实际运行过,上面说的 panic 问题主要是针对 map 之类的特殊类型

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

当然并不是所有的情况都是需要这样进行的,比如官方库提供的 sync.Mutex 也是直接使用的
Jirajine
2020-08-03 12:05:57 +08:00
这属于 go 的糟粕之一。
go 号称所有类型创建时就会默认初始化,但类型只要包含引用这个默认的初始化值基本上就没法用(像这里 map 被初始化成 nil 而不是一个空的 map )。
于是 go 在编译时不进行静态检查而是给你一个“脏的初始值”,你那样用编译时没问题但跑着跑着就炸了。
BingoXuan
2020-08-03 12:08:41 +08:00
类似于构造函数,直接声明创建变量没问题,但是变量初始值怎么确定是问题。
ysc3839
2020-08-03 12:21:55 +08:00
个人理解:这里的 map 类似于 C++ 中的 unique_ptr,它可以自动 delete,但不能自动 new 。
感觉根本问题是没有构造函数,只能用一个普通函数模拟构造函数。
jybox
2020-08-03 12:32:47 +08:00
简单来说就是代替构造函数
huweic
2020-08-03 13:07:03 +08:00
过度解读一下,除了 map 初始化的问题,同时 New 的好处是为了方便以后扩展维护,指定默认值,执行其他初始化操作等,先把口子留着
zjsxwc
2020-08-03 13:14:02 +08:00
指针空值问题,
总要有个初始化的构造函数吧
wangritian
2020-08-03 13:17:24 +08:00
这是我不喜欢 go 的一个地方,没有构造函数,方法没有默认参数
ss098
2020-08-03 13:51:49 +08:00
Go 语言没有类,同样也没有构造器语言特性。

NewType 是 Go 语言的构造器命名惯例,等同于 Python __init__,PHP __construct 。
araraloren
2020-08-03 14:02:28 +08:00
所以为啥不弄一个 new 之类的构造函数。。
go 开发团队:为了保持解析器简单 :D
szzhiyang
2020-08-03 14:09:25 +08:00
@Jirajine 那你能给出更好的解决方案吗?
virusdefender
2020-08-03 14:15:18 +08:00
没法强制指定构造函数确实是我不太喜欢的一点
hst001
2020-08-03 14:34:40 +08:00
这是一种类似工程规范的东西,普通的 NewXxx,有些会有多个初始变体会写成类似 NewXxxWithAaa 或 NewXxxFromAaa 之类的可读形式,隐藏掉没必要暴露的细节,无论对于调用者还是开发者,都可以提高可维护性。
zivn
2020-08-03 14:35:18 +08:00
1. 对结构体所有的属性进行必要的初始化,避免 panic ;
2. 方便做一些预处理,初始化函数可以传入参数,也可以返回错误,和 __init__ 、__construct 还不太一样;

另外 map 和 slice 底层定义都是结构体,一堆属性和指针的结合,区别在于 slice 的 append 函数可以自动初始化和扩容底层的数组结构,而 map 是直接 key-value 赋值的,不初始化底层结构,所以用 var 创建的 slice 一般可以正常使用,而 map 不行,会 panic,这个不算脏的初始值,也算不上是糟粕;
hakono
2020-08-03 14:37:40 +08:00
上面虽然基本都提到了,但是用更简单易懂的方法来说明的话,那就是因为 Go 语言的类( struct )不存在构造函数这种东西,导致创建类的时候做不到普通语言那样 MyClass(para1, para2, para3,...) 。
所以如果你想在创建类的时候初始化一些什么东西的时候,就只能用这种 NewXXXX() 的工厂函数来创建。总之这点我也用得很不爽,明明是面向对象,但用工厂函数写总给人一种自己在写面向过程一样
vvmint233
2020-08-03 14:54:56 +08:00
var r router 只是变量的声明而已, 对于初始化新的结构体我有时候会 new(router).Init("aa"), Init()用来返回原结构体指针, 这样可以链式. 而且已经有 new 这个函数了, 其他类似的关键字会显得的有些多余
另外 gin*Context 的那个, 其实可以自己把 handler 封装一下, 把 if else 压缩在一个函数里面, handler 主体依旧可以符合带返回值函数的样子, 对于 gin 来说, 请求的处理是 steaming 的, 所以没有返回符合框架的设计
CNife
2020-08-03 15:25:23 +08:00
这是 Go 语言设计中比较差的地方。
所有类型都有默认值,但这个默认值不一定有用,甚至可能都不是合法值。
对于 slice,你 var s []int 是合法的,nil 和空 slice 是一回事,append 和 len 也能正确处理 nil 的情况,这时候体验很好。
但对于 map,你 var m map[int]int 就是个非法值,不论是存是取都会炸,这时候 nil 和空 map 就不是一回事了。

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

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/695208

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX