前几天同事在学习Go语言,对Go的变量类型定义一直吐槽。 觉得和之前的编程习惯太违背,之前的编程习惯也就是C语言的变量定义。 很多人第一次接触Go的时候都喜欢这个槽点。
但是其实个人觉得主要原因在于惯性思维,C语言本身的类型定义才是反人类的。 举一个例子就可以很清楚的说明了,先看以下的C语言定义的全局变量x是什么类型?
#include <stdio.h> int*(*x[5])[5]; int main() { int i = 10; int* ii[5]; ii[0] = &i; x[0]=ⅈ printf("%d/n", *(*x[0])[0]); return 0; }
答案: x是数组,数组的元素是指针,指针的指向是数组,数组的元素是指针,指针指向的是int
而得知这个答案的过程是由里往外的推理, 先看最里面的x变量, 然后再在脑海里面想一想对于 *
运算符和 []
运算符哪个优先级更高? 结论是 []
优先级更高, 然后就推理x是数组,然后数组的元素则是指针,也就是左边的 *
运算符得知的。 然后再把 (*x[5])
当成一个整体去看, 可以假定先把 int*(*x[5])[5]
先写成 int*(y)[5]
去推理, y是x数组的元素指向的变量, 然后y是数组,数组的元素类型是指针,指针指向的是int类型。
是一种递归的方式从里面往外面推理。
而看看对于同样的变量类型,Go语言是如下的代码:
package main import "fmt" func main() { // x是数组,数组的元素是指针,指针的指向是数组,数组的元素是指针,指针指向的是int // y是数组,数组的元素是指针,指针的指向是int var x [5]*[5]*int var y [5]*int x[0] = &y fmt.Printf("%v/n", y) fmt.Printf("%v/n", x) }
在Go里面,对于x变量的类型推理,是从左往右推理即可(更符合人类的直觉,也更容易写语法的Parser)
比如对于 var x[5]*[5]*int
可以推理如下:
x[5]
是数组 x[5]*
是数组,数组的元素是指针。 x[5]*[5]
是数组,数组的元素是指针,指针指向的元素又是数组。 x[5]*[5]*
是数组,数组的元素是指针,指针指向的元素又是数组,数组的元素是指针。 x[5]*[5]*int
是数组,数组的元素是指针,指针指向的元素又是数组,数组的元素是指针,指针指向的元素是int。
是不是想到数学老师喜欢说的一句话:『从左往右,依次演算。』
符合人类的直接,写起Parser来也更简单,不需要递归, 理论上来讲Go的Parser对于类型解析应该是比C语言的类型解析更快的。
而C语言的类型定义则是由内向外的,写Parser也得递归地去解析。 而且对于人类的思维来说也是更复杂的,只不过因为大家是从C语言入门的。 所以没有感觉到这个语法的设计是反人类的。
而Go语言的作者之一 Ken Thompson 就是当年B语言和C语言设计者之一。 所以我觉得Go语言是有意修复C语言在类型定义中反人类的地方。 但是却被已经习惯了C语言语法的人以为Go是在故意刁难开发者。
所以写这篇文章,说这么多, 就是希望学习Go语言的开发者能理解关于类型定义与众不同的原因。