不灭的焱

革命尚未成功,同志仍须努力下载JDK17

作者:Albert.Wen  添加时间:2017-10-29 20:44:45  修改时间:2024-04-14 06:30:08  分类:Golang/Ruby  编辑

基本构成要素

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版)》