不那么简明的Go语言教程(1)

84 阅读6分钟

语言特点: 高并发,语法简洁,标准库丰富,静态链接,快速编译,跨平台,自带 GC。

Hello world:

package main
import "fmt"
func main() {
    fmt.Println("Hello world!")
}

基本类型

Boolean: bool

Numeric:

  • uint8 uint16 uint32 uint64
  • int8 int16 int32 int64
  • float32 float64 IEEE-754 32/64 位浮点数
  • complex64 complex128 (float32实部, float32虚部) / (float64实部, float64虚部)
  • byte: uint8 的别名
  • rune: int32 的别名,表示一个 Unicode 码点
  • implementation-specific:
    • int, uint 32 or 64 bits
    • uintptr 大小刚好能够存放一个指针

String:

  • string
    • len(字符串)返回的是永不为负的字节数
    • string是不可变的,一旦被创建就不能被修改
    • 如果是字符是编译期常数,那么在这个len(字符串)也是一个编译期常数
    • 使用字符串[index]返回的是原始字节,并且无法取这个表达式的地址

Array:

  • [N]T:长度为 N 的 T 类型数组,数组类型始终是一维的,但是可以组合成多维数组
// 数组声明
arr := [...]int{1, 2, 3}
arr1 := [3]int{1, 2, 3}
arr2 := [3]int{}
// len(arr) == len(arr1) == len(arr2) == 3
arr[0] = 1

Slice:

  • []T:长度可变的 T 类型切片
    • slice一旦被创建,就会一直与原数组保持关联
    • 如果使用makemake([]T, len, cap)cap可选。这样创建一个长度为len的数组并让slice指向它
// 切片声明
slice := []int{1, 2, 3}
slice1 := make([]int, 3, 10) // with cap = 10
array := [...]int{1, 2, 3, 4, 5}
slice2 := array[0:2] // slice2 = [1, 2],从0开始长度为2-0,也就是[0,2的左闭右开
// 方便起见
// arr[a:] 等同于 arr[a : len(a)]
// arr[:a] 等同于 arr[0 : a]
// arr[:]  等同于 arr[0 : len(a)]
slice3 := arr[1:3:5] // 1可以省略
// slice3 = [2, 3], len(slice3) == 3 - 1, cap(slice3) == 5 - 1
slice1 = append(slice1, 1) // append slice1 with 1

Pointer:

  • *T:T 类型指针,未初始化的指针为nil
// 指针声明
type MyInt = int
i := MyInt(1)
var p *int
p = &i

*p = 1

Structure:

  • struct{ ... }:结构体类型,接下里是几个例子
struct {
  T1   // embedded field
  _ T2 // padding
  a, b int
}
// 非法
struct {
  T     \\ 与其他字段同名
  *T    \\ 同上
  *P.T  \\ 同上
}
struct {
  a, b int "任何字符串都可以写在tag里" // tag可以用reflect包来获取,用于区别struct中的字段
}

使用方式:

// 声明
type Bar = struct {
  a int
}
type Foo struct {
	a int `Say something`
	b int
	Bar
}
func (*Foo) Greet() {
	fmt.Println("Hello there")
}
k := Foo{1, 2, Bar{2}};
println(k.Bar.a)
k.Greet()

Function:

  • 形如func(T...) T...:函数类型,例子
// 最后一个参数的类型可以有 `...` 前缀,叫做variadic,可以接受任意个该类型的参数
func(a, b int, _ float32, res ...int) (success bool, result int)
func(x int) int
func(n int) func(p *T)

使用方式:

func foo(a, b int, _ float32, res ...int) (success bool, result int) {
  if(len(res) == 1) {
  	return false, 2333
  }
  success = true
  result = 42
  return
}
foo(1, 2, 3.0, 4)

Interface:

  • interface{ ... }:接口类型,例子
// a simple file interface
interface {
	Read([]byte) (int, error)
	Write([]byte) (int, error)
	Close() error
}
// 其中每一个字段名必须是唯一的
interface {
  String() string
  String() string  // illegal: String not unique
  _(x int)         // illegal: method must have non-blank name
}
  • 一个接口定义了一个类型集(Type set),任何类型都可以实现多个不同的接口,比如:所有类型都实现了空接口interface{},空接口这个类型集包含了所有非接口类型。方便起见,interface{}被定义为any
  • Embedded Interface:
type A interface {
	a()
  foo()
}
type B interface {
	b()
  foo()
}
type C interface {
	A
	B
}
// C接口包含了A和B的所有方法,实现C接口的类型必须同时实现了A接口和B接口
// 换句话说,C类型集是A类型集和B类型集的交集(intersection)

General interface (泛型接口)

  • underlying type:
    • 每一个T都有一个 underlying type,如果T被声明为是boolean,numeric, string或者是类型字面量,那么T的 underlying type 就是T本身,否则T的 underlying type 就是就是T类型被定义的类型的 underlying type。对于泛型函数参数 T 来说,underlying type 就是 T 的类型约束。
  • 在泛型接口中,接口中的元素可能是任意类型T,或者任何 underlying type 是 T 的类型~T,亦或者是几个类型集的并集T1 | T2 | ... | Tn
    • 空接口:是所有非接口类型的集合
    • 非空接口:是其接口元素所代表的类型集的交集
    • 函数声明:包括了所有包含了该函数声明的非接口类型的集合
    • 非接口类型:是其自身
    • 形如~T:是所有 underlying type 是 T 的类型的集合
    • 形如 T1 | T2 | ... | Tn:是所有 T1, T2, ..., Tn 的类型集的并集

例子:

// 只有int的类型集
interface {
	int
}
// 个人对~T的理解就是:包括了T和所有别名是T和被定义为T的类型
// 也就是T, type Foo T 和 type Bar = T
// 一个代表所有underlying type是int的类型集
interface {
	~int
}
// 一个包含所有underlying type是int和string的类型集的并集
interface {
	~int
	String() string
}
// 空的类型集,因为没有哪个类型同时是int和string
interface {
	int
	string
}
  • 对于~T,underlying type of T 必须是它本身,并且 T 不是接口
  • 对于T1 | T2 ... | Tn中的非接口类型集必须互不相交(相交为空集),例如~int | MyInt就是非法的(type MyInt int),因为 underlying type ofMyIntint,但是float | Float则是合法的(type Float interface { float32 }),因为Float是一个接口。
  • 非基本接口只能用作类型约束,不能用作类型声明。
  • 类型 T 实现了接口 I 如果:
    • T 不是接口并且 T 属于 I 类型集,或者
    • T 是一个接口并且 T 类型集 I 的子集

Map:

  • map[K]V的哈希表,其中类型K之间的==!=必须被定义,而且K不能是function, map, slice类型。
  • 创建 map 可以用make,也可以用map字面量
// 创建一个空的map
make(map[string]int)
make(map[string]int, 100) // 100作为map初始容量的提示,可选
// 创建一个map并初始化
map[string]int{"one": 1, "two": 2}

Channel(FIFO):

  • chan T, chan <- T, <- chan T,表示了 channel 类型
  • 符号<-是可选的,表示了是仅发送还是仅接收还是两者都有
  • 使用 make 创建 channel
// cap为0或者写表示为无缓冲的channel,并在发送者和接收者同时准保好了之后才开始工作。
make(chan int)
make(chan int, 100) // 100为channel的缓冲区大小。
  • close来关闭 channel,多个返回值的接收运算符表示了在 channel 关闭前是否收到了新值。

变量声明:

// declaration
var a int
var a, b int = 1, 2
var (
    a int
    b int
)
var a, _, c = foo()
// short declaration
a, b := 1, 2
f := func() (int, int) { return 42, 1 }
a, _ := f()

if 语句:

if a := f(); a < b {
	return a
} else if x > z {
	return z
} else {
	return b
}

switch 语句:

对于每一个 case 都是隐式 none fallthrough,如果需要继续执行,使用 fallthrough 语句

switch tag := 1; tag {
default:
	fmt.Println("default")
case 0, 1:
	fmt.Println("0 or 1")
	fallthrough
case 2, 3:
	fmt.Println("2 or 3")
case 4:
}
// output:
//    0 or 1
//    2 or 3

switch {
case x < y:
  f1()
case x < z:
  f2()
case x == 4:
  f3()
}

for 语句:

// with single condition
for a < b {
  a += 1
}

// with for clause
// 基本格式:for init; condition; post { ... }
// 其中 init, condition, post都是可选的,格式类似与C语言
// init 可以是short variable declaration,但是post一定不是
for i := 0; i < 10; i++ {
  fmt.Println(i)
}
其中
for cond { S() } 等同于 for ; cond ; { S() }
for      { S() } 等同于 for true     { S() }

// with range clause
// 基本格式: for 变量名 = range 表达式 { ... }
//      或者 for 变量名 := range 表达式 { ... }
// 表达式可以内置的数组,切片,字符串,map,管道
其中
for i, _ := range foo { ... } 等同于 for i := range foo { ... }

对于 range 中不同的类型表达式:

表达式1st 值2nd 值
切片或者数组index: intvalue
字符串index: intrune
mapkey: Kvalue: V
channelelement: E
  • 对于字符串:如果遍历过程中遇到了无效的 UTF-8 序列,第二个返回值将会是 Unicode 替换字符(0xFFFD),并且下一次迭代将会跳过这个无效的字节,直接从下一个字节开始迭代。
  • 对于管道:如果管道是 nil,那么 range 将会永久阻塞。