# Go语言简介 Go是一门编译型的和静态的编译语言。 - 内置并发编程支持: a. 使用协程(goroutine)作为基本的计算单位。轻松地创建协程 b. 使用通道(channel)来实现协程间的同步和通信 - 内置映射(map)和切片(slice)类型 - 支持多态---? - 使用接口(interface)来实现装盒(value boxing)和反射(reflection) - 支持指针 - 支持函数闭包------? - 支持方法 - 支持延迟函数调用 - 支持类型内嵌------指方法的定义? - 支持类型推断-----? - 内存安全----? - 自动垃圾回收-----? - 良好的代码跨平台性----二进制文件? - 自定义泛型-----(指自定义库?) ## 最简单的Go程序 ```go package main func main() { } ``` `package` 和 `func` 是两个关键字。两个 `main` 是两个标识符。 此程序第一行指定了当前源代码文件所处的包的包名(此处为main)。 第三行和第四行声明了一个名为 `main` 的函数。此函数为程序的入口函数。 ## 运行一个Go程序 Go官方工具链工具要求所有的Go源码文件以.go后缀结尾 运行单个go源码文件 `go run test.go` 如果一个程序的main包中有若干Go源代码文件,在相应目录下可以用下面的命令运行此程序。 `go run .` ## Go 子命令 - go run 不推荐在正式的大型目中使用。对于正式的项目,最好用go build 或者go install 构建可执行文件来运行Go程序 - go mod init -------------? go mod tidy 扫描项目,添加依赖至 go.mod 或者从中删除不再被使用的依赖 go get 添加、升级、降级或者删除单个依赖。 - 以 _ 和 . 开头的源代码文件会被Go官方工具链工具忽略掉 - go run 、 go build 和 go install 只会输出代码语法错误 go vet 可以输出警告 - go fmt 格式化代码 - go test 运行单元和基准测试用例 - go doc 在终端中查看Go代码库包的文档 # 程序源码基本元素介绍 在大多数高级编程语言中,数据通常被抽象为各种类型(type)和值(value)。 一个类型可以看作是值的模板。一个值可以看作是某个类型的实例。 大多数编程语 言支持自定义类型和若干预声明类型(即内置类型)。 ### 函数说明 - 入参是int - 返回结果也是int,两个 ```go func StatRandomNumbers(numRands int) (int, int) { // 声明了两个变量(类型都为int,初始值都为0) var a, b int // 一个for循环代码块 for i := 0; i < numRands; i++ { // 一个if-else条件控制代码块 if rand.Intn(MaxRand) < MaxRand/2 { a = a + 1 } else { b++ // 等价于:b = b + 1 } } return a, b // 此函数返回两个结果 } x, y := StatRandomNumbers(num) ``` 函数左括号不能位于行首(单独换行) # 关键字和标识符 ## 关键字 - const、func、import、package、type和var用来声明各种代码元素。 - chan、interface、map和struct用做 一些组合类型的字面表示中。 - break、case、continue、default、 else、fallthrough、for、 goto、if、range、 return、select和switch用在流程控制语句中。 详见基本流程控制语法(第12章)。 - defer和go也可以看作是流程控制关键字, 但它们有一些特殊的作用。详见 协程和延迟函数调用(第13章)。 ## 标识符 一个标识符是一个以Unicode字母或者_开头并且完全由Unicode字母和Unicode数字 组成的单词。 - 导出标识符,需要首字母大写,不能以 _ 开头 - 东方字符都被视为非导出字符 # 基本类型及其字面量表示 这边有问题的话,具体还是看书吧,不好说 ## 基本内置类型 内置类型也称为预声明类型 Go支持如下内置基本类型: - 一种内置布尔类型:bool。 - 11种内置整数类型:int8、uint8、int16、uint16、int32、uint32、 int64、uint64、int、uint和uintptr。 - 两种内置浮点数类型:float32和float64。 - 两种内置复数类型:complex64和complex128。 - 一种内置字符串类型:string。 除了bool和string类型,其它的15种内置基本类型都称为数值类型(整型、浮点 数型和复数型)。 Go中有两种内置类型别名(type alias): - byte是uint8的内置别名。 我们可以将byte和uint8看作是同一个类型。 - rune是int32的内置别名。 我们可以将rune和int32看作是同一个类型。 **自定义类型** ```go type status bool // status和bool是两个不同的类型 type boolean = bool // boolean和bool表示同一个类型 ``` ## 零值 每种类型都有一个零值。一个类型的零值可以看作是此类型的默认值。 - bool 的零值是 false - 数组类型的零值都是 0 (不同类型的 0 在内存中占用空间可能不同)。 - 字符串类型的零值是一个空字符串 ## 基本类型的字面量表示形式 ### 布尔值的字面量形式 我们可以将false和true这两个预声 明的具名常量当作布尔类型的字面量形式。 但是,我们应该知道,从严格意义上 说,它们不属于字面量。 ### 整数类型值的字面量形式 整数类型值有四种字面量形式:十进制形式(decimal)、八进制形式(octal)、 十六进制形式(hex)和二进制形式(binary)。 ```go package main func main(){ println(15 == 017) //true println(15 == 0xF) //true } ``` ### 浮点数类型值的字面量形式 ```go 1.23 01.23 // == 1.23 .23 1. // 一个e或者E随后的数值是指数值(底数为10)。 // 指数值必须为一个可以带符号的十进制整数字面量。 1.23e2 // == 123.0 123E2 // == 12300.0 123.E+2 // == 12300.0 1e-1 // == 0.1 .1e0 // == 0.1 0010e-2 // == 0.1 0e+5 // == 0.0 ``` ### 虚部字面量形式 过 数值字面表示中使用下划线分段来增加可读性 ```go // 合法的使用下划线的例子 6_9 // == 69 0_33_77_22 // == 0337722 0x_Bad_Face // == 0xBadFace 0X_1F_FFP-16 // == 0X1FFFP-16 0b1011_0111 + 0xA_B.Fp2i // 非法的使用下划线的例子 _69 // 下划线不能出现在首尾 69_ // 下划线不能出现在首尾 6__9 // 下划线不能相连 0_xBadFace // x不是一个合法的八进制数字 1_.5 // .不是一个合法的十进制数字 1._5 // .不是一个合法的十进制数字 ``` ### rune值的字面量形式 ```go 1| package main 2| 3| func main() { 4| println('a' == 97) 5| println('a' == '\141') 6| println('a' == '\x61') 7| println('a' == '\u0061') 8| println('a' == '\U00000061') 9| println(0x61 == '\x61') 10| println('\u4f17' == '众') 11| } ``` ### 字符串的字面量形式 略 # 常量和变量 ## 类型不确定值和类型确定值 在Go中,有些值的类型是不确定的。换句话说,有些值的类型有很多可能性。 这些 值称为类型不确定值。对于大多数类型不确定值来说,它们各自都有一个默认类 型, 除了预声明的nil。nil是没有默认类型的。 默认类型,就是你声明一个变量,不说明它的类型,就被当成默认类型 - 字符串字面量的默认类型是预声明的string类型 - 一个布尔字面量的默认类型是预声明的bool类型。 - 一个整数型字面量的默认类型是预声明的int类型。 - 一个rune字面量的默认类型是预声明的rune(亦即int32)类型。 - 一个浮点数字面量的默认类型是预声明的float64类型。 - 如果一个字面量含有虚部字面量,则此字面量的默认类型是预声明的 complex128类型。 ### 类型不确定厂里的显示类型转换 ### 一些合法的转换 ```json // 结果为complex128类型的1.0+0.0i。虚部被舍入了。 complex128(1 + -1e-1000i) // 结果为float32类型的0.5。这里也舍入了。 float32(0.49999999) // 只要目标类型不是整数类型,舍入都是允许的。 float32(17000000000000000) float32(123) uint(1.0) int8(-123) int16(6+0i) complex128(789) string(65) // "A" string('A') // "A" string('\u68ee') // "森" string(-1) // "\uFFFD" string(0xFFFD) // "\uFFFD" string(0x2FFFFFFFF) // "\uFFFD" ``` ### 一些非法的转换 ```json int(1.23) // 1.23不能被表示为int类型值。 uint8(-1) // -1不能被表示为uint8类型值。 float64(1+2i) // 1+2i不能被表示为float64类型值。 // -1e+1000不能被表示为float64类型值。不允许溢出。 float64(-1e1000) // 0x10000000000000000做为int值将溢出。 int(0x10000000000000000) // 字面量65.0的默认类型是float64(不是一个整数类型)。 string(65.0) // 66+0i的默认类型是complex128(不是一个整数类型)。 string(66+0i) ``` ## 常量声明(具名) ```go 1| package main 2| 3| // 声明了两个单独的具名常量。(是的, 4| // 非ASCII字符可以用做标识符。) 5| const π = 3.1416 6| const Pi = π // 等价于:const Pi = 3.1416 7| 8| // 声明了一组具名常量。 9| const ( 10| No = !Yes 11| Yes = true 12| MaxDegrees = 360 13| Unit = "弧度" 14| ) 15| 16| func main() { 17| // 声明了三个局部具名常量。 18| const DoublePi, HalfPi, Unit2 = π * 2, π * 0.5, "度" 19| } ``` 常量声明中的等号=表示“绑定”而非“赋值”。每个常量描述将一个或多个字面 量绑定到各自对应的具名常量上。 或者说,每个具名常量其实代表着一个字面常 量。 常量可以直接声明在包中,也可以声明在函数体中。 声明在函数体中的常量 称为局部常量(local constant),直接声明在包中的常量称为包级常量 (package-level constant)。 包级常量也常常被称为全局常量。 包级常量声明中的常量描述的顺序并不重要。比如在上面的例子中,常量描述No和Yes的顺序可以掉换一下。 ### 类型确定具名常量 我们可以在声明一些常量的时候指定这些常量的确切类型。这样声明的常量称为类型确定具名常量。 ```go 1| const X float32 = 3.14 2| 3| const ( 4| A, B int64 = -3, 5 5| Y float32 = 2.718 6| ) //我们也可以使用显式类型转换来声明类型确定常量。 下面的例子和上面的例子是完全等价的 1| const X = float32(3.14) 2| 3| const ( 4| A, B = int64(-3), int64(5) 5| Y = float32(2.718) 6| ) ``` ### 常量声明中的自动补全 在一个包含多个常量描述的常量声明中,除了第一个常量描述,其它后续的常量描 述都可以只有标识符部分。 Go编译器将通过照抄前面最紧挨的一个完整的常量描述 来自动补全不完整的常量描述。 ```go 1| const ( 2| X float32 = 3.14 3| Y // 这里必须只有一个标识符 4| Z // 这里必须只有一个标识符 5| 6| A, B = "Go", "language" 7| C, _ 8| // 上一行中的空标识符是必需的(如果 9| // 上一行是一个不完整的常量描述)。 10| ) //自动补全 1| const ( 2| X float32 = 3.14 3| Y float32 = 3.14 4| Z float32 = 3.14 5| 6| A, B = "Go", "language" 7| C, _ = "Go", "language" 8| ) ``` ### 在常量声明中使用iota iota是Go预声明(内置)的一个特殊的具名常量。其被预声明为0,但是它的值在编译阶段并非恒定。当此预声明的iota出现在一个常量声明中的时候,它 的值在第n个常量描述中的值为n(从0开始)。 所以iota只对含有多个常量描述 的常量声明有意义。 例如 ```go package main 2| 3| func main() { 4| const ( 5| k = 3 // 在此处,iota == 0 6| 7| m float32 = iota + .5 // m float32 = 1 + .5 8| n // n float32 = 2 + .5 9| 10| p = 9 // 在此处,iota == 3 11| q = iota * 2 // q = 4 * 2 12| _ // _ = 5 * 2 13| r // r = 6 * 2 14| s, t = iota, iota // s, t = 7, 7 15| u, v // u, v = 8, 8 16| _, w // _, w = 9, 9 17| ) 18| 19| const x = iota // x = 0 (iota == 0) 20| const ( 21| y = iota // y = 0 (iota == 0) 22| z // z = 1 23| ) 24| 25| println(m) // +1.500000e+000 26| println(n) // +2.500000e+000 27| println(q, r) // 8 12 28| println(s, t, u, v, w) // 7 7 8 8 9 29| println(x, y, z) // 0 0 1 30| } ``` 实际编程中,我们应该用更有意义的方式。比如: ```go 1| const ( 2| Failed = iota - 1 // == -1 3| Unknown // == 0 4| Succeeded // == 1 5| ) 6| 7| const ( 8| Readable = 1 << iota // == 1 9| Writable // == 2 10| Executable // == 4 11| ) ``` ## 变量声明和赋值操作语句 所有的变量值都是类型确定值。当声明一个变量的时候,我们必须在代码中给编译 器提供足够的信息来让编译器推断出此变量的确切类型。 Go语言有两种变量声明形式。一种称为标准形式,另一种称为短声明形式。 短声明形式只能用来声明局部变量。 和常量声明一样,多个变量可以用一对小括号组团在一起被声明。 ```go 1| var ( 2| lang, bornYear, compiled = "Go", 2007, true 3| announceAt, releaseAt int = 2009, 2012 4| createdBy, website string 5| ) ``` ### 每个局部声明的变量至少要被有效使用一次 局部变量被声明后至少要被有效使用一次,否则编译器将报错。包级变量无此限制。 可以参考下图 `_, _ = r, s // 将r和s做为源值使用一次。` ### 值的可寻址性 所有变量都是可以寻址的,所有常量都是不可被寻址的。 ## 变量和常量的作用域 ```golang 1| package main 2| 3| const y = 70 4| var x int = 123 // 包级变量 5| 6| func main() { 7| // 此x变量遮挡了包级变量x。 8| var x = true 9| 10| // 一个内嵌代码块。 11| { 12| x, y := x, y-10 // 这里,左边的x和y均为新声明 13| // 的变量。右边的x为外层声明的 14| // bool变量。右边的y为包级变量。 15| 16| // 在此内层代码块中,从此开始, 17| // 刚声明的x和y将遮挡外层声明x和y。 18| 19| x, z := !x, y/10 // z是一个新声明的变量。 20| // x和y是上一句中声明的变量。 21| println(x, y, z) // false 60 6 22| } 23| println(x) // true 24| println(y) // 70 (包级变量y从未修改) 25| /* 26| println(z) // error: z未定义。 27| // z的作用域仅限于上面的最内层代码块。 28| */ 29| } ``` # 运算符 ## op=运算符 对于一个二元算数运算符op,语句x = x op y可以被简写为x op= y。 在这个 简写的语句中,x只会被估值一次。 就是 += 的描述 ## 字符串衔接运算符 被+的两个操作数,必须为同一类型的字符串值 +=运算符也适用 # 函数的声明和调用 ### 函数声明 标准的函数声明 ```go 1| func SquaresOfSumAndDiff(a int64, b int64) (s int64, d int64) { 2| x, y := a + b, a - b 3| s = x * x 4| d = y * y 5| return // <=> return s, d 6| } ``` Golang 函数不支持入参默认值;参数是默认值是其类型的零值。 连续输入的参数类型共用 ```go 1| func SquaresOfSumAndDiff(a, b int64) (s, d int64) { 2| return (a+b) * (a+b), (a-b) * (a-b) 3| // 上面这行等价于下面这行: 4| // s = (a+b) * (a+b); d = (a-b) * (a-b); return 5| } ```