6️⃣ 标准输入
- Scan
- Scanf
- Scanln
- bufio,推荐使用
- flag,命令行方式适合版本管理
//Scan
var (
appName string
version int
)
fmt.Println("请输入name:")
fmt.Scan(&appName)
fmt.Println("请输入version")
fmt.Scan(&version)
fmt.Printf("fmt.Scan appName:%s version:%d \n", appName, version)
//Scanf
_, err := fmt.Scanf("name=%s ver=%s", &appName, &version)
if err != nil {
fmt.Println(err)
}
fmt.Printf("fmt.Scanf appName:%s version:%s \n", appName, version)
//Scanln
fmt.Println("请输入name")
fmt.Scanln(&appName)
fmt.Println("请输入version")
fmt.Scanln(&version)
fmt.Printf("fmt.Scan appName:%s version:%s \n", appName, version)
// bufio
reader := bufio.NewReader(os.Stdin) // 从标准输入生成读对象
fmt.Println("请输入:")
text, _ := reader.ReadString('\n') // 读到换行
//text, _ := reader.ReadString(' ') // 读到换行
fmt.Println(text)
//flag
//go run -ldflags "-X 'main.appName=test' -X 'main.version=1'" main_test.go
//go build -ldflags "-X 'main.appName=test' -X 'main.version=1'" main_test.go
fmt.Printf("appName:%s version:%s \n", appName, version)
7️⃣ new & make
make:
分配内容 + 初始化 + 返回原始类型(T)- 只用于
slice
,map
,channel
- 只用于
new:
分配内存 + 返回指针类型(*T)- 无限制
- 填充0(仅初始化数组时)
// 源码定义
func make(t Type, size... IntegetType) Type // 例如初始化切片
func new(Type) *Type
以下初始化哪些为空?
- s1 := make([]int, 0)
- s2 := new([]int)
- s3 := *new([]int)
- var s4 []int
- var s5 = []int{} //字面量初始化
package main
import "fmt"
func main() {
// make 初始化切片
var slice1 = make([]int, 10) // = make([]int,10,10) len = cap
// [0 0 0 0 0 0 0 0 0 0] 10 10
fmt.Println(slice1, len(slice1), cap(slice1))
// new 初始化切片
var slice2 = new([]int)
// &[] 0 0
fmt.Println(slice2, len(*slice2), cap(*slice2))
s1 := make([]int, 0)
// s1 0 0![请添加图片描述](https://img-blog.csdnimg.cn/direct/c516e73c51e54fb38537cbf8ef558d4d.png)
fmt.Println("s1", len(s1), cap(s1))
s2 := new([]int)
s3 := *new([]int)
var s4 []int
var s5 = []int{} //字面量初始化
fmt.Println("s1 is nil?", s1 == nil) //false
fmt.Println("s2 is nil?", *s2 == nil) //true
fmt.Println("s3 is nil?", s3 == nil) //true
fmt.Println("s4 is nil?", s4 == nil) //true
fmt.Println("s5 is nil?", s5 == nil) //false
// 创建数组
var arr1 = *new([10]int)
var arr2 = [10]int{} //字面量初始化
// [0 0 0 0 0 0 0 0 0 0] [0 0 0 0 0 0 0 0 0 0]
fmt.Println(arr1, arr2)
}
8️⃣ 数组 切片 Map
数组 | 切片 | Map | |
---|---|---|---|
元素类型 | 全部元素类型相同 | 全部元素类型相同 | key值任何可比较值 |
地址连续 | 连续的内存块 | 连续的内存块 | 不一定连续的内存块 |
访问元素时间 | O(1) | O(1) | O(1)【会更慢】 |
内存占用上 | 连续,死 | 连续,死 | 灵活 |
零值 | 元素类型零值 | nil | — |
直接使用声明的指针类型 | 否(nil) | 否(nil) | 否(nil) |
关于元素类型的零值
- 数组和结构体:对应元素类型
- 整型:0
- 字符串:“”
- 其他:nil
① 初始化
数组
// var 字面量
arr1 := [10]int{} // 初始化会分配内存,不为nil
var arr2 [10]int // 初始化会分配内存,不为nil
fmt.Println(arr1,arr2) // [0 0 0 0 0 0 0 0 0 0] [0 0 0 0 0 0 0 0 0 0]
// new 指针
arr1 := new([10]int) // new会开辟空间
// var arr2 *[10]int = &[10] //错误写法,必须要初始化字面量
var arr3 *[10]int = &[10]{}
fmt.Println(arr1,arr3) // &[0 0 0 0 0 0 0 0 0 0] &[0 0 0 0 0 0 0 0 0 0]
// var 空指针情况
var arr1 *[10]int // 不会初始化分配内存
var arr2 [10]int // 初始化会分配内存,不为nil
arr[1] = 1 // 空指针异常
fmt.Println(arr1) // nil
// 字面量
a1 := [4]string{1:"a","b","c","d"} //❌ 1:a b:2 c:3 d:4 d越界了,只能到下标3
a2 := [4]string{1:"a",0:"b","c"} //❌ 1:a b:0 c:1 c重复下标了
a3 := [...]string{"a","b","c"}
a3[3] = "f" //❌
切片
var s1 = []int{} // 已初始化,不为nil
var s3 = make([]int,1) // 已初始化,不为nil
var s2 = new([]int) // 已初始化,不为nil
s1 = append(s1,1)
*s2 = append(*s2,1) // 指针类型,要解引用
var s1 []int // 未初始化,为nil
var m map[int]int // 未初始化,为nil
s1 = append(s1,1) // ✔ append会自动开辟新的内存,操作运允许
m[1] = 1 // ❌ 未初始化,操作不允许
var arr1 *[]int // 未初始化,为nil
*arr1 = append(*arr1,1) // ❌ 未初始化,操作不允许
// 字面量
s1 := []string{2:"c",0:"a","b"} // 和数组一样
fmt.Println(s1) // [a b c]
// 数组地址转切片
var s2 = new([10]int)[:]// = var s2 = new([&10]int{})[:]
// 常量可以定义,变量不行
const x uint = 1
var y uint = 1
s3 := []string{x:"b"} // ✔
s4 := []string{y:"b"} // ❌
小练习
package main
import "fmt"
func main() {
var s1 = []int{4: 33, 22, 11, 1: 55, 66}
s2 := []string{2: "c", 0: "a", "b"}
fmt.Println(s1, len(s1))
fmt.Println(s2, len(s2))
var a1 = [...]int{4: 33, 22, 11, 1: 55, 66}
a2 := [...]string{2: "c", 0: "a", "b"}
fmt.Println(a1, len(a1))
fmt.Println(a2, len(a2))
}
[0 55 66 0 33 22 11] 7
[a b c] 3
[0 55 66 0 33 22 11] 7
[a b c] 3
② 比较
- 数组,切片能否比较?
- 数组:等长同类型数组可以比较,普通数组不能和nil比较,指针数组可以
- 切片:不能比较
DeepEqual()
与bytes.Equal()
-
同样的申明方式,
DeepEqual()
与bytes.Equal()
无差别 -
不同样的申明方,
bytes.Equal()
准确
var b1 []byte // 仅变量申明,没有初始化
b2 := []byte{} // 字面量方式,自动调用了make函数初始化
fmt.Println(reflect.DeepEqual(b1, b2)) //false
//
v1 := reflect.ValueOf(b1)
v2 := reflect.ValueOf(b2)
fmt.Println(v1.IsNil(), v2.IsNil())
//
fmt.Println(bytes.Equal(b1, b2)) //true
DeepEqual()
与==
DeepEqual
源码
func DeepEqual(x, y any) bool {
// 判断空
if x == nil || y == nil {
return x == y
}
v1 := ValueOf(x)
v2 := ValueOf(y)
// 比较类型
if v1.Type() != v2.Type() {
return false
}
return deepValueEqual(v1, v2, make(map[visit]bool))
}
// visited递归时防止重复比较,比如后面例子中的循环链表
func deepValueEqual(v1, v2 Value, visited map[visit]bool) bool { ... }
- 由
DeepEqual
源码可知,DeepEqual
判断顺序是空值 -> 类型 -> 值
var m map[string]int
var n map[string]int
fmt.Println(m == nil) //true
fmt.Println(n == nil) //true
//不能通过编译,map不能比较
//fmt.Println(m ==n)
m = make(map[string]int, 10)
n = make(map[string]int, 100)
for s, i := range m {
fmt.Println(s, i)
}
m["a"] = 1
n["a"] = 1
fmt.Println(reflect.DeepEqual(m, n)) //true
==
只重视表面的值,如- 指针,只对比地址
- 结构体只比对内部的之,不管类型
// 结构体
type person struct {
name string
}
p1 := person{"test"}
p2 := struct {
name string
}{"test"}
fmt.Println(p1 == p2) //true
fmt.Println(reflect.DeepEqual(p1, p2)) //false
// 指针
x := new(int)
y := new(int)
fmt.Println("x==y:", x == y) //x==y: false
fmt.Println("DeepEqual(x, y):", reflect.DeepEqual(x, y)) //DeepEqual(x, y): true
// 循环链表
type link struct {
data interface{}
next *link
}
var a, b, c link
a.next = &b
b.next = &c
c.next = &a
fmt.Println(a == b) //false
fmt.Println(reflect.DeepEqual(a, b)) //true
③ Map
1. 问题: 哪些类型可以作为map的key?浮点型作为map的key有什么问题?
只有可比较类型才能做map的key(除了slice map function),当然还有含不可比较类型的数组和结构体
所以slice不能作为map的key
func main() {
a1 := [3]int{}
a2 := [3]int{}
fmt.Println(a1 == a2) //true
a3 := [3][]int{} // 数组中包含了不可比较的切片类型
a4 := [3][]int{}
fmt.Println(a3 == a4) //编译不通过
}
浮点型作为map的key时会自动调用math.Float64bits
,可能导致值被覆盖
func main() {
m := map[float64]int{}
m[1.1] = 1
m[1.2] = 2
m[0.3] = 5 //
m[0.30000000000000001] = 6
fmt.Printf("m:%+v\n", m) // math.Float64bits后,0.3 = 0.30000000000000001,所以m[0.3]会被覆盖为6
fmt.Println(math.Float64bits(0.3))
fmt.Println(math.Float64bits(0.30000000000000001))
}
m:map[0.3:6 1.1:1 1.2:2]
4599075939470750515
4599075939470750515
math.NAN【表示 “Not a Number”(不是一个数字)】。这个概念在数学中用来表示一个无法表示或计算的值,或者是一个无法定义的数,math.NaN() 函数的返回值类型为 float64)
func main() {
var x, y float64
x = math.NaN()
y = 123.456
fmt.Println(x == y) // false
fmt.Println(x == x) // false
fmt.Println(y == y) // true
}
func main() {
m := map[float64]int{}
m[math.NaN()] = 3
m[math.NaN()] = 4
fmt.Println("m[math.NaN]=", m[math.NaN()])
fmt.Println("math.NaN() == math.NaN()?", math.NaN() == math.NaN())
for k, v := range m {
fmt.Println("k:", k, "v:", v)
}
}
m[math.NaN]= 0
math.NaN() == math.NaN()? false
k: NaN v: 3
k: NaN v: 4
指针作为key时要注意地址问题
func main() {
p1 := new(int)
p2 := new(int)
fmt.Println(p1, p2)
//
m1 := map[*int]int64{}
m1[p1] = 1
m1[p2] = 2
fmt.Printf("m1:%v \n", m1)
}
0xc000012028 0xc000012040
m1:map[0xc000012028:1 0xc000012040:2]
2. 空struct()作用
- 不占空间
- 配合map实现集合类型(只查找元素是否存在)
// 不占空间
st := struct-demo{}{}
fmt.Println("unsafe.Sizeof.st:", unsafe.Sizeof(st)) //unsafe.Sizeof.st: 0
set1 := map[int]struct{}{1: {}}
set2 := map[int]bool{1: false}
fmt.Println("set1.size:", unsafe.Sizeof(set1[1]), "set2.size:", unsafe.Sizeof(set2[1]))
//set1.size: 0 set2.size: 1
// 配合map实现集合类型
package set
type Set map[int]struct{}
func (s Set) Put(x int) {
s[x] = struct{}{}
}
func (s Set) Has(x int) (exists bool) {
_, exists = s[x]
return
}
func (s Set) Remove(val int) {
delete(s, val)
}
s := make(set.Set)
s.Put(1)
s.Put(2)
fmt.Println(s.Has(1)) //true
s.Remove(1)
fmt.Println(s.Has(1)) //false
④ for range 遍历小细节
for range
变例会创造副本,且每次遍历元素都会指向同一个地址
//错误写法
slice := []int{0, 1, 2, 3}
m := make(map[int]*int)
for key, val := range slice {
m[key] = &val // 每次都是同一个val地址,所以当最后val=3时,m中所有key都为3
}
for k, v := range m {
fmt.Println(k, "->", *v)
}
0 -> 3
1 -> 3
2 -> 3
3 -> 3
- 解决方法
// 解决方法
for key, val := range slice {
value := val
m[key] = &value
}
- 同理,对副本的增减不会影响原来的值
slice := []int{0, 1, 2, 3}
for _, val := range slice {
val++
}
fmt.Println(slice)
arr := [3]int{1, 2, 3}
for _, val := range arr {
val++
}
fmt.Println(arr)
[0 1 2 3]
[1 2 3]
- 使用
for range
的小优化- 不使用副本
slice := []int{0, 1, 2, 3}
m := make(map[int]*int)
// 直接取slice地址
for key, _ := range slice {
m[key] = &slice[key]
}
for k, v := range m {
fmt.Println(k, "->", *v)
}
- 对于直接循环指针类型,需要舍弃value
var ap *[3]int
会导致panic
//for i, p := range ap {
// fmt.Println(i, p)
//}
//舍弃for range的第二个值
//不会导致panic
for i, _ := range ap {
fmt.Println(i)
}
9️⃣ 双引号、单引号、反引号
- rune是int32的别名,不是uint32的别名
- byte是uint8的别名
- 默认单引号是
rune
类型
char1 := 'a'
fmt.Println(char1,reflect.TypeOf(char1)) // a int32
- 双引号支持转义,反引号不支持转移,单引号也不支持
s1 := "\142\"\143\"\144"
s2 := `\142\"\143\"\144`
s3 := '\142\"\143\"\144' // ❌
fmt.Println(s1) // b"c"d
fmt.Println(s2) // \142\"\143\"\144
fmt.Println(s3)
- ⭐️ ASCII进制的打印
//a,b,c的ASCII码值的十进制分别是97,98,99,对应的8进制为141,142,143
//对于字符串abc我们可以通过
s3 := "\141\142\143"
fmt.Println("s3:", s3, reflect.TypeOf(s3))
//a,b,c的ASCII码值的十进制分别是97,98,99,对应的16进制为61,62,63
s4 := "\x61\x62\x63"
fmt.Println("s4:", s4, reflect.TypeOf(s4))
//unicode也是通过16进制的码值表示的
s5 := "\u0061\u0062\u0063"
fmt.Println("s5:", s5, reflect.TypeOf(s5))
s6 := "\U00000061\U00000062\U00000063"
fmt.Println("s6:", s6, reflect.TypeOf(s6))
s3: abc string
s4: abc string
s5: abc string
s6: abc string
- 双引号内
- 单双引号16进制ASCII ✔
- 单双引号8进制ASCII ❌
//单引号ASCII码值的八进制是47,十六进制是27
//双引号ASCII码值的八进制是42,十六进制是22
s7 := "'"
fmt.Println("s6:", s6, reflect.TypeOf(s7))
s8 := "\'" //编译不通过
s9 := ""
" //编译不通过
s10 := "\"" //需要加上转义符方可成为合法的字符串
//fmt.Println("s10:", s10, reflect.TypeOf(s10))
s11 := "\47" //编译不通过
s12 := "\42" //编译不通过
s12 := "\x22"
//fmt.Println("s12:", s12)
s13 := "\u0027"
//fmt.Println("s13:", s13)
s14 := "\U00000027"
//fmt.Println("s14:", s14)
1️⃣0️⃣ 字符串处理
TrimPrefix,
TrimSuffix
匹配到一个就停止Trim = TrimLeft + TrimRight
, 贪婪模式,会找到不满足条件为止
请想想运行的结果
package main
import (
"fmt"
"strings"
)
func main() {
var s = "gogo123go请想想运行的结果goabcgogo"
p := fmt.Println
p("1:", strings.TrimPrefix(s, "go"))
p("2:", strings.TrimSuffix(s, "go"))
p("3:", strings.TrimLeft(s, "go"))
p("4:", strings.TrimRight(s, "go"))
p("5:", strings.Trim(s, "go"))
p("6:", strings.TrimFunc(s, func(r rune) bool {
return r < 128 // trim all ascii chars
}))
}
p("1:", strings.TrimPrefix(s, "go")) // go123go请想想运行的结果goabcgogo
p("2:", strings.TrimSuffix(s, "go")) // gogo123go请想想运行的结果goabcgo
p("3:", strings.TrimLeft(s, "go")) // 123go请想想运行的结果goabcgogo
p("4:", strings.TrimRight(s, "go")) // gogo123go请想想运行的结果goabc
p("5:", strings.Trim(s, "go")) // 请想想运行的结果
#️⃣ 参考资料
说明:文中代码主要来源于