post:暂时完成基本类型。

This commit is contained in:
徐涛 2023-09-12 16:04:05 +08:00
parent 9e78e85613
commit 44caae4fc8
5 changed files with 296 additions and 2 deletions

View File

@ -23,8 +23,9 @@
- [依赖版本的发布](./project-structure/module/release.md)
- [Go 基本语法](./grammar/index.md)
- [标识符与关键字](./grammar/identifier-keyword.md)
- [基本数据类型]()
- [表达式]()
- [常量与变量](./grammar/constant-variable.md)
- [基本数据类型](./grammar/basic-types.md)
- [表达式](./grammar/expression.md)
- [语句]()
- [函数]()
- [指针与引用]()
@ -37,6 +38,7 @@
- [结构体]()
- [接口]()
- [泛型]()
- [类型定义]()
- [错误处理]()
- [反射]()
- [并发]()

109
src/grammar/basic-types.md Normal file
View File

@ -0,0 +1,109 @@
# 基本数据类型
Go 中的数据类型主要有布尔、数字、字符串、数组、切片、结构体、指针、函数、接口、Map 和通道。这一章中将主要介绍各个基本类型的语法定义和基本功能,其具体的使用方法将在后文中的独立章节中进行具体说明。
在 Go 中数据类型的语法定义如下:
```
Type = TypeName [ TypeArgs ] | TypeLit | "(" Type ")" .
TypeName = identifier | QualifiedIdent .
TypeArgs = "[" TypeList [ "," ] "]" .
TypeList = Type { "," Type } .
TypeLit = ArrayType | StructType | PointerType | FunctionType | InterfaceType | SliceType | MapType | ChannelType .
```
在这个语法定义中,`TypeArgs`表示一个类型参数,是后面索要介绍的泛型中使用的,目前可以暂时不必理会。目前需要从语法定义中了解的只有 Go 中常用的数据类型的书写方法。
## 布尔类型
布尔类型是 Go 语言中最简单也是使用最广的类型,其类型标识符为`bool`,可取值为预定义的常量`true`和`false`,分别对应真值和假值。
## 数字类型
数字类型是整型、浮点型、复数型三种类型的统称,它们分别用于表示整数、浮点值和复数值。用于表示数字类型的标识符都是 Go 语言预先定义好的,全局有效的。数字类型主要有以下这些。
| 类型标识符 | 指代类型 | 长度(位) | 最小值 | 最大值 |
| ------------ | ---------------------------- | ---------- | -------------------- | -------------------- |
| `uint8` | 无符号 8 位整型 | 8 | 0 | 255 |
| `uint16` | 无符号 16 位整型 | 16 | 0 | 65535 |
| `uint32` | 无符号 32 位整型 | 32 | 0 | 4294967295 |
| `uint64` | 无符号 64 位整型 | 64 | 0 | 18446744073709551615 |
| `int8` | 有符号 8 位整型 | 8 | -128 | 127 |
| `int16` | 有符号 16 位整型 | 16 | -32768 | 32767 |
| `int32` | 有符号 32 位整型 | 32 | -2147483648 | 2147483647 |
| `int64` | 有符号 64 位整型 | 64 | -9223372036854775808 | 9223372036854775807 |
| `float32` | IEEE--754 32 位浮点 | 32 | | |
| `float64` | IEEE-754 64 位浮点 | 64 | | |
| `complex64` | `float32`型实数和虚数复数 | 64 | | |
| `complex128` | `float64`型实数和虚数复数 | 128 | | |
| `byte` | 单字节整型,`uint8`别名 | 8 | | |
| `rune` | 字符型,`int32`别名 | 32 | | |
| `uint` | 机器架构长度无符号整型 | 32 或 64 | | |
| `int` | 机器架构长度有符号整型 | 32 或 64 | | |
| `uintptr` | 足以存放内存地址的无符号整型 | | | |
## 字符串类型
字符串类型是一个可能为空的字节序列,是一个字符值的集合。字符串类型使用`string`预定义标识符声明。字符串的内容一旦创建即不可更改,
字符串中的元素可以通过下标访问,其中元素的索引从 0 开始。
## 数组类型
数组类型是由一个单一类型元组组成的序列类型,数组中元素的数量称为数组的长度。数组类型的定义语法格式如下。
```
ArrayType = "[" ArrayLength "]" ElementType .
ArrayLength = Expression .
ElementType = Type .
```
根据数组类型的语法定义,一个数组类型可以使用`[7]int`、`[20]byte`来表示。这里需要注意的是,数组类型中的长度是数组类型的一部分,必须是能够确定的`int`类型非负常数,数组一旦定义,其长度在数组的作用域内就不再可变。数组总是一维的,但是可以通过组合来形成多维数组,例如`[4][5]int`。
数组中的元素可以通过整型索引来访问。
## 切片类型
之前介绍过的数组类型是一个长度固定不可变的序列类型,切片则是一个不固定长度的序列类型。与数组一样,切片也是通过下标去访问其中元素的。切片类型的定义语法如下。
```
SliceType = "[" "]" ElementType .
```
根据切片类型的语法定义,一个切片类型可以使用`[]int`、`[]byte`来表示。与数组类型不同的是,切片类型的类型中没有切片长度的部分。但是与数组一样,切片也是一维的,并且可以通过组合形成多维切片,但是由于切片的长度是可以动态变化的,所以多维切片中的每一个内部切片都需要单独完成初始化。
## 结构体
结构体是一个由多种类型组合在一起形成的一个能够记录和描述复杂内容的类型。其中每一个命名元素都有自己端粒的类型,被称为字段。结构体的具体语法定义将放在后面专门讲述结构体的章节详细说明。
## 指针类型
指针无论在哪个语言中都是一个非常神奇和灵活的类型,它在理论上可以指向任意的内存地址。在 Go 中一个指针只能指向给定类型的变量地址,这个给定的类型被称为指针的基类型。指针类型的语法定义如下。
```
PointerType = "*" BaseType .
BaseType = Type .
```
根据指针类型的语法定义,一个指针类型可以使用`*int`、`*[]byte`的形式来表示。
## Map 类型
Map 类型主要功能是用来保存键值对是一种无序的序列由两种类型组成其中作为值的类型也被称为元素类型另一种则作为值的索引也被称为键类型。Map 类型的语法定义如下。
```
MapType = "map" "[" KeyType "]" ElmentType .
KeyType = Type .
```
根据 Map 类型的语法定义,以下 Map 类型的书写都是合法的:`map[string]int`、`map[string]iterface{}`。
## 通道类型
Go 里提供的通道类型提供了一种机制,可以在不同的协程和线程之间传输指定类型的元素来实现通信。用于实现这种通信功能的类型就是通道。通道的语法定义如下。
```
ChannelType = ( "chan" | "chan" "<-" | "<-" "chan" ) ElementType .
```
具体通道类型的使用将在后续的独立章节中介绍。

View File

@ -0,0 +1,178 @@
# 常量和变量
常量和变量是程序中所要操作的数据的载体。常量和变量的区别其实主要在于其中所承载的数据是否可变,数据的可变性决定了承载指代它们的容器,并且决定了它们在程序中能够发挥的作用。
## 常量
常量顾名思义就是不可变的量,在程序中定义的常量一般都携带有特殊的含义。根据常量类型的不同,一般可以按参照类型分为布尔常量、字符常量、整数常量、浮点常量、复数常量和字符串常量,其中字符常量、整数常量、浮点常量和复数常量一般统称为数字常量。
常量主要是通过字面量、其他的常量标识符、常量表达式等定义的,其定义原则就是用来定义常量的值在 Go 项目编译的时候是能够确定的,只要是能够符合这个条件的值,都可以用来定义常量。常量可以是类型化的也可以是非类型化的,像是字面量、`true`、`false`、`iota`或者是包含非类型化常量的常量表达式都是非类型化的。常量的类型可以在常量定义表达式中显式声明,也可以通过在赋值表达式中为变量赋值时隐式声明。
```admonish tip
隐式类型声明都是通过类型推断完成的。如果给定常量值不能匹配常量或者变量的类型那么Go将产生一个错误。如果目标类型是一个类型参数那么这个常量值将会被转换成匹配类型参数的非常量值。
非类型化的常量并不等于常量没有类型,非类型化只是指常量的类型是隐式声明的而已。
```
数字常量通常都标识任意精度的准确值,不会存在溢出的情况,所以不会存在负零、无穷大和非数字值的常量。尽管数字常量在 Go 中拥有任意精度,但是在编译器内它们的精度还是有限制的。
程序所使用的常量都是通过`const`关键字定义的,其语法定义如下:
```
ConstDecl = "const" ( ConstSpec | "(" { ConstSpec ";" } ")" ) .
ConstSpec = IdentifierList [ [ Type ] "=" ExpressionList ] .
IdentifierList = identifier { "," identifier } .
ExpressionList = Expression { "," Expression } .
```
以下是一些常见的常量定义,以及它们所定义出的内容类型。
```go
const a = 2 + 3.0 // a = 5.0,非类型化浮点常量
const b = 15 / 4 // b = 3非类型化整型常量
const c = 15 / 4.0 // c = 3.75,非类型化浮点常量
const θ float64 = 3 / 2 // θ = 1.0,类型化双精度浮点常量,
const λ float64 = 3 / 2. // λ = 1.5,类型化双精度浮点常量,
const d = 1 << 3.0 // d = 8非类型化整型常量
const e = 1.0 << 3 // e = 8非类型化整型常量
const f = int32(1) << 33 // 非法常量将常量值赋予int32类型常量是发生了溢出
const g = float64(2) >> 1 // 非法常量float64(2)是一个类型化的常量
const h = "foo" > "bar" // h = true非类型化布尔常量
const j = true // j = true非类型化布尔常量
const k = 'w' + 1 // k = 'x',非类型化字符常量
const l = "hi" // l = "hi",非类型化字符串常量
const m = string(k) // m = "x",类型化字符串常量
const Σ = 1 - 0.7i // 非类型化复数常量
const Δ = Σ + 2.0e-4 // 非类型化复数常量
const φ = iota * 1i - 1 / 1i // 非类型化复数常量
const ic = complex(0, c) // ic = 3.75i,非类型化复数常量
const iθ = complex(0, θ) // iθ = 1i类型化复数常量
```
在上面这些示例中,阅读时清留意常量的类型化和非类型化的区别。
根据`const`语法定义,在定义常量的时候还可以通过常量列表来进行批量定义,例如:
```go
const a, b, c = 1, 3, "hi" // a = 1b = 3c = "hi",分别是非类型化整型常量和非类型化字符串常量
const u, v float32 = 0, 3 // u = 0.0v = 3.0,都是类型化单精度浮点常量
```
### iota
在使用带括号的常量声明列表时,用于定义常量的表达式可以从第二个常量(`ConstSpec`)处省略,这样形成的空列表就相当于前面第一个非空表达式列表及其类型。所以省略表达式列表就相当于重复之前的表达式列表。这种用法通常都会结合`iota`这个预定义标识符来使用。
`iota`标识连续的非类型化整数常量,它其实是一个索引,值从`0`开始,可以用来构建一组相关的常量。在每一个常量列表开始的时候,`iota`的索引都会被重置为`0`。
以下是几个利用`iota`定义常量列表的例子。
```go
const (
a = 1 << iota // a = 1此时iota = 0
b // b = 2此时iota = 1
c // c = 4此时iota = 2
)
const (
a = 1 << iota // a = 1
b = 1 << iota // b = 2
c = 3 // c = 3此处没有使用iota的值
d = 1 << iota // d = 8此时iota = 3
)
const a = iota // a = 0
const b = iota // b = 0每一个const定义序列开始iota的索引值就被重置了
```
如果在常量定义列表中,有一些值是不需要的,可以使用`_`来跳过,例如:
```go
const (
a = iota // a = 0
b // b = 1
_ // 空白标识符可以跳过某一个值的获取此处就跳过了iota = 2
d // d = 3
)
```
```admonish tip
注意Go中的开放性规则以上示例中的所有内容都是仅能在当前包中访问的也就是私有的。如果需要在其他包中可以访问常量的开头字母必须是大写例如`const A = 5`。
`const`关键字引领的常量定义是Go语言中的一等公民可以直接在包中声明。
```
## 变量
变量的主要用途是保存值的存储位置。是的,变量保存的不是值本身,而是值在内存中的位置,这里请不要是指针的概念混淆了。一个变量所能够指代的值是由其类型决定的。
Go 会在编译时对**变量声明**、**函数参数**、**函数返回结果**、**函数声明**、**函数签名**预留命名变量存储空间,而调用内置函数`new`或者获取一个复杂字面量的地址,都会在运行时为变量分配一个存储空间。
变量都是通过`var`关键字声明的,其语法定义如下:
```
VarDecl = "var" ( VarSpec | "(" { VarSpec ";" } ")" ) .
VarSpec = IdentifierList ( Type [ "=" ExpressionList ] | "=" ExpressionList ) .
```
根据`var`关键字的语法定义,以下示例中的变量定义都是合法的。
```go
var i int // 变量i仅声明自动使用零值初始化
var U, V float64 // 可悲外部访问的变量U、V仅声明自动使用零值初始化
var k = 0 // 使用赋值表达式初始化变量kk的类型由提供的值推断
var x, y float64 = -1, -2 // 同时定义双精度浮点类型变量x和y并对其赋值
var (
a int
r, s, t = 2.0, 3, "world" // 利用赋值语句同时定义不同类型的变量
)
```
在上面的示例中,有一些变量在声明的时候没有初始化,此时这个变量是使用其零值进行默认初始化的。所以在声明变量的时候,需要注意每个类型的零值是什么样的。
变量声明中同样也可以使用`_`空白标识符来略过一些赋值过程中不需要的内容。例如在判断一个变量的类型的时:`var _, ok = x.(T)`。
```admonish tip
`var`关键字引领的变量定义是Go语言中的一等公民可以直接在包中声明。
```
### 简短变量定义
Go 除了可以使用`var`关键字声明和定义变量以外,还提供了一种简短的变量声明方式,这种简短的变量声明方式在 Go 语言代码中被广泛的使用。这种简短的变量声明语法定义如下。
```
ShortVarDecl = IdentifierList ":=" ExpressionList .
```
使用这种简短的变量定义方式,上面的变量定义示例就可以改写成以下样子。
```go
x, y := -1, -2
k := 0
r, s, t := 2.0, 3, "world"
_, ok := x.(T)
```
使用这种简短变量定义时需要注意的是,简短变量定义中不支持书写变量的类型,也就是说变量的类型是完全依靠推断的。而且与使用`var`关键字的变量声明不同,简短变量声明只能够使用在函数内部。
## 作用域
常量和变量的声明都是将一个非空白标识符绑定到常量和变量的过程,实际上这个声明的过程除了可以绑定常量和变量以外,还可以绑定类型、类型参数、函数、标签、包等,这些其他内容的声明会在介绍相应内容的时候进行说明。
```admonish caution
Go中所有声明的限制是同一个非空白标识符在同一个块中不能声明两次在同一个包的不同文件中也一样。空白标识符因为不会引入绑定所以它不是一个声明所以其使用次数不受限制。
```
一个标识符的作用范围是声明它的代码区块范围Go 语言使用`{}`来定义一个块。那么对于一个标识符来说,其作用域的确定主要依赖于以下几种规则。
1. Go 语言预定义标识符的作用域恒为程序全局。
1. 在函数之外定义的标识符,作用域为包(`package`)。
1. 被导入包的包名作用域是包含导入声明的文件。
1. 方法的接收器、函数参数、函数返回值变量的作用域是函数体。
1. 函数的类型参数的作用域是从方法接收器开始到函数体末尾。
1. 类型参数标识符的作用域是从类型名称开始直到类型定义结束。
1. 函数内部声明的常量和变量的作用域是从其声明结束开始到包含其声明的块的结束。
1. 函数内部声明的类型标识符的作用域是从类型声明开始直到包含其声明的块的结束。
```admonish tip "作用域遮罩"
如果在一个块的内部构建了一个新的块,而且在这个内部块内声明了与外部块中相同的标识符,那么内部块中声明的标识符就将在其作用域范围内屏蔽外部块的同名标识符,这种情况通常被称为作用域遮罩,也是在声明标识符的时候,比较容易被忽略的一个问题。
```

View File

@ -0,0 +1 @@
# 表达式

View File

@ -60,6 +60,10 @@ identifier = letter { letter | unicode_digit } .
| `real` | 函数 | 获得一个复数的实数部分 |
| `recover` | 函数 | 捕获`panic`抛出的异常,并使程序从异常状态中恢复 |
### 空白标识符
空白标识符在之前引入包的章节中就已经见识过了,空白标识符就是一个`_`,它在表达式中充当匿名占位符,在声明表达式、赋值表达式中都具有特殊的含义。
## 关键字
关键字实际上也是标识符但是是语言中保留的内容因为其在语言中具有专门的用途和意义所以不能被用作普通的标识符。Go 中的关键字主要有以下这些。