基本构成要素
Go 的语言符号 又称 词法元素,共包括 5 类内容——标识符(identifier)、关键字(keyword)、字面量(literal)、分隔符(delimiter) 和 操作符(operator),它们可以组成各种表达式 和 语句,而后者都无需以分号结尾。
标识符
标识符可以表示 程序实体,前者即为后者的名称。在一般情况下,同一个代码块中不允许出现同名的程序实体。使用不同代码包中的程序实体需要用到限定标识符,比如:os.O_RDONLY。
另外,Go 中还存在着一些特殊的标识符,叫作预定义标识符,它们在 Go 源码中声明的。这类标识符包括以下几种。
- 所有基本数据类型的名称
- 接口类型 error
- 常量 true、false 和 iota。
所有的 内建函数 的名称,即 append、cap、close、complex、copy、delete、imag、len、make、new、panic、print、println、real 和 recover。
这里强调一下 空标识符,它由一个下划线 _ 表示,一般用在变量声明 或 代码包 导入语句中。若在代码中存在一个变量 x,但是却不存在任何对它的使用,则编译器会报错。如果在变量 x 的声明代码后添加这样一行代码:
_ = x
就可以绕过编译器检查,使它不产生任何编译错误。这是因为这段代码确实用到了变量 x,只不过它没有在变量 x 上进行任何操作,也没有将它赋值给任何其他变量。空标识符就像一个垃圾桶。在相关初始化工作完成后,操作对象就会被弃之不用。
关键字
关键字 是被编程语言保留的字符序列,编程人员不能把它们用作标识符。因此,关键字也称为 保留字。
Go 的关键字可以分为 3 类,包括用于 程序声明的关键字、用于程序实体声明 和 定义 的关键字,以及用于程序流程控制的关键字,如下图所示:
类 别 | 关 键 字 |
---|---|
程序声明 | import 和 package |
程序实体 声明 和 定义 | chan,const,func,interface,map,struct,type 和 var |
程序流程控制 | go,select,break,case,continue,default,defer,else,fallthrough,for,goto,if,range,return 和 switch |
Go 关键字共有 25 个,其中与并发编程有关的关键字有 go、chan 和 select。
这里特别说明一下关键字 type 的用途——类型声明。我们可以使用它声明一个 自定义类型:
type myString string
这里把名为 myString 的类型声明为 string 类型的一个别名类型。反过来说,string 类型是 myString 类型的 潜在类型。再看另一个例子,基本类型 rune 是 int32 类型的一个别名类型。int32 类型就是 rune 类型的潜在类型。虽然类型及其潜在类型是不同的 两个 类型,但是它们的值可以进行类型转换,例如:string(myString("ABC"))
。这样的类型转换不会产生新值,几乎没什么代价。
自定义的类型一般都会基于 Go 中一个或多个预定义类型,就像上面的 myString 和 string 那样。如果为自定义类型关联若干方法(函数的变体),那么还可以让它成为某个或某些接口类型的实现类型。另外,还有一个比较特殊的类型,叫 空接口。它的类型字面量是 interface{}
。在 Go 语言中,任何类型都是 空接口 类型的实现类型。
字面量
简单来说,字面量就是 值 的一种 标记法。但是,在 Go 中,字面量的含义要更加广泛一些。我们常常用到的字面量有以下 3 类。
1、用于表示基础数据类型 值 的各种字面量。这是最常用的一类字面量,例如,表示浮点数类型值的 12E-3。
2、用于构造各种自定义的复合数据类型的 类型 字面量。例如,下面的字面量定义了一个名称为 Name 的结构体类型:
type Name struct {
Forename string
Surname string
}
3、用于表示复合数据类型的 值 的复合字面量,它可以用来构造 struct(结构体)、array(数组)、slice(切边) 和 map(字典) 类型的值。复合字面量 一般由字面类型 以及 被花括号包裹的复合元素的列表组成。字面类型 指的就是 复合数据类型 的名称。
例如,下面的复合字面量构造出了一个 Name 类型的值:
Name{Forename: "Robert", Surname: "Hao"}
其中 Name 表示这个值的类型,紧随其后的就是由键值对表示的复合元素列表。
操作符
操作符,也称 运算符。它是用于执行特定算术 或 逻辑操作的符号,操作的对象称为 操作数。Go 有如下操作符:
符 号 | 说 明 | 示 例 |
---|---|---|
|| | 逻辑或操作,二元操作符,逻辑操作符 | true || false // 结果为 true |
&& | 逻辑与操作,二元操作符,逻辑操作符 | true && false // 结果为 false |
== | 相等判断操作,二元操作符,比较操作符 | "abc" == "abc" // 结果为 true |
!= | 不等判断操作,二元操作符,比较操作符 | "abc" != "Abc" // 结果为 true |
< | 小于判断操作,二元操作符,比较操作符 | 1 < 2 // 结果为 true |
<= | 小于或等于判断操作,二元操作符,比较操作符 | 1 <= 2 // 结果为 true |
> | 大于判断操作,二元操作符,比较操作符 | 3 > 2 // 结果为 true |
>= | 大于或等于操作判断,二元操作符,比较操作符 | 3 >= 2 // 结果为 true |
+ | 求和操作,一元操作符,二元操作符,算术操作符。若为一元操作符,不会对原值产生任何影响 | +1 // 结果为 1 1 + 2 // 结果为 3 |
- | 求差操作,一元操作符,二元操作符,算术操作符。若为一元操作符,则表示求反操作。 | -1 // 结果为 -1(1的相反数) 1 - 3 // 结果为 -2 |
| | 按位或操作,二元操作符,算术操作符 | 5 | 11 // 结果为 15 |
^ | 按位异或操作,一元操作符,二元操作符,算术操作符。若为一元操作符,则表示按位补码操作。 | 5 ^ 11 // 结果为 14 ^5 // 结果为 -6 |
* | 求乘操作,一元操作符,二元操作符,算术操作符,地址操作符。若为地址操作符,则表示取值操作 | *p // 若 p 为指向整数类型值 2 的指针类型值,则结果为 2 2 * 5 // 结果为 10 |
/ | 求商操作,二元操作符,算术操作符 | 10 / 5 // 结果为 2 |
% | 求余数操作,二元操作符,算术操作符 | 12 % 5 // 结果为 2 |
<< | 按位左移操作,二元操作符,算术操作符 | 4 << 2 // 结果为 16 |
>> | 按位右移操作,二元操作符,算术操作符 | 4 >> 2 // 结果为 1 |
& | 按位与操作,一元操作符,二元操作符,算术操作符,地址操作符,则表示取址操作 | &v // 结果为标识符 v 所代表的值在内存中的地址 5 & 11 // 结果为 1 |
&^ | 按位清除操作,二元操作符,算术操作符 | 5 &^ 11 // 结果为 4 |
! | 逻辑非操作,一元操作符,逻辑操作符 | !b // 若 b 的值为 false,则表达式的结果为 true |
<- | 接收操作,一元操作符,接收操作符 | <- ch // 若 ch 代表了元素类型为 byte 的通道类型值,则此表达式就表示从 ch 中接收一个 byte 类型值的操作 |
Go 的操作符一共有 21 个,并分为 5 类:算术操作符、比较操作符、逻辑操作符、地址操作符 和 接收操作符。
当一个表达式中存在多个操作符时,就涉及操作顺序的问题。在 Go 中,一元操作符拥有最高的优先级,而二元操作符的优先级如下表所示:
优 先 级 (数字越大,优先级越高) |
操 作 符 |
---|---|
5 | *、/、%、<<、>>、&、&^ |
4 | +、-、|、^ |
3 | ==、!=、<、<=、>、>= |
2 | && |
1 | || |
如果在一个表达式中出现了处于相同优先级的多个操作符,且这些操作符之间存在操作数,那么就会按照从左到右的顺序进行操作。当然,我们可以使用圆括号显示地改变原有的操作顺序,例如表达式 a << (4 * b) & c
等同于 (a << (4 * b)) & c
,即子表达式 4 * b
会先被求值。
最后需要注意的是,++ 和 -- 是语句,而不是表达式,因而他们不存在于任何操作优先级层次之内。例如 表达式 *p--
等同于 (*p)--
。
表达式
表达式 是把 操作符 和 函数 作用于操作数的计算方法。在 Go 中,表达式 是构成具有词法意义的代码的最基本元素。Go 的表达式有很多种,具体如下表所示:
种 类 | 用 途 | 示例 |
---|---|---|
选择表达式 | 选择一个值中的字段或方法 | context.Speaker // context 是变量名 |
索引表达式 | 选取数组、切片、字符串 或 字典值 中的某个元素 | array1[1] // array1 表示一个数组值,其长度必须大于 1 |
切片表达式 | 选取数组、数组指针、切片 或 字符串值中的某个范围的元素 | slice[0:2] // slice1 表示一个切片值,其容量必须大于 或 等于 2 |
类型断言 | 判断一个接口值的实际类型是否为某个类型,或一个非接口值的类型是否实现了某个接口类型 | v1.(I1) // v1 表示一个接口值,I1 表示一个接口类型 |
调用表达式 | 调用一个函数 或 一个值的方法 | v1.M1() // v1 表示一个值,M1 表示与该值关联的一个方法 |
关于 类型断言,有 2 点需要注意一下:
1、如果 v1 是一个非接口值,那么必须在做类型断言之前把它转换成 接口值。
因为 Go 中的任何类型都是 空接口类型 的实现类型,所以一般会这样做:interface{}(v1).(I1)
。
2、如果类型断言的结果为否,就意味着该类型断言是失败的。失败的类型断言会引发一个运行时恐慌(或称运行时异常),解决方法是:
var i1, ok = interface{}(v1).(I1)
这里声明并赋值了 2 个变量,其中 ok 是布尔类型的变量,它的值体现了类型断言的成败。如果成功,i1 就会是经过类型转换后的 I1 类型的值,否则它将会是 I1 类型的零值(或称默认值)。如此一来,当类型断言失败时,运行时恐慌就不会发生。
关键字 var 用于变量的声明。在它 和 等号(=) 之间可以有多个由逗号(,)隔开的变量名。这种在一条语句中同时为多个变量赋值的方式叫 平行赋值。另外,如果在声明变量的同时进行赋值,那么等号左边的变量类型可以省略。如果不使用上述几个技巧的话,上面那条语句可以写成:
var i1 I1
var ok bool
i1, ok = interface{}(v1).(I1)
另一方面,上面那条语句还可以简写成:
i1, ok := interface{}(v1).(I1)
这种简写方式只能出现在函数中。有了符号 :=,关键字 var 也可以省略了,这叫 短变量声明。
摘自:《Go并发编程实战(第2版)》