流程控制
类比 clang
if
- 条件表达式没有括号
- 左大括号必须和条件语句或
else
在同一行
- package main
- func main() {
- a,b:=1,2
- // 没有小括号
- if a>b{
- println(b)
- }else{
- print(a)
- }
- }
go
- 支持一个初始化表达式(可以是并行方式)
- 支持单行模式
- package main
- func main() {
- if a,b:=1,2;a>b{
- println(b)
- }else{
- print(a)
- }
- }
go
- 初始化语句中的变量为
block
级别,同时隐藏外部同名变量
- package main
- func main() {
- var a =true
- if a,b:=1,2;a>b{
- println(b)
- }else{
- print(a)
- }
- println(a)
- }
go
for
- Go只有
for
一个循环语句关键字,但支持3种形式 - 初始化和步进表达式可以是多个值
- 条件语句每次循环都会被重新检查,因此不建议在条件语句中使用函数,尽量提前计算好条件并以变量或常量代替
- 左大括号必须和条件语句在同一行
- package main
- func main() {
- for{
- //死循环
- }
- }
go
- package main
- func main() {
- flag := 1
- //while
- for flag < 5 {
- flag++
- println(flag)
- }
- }
go
- package main
- func main() {
- //index:=1
- // for ;index < 5;index++ {
- // println(index)
- // }
- for idx:=0;idx<5;idx++{
- println(idx)
- }
- }
go
switch
- 可以使用任何类型或表达式作为条件语句
- 不需要写
break
,一旦条件符合自动终止 - 如希望继续执行下一个
case
,需使用fallthrough
语句 - 支持一个初始化表达式(可以是并行方式),右侧需跟分号
- 左大括号必须和条件语句在同一行
- package main
- func main() {
- swh := 1
- switch swh {
- case 0:
- println(0)
- case 1:
- {
- println(1)
- println("OK")
- }
- default:
- println("default")
- }
- }
go
- package main
- func main() {
- switch swh:=1;{
- case swh > 0:
- println(0)
- fallthrough
- case swh == 1:
- {
- println("OK")
- }
- default:
- println("default")
- }
- }
go
goto, break, continue
- 三个语法都可以配合标签使用
- 标签名区分大小写,若不使用会造成编译错误
break
与continue
配合标签可用于多层循环的跳出标签同级goto
是调整执行位置,与其它2个语句配合标签的结果并不相同
- package main
- func main() {
- FLG:
- for{
- for i:=0;i<10;i++{
- if i>2{
- break FLG
- }else{
- println(i)
- }
- }
- }
- }
go
- package main
- func main() {
- FLG:
- for i := 0; i < 10; i++ {
- for {
- println(i)
- continue FLG
- }
- }
- }
go
将上面中的continue替换成goto,程序运行的结果还一样吗❓
数组
- 数组是一个由固定长度的特定类型元素组成的序列,一个数组可以由零个或多个元素组成。
- 数组的每个元素可以通过索引下标来访问,索引下标的范围是从0开始到数组长度减1的位置。
- 内置的len函数将返回数组中元素的个数。
- 值类型 数组的长度是数组类型的一个组成部分,因此[3]int和[4]int是两种不同的数组类型。
- var a [3]int
- fmt.Println(a[0])
- fmt.Println(a[len(a)-1])
- for i, v := range a {
- fmt.Printf("%d %d\n", i, v)
- }
- // Print the elements only.
- for _, v := range a {
- fmt.Printf("%d\n", v)
- }
go
数组的每个元素都被初始化为元素类型对应的零值,也可以使用数组字面值语法用一组值来初始化数组。
- var q [3]int = [3]int{1, 2, 3}
- var r [3]int = [3]int{1, 2}
- fmt.Println(r[2]) // "0"
在数组字面值中,如果在数组的长度位置出现的是“…”省略号,则表示数组的长度是根据初始化值的个数来计算。
- q := [...]int{1, 2, 3}
- fmt.Printf("%T\n", q) // "[3]int"
数组的长度必须是常量表达式,因为数组的长度需要在编译阶段确定。
- q := [3]int{1, 2, 3}
- q = [4]int{1, 2, 3, 4} // compile error: cannot assign [4]int to [3]int
可以指定一个索引和对应值列表的方式初始化,初始化索引的顺序是无关紧要的。
- func main() {
- arr := [...]int{2: 10}
- fmt.Println(arr)//[0 0 10]
- }
go
- package main
- import "fmt"
- func main() {
- var arr1 [5]int = [5]int{}
- arr1[1] = 99
- var arr2 = [4]int{}
- // paintln 只能输出简单类型
- //println(arr1)
- fmt.Println(arr1)
- // 不同的类型不能比较
- //invalid operation: arr1 == arr2 (mismatched types [5]int and [4]int)
- // if arr1 == arr2 {
- // fmt.Println("arr1==arr2")
- // }
- //指向数组的指针
- var arr3 = new([3]int)
- fmt.Println(&arr2, arr3)
- // 由编译器推断数组大小
- arr4 := [...]int{1, 2, 3, 4, 5, 6, 10: 9}
- fmt.Println(arr4, len(arr4))
- // 值类型 copy
- arr5 := arr4
- fmt.Printf("%p,%p\n", &arr4[1], &arr5[1])
- //arr6:=[2][3]int{}
- //多维数组
- arr6 := [2][3]int{
- {1, 2, 3},
- {4, 5, 6},
- }
- fmt.Println(arr6)
- }
range
- 完整使用方式
for k,v:=range arr{ /* do something*/}
- 索引方式
for item:=range { /* do something*/}
- 值方式
for _,v:=range arr{/* do something*/}
- package main
- import "fmt"
- func main() {
- var arr = [10]int{2, 3, 4, 5, 6, 7, 8, 9}
- for k,v:=range arr{
- fmt.Println(k,v)
- //i:=0
- //fmt.Printf("i:%p\n",&i)
- fmt.Printf("%p,%p\n",&k,&v)
- }
- println("oth")
- for item:=range arr{
- fmt.Println(item)
- }
- for _,v:=range arr{
- fmt.Println(v)
- }
- }
go
Slice
- Slice(切片)代表变长的序列,序列中每个元素都有相同的类型。
- 一个slice类型一般写作
[]T
,其中T
代表slice
中元素的类型。 - 变长数组啦
- type slice struct{
- len int
- cap int
- data point
- }
数组和slice之间有着紧密的联系。
一个slice由三个部分构成:指针、长度和容量。
指针指向第一个slice元素对应的底层数组元素的地址,slice
的第一个元素不一定就是数组的第一个元素。
长度对应slice中元素的数目;长度不能超过容量,容量一般是从slice的开始位置到底层数据的结尾位置。
len
和cap
函数分别返回slice的长度和容量。
多个slice之间可以共享底层的数据,并且引用的数组部分区间可能重叠。
字符串的切片操作和[]byte
字节类型切片的切片操作是类似的。都写作x[m:n]
,并且都是返回一个原始字节系列的子序列,底层都是共享之前的底层数组,因此这种操作都是常量时间复杂度。
- Array_ori := [...]byte{'a', 'b', 'c', 'd', 'e', 'f', 'h', 'i', 'j', 'k'}
- Slice_a := Array_ori[2:5]
- Slice_b:= Array_ori[3:5]
- fmt.Println(Array_ori, len(Array_ori))
- fmt.Println(Slice_a, len(Slice_a), cap(Slice_a))
- fmt.Println(Slice_b, len(Slice_b), cap(Slice_b))
go
slice唯一合法的比较操作是和nil
比较。
- if summer == nil { /* ... */ }
一个零值的slice
等于nil
。一个nil
值的slice
并没有底层数组。一个nil
值的slice
的长度和容量都是0
。
但也有非nil
值的slice
的长度和容量也是0
的。
- var s []int // len(s) == 0, s == nil
- s = nil // len(s) == 0, s == nil
- s = []int(nil) // len(s) == 0, s == nil
- s = []int{} // len(s) == 0, s != nil
go
内置的make函数创建一个指定元素类型、长度和容量的slice
。容量部分可以省略(容量将等于长度)。
- make([]T, len)
- make([]T, len, cap)
append函数
内置的append函数用于向slice追加元素
- var arr []rune
- for _, r := range "Hello,世界" {
- arr = append(arr, r)
- }
- fmt.Println(arr) //[72 101 108 108 111 44 19990 30028]
- //%q 单引号围绕的字符字面值
- fmt.Printf("%q\n", arr) // ['H' 'e' 'l' 'l' 'o' ',' '世' '界']
go
append函数则可以追加多个元素,甚至追加一个slice。
面向过程
- var x []int
- x = append(x, 1)
- x = append(x, 2, 3)
- x = append(x, 4, 5, 6)
- x = append(x, x...) // append the slice x
- fmt.Println(x) // "[1 2 3 4 5 6 1 2 3 4 5 6]"
go
Map
- 无序的
key/value
对的集合,其中所有的key
都是不同的,然后通过给定的key
可以在常数时间复杂度内检索、更新或删除对应的value
。 - 一个
map
就是一个哈希表的引用,map
类型可以写为map[K]V
,其中K
和V
分别对应key
和value
的数据类型,key
和value
可以是不同的数据类型。 K
对应的key
必须是支持==
比较运算符的数据类型,V
对应的value
数据类型则没有限制。
内置的make函数可以创建一个map:
- ages := make(map[string]int)
也可以用map字面值的语法创建map
,同时还可以指定一些最初的key/value
。
- ages := map[string]int{
- "zxy": 24,
- "zxysilent": 24,
- }
go
等价于
- ages := make(map[string]int)
- ages["zxy"] =24
- ages["zxysilent"] = 24
创建空的map
的表达式是map[string]int{}
。
Map
中的元素通过ke
y对应的下标语法访问。
- ages["zxy"] = 32
- fmt.Println(ages["zxy"]) // "32"
内置的delete函数可以删除元素。
- delete(ages, "zxy")
⚠️所有这些操作是安全的,即使这些元素不在map中也没有关系;如果一个查找失败将返回value类型对应的零值。
- ages["xxx"] = ages["xxx"] + 1
x += y
和x++
等简短赋值语法也可以用在map上。
- ages["xxx"] += 1
更简单的写法
- ages["xxx"]++
不能对map的元素进行取址操作⁉️。
遍历 map
- ages := make(map[string]int)
- ages["zxysilent"] = 24
- ages["zxy"] = 24
- for k, v := range ages {
- fmt.Println(k, v)
- }
- fmt.Println("------------")
- for k := range ages {
- fmt.Println(k)
- }
- fmt.Println("------------")
- for _, v := range ages {
- fmt.Println(v)
- }
go
遍历的顺序是随机的
map上的大部分操作,包括查找、删除、len和range循环都可以安全工作在nil值的map上,它们的行为和一个空的map类似。但向一个nil值的map存入元素将导致一个panic异常:
- ages["carol"] = 21 // panic: assignment to entry in nil map
⛔️在向map存数据前必须先创建map。
通过key作为索引下标来访问map将产生一个value。如果key在map中是存在的,那么将得到与key对应的value;如果key不存在,那么将得到value对应类型的零值,有时候可能需要知道对应的元素是否真的是在map之中。
- age, ok := ages["zxy"]
- if !ok { /* todo */ }
变成一行
- if age, ok := ages["bob"]; !ok { /* ... */ }
map的下标语法将产生两个值;第二个是一个布尔值,用于报告元素是否真的存在。
结构体
- 结构体是一种聚合的数据类型,是由零个或多个任意类型的值聚合成的实体。
- 每个值称为结构体的成员。
- // Person 结构体 描述人
- type Person struct {
- Name string //姓名
- Age int //年龄
- Addr string //地址
- Birthday time.Time //生日
- }
- var zxy Person
go
声明了一个叫Person
的命名的结构体类型,并且声明了一个Person
类型的变量zxy
。
zxy
结构体变量的成员可以通过点操作符访问。
- zxy.Name = "曾祥银"
- fmt.Println(zxy.Name)
对成员取地址,然后通过指针访问。
- age := &zxy.Age
- *age = 24
- fmt.Println(zxy.Age)
指向结构体的指针也可用点操作符。
- alias := &zxy
- alias.Age = 24
- fmt.Println(zxy.Age)
- (*alias).Age = 24
- fmt.Println(zxy.Age)
go
一个命名为S
的结构体类型将不能再包含S
类型的成员但可以包含*S
指针类型的成员。
- type tree struct {
- data interface{}
- left *tree
- right *tree
- }
go
结构体字面值
结构体值也可以用结构体字面值表示,结构体字面值可以指定每个成员的值。
两种形式的结构体字面值语法:
- 以结构体成员定义的顺序为每个结构体成员指定一个字面值。
- 以成员名字和相应的值来初始化,可以包含部分或全部的成员,顺序并不重要❗️。
- type Point struct {
- X int
- Y int
- }
- func main() {
- p := Point{1, 2}
- p1 := Point{
- Y: 10,
- X: 3,
- }
- }
go
结构体可以作为函数的参数和返回值。
- func Scale(p Point, f int) Point {
- return Point{p.X * f, p.Y * f}
- }
- fmt.Println(Scale(Point{1, 2}, 5)) // "{5 10}"
go
较大的结构体通常会用指针的方式传入和返回,
要在函数内部修改结构体成员的话,用指针传入是必须的⛵️(值传递)。
创建并初始化一个结构体变量,并返回结构体的地址。
- p := &Point{1, 2}
等价于
- p := new(Point)
- *p = Point{1, 2}
- 结构体类型的零值是每个成员都是零值。
- 结构体没有任何成员的话就是空结构体,写作struct{}。
结构体高级操作
- //Point 一个点
- type Point struct {
- X int
- Y int
- }
- //Circle 圆
- type Circle struct {
- Point //圆心//嵌套结构体匿名成员
- Radius int //半径
- }
- func main() {
- c := Circle{
- Point: Point{0, 0},
- Radius: 10,
- }
- c1 := Circle{Point{0, 0}, 10}
- c1.X
- c1.Point.X
- }
JSON
-
JavaScript
对象表示法(JSON
)是一种用于发送和接收结构化信息的标准协议。
还有XML
、Protocol Buffers
等。 -
标准库支持
encoding/json
✴️
序列化/编码 Marshal
Go语言的数据结构数据转换为
json
字符串
- //Point 一个点
- type Point struct {
- X int
- Y int
- }
- //Circle 圆
- type Circle struct {
- Point //圆心
- Radius int //半径
- }
- func main() {
- c := Circle{
- Point: Point{1, 2},
- Radius: 10,
- }
- //序列化
- buf, _ := json.Marshal(c)
- fmt.Println(buf) //[123 34 88 34 58 49 44 34 89 34 58 50 44 34 82 97 100 105 117 115 34 58 49 48 125]
- fmt.Println(string(buf)) //{"X":1,"Y":2,"Radius":10}
- }
go
在编码时,默认使用Go语言结构体的成员名字作为JSON的对象。
一个结构体成员标签Tag
可以修改映射关系
- //Circle 圆
- type Circle struct {
- Point //圆心
- Radius int `json:"半径"` //半径
- }
- c := Circle{
- Point: Point{1, 2},
- Radius: 10,
- }
- //序列化
- buf, _ := json.Marshal(c)
- fmt.Println(string(buf)) //{"X":1,"Y":2,"半径":10}
反序列化/解码 Unmarshal
json
字符串转换为Go语言的数据结构
- s := `{"X":1,"Y":2,"半径":10}`
- c := Circle{}
- json.Unmarshal([]byte(s), &c)
- fmt.Println(c)//{{1 2} 10}
go
文本和HTML模板
- Go语言提供了对模板的支持(数据驱动模板)在"text/template"和"html/template"两个包下。
- 使用方式类似,
html/template
主要针对html
而text/template
主要针对文本。
- package main
- import (
- "html/template"
- "os"
- )
- // Person 结构体 描述人
- type Person struct {
- Name string //姓名
- Age int //年龄
- Addr string //地址
- Arr []string //oth
- }
- const str = `
- <html>
- <head>
- <title>template</title>
- </head>
- <body>
- <h2>{{.Name}}</h2>
- <ul>
- {{range $k,$v := .Arr}}
- <li>{{$v}}</li>
- {{end}}
- </ul>
- </body>
- </html>
- `
- func main() {
- z := Person{
- Name: "曾祥银",
- Age: 24,
- Arr: []string{"A", "B", "C", "D"},
- }
- t, _ := template.New("模板名称").Parse(str) //.ParseFiles 读取文件
- t.Execute(os.Stdout, z) //os.Stdout 标准输出流 相当于 fmt.Ptintln
- }
Comments