第一个go程序
注释
package main // 声明 main 包
import "fmt" // 导入 fmt 包,打印字符串时需要用到
func main(){ // 声明 main 主函数
fmt.Println("Hello, Go!") // 打印 Hello Go!
}
与C语言一样,GO的注释分为两种:
- 单行注释
fmt.Println("Hello, Go!") // 这里的部分作为说明,一般不跨行
- 多行注释
func main() {
/*
以下两行代码都是将信息打印在屏幕上
*/
fmt.Println("可爱淞淞超爱学习!")
fmt.Printf("仿生程序员会梦见代码羊吗")
}
package,包的创建
对于GO语言,package,也就是包,是其管理单位。
每个GO源文件必须先声明它所属的包,所以我们会看到每个GO源文件的开头都是一个package声明。
package name //其中,package是声明 包 名的关键字,name则是包的名字
Go 语言的包与文件夹是一一对应的,
它具有以下几点特性:
-在 一个目录下的同级文件属于同一个包。
- 包的名称可以与其目录名不同。
- main 包是 Go 语言程序的入口包,一个 Go 语言程序必须有且仅有一个 main 包。如果一个程序没有 main 包,那么编译时将会出错,无法生成可执行文件。
import,包的导入
import语句用于导入程序中所依赖的包,导入的包名使用双引号包围,格式如下:
import "name" // import 是导入包的关键字,name 为所导入包的名字。
另外有一点需要注意,导入的包中不能含有代码中没有使用到的包,否则 Go 编译器会报编译错误,例如 imported and not used: "xxx"
,”xxx” 表示包名。
变量与常量
变量
声明变量的一般形式是使用 var 关键字。
-
方法一:声明一个变量, 默认的值是 0
package main import "fmt" func main(){ // 方法一:声明一个变量, 默认的值是0 var a int fmt.Println("a = ", a) fmt.Printf("a的类型是: %T\n", a) }
-
方法二:声明一个变量, 并初始化一个值
package main import "fmt" func main(){ // 方法二:声明一个变量, 初始化一个值 var b int = 100 fmt.Printf("b = %d, type of b = %T\n", b, b) var bb string = "千朵玫瑰带来的黎明" fmt.Printf("bb = %s, bb的类型是: %T\n", bb, bb) }
-
方法三:在初始化的时候,可以省去数据类型,通过值去自动匹配当前变量的数据类型
package main import "fmt" func main(){ // 方法三:在初始化的时候,可以省去数据类型,通过值去自动匹配当前变量的数据类型 var c = 100 fmt.Printf("c = %d, type of c = %T\n", c, c) var cc = "赤土之王与三朝圣者" fmt.Printf("cc = %s, cc的类型是: %T\n", cc, cc) }
-
短声明,只能在函数内
package main import "fmt" func main(){ // 方法四:(常用的方法) 省去var关键字,使用:=,既推导数据类型又赋值 // 注: 短声明是在函数或方法内部使用, 不支持全局变量声明!!!! e := 100 fmt.Printf("e = %d, e的类型是: %T\n", e, e) f := "为了果实、种子还有树" fmt.Printf("f = %s, f的类型是: %T\n", f, f) }
-
多变量声明
package main import "fmt" func main(){ // 声明多个变量 var xx, yy int = 100, 200 var kk, wx = 300, "write_code_666(电子羊)" var ( nn int = 100 mm bool = true ) }
常量
常量(constant)表示固定的值。在计算机程序运行时,不会被程序修改的。
-
定义一个常量,使用 const 关键字。常量定义的时候就要赋值。
package main import "fmt" func main(){ // 常量(只读属性) const length int = 10 // length = 100 // 常量是不允许被修改的 fmt.Println("length = ", length) }
-
使用 const 来定义枚举类型
package main import "fmt" // const来定义枚举类型 const ( BEIJING = 0 SHANGHAI = 1 SHENZHEN = 2 ) func main() { fmt.Println("BEIJING = ", BEIJING) // 0 fmt.Println("SHANGHAI = ", SHANGHAI) // 1 fmt.Println("SHENZHEN = ", SHENZHEN) // 2 }
-
iota
iota 是 Go 语言的常量计数器,只能在常量的表达式中使用。
iota 在 const 关键字出现时将被重置为 0(const 内部的第一行之前),const 中每新增一行常量声明将使 iota 计数一次(iota 可理解为 const 语句块中的行索引)。使用 iota 能简化定义,在定义枚举时很有用package main import "fmt" // const来定义枚举类型 const ( // 可以在const()中添加一个关键字iota, 每行的iota都会累加1, 第一行的iota默认是0 BEIJING = 10 * iota // iota = 0 SHANGHAI // iota = 1 SHENZHEN // iota = 2 ) func main() { fmt.Println("BEIJING = ", BEIJING) // 0 fmt.Println("SHANGHAI = ", SHANGHAI) // 10 fmt.Println("SHENZHEN = ", SHENZHEN) // 20 }
关键字
关键字是 Go 语言中预先保留的单词,在程序中有特殊含义,不能用来定义变量或常量。
Go 语言中有 25 个关键字:
break | default | func | interface | select |
case | defer | go | map | struct |
chan | else | goto | package | switch |
const | fallthrough | if | range | type |
continue | for | import | return | var |
基础数据类型
与C语言基本相同
整型
package main
import (
"fmt"
"math"
)
// 有符号整型
func Integer() {
var num8 int8 = 127
var num16 int16 = 32767
var num32 int32 = math.MaxInt32
var num64 int64 = math.MaxInt64
var num int = math.MaxInt
fmt.Printf("num8的类型是 %T, num8的大小 %d, num8是 %d\n",
num8, unsafe.Sizeof(num8), num8)
fmt.Printf("num16的类型是 %T, num16的大小 %d, num16是 %d\n",
num16, unsafe.Sizeof(num16), num16)
fmt.Printf("num32的类型是 %T, num32的大小 %d, num32是 %d\n",
num32, unsafe.Sizeof(num32), num32)
fmt.Printf("num64的类型是 %T, num64的大小 %d, num64是 %d\n",
num64, unsafe.Sizeof(num64), num64)
fmt.Printf("num的类型是 %T, num的大小 %d, num是 %d\n",
num, unsafe.Sizeof(num), num)
}
// 无符号整型
func unsignedInteger() {
var num8 uint8 = 128
var num16 uint16 = 32768
var num32 uint32 = math.MaxUint32
var num64 uint64 = math.MaxUint64
var num uint = math.MaxUint
fmt.Printf("num8的类型是 %T, num8的大小 %d, num8是 %d\n",
num8, unsafe.Sizeof(num8), num8)
fmt.Printf("num16的类型是 %T, num16的大小 %d, num16是 %d\n",
num16, unsafe.Sizeof(num16), num16)
fmt.Printf("num32的类型是 %T, num32的大小 %d, num32是 %d\n",
num32, unsafe.Sizeof(num32), num32)
fmt.Printf("num64的类型是 %T, num64的大小 %d, num64是 %d\n",
num64, unsafe.Sizeof(num64), num64)
fmt.Printf("num的类型是 %T, num的大小 %d, num是 %d\n",
num, unsafe.Sizeof(num), num)
}
func main() {
Integer()
println("---------------------------------------")
unsignedInteger()
}
- 除非对整型的大小有特定的需求,否则你通常应该使用
int
表示整型宽度,在32
位系统下是32
位,而在64
位系统下是64
位。表示范围:在32
位系统下是-2147483648
~2147483647
,而在64
位系统是-9223372036854775808
~9223372036854775807
。 - 对于
int8
,int16
等这些类型后面有跟一个数值的类型来说,它们能表示的数值个数是固定的。所以,在有的时候:例如在二进制传输、读写文件的结构描述(为了保持文件的结构不会受到不同编译目标平台字节长度的影响)等情况下,使用更加精确的int32
和int64
是更好的。
浮点型
浮点型表示存储的数据是实数,如 3.145。关于浮点型的说明,如表所示。
类型 | 字节数 | 说明 |
---|---|---|
float32 | 4 | 32 位的浮点型 |
float64 | 8 | 64 位的浮点型 |
示例程序:
package main
import (
"fmt"
"math"
)
func showFloat() {
var num1 float32 = math.MaxFloat32
var num2 float64 = math.MaxFloat64
fmt.Printf("num1的类型是%T,num1是%g\n", num1, num1)
fmt.Printf("num2的类型是%T,num1是%g\n", num2, num2)
}
func main() {
showFloat()
}
- 通过上面的程序,我们知道浮点数能表示的数值很大,但是浮点数的精度却没有那么大:
float32
的精度只能提供大约6
个十进制数(表示小数点后6
位)的精度。float64
的精度能提供大约15
个十进制数(表示小数点后15
位)的精度。
字符类型
字符串中的每一个元素叫作“字符”,定义字符时使用单引号。Go 语言的字符有两种,如表所示。
类 型 | 字 节 数 | 说 明 |
---|---|---|
byte | 1 | 表示 UTF-8 字符串的单个字节的值,表示的是 ASCII 码表中的一个字符,uint8 的别名类型 |
rune | 4 | 表示单个 unicode 字符,int32 的别名类型 |
声明示例如下:
package main
import "fmt"
import "unsafe"
func showChar() {
var x byte = 65
var y uint8 = 65
fmt.Printf("x = %c\n", x) // x = A
fmt.Printf("y = %c\n", y) // y = A
}
func sizeOfChar() {
var x byte = 65
fmt.Printf("x = %c\n", x)
fmt.Printf("x 占用 %d 个字节\n", unsafe.Sizeof(x))
var y rune = 'A'
fmt.Printf("y = %c\n", y)
fmt.Printf("y 占用 %d 个字节\n", unsafe.Sizeof(y))
}
func main() {
showChar();
sizeOfChar();
}
byte 类型只能表示 28个值,所以你想表示其他一些值,例如中文的话,就得使用 rune 类型
var y rune = '原'
字符串
字符串在 Go 语言中是以基本数据类型出现的,使用字符串就像使用其他原生基本数据类型 int、float32、float64、bool 一样。
示例程序:
var study string // 定义名为str的字符串类型变量
study = "《阿赫玛尔的故事 》" // 将变量赋值
study2 := "《森林书》" // 以自动推断方式初始化
有些字符串没有现成的文字代号,所以只能用转义字符来表示。常用的转义字符如表所示。
转 义 字 符 | 含 义 |
---|---|
\r | 回车符 return,返回行首 |
\n | 换行符 new line, 直接跳到下一行的同列位置 |
\t | 制表符 TAB |
\' | 单引号 |
\" | 双引号 |
\\ | 反斜杠 |
定义多行字符串的方法如下。
- 双引号书写字符串被称为字符串字面量(string literal),这种字面量不能跨行。
- 多行字符串需要使用反引号“`”,多用于内嵌源码和内嵌数据。
- 在反引号中的所有代码不会被编译器识别,而只是作为字符串的一部分。
多行字符串定义方式如例所示:
package main
import "fmt"
func main() {
var s1 string
s1 = `
study := '你好,谢谢,最后是再见'
fmt.Println(study)
`
fmt.Println(s1)
}
布尔类型
关于 布尔(bool) 类型,即两个值:true 或者 false 。
示例程序:
func showBool(){
a := true
b := false
fmt.Println("a=", a)
fmt.Println("b=", b)
fmt.Println("true && false = ", a && b)
fmt.Println("true || false = ", a || b)
}
func main() {
showBool()
}
Tip:
- 如果你学过其他编程语言或许会发现,布尔型可以参与数值运算,也可以与其他类型进行转换。但是在 Go 中,真值是用
true
表示,并且 不与1
相等;同样的,假值是用false
表示,并且 不与0
相等。
复数型
复数型用于表示数学中的复数,如 1+2j、1-2j、-1-2j 等。在 Go 语言中提供了两种精度的复数类型:complex64 和 complex128 ,分别对应 float32 和 float64 两种浮点数精度,如表所示。
类 型 | 字 节 数 | 说 明 |
---|---|---|
complex64 | 8 | 64 位的复数型,由 float32 类型的实部和虚部联合表示 |
complex128 | 16 | 128 位的复数型,由 float64 类型的实部和虚部联合表示 |
示例程序:
func showComplex() {
// 内置的 complex 函数用于构建复数
var x complex64 = complex(1, 2)
var y complex128 = complex(3, 4)
var z complex128 = complex(5, 6)
fmt.Println("x = ", x)
fmt.Println("y = ", y)
fmt.Println("z = ", z)
// 内建的 real 和 imag 函数分别返回复数的实部和虚部
fmt.Println("real(x) = ", real(x))
fmt.Println("imag(x) = ", imag(x))
fmt.Println("y * z = ", y*z)
}
func main() {
showComplex()
}
-
当然,我们可以对声明进行简化,使用自然的方式书写复数:
x := 1 + 2i y := 3 + 4i z := 5 + 6i
格式化输出
%% | 一个%字面量 |
---|---|
%b | 一个二进制整数值(基数为 2),或者是一个(高级的)用科学计数法表示的指数为 2 的浮点数 |
%c | 字符型。可以把输入的数字按照 ASCII 码相应转换为对应的字符 |
格式 | 含义 |
%d | 一个十进制数值(基数为 10) |
%f | 以标准记数法表示的浮点数或者复数值 |
%o | 一个以八进制表示的数字(基数为 8) |
%p | 以十六进制(基数为 16)表示的一个值的地址,前缀为 0x,字母使用小写的 a-f 表示 |
%q | 使用 Go 语法以及必须时使用转义,以双引号括起来的字符串或者字节切片[]byte,或者是以单引号括起来的数字 |
%s | 字符串。输出字符串中的字符直至字符串中的空字符(字符串以’\0‘结尾,这个’\0’即空字符) |
%t | 以 true 或者 false 输出的布尔值 |
%T | 使用 Go 语法输出的值的类型 |
%x | 以十六进制表示的整型值(基数为十六),数字 a-f 使用小写表示 |
%X | 以十六进制表示的整型值(基数为十六),数字 A-F 使用小写表示 |
容器类型
数组
数组 是一个由 固定长度 的特定类型元素组成的序列,一个数组可以由零个或多个元素组成。因为数组的长度是固定的,因此在 Go 语言中很少直接使用数组。和数组对应的类型是 slice(切片) ,它是可以动态的增长和收缩的序列, slice
功能也更灵活,下面我们再讨论 slice
。
数组声明
可以使用 [n]Type
来声明一个数组。其中 n
表示数组中元素的数量, Type
表示每个元素的类型。
package main
import "fmt"
func test01() {
// 声明时没有指定数组元素的值, 默认为零值
var arr [5]int
fmt.Println(arr)
arr[0] = 1
arr[1] = 2
arr[2] = 3
fmt.Println(arr)
}
func test02() {
// 直接在声明时对数组进行初始化
var arr1 = [5]int{15, 20, 25, 30, 35}
fmt.Println(arr1)
// 使用短声明
arr2 := [5]int{15, 20, 25, 30, 35}
fmt.Println(arr2)
// 部分初始化, 未初始化的为零值
arr3 := [5]int{15, 20} // [15 20 0 0 0]
fmt.Println(arr3)
// 可以通过指定索引,方便地对数组某几个元素赋值
arr4 := [5]int{1: 100, 4: 200}
fmt.Println(arr4) // [0 100 0 0 200]
// 直接使用 ... 让编译器为我们计算该数组的长度
arr5 := [...]int{15, 20, 25, 30, 35, 40}
fmt.Println(arr5)
}
func test03() {
// 特别注意数组的长度是类型的一部分,所以 [3]int 和 [5]int 是不同的类型
arr1 := [3]int{15, 20, 25}
arr2 := [5]int{15, 20, 25, 30, 35}
fmt.Printf("type of arr1 is %T\n", arr1)
fmt.Printf("type of arr2 is %T\n", arr2)
}
func test04() {
// 定义多维数组
arr := [3][2]string{
{"1", "你好"},
{"2", "谢谢"},
{"3", "最后是 再见"}}
fmt.Println(arr) // [[15 20] [25 22] [25 22]]
}
func main() {
test01()
test02()
test03()
test04()
}
数组长度
使用内置的 len
函数将返回数组中元素的个数,即数组的长度。
func arrLength() {
arr := [...]string{"为了果实", "种子", "还有树"}
fmt.Println("数组的长度是:", len(arr)) //数组的长度是: 3
}
数组遍历
使用 for range
循环可以获取数组每个索引以及索引上对应的元素。
func showArr() {
arr := [...]string{"你好", "谢谢", "最后是再见"}
for index, value := range arr {
fmt.Printf("arr[%d]=%s\n", index, value)
}
for _, value := range arr {
fmt.Printf("value=%s\n", value)
}
}
数组是值类型
Go 中的数组是值类型而不是引用类型。当数组赋值给一个新的变量时,该变量会得到一个原始数组的一个副本。如果对新变量进行更改,不会影响原始数组。
func arrByValue() {
arr := [...]string{"兰拉娜", "兰罗摩", "兰百梨迦"}
copy := arr
copy[0] = "Golang"
fmt.Println(arr)
fmt.Println(copy)
}
切片(Slice)
切片是对数组的一个连续片段的引用,所以切片是一个引用类型。切片 本身不拥有任何数据,它们只是对现有数组的引用,每个切片值都会将数组作为其底层的数据结构。slice 的语法和数组很像,只是没有固定长度而已。
创建切片
使用 []Type
可以创建一个带有 Type
类型元素的切片。
// 声明整型切片
var numList []int
// 声明一个空切片
var numListEmpty = []int{}
你也可以使用 make
函数构造一个切片,格式为 make([]Type, size, cap)
。
numList := make([]int, 3, 5)
当然,我们可以通过对数组进行片段截取创建一个切片。
arr := [5]string{"兰罗摩", "兰穆护昆达", "兰利遮", "兰娜库拉", "兰宁巴"}
var s1 = arr[1:4]
fmt.Println(arr) //
fmt.Println(s1) //
切片的长度和容量
一个 slice 由三个部分构成:指针 、 长度 和 容量 。指针指向第一个 slice 元素对应的底层数组元素的地址,要注意的是 slice 的第一个元素并不一定就是数组的第一个元素。长度对应 slice 中元素的数目;长度不能超过容量,容量一般是从 slice 的开始位置到底层数据的结尾位置。简单的讲,容量就是从创建切片索引开始的底层数组中的元素个数,而长度是切片中的元素个数。
内置的 len
和 cap
函数分别返回 slice 的长度和容量。
s := make([]string, 3, 5)
fmt.Println(len(s)) // 3
fmt.Println(cap(s)) // 5
如果切片操作超出上限将导致一个 panic
异常。
s := make([]int, 3, 5)
fmt.Println(s[10]) //panic: runtime error: index out of range [10] with length 3
Tips:
-
由于 slice 是引用类型,所以你不对它进行赋值的话,它的默认值是
nil
var numList []int fmt.Println(numList == nil) // true
-
切片之间不能比较,因此我们不能使用
==
操作符来判断两个 slice 是否含有全部相等元素。特别注意,如果你需要测试一个 slice 是否是空的,使用len(s) == 0
来判断,而不应该用s == nil
来判断。
切片元素的修改
切片自己不拥有任何数据。它只是底层数组的一种表示。对切片所做的任何修改都会反映在底层数组中。
func modifySlice() {
var arr = [...]string{"正法炬书", "水天供书", "吉祥具书"}
s := arr[:] //[0:len(arr)]
fmt.Println(arr) //
fmt.Println(s) //
s[0] = "森林书"
fmt.Println(arr) //[
fmt.Println(s) //
}
这里的 arr[:]
没有填入起始值和结束值,默认就是 0
和 len(arr)
。
追加切片元素
使用 append
可以将新元素追加到切片上。append
函数的定义是 func append(slice []Type, elems ...Type) []Type
。其中 elems ...Type
在函数定义中表示该函数接受参数 elems
的个数是可变的。这些类型的函数被称为可变函数。
func appendSliceData() {
s := []string{"终天的闭幕曲"}
fmt.Println(s)
fmt.Println(cap(s))
s = append(s, "岩壑之崩")
fmt.Println(s)
fmt.Println(cap(s))
s = append(s, "冰风回荡", "冰封交响曲 ")
fmt.Println(s)
fmt.Println(cap(s))
s = append(s, []string{"哀息之茧", "燃尽之舞"}...)
fmt.Println(s)
fmt.Println(cap(s))
}
当新的元素被添加到切片时,如果容量不足,会创建一个新的数组。现有数组的元素被复制到这个新数组中,并返回新的引用。现在新切片的容量是旧切片的两倍。
多维切片
类似于数组,切片也可以有多个维度。
func mSlice() {
numList := [][]string{
{"1", "捕风的异乡人"},
{"2", "辞别久远之躯"},
{"3", "千手百眼天下人间"},
}
fmt.Println(numList)
}
Map
在 Go 语言中,map 是散列表(哈希表)的引用。它是一个拥有键值对元素的无序集合,在这个集合中,键是唯一的,可以通过键来获取、更新或移除操作。无论这个散列表有多大,这些操作基本上是通过常量时间完成的。所有可比较的类型,如 整型
,字符串
等,都可以作为 key
。
创建 Map
使用 make
函数传入键和值的类型,可以创建 map 。具体语法为 make(map[KeyType]ValueType)
。
// 创建一个键类型为 string 值类型为 int 名为 scores 的 map
scores := make(map[string]int)
steps := make(map[string]string)
我们也可以用 map 字面值的语法创建 map ,同时还可以指定一些最初的 key/value :
var steps2 map[string]string = map[string]string{
"第一步": "穿越雾霭与森林",
"第二步": "千朵玫瑰带来的黎明",
"第三步": "赤土之王与三朝圣者",
}
fmt.Println(steps2)
或者
steps3 := map[string]string{
"第一步": "穿越雾霭与森林",
"第二步": "千朵玫瑰带来的黎明",
"第三步": "赤土之王与三朝圣者",
}
fmt.Println(steps3)
Map 操作
-
添加元素
// 可以使用 `map[key] = value` 向 map 添加元素。 steps3["第四步"] = "总监"
-
更新元素
// 若 key 已存在,使用 map[key] = value 可以直接更新对应 key 的 value 值。 steps3["第四步"] = "CTO"
-
获取元素
// 直接使用 map[key] 即可获取对应 key 的 value 值,如果 key不存在,会返回其 value 类型的零值。 fmt.Println(steps3["第四步"] )
-
删除元素
//使用 delete(map, key)可以删除 map 中的对应 key 键值对,如果 key 不存在,delete 函数会静默处理,不会报错。 delete(steps3, "第四步")
-
判断 key 是否存在
// 如果我们想知道 map 中的某个 key 是否存在,可以使用下面的语法:value, ok := map[key] v3, ok := steps3["第三步"] fmt.Println(ok) fmt.Println(v3) v4, ok := steps3["第四步"] fmt.Println(ok) fmt.Println(v4)
这个语句说明 map
的下标读取可以返回两个值,第一个值为当前 key
的 value
值,第二个值表示对应的 key
是否存在,若存在 ok
为 true
,若不存在,则 ok
为 false
。
-
遍历 map
// 遍历 map 中所有的元素需要用 for range 循环。 for key, value := range steps3 { fmt.Printf("key: %s, value: %d\n", key, value) }
-
获取 map 长度
// 使用 len 函数可以获取 map 长度 func createMap() { //... fmt.Println(len(steps3)) // 4 }
map 是引用类型
当 map
被赋值为一个新变量的时候,它们指向同一个内部数据结构。因此,改变其中一个变量,就会影响到另一变量。
func mapByReference() {
steps4 := map[string]string{
"第一步": "穿越雾霭与森林",
"第二步": "千朵玫瑰带来的黎明",
"第三步": "赤土之王与三朝圣者",
}
fmt.Println("steps4: ", steps4)
newSteps4 := steps4
newSteps4["第一步"] = "穿越雾霭与烟林"
newSteps4["第二步"] = "花神之舞"
newSteps4["第三步"] = "千柱沙原"
fmt.Println("steps4: ", steps4)
fmt.Println("newSteps4: ", newSteps4)
}
当 map
作为函数参数传递时也会发生同样的情况。
指针
指针也是一种类型,也可以创建变量,称之为指针变量。指针变量的类型为 *Type
,该指针指向一个 Type
类型的变量。指针变量最大的特点就是存储的某个实际变量的内存地址,通过记录某个变量的地址,从而间接的操作该变量。
创建指针
创建指针有三种方法。
-
首先定义普通变量,再通过获取该普通变量的地址创建指针:
GO // 定义普通变量 x x := "可爱电子羊!" // 取普通变量 x 的地址创建指针 p ptr := &x fmt.Println(ptr) fmt.Println(*ptr)
-
先创建指针并分配好内存,再给指针指向的内存地址写入对应的值:
GO // 创建指针 ptr2 := new(string) // 给指针指向的内存地址写入对应的值 *ptr2 = "更可爱的电子羊!!" fmt.Println(ptr2) fmt.Println(*ptr2)
-
首先声明一个指针变量,再从其他变量获取内存地址给指针变量:
GO // 定义变量 x2 x2 := "超可爱的仿生电子羊!!!" // 声明指针变量 var p *string // 指针初始化 p = &x2 fmt.Println(p)
Tip:
上面举的创建指针的三种方法对学过 C 语言的人来说可能很简单(比如在下),但没学过指针相关知识的人可能不太明白,特别是上面代码中出现的指针操作符 &
和 *
。
&
操作符可以从一个变量中取到其内存地址。*
操作符如果在赋值操作值的左边,指该指针指向的变量;*
操作符如果在赋值操作符的右边,指从一个指针变量中取得变量值,又称指针的解引用。
通过下面的例子,你应该就会比较清楚的理解上面两个指针操作符了。
GO
package main
import "fmt"
func main() {
x := "山巅雪国的记忆"
ptr := &x
fmt.Println("x = ", x)
fmt.Println("*ptr = ", *ptr)
fmt.Println("&x = ", &x)
fmt.Println("ptr = ", ptr)
}
指针的类型
*(指向变量值的数据类型)
就是对应的指针类型。
GO
func pointerType() {
mystr := "雪葬的星银"
myint := 1
mybool := false
myfloat := 3.2
fmt.Printf("type of &mystr is :%T\n", &mystr)
fmt.Printf("type of &myint is :%T\n", &myint)
fmt.Printf("type of &mybool is :%T\n", &mybool)
fmt.Printf("type of &myfloat is :%T\n", &myfloat)
}
func main() {
//...
pointerType()
}
指针的零值
如果指针声明后没有进行初始化,其默认零值是 nil
GO
func zeroPointer() {
x := "这两周小组作业怎么这么多"
var ptr *string
fmt.Println("ptr is ", ptr)
ptr = &x
fmt.Println("ptr is ", ptr)
}
func main() {
//...
zeroPointer()
}
函数传递指针参数
在函数中对指针参数所做的修改,在函数返回后会保存相应的修改。
GO
package main
import (
"fmt"
)
func changeByPointer(value *int) {
*value = 200
}
func main() {
x3 := 99
p3 := &x3
fmt.Println("执行changeByPointer函数之前p3是", *p3)
changeByPointer(p3)
fmt.Println("执行changeByPointer函数之后p3是", *p3)
}
运行程序输出如上,函数传入的是指针参数,即内存地址,所以在函数内的修改是在内存地址上的修改,在函数执行后还会保留结果。
指针与切片
切片与指针一样是引用类型,如果我们想通过一个函数改变一个数组的值,可以将该数组的切片当作参数传给函数,也可以将这个数组的指针当作参数传给函数。但 Go 中建议使用第一种方法,即将该数组的切片当作参数传给函数,因为这么写更加简洁易读。
GO
package main
import "fmt"
// 使用切片
func changeSlice(value []int) {
value[0] = 200
}
// 使用数组指针
func changeArray(value *[3]int) {
(*value)[0] = 200
}
func main() {
x := [3]int{10, 20, 30}
changeSlice(x[:])
fmt.Println(x) // [200 20 30]
y := [3]int{100, 200, 300}
changeArray(&y)
fmt.Println(y) // [200 200 300]
}
Go 中不支持指针运算
学过 C 语言的人肯定知道在 C 中支持指针的运算,例如:p++
,但这在 Go 中是不支持的。
GO
package main
func main() {
x := [...]int{20, 30, 40}
p := &x
p++ // error
}
结构体
结构体(struct) 是一种聚合的数据类型,是由零个或多个任意类型的值聚合成的实体。每个值称为结构体的成员。学过 C 或 C++ 的人都知道结构体,但在 Go 中,没有像 C++ 中的 class
类的概念,只有 struct
结构体的概念,所以也没有继承。
结构体的声明
在 Go 语言 中使用下面的语法是对结构体的声明。
GO
type struct_name struct {
attribute_name1 attribute_type
attribute_name2 attribute_type
...
}
例如下面是定义一个名为 Lesson(课程)
的结构体。
GO
type Lesson struct {
name string //名称
target string //学习目标
spend int //学习花费时间
}
上面的代码声明了一个结构体类型 Lesson
,它有 name
、 target
和 spend
三个属性。可以把相同类型的属性声明在同一行,这样可以使结构体变得更加紧凑,如下面的代码所示。
GO
type Lesson2 struct {
name, target string
spend int
}
上面的结构体 Lesson
称为 命名的结构体(Named Structure) 。我们创建了名为 Lesson
的新类型,而它可以用于创建 Lesson
类型的结构体变量。
声明结构体时也可以不用声明一个新类型,这样的结构体类型称为 匿名结构体(Anonymous Structure) 。
GO
var Lesson3 struct {
name, target string
spend int
}
上面的代码创建了一个匿名结构体 lesson
。
创建命名的结构体
GO
package main
import "fmt"
type Lesson struct {
name, target string
spend int
}
func main() {
// 使用字段名创建结构体
lesson1 := Lesson{
name: "《森林树》",
target: "森林会记住一切",
spend: 5,
}
// 不使用字段名创建结构体
lesson2 := Lesson{"《森林书》", "有点饿了", 5}
fmt.Println("lesson1 ", lesson1)
fmt.Println("lesson2 ", lesson2)
}
上面的例子使用了两种方法创建了结构体,第一种是在创建结构体时使用字段名对每个字段进行初始化,而第二种方法是在创建结构体时不使用字段名,直接按字段声明的顺序对字段进行初始化。
创建匿名结构体
GO
package main
import "fmt"
func main() {
// 创建匿名结构体变量
lesson3 := struct {
name, target string
spend int
}{
name: "水天供书",
target: "大大铁块不懂道理,兰百梨迦就让它懂道理",
spend: 3,
}
fmt.Println("lesson3 ", lesson3)
}
结构体的零值(Zero Value)
当定义好的结构体没有被显式初始化时,结构体的字段将会默认赋为相应类型的零值。
package main
import "fmt"
type Lesson struct {
name, target string
spend int
}
func main() {
// 不初始化结构体
var lesson4 = Lesson{}
fmt.Println("lesson4 ", lesson4)
}
初始化结构体
package main
import "fmt"
type Lesson struct {
name, target string
spend int
}
func main() {
// 为结构体指定字段赋初值
var lesson5 = Lesson{
name: "正法炬书",
target: "森林会记住一切",
}
// 上面的结构体变量 lesson5 只初始化了 name 和 target 字段, spend字段没有初始化,所以会被初始化为零值
fmt.Println("lesson5 ", lesson5)
}
访问结构体的字段
点操作符 .
用于访问结构体的字段。
GO
package main
import "fmt"
type Person struct {
name, gender string
age int
}
func main() {
var lesson6 = Lesson{
name: "森林书",
target: "森林与树的孩子们",
spend: 50,
}
fmt.Println("lesson6 name: ", lesson6.name)
fmt.Println("lesson6 target: ", lesson6.target)
fmt.Println("lesson6 spend: ", lesson6.spend)
}
当然,使用点操作符 .
可以用于对结构体的字段的赋值。
GO
package main
import "fmt"
type Lesson struct {
name, target string
spend int
}
func main() {
var lesson7 = Lesson{}
lesson7.name = "从0到Go语言微服务架构师"
lesson7.target = "全面掌握Go语言微服务如何落地,代码级彻底一次性解决微服务和分布式系统。"
lesson7.spend = 50
fmt.Println("lesson7 ", lesson7)
}
指向结构体的指针
GO
package main
import "fmt"
type Lesson struct {
name, target string
spend int
}
func main() {
lesson8 := &Lesson{"无留陀", "失落的苗圃", 50}
fmt.Println("lesson8 name: ", (*lesson8).name)
fmt.Println("lesson8 name: ", lesson8.name)
}
在上面的程序中, lesson8
是一个指向结构体 Lesson
的指针,上面用 (*lesson8).name
访问 lesson8
的 name
字段,上面的 lesson8.name
代替 (*lesson8).name
的解引用访问。
- 注意:学过 C 语言的同学会认为
lesson8->name
才是正确的访问形式,但是在 Go 语言中,没有->访问的形式,访问结构体中的字段统一都是用.
操作符
匿名字段
在创建结构体时,字段可以只有类型没有字段名,这种字段称为 匿名字段(Anonymous Field) 。
GO
package main
import "fmt"
type Lesson4 struct {
string
int
}
func main() {
lesson9 := Lesson4{"祖拜尔剧场", 50}
fmt.Println("lesson9 ", lesson9)
fmt.Println("lesson9 string: ", lesson9.string)
fmt.Println("lesson9 int: ", lesson9.int)
}
上面的程序结构体定义了两个匿名字段,虽然这两个字段没有字段名,但匿名字段的名称默认就是它的类型。所以上面的结构体 Lesoon4
有两个名为 string
和 int
的字段。
嵌套结构体
结构体的字段也可能是另外一个结构体,这样的结构体称为 嵌套结构体(Nested Structs)
GO
package main
import "fmt"
type Author struct {
name string
wx string
}
type Lesson5 struct {
name,target string
spend int
author Author
}
func main() {
lesson10 := Lesson5{
name: "赛诺",
spend: 50,
}
lesson10.author = Author{
name: "妮露",
wx: "write_code_666",
}
fmt.Println("lesson10 name:", lesson10.name)
fmt.Println("lesson10 spend:", lesson10.spend)
fmt.Println("lesson10 author name:", lesson10.author.name)
fmt.Println("lesson10 author wx:", lesson10.author.wx)
}
上面的程序 Lesson5
结构体有一个字段 author
,而且它的类型也是一个结构体 Author
。
提升字段
结构体中如果有匿名的结构体类型字段,则该匿名结构体里的字段就称为 提升字段(Promoted Fields) 。这是因为提升字段就像是属于外部结构体一样,可以用外部结构体直接访问。就像刚刚上面的程序,如果我们把 Lesson
结构体中的字段 author
直接用匿名字段 Author
代替, Author
结构体的字段例如 name
就不用像上面那样使用 lesson10.author.wx
访问,而是使用 lesson10.wx
就能访问 Author
结构体中的 wx
字段。现在结构体 Author
有 name
、 wx
两个字段,访问字段就像在 Lesson
里直接声明的一样,因此我们称之为提升字段。
GO
package main
import "fmt"
type Author struct {
name string
wx string
}
type Lesson6 struct {
name,target string
spend int
Author
}
func main() {
lesson10 := Lesson6{
name: "赛诺",
target: "大风纪官",
}
lesson10.author = Author{
name: "艾尔海森",
wx: "write_code_666",
}
fmt.Println("lesson10 name:", lesson10.name)
fmt.Println("lesson10 target:", lesson10.target)
fmt.Println("lesson10 author wx:", lesson10.wx)
}
结构体比较
如果结构体的全部成员都是可以比较的,那么结构体也是可以比较的,那样的话两个结构体将可以使用 ==
或 !=
运算符进行比较。可以通过==运算符或 DeeplyEqual()函数比较两个结构相同的类型并包含相同的字段值。因此下面两个比较的表达式是等价的:
GO
package main
import "fmt"
type Lesson struct{
name,target string
spend int
}
func main() {
lesson11 := Lesson{
name: "赛诺",
target: "大风纪官",
}
lesson12 := Lesson{
name: "赛诺",
target: "大风纪官",
}
fmt.Println(lesson11.name == lesson12.name && lesson11.target == lesson12.target) // true
fmt.Println(lesson11 == lesson12) // true
}
给结构体定义方法
在 Go 中无法在结构体内部定义方法,这一点与 C 语言类似。
GO
package main
import "fmt"
// Lesson 定义一个名为 Lesson 的结构体
type Lesson struct {
name,target string
spend int
}
// PrintPersonInfo 定义一个与 Person 的绑定的方法
func (l Lesson) ShowLessonInfo() {
fmt.Println("name:", l.name)
fmt.Println("target:", l.target)
}
func main() {
lesson13 := Lesson{
name: "艾尔海森",
target: "教令院书记官",
}
lesson13.ShowPersonInfo()
}
上面的程序中定义了一个与结构体 Lesson
绑定的方法 ShowLessonInfo()
,其中 ShowLessonInfo
是方法名, (l Lesson)
表示将此方法与 Lesson
的实例绑定,这在 Go 语言中称为接收者,而 l
表示实例本身,相当于 Python 中的 self
,在方法内可以使用 实例本身.属性名称
来访问实例属性。
方法的参数传递方式
如果绑定结构体的方法中要改变实例的属性时,必须使用指针作为方法的接收者。
GO
package main
import "fmt"
// Lesson 定义一个名为 Lesson 的结构体
type Lesson struct {
name,target string
spend int
}
// ShowLessonInfo 定义一个与 Lesson 的绑定的方法
func (l Lesson) ShowLessonInfo() {
fmt.Println("name:", l.name)
fmt.Println("target:", l.target)
}
// AddTime 定义一个与 Lesson 的绑定的方法,使 spend 值加 n
func (l *Lesson) AddTime(n int) {
l.spend = l.spend + n
}
func main() {
lesson13 := Lesson{
name: "艾尔海森",
target: "教令院书记官",
spend:50,
}
fmt.Println("添加add方法前")
lesson13.ShowLessonInfo()
lesson13.AddTime(5)
fmt.Println("添加add方法后")
lesson13.ShowLessonInfo()
}
函数
函数 是基于功能或逻辑进行封装的可复用的代码结构。将一段功能复杂、很长的一段代码封装成多个代码片段(即函数),有助于提高代码可读性和可维护性。由于 Go 语言是编译型语言,所以函数编写的顺序是无关紧要的。
函数的声明
在 Go 语言中,函数声明语法如下:
GO
func function_name(parameter_list) (result_list) {
//函数体
}
函数的声明使用 func
关键词,后面依次接 function_name(函数名)
, parameter_list(参数列表)
, result_list(返回值列表)
以及 函数体
。
- 形式参数列表:函数的参数名以及参数类型,这些参数作为局部变量,其值由参数调用者提供,函数中的参数列表和返回值并非是必须的。
- 返回值列表:函数返回值的变量名以及类型,如果函数返回一个无名变量或者没有返回值,返回值列表的括号是可以省略的。
- 如果有连续若干个参数的类型一致,那么只需在最后一个参数后添加该类型。
GO
package main
import "fmt"
// 函数返回一个无名变量,返回值列表的括号省略
func sum(x int, y int) int {
return x + y
}
// 无参数列表和返回值
func printBookName() {
fmt.Println("《森林书》")
}
// 参数的类型一致,只在最后一个参数后添加该类型
func sub(x , y int) int {
return x - y
}
func main() {
fmt.Println("56 + 1 = ", sum(56, 1))
printBookName()
fmt.Println("58 - 2 =", sub(58, 2))
}
可变参数
上面的程序参数个数都是固定的,在 Go 语言 中也可以使用可变参数的函数。
多个类型一致的参数
在参数类型前面加 ...
表示一个切片,用来接收调用者传入的参数。注意,如果该函数下有其他类型的参数,这些其他参数必须放在参数列表的前面,切片必须放在最后。
GO
package main
import "fmt"
func show(args ...string) int {
sum := 0
for _, item := range args {
fmt.Println(item)
sum += 1
}
return sum
}
func main() {
fmt.Println(show("兰","那","罗"))
}
多个类型不一致的参数
如果传多个参数的类型都不一样,可以指定类型为 ...interface{}
,然后再遍历。
GO
package main
import "fmt"
func PrintType(args ...interface{}) {
for _, arg := range args {
switch arg.(type) {
case int:
fmt.Println(arg, "type is int.")
case string:
fmt.Println(arg, "type is string.")
case float64:
fmt.Println(arg, "type is float64.")
default:
fmt.Println(arg, "is an unknown type.")
}
}
}
func main() {
PrintType(57, 3.14, "兰那罗")
}
解序列
使用 ...
可以用来解序列,能将函数的可变参数(即切片)一个一个取出来,传递给另一个可变参数的函数,而不是传递可变参数变量本身。
GO
package main
import "fmt"
func main() {
var s []string
s = append(s, []string{"兰", "那", "罗"}...)
fmt.Println(s)
}
函数的返回值
当函数没有返回值时,函数体可以使用 return
语句返回。在 Go 中一个函数可以返回多个值。
GO
package main
import "fmt"
func showBookInfo(bookName, authorName string) (string, error) {
if bookName == "" {
return "", errors.New("图书名称为空")
}
if authorName == "" {
return "", errors.New("作者名称为空")
}
return bookName + ",作者:" + authorName, nil
}
func main() {
bookInfo, err := showBookInfo("《千朵玫瑰带来的黎明》", "须弥")
fmt.Printf("bookInfo = %s, err = %v", bookInfo, err)
}
当然,在 Go 中支持返回带有变量名的值。可以将上面的函数修改如下:
GO
func showBookInfo2(bookName, authorName string) (info string, err error) {
info = ""
if bookName == "" {
err = errors.New("图书名称为空")
return
}
if authorName == "" {
err = errors.New("作者名称为空")
return
}
// 不使用 := 因为已经在返回值那里声明了
info = bookName + ",作者:" + authorName
// 直接返回即可
return
}
匿名函数
没有名字的函数就叫 匿名函数 ,它只有函数逻辑体,而没有函数名。匿名函数只拥有短暂的生命,一般都是定义后立即使用。
GO
func (parameter_list) (result_list) {
body
}
内部方法与外部方法
在 Go 语言中,函数名通过首字母大小写实现控制对方法的访问权限。
- 当方法的首字母为 大写 时,这个方法对于 所有包 都是 Public ,其他包可以随意调用。
- 当方法的首字母为 小写 时,这个方法是 Private ,其他包是无法访问的。
包
包(package) 用于组织 Go 源代码,提供了更好的可重用性与可读性。Go 语言有超过 100 个的标准包,可以用 go list std | wc -l
命令查看标准包的具体数目,标准库为大多数的程序提供了必要的基础组件。
main 包
首先,我们先来看看 main
包,该包中包含一个 main()
函数,该函数是程序运行的入口。
package packagename
代码指定了某一源文件属于某一个包。它应该放在每一个源文件的第一行。例如我们 Go 的第一个程序。
GO
// hello go
package main
import "fmt"
func main() {
fmt.Println("《千朵玫瑰带来的黎明》")
}
package main
这一行指定该文件属于 main
包。import "fmt"
语句用于导入一个已存在的名为 fmt
的包。
创建包
下面我们创建自定义的 book
包,其中,属于某一个包的源文件都应该放置于一个单独命名的文件夹里,按照 Go 的惯例,应该用包名命名该文件夹。所以应当先创建一个 book
文件夹,位于该目录下创建一个 book.go
源文件,里面实现我们自定义的数学加法函数。请注意函数名的首字母要大写。
GO
// book.go
package book
func ShowBookInfo(bookName, authorName string) (string, error) {
if bookName == "" {
return "", errors.New("图书名称为空")
}
if authorName == "" {
return "", errors.New("作者名称为空")
}
return bookName + ",作者:" + authorName, nil
}
Tips:
- 导出名字(Exported Names)
- 我们将
book
包中的函数ShowBookInfo
首字母大写。在 Go 中这具有特殊意义。在 Go 中,任何以大写字母开头的变量或者函数都是被导出的名字。其它包只能访问被导出的函数和变量。在这里,我们需要在main
包中访问ShowBookInfo
函数,因此会将它们的首字母大写。 - 如果在
book.go
中将函数名从ShowBookInfo
变为showBookInfo
,并且在main.go
中调用book.showBookInfo
函数,则该程序编译不通过。因为如果想在包外访问一个函数,它应该首字母大写。
- 我们将
导入包
使用包之前我们需要导入包,在 GoLand 中会帮你自动导入所需要的包。导入包的语法为 import path
,其中 path
可以是相对于工作区文件夹的相对路径,也可以是绝对路径。
GO
package main
import (
"fmt"
"book"
)
func main() {
bookName := "《千朵玫瑰带来的黎明》"
author := "须弥"
bookInfo, _ := book.ShowBookInfo(bookName, author)
fmt.Println("bookInfo = ", bookInfo)
}
导入包可以单行导入也可以多行导入,像上面的程序代码就是多行导入的例子,一般我们也建议使用多行导入,当然你也可以使用单行导入:
GO
import "fmt"
import "book"
使用别名
如果我们导入了两个具有同一包名的包时会产生冲突,这时我们可以为其中一个包定义别名:
GO
import (
"crypto/rand"
mrand "math/rand" // 将名称替换为 mrand 避免冲突
)
当然,我们也可以使用别名代替名字很长的包名。
使用点操作
导入一个包后,如果要使用该包中的函数,都要使用 包名.方法名
语法进行调用,对于一些使用高频的包,例如 fmt
包,每次调用打印函数时都要使用 fmt.Println()
进行调用,很不方便。我们可以在导入包的时,使用 import . package_path
语法。从此,我们打印再也不用加 fmt
了。
GO
import . "fmt"
func main() {
Println("hello, world")
}
但这种用法,会有一定的隐患,就是导入的包里可能有函数,会和我们自己的函数发生冲突。
包的初始化
每个包都允许有一个或多个 init
函数, init
函数不应该有任何返回值类型和参数,在代码中也不能显式调用它,当这个包被导入时,就会执行这个包的 init
函数,做初始化任务, init
函数优先于 main
函数执行。该函数形式如下:
GO
func init() {
}
包的初始化顺序:首先初始化 包级别(Package Level) 的变量,紧接着调用 init
函数。包可以有多个 init
函数(在一个文件或分布于多个文件中),它们按照编译器解析它们的顺序进行调用。如果一个包导入了另一个包,会先初始化被导入的包。尽管一个包可能会被导入多次,但是它只会被初始化一次。
包的匿名导入
之前说过,导入一个没有使用的包编译会报错。但有时候我们只是想执行包里的 init
函数来执行一些初始化任务的话应该怎么办呢?
我们可以使用匿名导入的方法,使用 空白标识符(Blank Identifier) :
GO
import _ "fmt"
由于导入时会执行该包里的 init
函数,所以编译仍会将此包编译到可执行文件中。
流程控制
所谓流程控制就是指“程序怎么执行”或者说“程序执行的顺序”。程序整体上确实是从上往下执行,但又不单纯是从上往下。
流程控制可分为三类:
- 顺序执行。这个非常简单,就是先执行第一行再执行第二行……这样依次从上往下执行。
- 选择执行。有些代码可以跳过不执行,有选择地执行某些代码。
- 循环执行。有些代码会反复执行。
条件语句
在 Go 中 条件语句模型 如下:
GO
if 条件1 {
逻辑代码1
} else if 条件2 {
逻辑代码2
} else if 条件 ... {
逻辑代码 ...
} else {
逻辑代码 else
}
如果分支的 condition
为真,则执行该分支 {
和 }
之间的代码。在 Go 中,对于 {
和 }
的位置有严格的要求,它要求 else if
(或 else
) 和两边的花括号,必须在同一行。特别注意,即使在 {
和 }
之间只有一条语句,这两个花括号也是不能省略的。
-
单分支判断
只有一个
if
为单分支判断:GO score := 88 if score >= 60 { fmt.Println("成绩及格") }
-
双分支判断
有
if
和一个else
为两分支判断:GO score := 88 if score >= 60 { fmt.Println("成绩及格") } else { fmt.Println("成绩不及格") }
-
多分支判断
有
if
、else if
以及else
为多分支判断:GO score := 88 if score >= 90 { fmt.Println("成绩等级为A") } else if score >= 80 { fmt.Println("成绩等级为B") } else if score >= 70 { fmt.Println("成绩等级为C") } else if score >= 60 { fmt.Println("成绩等级为D") } else { fmt.Println("成绩等级为E 成绩不及格") }
-
条件语句高级写法
if
还有另外一种写法,它包含一个statement
可选语句部分,该可选语句在条件判断之前运行。它的语法是:GO if statement; condition { }
上面单分支判断的那个例子可以重写如下:
GO if score := 88; score >= 60 { fmt.Println("成绩及格") }
选择语句
在 Go 选择语句模型 如下:
GO
switch 表达式 {
case 表达式值1:
业务逻辑代码1
case 表达式值2:
业务逻辑代码2
case 表达式值3:
业务逻辑代码3
case 表达式值 ...:
业务逻辑代码 ...
default:
业务逻辑代码
}
switch
语句是一个选择语句,用于将 switch
后的表达式的值与可能匹配的选项 case
后的表达式进行比较,并根据匹配情况执行相应的代码块,执行完匹配的代码块后,直接退出 switch-case
。如果没有任何一个匹配,就会执行 default
的代码块。它可以被认为是替代多个 if-else
子句的常用方式。注意:case
不允许出现重复项。例如,下面的例子会输出 Your score is between 80 and 90.
。
GO
grade := "B"
switch grade {
case "A":
fmt.Println("Your score is between 90 and 100.")
case "B":
fmt.Println("Your score is between 80 and 90.")
case "C":
fmt.Println("Your score is between 70 and 80.")
case "D":
fmt.Println("Your score is between 60 and 70.")
default:
fmt.Println("Your score is below 60.")
}
-
一个 case 多个条件
在 Go 中,
case
后可以接多个条件,多个条件之间是 或 的关系,用逗号,
相隔。GO month := 5 switch month { case 1, 3, 5, 7, 8, 10, 12: fmt.Println("该月份有 31 天") case 4, 6, 9, 11: fmt.Println("该月份有 30 天") case 2: fmt.Println("该月份闰年为 29 天,非闰年为 28 天") default: fmt.Println("输入有误!") }
-
选择语句高级写法
switch
还有另外一种写法,它包含一个statement
可选语句部分,该可选语句在表达式之前运行。它的语法是:GO switch statement; expression { }
可以将上面的例子改写为:
GO switch month := 5; month { case 1, 3, 5, 7, 8, 10, 12: fmt.Println("该月份有 31 天") case 4, 6, 9, 11: fmt.Println("该月份有 30 天") case 2: fmt.Println("该月份闰年为 29 天,非闰年为 28 天") default: fmt.Println("输入有误!") }
这里 month
变量的作用域就仅限于这个 switch
内。
-
switch 后可接函数
switch
后面可以接一个函数,只要保证case
后的值类型与函数的返回值一致即可。GO package main import "fmt" func getResult(args ...int) bool { for _, v := range args { if v < 60 { return false } } return true } func main() { chinese := 88 math := 90 english := 95 switch getResult(chinese, math, english) { case true: fmt.Println("考试通过") case false: fmt.Println("考试未通过") } }
-
无表达式的 switch
switch
后面的表达式是可选的。如果省略该表达式,则表示这个switch
语句等同于switch true
,并且每个case
表达式都被认定为有效,相应的代码块也会被执行。GO score := 88 switch { case score >= 90 && score <= 100: fmt.Println("grade A") case score >= 80 && score < 90: fmt.Println("grade B") case score >= 70 && score < 80: fmt.Println("grade C") case score >= 60 && score < 70: fmt.Println("grade D") case score < 60: fmt.Println("grade E") }
该
switch-case
语句相当于if-elseif-else
语句。 -
fallthrough 语句
正常情况下
switch-case
语句在执行时只要有一个case
满足条件,就会直接退出switch-case
,如果一个都没有满足,才会执行default
的代码块。不同于其他语言需要在每个case
中添加break
语句才能退出。使用fallthrough
语句可以在已经执行完成的case
之后,把控制权转移到下一个case
的执行代码中。fallthrough
只能穿透一层,不管你有没有匹配上,都要退出了。fallthrough
语句是case
子句的最后一个语句。如果它出现在了case
语句的中间,编译会不通过。GO s := "森林书" switch { case s == "森林书": fmt.Println("森林书") fallthrough case s == "正法炬书": fmt.Println("正法炬书") case s != "水天供书": fmt.Println("水天供书") }
循环语句
与C及python中用法相同
// for 接三个表达式
for initialisation; condition; post {
code
}
// for 接一个条件表达式
for condition {
code
}
// for 接一个 range 表达式
for range_expression {
code
}
// for 不接表达式
for {
code
}
num := 0
for num < 4 {
fmt.Println(num)
num++
}
for num := 0; num < 4; num++ {
fmt.Println(num)
}
str := "千朵玫瑰带来的黎明"
for index, value := range str{
fmt.Printf("index %d, value %c\n", index, value)
}