这是我参与「第三届青训营 -后端场」笔记创作活动的的第1篇笔记.
第一部分:基础语法与变量类型
1.1 hello world
package main // 一个package只有一个main方法
import "fmt" // 导入标准库
func main(){
fmt.Println("Hello,world!")
}
1.2 基础数据类型
var a = "initial"
var b, c int = 1, 2
var d = true
var e float64
f := float32(e)
g := a + "foo"
fmt.Println(a, b, c, d, e, f) // initial 1 2 true 0 0
fmt.Println(g) // initialfoo
const s string = "constant"
const h = 500000000
// 3e(+)20 表示3x10^20
const i = 3e20 / h
fmt.Println(3e+5)
// math.Sin 返回弧度参数的正弦值
fmt.Println(s, h, i, math.Sin(h), math.Sin(i))
go中的基本数据类型主要有:整型,浮点型,布尔型,字符串
- 整型有int,int8,int16,int32,int64之分,还有uint8,uint16,uint32,uint64等无符号整数以及还有uint8重定义的byte类型和int32重定义的rune类型;未写明类型的uint和int代表32位或64位
- 浮点型包括float32和float64,分别表示32位和64位的浮点型数;以及complex64和complex128,分别表示32位和64位的实数和虚数
- 布尔型,只能是true或false
- 字符串,可以用+号进行简单拼接,可以用==进行比较,可以遍历,遍历得到的字节是rune类型。更详细的操作在strings包里进行讲解。
变量的命名有几种方式:
- 形如var a int = 2 ,其中类型int可以省略,go语言会根据后面的值自动判断类型;但如果需要int64,int32这种需要指定精度的,则务必要写明
- 短声明,形如a := 1,这种形式比较简单,但全局变量的声明不能用短变量声明
go是一门强类型的语言,变量一旦命名必须被使用;变量类型一旦确定,就不能更改,除非用转换的方式,比如下面的情况:
f1 := 1.234435
f2 := 1.9
a := int(f1)
b := int(f2)
fmt.Println(a) // 1
fmt.Println(b) // 1
可以看出,类型转换可能会有一定的精度损失,还有部分类型不能相互转换,比如string和int或float64是不能相互转换的;比较常用的转换是string和[]byte,也就是字节数组和字符串类型可以相互转换:
s := "bytedance"
b := []byte(s)
fmt.Println(b) // [98 121 116 101 100 97 110 99 101]
fmt.Println(string(b)) // bytedance
常量类型const更是不可更改的,不允许有任何改变; iota,特殊常量,可以认为是一个可以被编译器修改的常量。 iota 在 const关键字出现时将被重置为 0(const 内部的第一行之前),const 中每新增一行常量声明将使 iota 计数一次(iota 可理解为 const 语句块中的行索引)。 iota 可以被用作枚举值
1.3 循环
i := 1
for {
fmt.Println("loop") // loop
break
}
for j := 7; j < 9; j++ {
fmt.Println(j) // 7 8
}
for n := 0; n < 5; n++ {
if n%2 == 0 {
continue
}
fmt.Println(n) // 1 3
}
for i <= 3 {
fmt.Println(i) // 1 2 3
i = i + 1
}
go里面只有for循环,
- for后面可以定义循环变量,循环条件等等;如果什么也不加就是无限循环;
- 只加一个条件就类似其他语言里面的while循环
- 循环控制语句有break,continue, goto等等
1.4 条件语句
if 7%2 == 0 {
fmt.Println("7 is even")
} else {
fmt.Println("7 is odd")
}
if 8%4 == 0 {
fmt.Println("8 is divisible by 4")
}
if num := 9; num < 0 {
fmt.Println(num, "is negative")
} else if num < 10 {
fmt.Println(num, "has 1 digit")
} else {
fmt.Println(num, "has multiple digits")
}
go里面的if/else语句,条件部分不用加括号,if后可以接变量命名,作用域只在if条件体内
a := 2
switch a {
case 1:
fmt.Println("one")
case 2:
fmt.Println("two")
case 3:
fmt.Println("three")
case 4, 5:
fmt.Println("four or five")
default:
fmt.Println("other")
}
go中的switch语句,
- 每个分支是相对独立的,不加break也能自动结束;
- 可以对单个变量进行条件判断,也可以switch后面什么都不加,case分支用条件语句;
- 如果想执行多个分支,可以用fallthrough(也可以用break中止)
switch还可以用于interface类型判断
switch i := x.(type) {
case nil:
fmt.Printf(" x 的类型 :%T",i)
case int:
fmt.Printf("x 是 int 型")
case float64:
fmt.Printf("x 是 float64 型")
case func(int) float64:
fmt.Printf("x 是 func(int) 型")
case bool, string:\
fmt.Printf("x 是 bool 或 string 型" )
default:\
fmt.Printf("未知型")
}
1.5 Array(数组)
var a [5]int
a[4] = 100
fmt.Println("get:", a[2]) // 0
fmt.Println("len:", len(a)) // 5
b := [5]int{1, 2, 3, 4, 5}
fmt.Println(b)
var twoD [2][3]int
for i := 0; i < 2; i++ {
for j := 0; j < 3; j++ {
twoD[i][j] = i + j
}
}
fmt.Println("2d: ", twoD)
数组也是一种数据类型,
- 方括号内的数字表示数组的长度,一旦确定便不能更改;方括号后面接数组存储的元素类型
- 数组的声明方式和变量的声明方式相同,可以用var a [5]int也可以用短声明
- 数组支持通过索引index获取元素,以及通过len获取数组长度
- 如果数组长度不确定,可以使用 ... 代替数组的长度,编译器会根据元素个数自行推断数组的长度:
1.6 Slice(切片)
s := make([]string, 3)
s[0] = "a"
s[1] = "b"
s[2] = "c"
fmt.Println("get:", s[2]) // c
fmt.Println("len:", len(s)) // 3
s = append(s, "d")
s = append(s, "e", "f")
fmt.Println(s) // [a b c d e f]
c := make([]string, len(s))
copy(c, s) // 深拷贝 deep copy
fmt.Println(c) // [a b c d e f]
fmt.Println(s[2:5]) // [c d e]
fmt.Println(s[:5]) // [a b c d e]
fmt.Println(s[2:]) // [c d e f]
good := []string{"g", "o", "o", "d"}
fmt.Println(good) // [g o o d]
切片是一种元素个数不固定的数组,它一般有len长度和cap容量两个参数
- 切片的声明方式除了数组的两种以外,多了一种make的方式:s := make([]string, 3, 5) 前一个是len,后一个是cap
- 通过append函数向切片的末尾追加元素,而删除的话只能通过切片的方式
- 切片的复制一般是浅拷贝,比如[:]的方式和直接赋值,内部指针其实指向同一个底层数组,修改其中一个,另一个也会随之改变
- 如果要实现深拷贝,应该使用copy方法,copy前后的两个切片就不再指向同一个数组
1.7 map
m := make(map[string]int)
m["one"] = 1
m["two"] = 2
fmt.Println(m) // map[one:1 two:2]
fmt.Println(len(m)) // 2
fmt.Println(m["one"]) // 1
fmt.Println(m["unknow"]) // 0
r, ok := m["unknow"]
fmt.Println(r, ok) // 0 false
delete(m, "one")
// it deletes the element with the specified key (m[key]) from the map
m2 := map[string]int{"one": 1, "two": 2}
var m3 = map[string]int{"one": 1, "two": 2}
fmt.Println(m2, m3)
map是go语言中的哈希表,key和value的类型确定了之后就不能改变
- map声明之后一定要初始化;如果不初始化 map,那么就会创建一个 nil map。nil map 不能用来存放键值对。会报空指针的错
- 可以校验某个key值是否存在,如
r, ok := m["unknow"],这里ok是一个布尔值,true表示存在,反之则不存在 - 可以通过delete函数删除map中的某个键值对
1.8 range
nums := []int{2, 3, 4}
sum := 0
for i, num := range nums {
sum += num
if num == 2 {
fmt.Println("index:", i, "num:", num) // index: 0 num: 2
}
}
fmt.Println(sum) // 9
m := map[string]string{"a": "A", "b": "B"}
for k, v := range m {
// todo 为什么顺序不一致
fmt.Println(k, v) // b 8; a A
}
for k := range m {
fmt.Println("key", k) // key a; key b
}
Go 语言中 range 关键字用于 for 循环中迭代数组(array)、切片(slice)、通道(channel)或集合(map)的元素。在数组和切片中它返回元素的索引和索引对应的值,在集合中返回 key-value 对。
在map中也可以只读取key:for key := range m;其他情况忽略另一个值可以用下划线_来代替