14 Go's Declaration Syntax go语言声明语法
Go's Declaration Syntax go语言声明语法
7 July 2010
Introduction
Newcomers to Go wonder why the declaration syntax is different from the tradition established in the C family. In this post we'll compare the two approaches and explain why Go's declarations look as they do.
C syntax
First, let's talk about C syntax. C took an unusual and clever approach to declaration syntax. Instead of describing the types with special syntax, one writes an expression involving the item being declared, and states what type that expression will have. Thus
int x;
declares x to be an int: the expression 'x' will have type int. In general, to figure out how to write the type of a new variable, write an expression involving that variable that evaluates to a basic type, then put the basic type on the left and the expression on the right.
Thus, the declarations
int *p;
int a[3];
state that p is a pointer to int because '*p' has type int, and that a is an array of ints because a[3] (ignoring the particular index value, which is punned to be the size of the array) has type int.
What about functions? Originally, C's function declarations wrote the types of the arguments outside the parens, like this:
int main(argc, argv)
int argc;
char *argv[];
{ /* ... */ }
Again, we see that main is a function because the expression main(argc, argv) returns an int. In modern notation we'd write
int main(int argc, char *argv[]) { /* ... */ }
but the basic structure is the same.
This is a clever syntactic idea that works well for simple types but can get confusing fast. The famous example is declaring a function pointer. Follow the rules and you get this:
int (*fp)(int a, int b);
Here, fp is a pointer to a function because if you write the expression (*fp)(a, b) you'll call a function that returns int. What if one of fp's arguments is itself a function?
int (*fp)(int (*ff)(int x, int y), int b)
That's starting to get hard to read.
Of course, we can leave out the name of the parameters when we declare a function, so main can be declared
int main(int, char *[])
Recall that argv is declared like this,
char *argv[]
so you drop the name from the middle of its declaration to construct its type. It's not obvious, though, that you declare something of type char *[] by putting its name in the middle.
And look what happens to fp's declaration if you don't name the parameters:
int (*fp)(int (*)(int, int), int)
Not only is it not obvious where to put the name inside
int (*)(int, int)
it's not exactly clear that it's a function pointer declaration at all. And what if the return type is a function pointer?
int (*(*fp)(int (*)(int, int), int))(int, int)
It's hard even to see that this declaration is about fp.
You can construct more elaborate examples but these should illustrate some of the difficulties that C's declaration syntax can introduce.
There's one more point that needs to be made, though. Because type and declaration syntax are the same, it can be difficult to parse expressions with types in the middle. This is why, for instance, C casts always parenthesize the type, as in
(int)M_PI
Go syntax
Languages outside the C family usually use a distinct type syntax in declarations. Although it's a separate point, the name usually comes first, often followed by a colon. Thus our examples above become something like (in a fictional but illustrative language)
x: int
p: pointer to int
a: array[3] of int
These declarations are clear, if verbose - you just read them left to right. Go takes its cue from here, but in the interests of brevity it drops the colon and removes some of the keywords:
x int
p *int
a [3]int
There is no direct correspondence between the look of [3]int and how to use a in an expression. (We'll come back to pointers in the next section.) You gain clarity at the cost of a separate syntax.
Now consider functions. Let's transcribe the declaration for main as it would read in Go, although the real main function in Go takes no arguments:
func main(argc int, argv []string) int
Superficially that's not much different from C, other than the change from char arrays to strings, but it reads well from left to right:
function main takes an int and a slice of strings and returns an int.
Drop the parameter names and it's just as clear - they're always first so there's no confusion.
func main(int, []string) int
One merit of this left-to-right style is how well it works as the types become more complex. Here's a declaration of a function variable (analogous to a function pointer in C):
f func(func(int,int) int, int) int
Or if f returns a function:
f func(func(int,int) int, int) func(int, int) int
It still reads clearly, from left to right, and it's always obvious which name is being declared - the name comes first.
The distinction between type and expression syntax makes it easy to write and invoke closures in Go:
sum := func(a, b int) int { return a+b } (3, 4)
Pointers
Pointers are the exception that proves the rule. Notice that in arrays and slices, for instance, Go's type syntax puts the brackets on the left of the type but the expression syntax puts them on the right of the expression:
var a []int
x = a[1]
For familiarity, Go's pointers use the * notation from C, but we could not bring ourselves to make a similar reversal for pointer types. Thus pointers work like this
var p *int
x = *p
We couldn't say
var p *int
x = p*
because that postfix * would conflate with multiplication. We could have used the Pascal ^, for example:
var p ^int
x = p^
and perhaps we should have (and chosen another operator for xor), because the prefix asterisk on both types and expressions complicates things in a number of ways. For instance, although one can write
[]int("hi")
as a conversion, one must parenthesize the type if it starts with a *:
(*int)(nil)
Had we been willing to give up * as pointer syntax, those parentheses would be unnecessary.
So Go's pointer syntax is tied to the familiar C form, but those ties mean that we cannot break completely from using parentheses to disambiguate types and expressions in the grammar.
Overall, though, we believe Go's type syntax is easier to understand than C's, especially when things get complicated.
Notes
Go's declarations read left to right. It's been pointed out that C's read in a spiral! See The "Clockwise/Spiral Rule"by David Anderson.
By Rob Pike
Related articles
14 Go's Declaration Syntax go语言声明语法的更多相关文章
- 如何读懂复杂的C语言声明
本文已迁移至: http://www.danfengcao.info/c/c++/2014/02/25/howto-understand-complicated-declaration-of-c.ht ...
- 如何解析复杂的C语言声明
C语言中有时会出现复杂的声明,比如 char * const * (*next) (); //这是个什么东东? 在讲复杂声明的分析方法前,先来个补充点. C语言变量的声明始终贯彻两点 : ...
- C语言声明解析方法
1.C语言声明的单独语法成份 声明器是C语言声明的非常重要成份,他是所有声明的核心内容,简单的说:声明器就是标识符以及与它组合在一起的任何指针.函数括号.数组下表等,为了方便起见这里进行分类表 ...
- [C语言]声明解析器cdecl修改版
一.写在前面 K&R曾经在书中承认,"C语言声明的语法有时会带来严重的问题.".由于历史原因(BCPL语言只有唯一一个类型——二进制字),C语言声明的语法在各种合理的组合下 ...
- Why Go's Declaration Syntax is better than C++?
[Why Go's Declaration Syntax is better than C++?] Newcomers to Go wonder why the declaration syntax ...
- Go's Declaration Syntax
Introduction Newcomers to Go wonder why the declaration syntax is different from the tradition estab ...
- 02. Go 语言基本语法
Go语言基本语法 变量.数据类型和常量是编程中最常见,也是很好理解的概念.本章将从 Go 语言的变量开始,逐步介绍各种数据类型及常量. Go 语言在很多特性上和C语言非常相近.如果读者有C语言基础,那 ...
- Java语言基本语法
Java语言基本语法 一.标识符和关键字 标识符 在java语言中,用来标志类名.对象名.变量名.方法名.类型名.数组名.包名的有效字符序列,称为“标识符”: 标识符由字母.数字.下划线.美元符号组成 ...
- C语言基础语法
#include <stdio.h> int main() { int age; printf("input your age"); scanf("%d&qu ...
随机推荐
- 【BZOJ4035】数组游戏(博弈论)
[BZOJ4035]数组游戏(博弈论) 题面 BZOJ 洛谷 题解 很明显是一个翻硬币游戏的变形,因此当前局面的\(SG\)函数值就是所有白格子单独存在的\(SG\)函数的异或和. 那么,对于每一个位 ...
- 【linux之文件查看,操作,权限管理】
一.shell如何处理命令 1.shell会根据在命令中出现的空格字符,将命令划分为多个部分 2.判断第一个字段是内部命令还是外部命令 内部命令:内置于shell的命令(shell builtin) ...
- 项目管理---git----遇到问题------.gitignore不起作用
情况 在管理一个版本库时,有时候不想要管理某些文件,这个时候我就把这个问价写到.gitignore文件中,这样应该就可以将这个文件忽略,不再进行·版本管理了,但是经常出现的情况是:将这些文件名写到其中 ...
- 【cf859E】Desk Disorder
Portal --> cf859E Solution 我们可以将每一个人看成一条边,将位置看成点,然后一个人在新的方案中可以选择的位置就是这条边连接的两个点,然后我们就得到了一个图 注 ...
- Python 爬虫入门(二)—— IP代理使用
上一节,大概讲述了Python 爬虫的编写流程, 从这节开始主要解决如何突破在爬取的过程中限制.比如,IP.JS.验证码等.这节主要讲利用IP代理突破. 1.关于代理 简单的说,代理就是换个身份.网络 ...
- PostgreSQL 修改字段类型从int到bigint
由于现在pg的版本,修改int到bigint仍然需要rewrite表,会导致表阻塞,无法使用.但可以考虑其他方式来做.此问题是排查现网pg使用序列的情况时遇到的. 由于int的最大值只有21亿左右,而 ...
- BP神经网络人口预测程序(matlab实现)
自己测试人口预测的matlab实现: x=[54167 55196 56300 57482 58796 60266 61465 62828 64653 ...
- bzoj 2243
2243: [SDOI2011]染色 Time Limit: 20 Sec Memory Limit: 512 MBSubmit: 8800 Solved: 3305[Submit][Status ...
- Lvs+Keepalived实现MySQL高可用
LVS+Keepalived+MySQL高可用配置 本文所有配置前提是已实现MySQL双主备份(MySQL双主) 安装前的准备: VIP:192.168.0.201 Keepalived: Keepa ...
- bzoj千题计划123:bzoj1027: [JSOI2007]合金
http://www.lydsy.com/JudgeOnline/problem.php?id=1027 因为x+y+z=1,所以z=1-x-y 第三维可以忽略 将x,y 看做 平面上的点 简化问题: ...