Go语言『饱受非议』的类型定义

前几天同事在学习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]=&ii;
  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语言的开发者能理解关于类型定义与众不同的原因。

转载请注明出处: Go语言『饱受非议』的类型定义