Go语言学习教程之声明语法(译)

前言

学习一门新的语言肯定是要从他的基本语法开始,语法构成了整个程序设计的基础,从语法中我们也可以看到这门语言的一些特性,但是话说回来,语法这东西,不同的语言大同小异,所以这也对语法的记忆造成了一定的难度,其实最好的方法应该是旁边有本书,随时可以拿过来查阅或者纠正。

Go 的初学者可能会有这样的疑问:为什么 Go 的声明语法与传统的其他 C 家族编程语言不太一样?在这篇文章中我们会比较这两种不同的方式,并且也会解释为什么。下面话不多说了,来一起看看详细的介绍吧。

C 变量

首先,让我们说说 C 中的语法。C 使用了一种不寻常的巧妙的方法来实现声明语法。我们不是用什么特殊的语法来描述类型,而是写一个表达式,这个表达式包含两个部分:被声明的变量和变量的类型。

int x;

上面这行代码声明了一个类型为 int 的变量 x。一般来说,为了弄清楚如何编写新变量的类型,可以先写一个含基本类型变量的表达式,然后将基本类型放在左边,将表达式放在右边。

因此,下面的声明:

int *p;
int a[3];

描述的是 p 是一个指向 int 类型的指针,因为 ‘*p' 的类型为 int。而 a 是一个 int 类型的数组,因为 ‘a[3]' (这里请忽略下标的值 3,它只是说明数组的大小)的类型是 int。

那函数呢?在最开始的时候,C 的函数声明是将 参数的类型写在括号外面的,像这样:

int main(argc, argv)
 int argc;
 char *argv[];
{ /* ... */ }

再一次,我们可以看到 main 是一个函数,因为表达式 main(argc, argv) 返回了一个 int 类型的值。现在大家比较习惯写成这样:

int main(int argc, char *argv[]) { /* ... */ }

但是基本的结构还是一样的。

对于简单的类型来说这种巧妙的语法思想是能很好工作的,但是一旦类型变得复杂就会令人感到困惑了。非常经典的一个例子就是声明一个函数指针。遵循着规则,你得到了下面的这种写法:

int (*fp)(int a, int b);

fp 是一个指向函数的指针,因为如果你写一个表达式 (*fp)(a, b) 你会调用函数并得到一个 int 类型的值。那如果 fp 的其中一个入参它本身也是一个函数呢?

int (*fp)(int (*ff)(int x, int y), int b)

这就变得开始难以阅读了。

当然,我们可以在声明一个函数的时候去掉参数名,那么 main 函数可以声明成:

int main(int, char *[])

让我们回想一下,argv 是这样声明的,

char *agrv[]

通过把变量名放在中间来声明类似 char *[] 这样类型的时候其实是令人困惑的。

然后我们再来看看如果我们将入参变量名去掉的情况下 fp 函数的声明是怎么样的:

int (*fp)(int (*)(int, int), int)

无论将变量名放在内部的哪里都不那么清晰明了。对于第一个入参:

int (*)(int, int)

我想这不太容易能一眼看出是在声明一个指向函数的指针。再进一步,如果我们的返回值也是一个函数指针呢?

int (*(*fp)(int (*)(int, int), int))(int, int)

这根本就看不清声明出来的 fp 到底是个啥玩意。。。

你自己也可以构造出更多这类详细的例子,但是这些都说明了 C 的声明语法可能引入的一些困难。

不过还有一点需要提出。因为类型和声明的语法是相同的,所以解析中间类型的表达式是很困难的。这就是为什么 C 的类型转换总是用括号括起来:

(int)M_PI

Go 语法

非 C 家族的编程语言通常使用不同的声明类型的语法:变量名通常放在前面,然后紧跟着一个冒号。因此我们上面的例子就变成了这样:

x: int
p: pointer to int
a: array[3] of int

这些声明是明确的,如果从左往右读你会发现也是详细的。Go 语言从中得到了启发,但为了简洁起见,删除了冒号和一些关键字:

x int
p *int
a [3]int

这个例子中 [3]int 与如何在表达式中使用 a 这两者似乎没有直接的对应。(后面一小节中我们会讲到指针的。)你可以通过单独的语法来获得清晰的结果。

现在让我们考虑下函数。让我们把这个声明写成 Go 的形式,尽管在 Go 中真正的 main 函数是没有入参的:

func main(argc int, argv []string) int

表面上这和 C 语言并没什么不同,除了将字符数组改成了字符串形式。但是从左往右读起来却很顺畅:

函数 main 需要传入一个整型和字符串切片并且返回一个整型。(译者注:直到译者看到这篇文章,译者才发现原来这么写读起来竟这么顺畅。。。)

即便舍去变量名还是很明确——因为对于类型声明上没有位置的变化,所以也没有什么困惑。

func main(int, []string) int

这种从左到右的风格有一个优点:就算类型变得越来越复杂,这种方式还是表现得很得当。

举个声明函数变量的例子(类似在 C 语言中的函数指针):

f func(func(int, int) int, int) int

或者如果 f 返回的也是一个函数(译者注:边写边读你会再次惊讶于这丝滑般的顺畅感。。。):

f func(func(int, int) int, int) func(int, int) int

从左到右依然读起来很顺畅,并且当变量名被声明的时候也很明显。

类型和表达式的语法的不同点使得在 Go 中编写和调用闭包是那么的简单:

sum := func(a, b int) int { return a + b } (3, 4)

指针

指针这家伙总是表现得“与众不同”一点。观察下数组和切片,举个例子,Go 的类型语法将方括号放在类型的左边,但是赋值表达式语法却是将其放在表达式的右边:

var a []int
x = a[1]

为了让大家有一种熟悉的感觉,Go 的指针同样延续 C 语言中的 * 符号,但是我们不能简单的将指针类型也反转一下。所以指针使用方式如下:

var p *int
x = *p

我们不能简单粗暴地改成这样:

var p *int
x = p*

因为后缀 会与乘法的 相混淆。那或许我们可以使用 ^,举个例子:

var p ^int
x = p^

但同样的这个符号也已经有其他含义了,类型和表达式在前缀后缀的问题上总是在许多方面使事情复杂化。举个例子,

[]int("hi")

这是一种写法,但一旦以 * 打头就必须用括号将其包住:

(*int)(nil)

如果我们愿意放弃 * 作为指针语法,那么这些括号就不是必要的了。(译者注:但还能有更好的指针语法吗。。。)

所以 Go 的指针语法与熟悉的 C 语言是类似的,但这个关联也意味着我们不得不使用括号来消除语法中的类型和表达式之间的差异。

总体而言,我们相信 Go 的类型语法比 C 的要更容易理解,尤其是当事情变得复杂的时候。

关于Go语言为何要采用这种倒序语法呢?

Go的设计者Rob Pike的一篇介绍Go声明语法的文章给出了答案,其中谈到了Go声明语法的设计考量。

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对我们的支持。

(0)

相关推荐

  • Go语言基础知识总结(语法、变量、数值类型、表达式、控制结构等)

    一.语法结构 golang源码采用UTF-8编码.空格包括:空白,tab,换行,回车. - 标识符由字母和数字组成(外加'_'),字母和数字都是Unicode编码. - 注释: 复制代码 代码如下: /* This is a comment; no nesting */ // So is this. 二.字面值(literals)类似C语言中的字面值,但数值不需要符号以及大小标志: 复制代码 代码如下: 23 0x0FF 1.234e7类似C中的字符串,但字符串是Unicode/UTF-8编码的

  • Go语言基本的语法和内置数据类型初探

    Go令牌 Go程序包括各种令牌和令牌可以是一个关键字,一个标识符,常量,字符串文字或符号.例如,下面的Go语句由六个令牌: 复制代码 代码如下: fmt.Println("Hello, World!") 个体令牌是: 复制代码 代码如下: fmt . Println ( "Hello, World!" ) 行分离器 在Go程序,行的分隔符关键是一个语句终止.也就是说,每一个单独语句不需要特殊的分隔线; 在C编译器转到内部的地方; 作为语句终止符,表示一个逻辑实体的结

  • Go语言中的变量声明和赋值

    1.变量声明和赋值语法 Go语言中的变量声明使用关键字var,例如 复制代码 代码如下: var name string //声明变量 name = "tom" //给变量赋值 这边var是定义变量的关键字,name是变量名称,string是变量类型,=是赋值符号,tom是值.上面的程序分两步,第一步声明变量,第二步给变量赋值.也可以将两步合到一起. 复制代码 代码如下: var name string = "tom" 如果在声明时同时赋值,可以省略变量类型,Go语

  • golang中单向channel的语法介绍

    本文主要给大家介绍的是关于golang单向channel语法的相关内容,分享出来供大家参考学习,下面话不多说,来一起看看详细的介绍: 今天闲来无事补充一下golang的语法知识,想起来看看context的用法,结果碰到了一个没见过的channel语法: // A Context carries a deadline, cancelation signal, and request-scoped values // across API boundaries. Its methods are sa

  • go语言基础语法示例

    周末天气不好,只能宅在家里,于是就顺便看了一下Go语言,觉得比较有意思,所以写篇文章介绍一下.我想写一篇你可以在乘坐地铁或公交车上下班时就可以初步了解一门语言的文章.所以,下面的文章主要是以代码和注释为主.只需要你对Java,Python,C等编程语言有一点基础,我相信你会在30分钟左右读完并对Go语言有一些初步了解的. 本文的唯一目的,就是希望大家阅读之后,能够了解go语言长什么样子... Hello World package main //声明本文件的package名 import "fm

  • Go语言声明一个多行字符串的变量

    Go如何声明一个多行字符串的变量?使用 ` 来包含即可. package main import ( "fmt" ) func main() { str := `hello world v2.0` fmt.Println(str) } Demo:http://play.golang.org/p/BOL8_SwQ0D 以上所述就是本文的全部内容了,希望大家能够喜欢.

  • Go语言入门教程之基础语法快速入门

    Go语言是一个开源的,为创建简单的,快速的,可靠的软件而设计的语言. Go语言实(示)例教程,通过过实例加注释的方式来介绍Go语言的用法. Hello World 第一个程序会输出"hello world"消息.源代码如下: 复制代码 代码如下: package main import "fmt" func main() {     fmt.Println("hello world") } //通过go run来运行Go程序 $ go run h

  • Go语言学习教程之声明语法(译)

    前言 学习一门新的语言肯定是要从他的基本语法开始,语法构成了整个程序设计的基础,从语法中我们也可以看到这门语言的一些特性,但是话说回来,语法这东西,不同的语言大同小异,所以这也对语法的记忆造成了一定的难度,其实最好的方法应该是旁边有本书,随时可以拿过来查阅或者纠正. Go 的初学者可能会有这样的疑问:为什么 Go 的声明语法与传统的其他 C 家族编程语言不太一样?在这篇文章中我们会比较这两种不同的方式,并且也会解释为什么.下面话不多说了,来一起看看详细的介绍吧. C 变量 首先,让我们说说 C

  • Go语言学习教程之结构体的示例详解

    目录 前言 可导出的标识符 嵌入字段 提升 标签 结构体与JSON相互转换 结构体转JSON JSON转结构体 练习代码步骤 前言 结构体是一个序列,包含一些被命名的元素,这些被命名的元素称为字段(field),每个字段有一个名字和一个类型. 结构体用得比较多的地方是声明与数据库交互时需要用到的Model类型,以及与JSON数据进行相互转换.(当然,项目中任何需要多种数据结构组合在一起使用的地方,都可以选择用结构体) 代码段1:声明一个待办事项的Model类型: type Todo struct

  • kotlin 官方学习教程之基础语法详解

    kotlin 官方学习教程之基础语法详解 Google 在今天的举行了 I/O 大会,大会主要主要展示内有容 Android O(Android 8.0)系统.Google Assistant 语音助手.Google 智能音箱.人工智能.机器学习.虚拟现实等.作为一个 Android 开发者,我关心的当然是 Android O(Android 8.0)系统了,那么关于 Android O 系统的一个重要消息是全面支持 Kotlin 编程语言,使得 Kotlin 成为了 Android 开发的官方

  • Go语言学习教程之反射的示例详解

    目录 介绍 反射的规律 1. 从接口值到反射对象的反射 2. 从反射对象到接口值的反射 3. 要修改反射对象,该值一定是可设置的 介绍 reflect包实现运行时反射,允许一个程序操作任何类型的对象.典型的使用是:取静态类型interface{}的值,通过调用TypeOf获取它的动态类型信息,调用ValueOf会返回一个表示运行时数据的一个值.本文通过记录对reflect包的简单使用,来对反射有一定的了解.本文使用的Go版本: $ go version go version go1.18 dar

  • Go语言结构体Go range的学习教程

    目录 正文 Go Range 正文 在前一篇博客我们学习了 Go 数组,其要求所有元素为同一数据类型,如果希望存储不同类型的数据,就要用到结构体相关知识. 结构体的定义:存储相同或不同类型的数据集合. 有 C 相关经验,结构体还是比较容易理解的,语法格式如下所示: type struct_variable_type struct { member definition member definition ... member definition } 上述语法格式的关键字是 struct 和 t

  • Go语言学习之golang-jwt/jwt的教程分享

    目录 一.介绍 二.代码实现 -对称加密 hsa(hash加密) 2.1 加密生成token 2.2 解密token得到原始数据 三.代码实现 -非对称加密 rsa(我们这里使用rsa256) 2.1 生成 rsa 私钥和公钥 2.2 加密生成token 2.2 解密token 一.介绍 jwt是 json web token的简称. go使用jwt目前,主流使用的jwt库是golang-jwt/jwt 1.1 如何安装 go get -u github.com/golang-jwt/jwt/v

  • 计算机二级python学习教程(3) python语言基本数据类型

    本文继续计算机二级python教程的学习,之前已经学习过了计算机二级python学习教程(1).计算机二级python学习教程(2) 3.1 数字类型 数字类型:整数类型.浮点数类型.复数类型. 整数类型:十进制.二进制.八进制和十六进制. #不同进制的整数之间可以直接运算 >>> 0x3F2/1010 1.0 >>> (0x3F2+1010)/0o1762 2.0 浮点数类型:必须带有小数部分,小数部分可以是0,例如1010.0.表示方法有一般表示(只有十进制)和科学

  • Golang语言学习拿捏Go反射示例教程

    目录 1. 反射简介 1.1 反射是什么? 1.2 为什么需要反射? 2. reflect包 2.1 基本反射 2.2 反射与指针 2.3 反射与对象 2.4 反射与函数 2.5 反射例子 3. 总结 1. 反射简介 1.1 反射是什么? Go语言提供了一种机制在运行时更新和检查变量的值.调用变量的方法和变量支持的内在操作,但是在编译时并不知道这些变量的具体类型,这种机制被称为反射.反射也可以让我们将类型本身作为第一类的值类型处理. 反射是指在程序运行期对程序本身进行访问和修改的能力,程序在编译

  • C语言学习笔记之VS2022安装使用教程

    目录 一.安装VS2022 二.创建项目测试程序 三.遇到问题 四.解决办法 一.安装VS2022 参照B站安装教程安装. Tips:选择安装信息的时候,学习C语言勾选“通用Windows平台开发”,考虑到后续会用到C++,所以勾选了“使用C++的桌面开发”.值得一提的是,安装完成之后在后续学习过程中此安装信息也是可以修改的. 二.创建项目测试程序 点击最上方状态栏调试-开始执行(不调试),运行程序. 成功! 三.遇到问题 在VS2022中,在使用scanf函数编译出错:error C4996:

随机推荐