Go-常识补充-切片-map(类似字典)-字符串-指针-结构体
目录
Go 常识补充
Go 命名
名字首字母大写可以在外部引用,如果是小写则不能(结构体里面的变量名也是)
打印变量类型科普
fmt.Printf("%T", a),注意,用的是
fmt.Printf函数,a 指的是要查看类型的变量
package main import "fmt" func main() { fmt.Printf("%T", "asfasf") //string }
_
关键字
_关键字可以用来占位,把不需要的值赋给它(有点像 python 解压赋值,把不需要的值扔给
_)
命名规范相关
go 没有规定变量 或 函数名的命名规范是驼峰体还是下划线,文件名推荐使用下划线
不过一般命名都是用驼峰体(不然要暴露给外界的首字母要大写,你后面又小写,就很恶心了
Color_of_bottle
)
包目录规范
所有的包都必须在 gopath 路径下的 src 文件夹下
目前 go 的包管理还不是很成熟,如果依赖了第三方包的不同版本,会不能兼容(只能有一个版本)
如果非要兼容两个版本,开发的时候可以暂时先把 gopath 改一下,这样就可以放另一个版本了
Go 语言架构
GOPATH 的工作区包含 bin、src 和 pkg(没有这三个文件夹时需要自己创建)
- src ------- 源码(包含第三方的和自己项目的)
- bin ------- 编译生成的可执行程序
- pkg ------ 编译时生成的对象文件
切片
切片就是对数组的一个引用,其相对数组更灵活一点
切片的长度是切片中的元素数。
切片的容量是从创建切片索引(位置)开始的底层数组中元素数。
1)切片本身不拥有任何数组,只是对现有数组的引用
- 其实就是一个内存地址,指向某个地方
2)切片的原则是前闭后开
package main import "fmt" func main() { var a = [8]int{1, 2, 3, 4, 5, 6, 7, 8} // 1)利用数组来创建切片 // 切片,前闭后开,这里取的是第3到第6个,但是第6个没取到 var b []int = a[2:5] fmt.Println(b) //[3 4 5] // 2)用 make 来创建切片,第二个参数5是切片长度,第三个参数6是容量 i := make([]int, 5, 6) fmt.Println(i) //[0 0 0 0 0] // 切片只是对数组的一个引用,指向的是该数组 a[3] = 111 fmt.Println(a) fmt.Println(b) //[1 2 3 111 5 6 7 8] //[3 111 5] b[0] = 222 fmt.Println(a) fmt.Println(b) //[1 2 222 111 5 6 7 8] //[222 111 5] // 3)直接初始化切片 var g []int = []int{1, 2, 3} }
3)切片没有步长这么一说(别和 python 的列表切片搞混了)
4)切片的空值是
nil(虽然打印出来是
[])
- go 里所有的引用类型的空值是
nil
5)切片依附于底层数组,底层数组修改会影响切片,切片修改也会影响底层数组
- 切片可以通过 内置函数
append()
来扩容,如果超过了切片的长度,切片会自动扩容,申请一个新的数组,变为原来切片容量的两倍,然后与原来的底层数组断开依附,关联新的这个数组(在没有超长扩容时,依旧会与定义时的底层数组相关联)
6)切片要修改值,直接改索引对应的值就行了(会影响到依附的数组)
package main import "fmt" func main() { var a [8]int = [8]int{1, 2, 3, 4, 5, 6, 7, 8} var b []int = a[2:5] fmt.Println(len(b)) fmt.Println(cap(b)) //3 //6 // 内置函数 append b = append(b, 555) fmt.Println(b) fmt.Println(a) //[3 4 5 555] //[1 2 3 4 5 555 7 8] b[0] = 1111 // 切片要修改值,直接按索引改就行了 fmt.Println(a) fmt.Println(b) //[1 2 1111 4 5 555 7 8] //[1111 4 5 555] var e []int = []int{1, 2} fmt.Println(e) fmt.Println(len(e)) fmt.Println(cap(e)) //[1 2] //2 //2 e = append(e, 3) fmt.Println(e) fmt.Println(len(e)) fmt.Println(cap(e)) // 如果超过了切片的长度,切片会自动扩容,申请一个新的数组,变为原来切片容量的两倍 //[1 2 3] //3 //4 e[2] = 99 // 切片要修改值,直接改索引对应的值就行了 fmt.Println(e) fmt.Println(len(e)) fmt.Println(cap(e)) //[1 2 99] //3 //4 }
7)切片作为函数参数传递
- go 参数传递是 copy 传递,所以传过去也是引用,改动会影响原来的数组(间接又影响那个切片)
多维切片
package main import "fmt" func main() { // 多维切片 var a [][]string = make([][]string, 2, 3) fmt.Println(a[0]) //[] //a[0] = make([]string, 2, 3) // 定义了下面就不会打印 a[0] == nil 了 if a[0] == nil { fmt.Println("a[0] == nil") } //a[0] == nil }
切片初始化的方法
package main import "fmt" func main() { var a []int = []int{1,2,3} fmt.Println(a) fmt.Println(len(a)) fmt.Println(cap(a)) //[1 2 3] //3 //3 }
多维切片初始化
二维切片只能放切片类型
a[0] = "xxx"=>
a[0][1] = "8787
package main import "fmt" func main() { // 多维切片初始化 var a [][]string = [][]string{{"1", "2"}, {"3", "4"}} fmt.Println(a) fmt.Println(len(a)) fmt.Println(cap(a)) a[0][1] = "999" fmt.Println(a) //[[1 2] [3 4]] //2 //2 //[[1 999] [3 4]] }
6)索引只能取长度范围内的索引,(容量比长度大)要操作容量的,要用
append()内置方法
切片删除元素(会略微影响效率 ,少用)
跳过了某些元素(即便是这样,也比 python 的效率还要高得多)
package main import "fmt" func main() { var a = []int{1, 2, 3, 4, 5, 6} fmt.Println(a) //[1 2 3 4 5 6] // 删除第三个元素 a = append(a[0:2], a[3:]...) // 前开后闭原则,索引为2的元素(即第三个元素)被忽略掉了 fmt.Println(a) //[1 2 4 5 6] }
copy 函数
可以用来把一个切片 copy 到另一个切片上,然后就可以回收底层数组占用的内存
- 比如一个切片:容量是10000,长度为3,浪费内存
- 此时把它 copy 到另一个切片上,让 go 的垃圾回收机制自动取回收不用的内存(那个没用的切片)
- copy 过去的那个切片对应的底层数组又是一个新的了,所以 copy 过的切片不会影响之前那个切片
需要 copy 到的切片,长度比要 copy 的切片长就 copy 过来,短就丢掉超了的那部分
package main import "fmt" func main() { // 创建一个长度为3,容量为10000的切片 var a = make([]int, 3, 10000) a[1] = 99 a[2] = 888 fmt.Println(a) //[0 99 888] // 创建一个长度为2,容量为4的切片 var b = make([]int, 2, 4) fmt.Println(b) //[0 0] copy(b, a) // 把 a 拷贝给 b (右边的拷贝给左边) fmt.Println(b) //[0 99] var c = make([]int, 5, 10000) c[1] = 2 c[3] = 4 //c[9] = 9 // 会报错,超过 c 的长度了(虽然容量还够的-->要想加用内置函数 append) fmt.Println(c) //[0 2 0 4 0] var d = make([]int, 2) fmt.Println(d) //[0 0] copy(d, c) // 把 c 拷贝给 d (右边的拷贝给左边) fmt.Println(d) //[0 2] //超出长度的就丢弃掉了 fmt.Println(c) //[0 2 0 4 0] }
打散切片
package main import "fmt" func main() { a := [8]int{1,2,3,4,5,6,7,8} b := a[:] // 取一个内容和 a 数组一模一样的切片 fmt.Println(b) //[1 2 3 4 5 6 7 8] printGTE6(b) //下面将返回大于等于6的元素 //6 //7 //8 printGTE7(1,2,34,5,6,8) //下面将返回大于等于7的元素 //34 //8 printGTE7(b...) // 可以利用 ... 将切片打散成一个个参数传递 //下面将返回大于等于7的元素 //7 //8 } func printGTE6(a []int) { // 一个函数,接收一个数组,打印出大于等于6的元素 fmt.Println("下面将返回大于等于6的元素") for _, v := range a{ if v >= 6 { fmt.Println(v) } } } func printGTE7(a ...int) { // 一个函数,接收一堆参数,打印出大于等于7的那几个参数 fmt.Println("下面将返回大于等于7的元素") for _, v := range a{ if v >= 7 { fmt.Println(v) } } }
循环打印多维切片
package main import "fmt" func main() { pls := [][]string{ {"C", "C++"}, {"JavaScript"}, {"Go", "Rust"}, } fmt.Println(pls) //[[C C++] [JavaScript] [Go Rust]] // 遍历打印多维切片 for _, v := range pls { for _,v1 := range v{ fmt.Println(v1) } } //C //C++ //JavaScript //Go //Rust }
Map (类似 python 字典)
类似于 python 中的字典
1)go 是强类型语言,所以 maps 的 key 和 value 类型都是固定的
2)map 的空值是 nil 类型(说明它也是引用类型)(赋了初值就不是 nil 了)
3)map 的赋值
map 定义及初始化
package main import "fmt" func main() { // map 定义 //var 变量名 map[键类型]值类型 var a map[string]string fmt.Println(a) if a == nil { fmt.Println("a == nil") } //map[] //a == nil // map 的初始化 var b map[int]string = make(map[int]string) fmt.Println(b) if b == nil { fmt.Println("b == nil") } //map[] // 定义和初始化的方式二 var c = map[int]string{1: "10", 2: "20"} fmt.Println(c) //map[1:10 2:20] }
赋值、取值、改值
4)取的 key 不存在就会返回 值类型的零值(string “”、int 0、bool false)
5)map 取值时,有两个返回值,第二个值一般取名
ok,
ok的值为 true 表示该键存在,false 表示该键不存在
package main import "fmt" func main() { // map 的赋值、取值、改值 goods := map[string]uint8{"apple": 3, "bear": 2} fmt.Println(goods) //map[apple:3 bear:2] goods["milk"] = 4 fmt.Println(goods) //map[apple:3 bear:2 milk:4] fmt.Println(goods["milk"]) // 取值 //4 goods["apple"] = 5 // 修改值 fmt.Println(goods["apple"]) fmt.Println(goods) //5 //map[apple:5 bear:2 milk:4] fmt.Println(goods["laptop"]) //0 // 因为 goods 这个 map 值的类型是 int,取不到键对应的值时,会默认返回值的零值 int 即 0 // 那么怎么知道这个键到底存不存在呢?万一那个值凑巧等于值类型的零值呢? count, ok := goods["laptop"] fmt.Println(count, ok) //0 false // ok 是 false,表示 map 中无该键 laptop,然后会把 count 赋值为 map 值类型的零值,int 类型即 0 count, ok = goods["apple"] fmt.Println(count, ok) //5 true // ok 是 true,表示 map 中有该键 apple,然后会把 count 赋值为 map 中该键对应的值 if v, okLaptop := goods["laptop"]; okLaptop{ fmt.Println("goods 中存在 laptop,有", v, "个") }else { fmt.Println("goods 中不存在 laptop") } //goods 中不存在 laptop }
map 删除元素、参数传递
内置函数
delete()
6)删值用 内置函数
delete()该函数没有返回值,删不存在的也不会报错(只管删,不管你 map 里原来有没有该键)
7)map 是引用类型,作为参数传给函数,改变了会影响到外部的那个 map(是引用)
package main import "fmt" func main() { // map 的删值 shopcart := map[string]string{"15411231": "小米电视机", "15115134": "小米9"} fmt.Println(shopcart) //map[15115134:小米9 15411231:小米电视机] //fmt.Println(delete(shopcart, "15411231")) // 这个语句会直接报错,因为 delete 没有返回值,也不能被接收(包括打印) delete(shopcart, "15411231") fmt.Println(shopcart) //map[15115134:小米9] // map 的长度 fmt.Println(shopcart) fmt.Println(len(shopcart)) //map[15115134:小米9] // //1 // map 是值类型还是引用类型?作为函数参数传递后,在函数内部修改是否会影响原来的 map? testMap(shopcart) //map[15115134:小米9] //map[12221231:小米手环 15115134:小米9] fmt.Println(shopcart) //map[12221231:小米手环 15115134:小米9] // => map 是引用类型,会影响原来的 map } func testMap(shopcart map[string]string) { fmt.Println(shopcart) shopcart["12221231"] = "小米手环" fmt.Println(shopcart) }
map 相等比较、循环遍历
8)map 之间不能直接
==比较,只能直接和
nil比,要实在想比较两个 map,可以自定义规则遍历去比较
9)map 是无序的
10)map 的键的类型必须是可 hash 的
package main import "fmt" func main() { // map 的相等比较 goodPrice := map[string]float32{"apple": 13.5, "one plus 8": 3899} var goodPrice2 map[string]float32 //if goodPrice == goodPrice2{ // 直接就报错,map 之间不能直接比较 // //} if goodPrice == nil { // map 可以和 nil 比较 fmt.Println("goodPrice == nil") } if goodPrice2 == nil { fmt.Println("goodPrice2 == nil") } //goodPrice2 == nil // 循环出 map 中的所有元素 fmt.Println(goodPrice) for k, v := range goodPrice { // 这里拿出的 key 不是索引,而是 map 的键 fmt.Println(k, v) } //map[apple:13.5 one plus 8:3899] //apple 13.5 //这一行和下一行的打印顺序不是固定的(因为 map 是无序的) //one plus 8 3899 //goodPrice3 := map[[]int]string{[]int{1,3,4}: "123"} // 直接报错,map 的 key 必须是可 hash 的 }
字符串
字符串的定义、长度、遍历字节、字符
1)两种方式 双引号
""反引号 ``` `
2)在 go 中,一个中文字符占 3 个字节长度,英文字母与数字均为1个字节长度
- 在 go 中 string 类型采用 utf-8 编码,每个中文字符占 3 个字节,英文字母、数字、常见符号占 1 个字节
- 其他语言中是 unicode 编码,unicode 统一用2Bytes(16 bit) 表示所有字符(有关编码的知识可以参考我的博客)
3)用
utf8.RuneCountInString(name)来统计字符串长度,内置方法
len()统计的是字节数
4)字符串是个只读切片
可以索引取值(取到的是数字码,用 string 括起来可以变成字符),但是不能改它的值(只读)
5)byte 就是 uint8 的别名、rune 是 int32 的别名,在字符串里分别代表字节、字符
package main import ( "fmt" "unicode/utf8" ) func main() { // 字符串的定义有两种 var s1 = "i'm s1" // 双引号的定义方式 var s2 = `i' am s2` // 反引号的定义方式,可以是多行 fmt.Println(s1) //i'm s1 fmt.Println(s2) //i' //am // s2 // 字符串的长度 var str1 = "i am is a string 啊." fmt.Println(len(str1)) // len 获取到的是字符串的字节长度 //21 fmt.Println(utf8.RuneCountInString(str1)) // utf8.RuneCountInString 获取到的是字符个数 //19 // 字符串是一个只读切片 fmt.Println(str1) fmt.Println(str1[0], string(str1[0])) //i am is a string 啊. //105 i //str1[0] = 213 // 会直接报错,不能更改字符串 (字符串是个只读切片) // 遍历字符串 str2 := "1测2s" for i := 0; i < len(str2); i++ { fmt.Printf("%T ", str2[i]) fmt.Println(i, str2[i], string(str2[i])) } //uint8 0 49 1 // int8 即 byte,代表取的是一个字节 //uint8 1 230 æ //uint8 2 181 µ //uint8 3 139 //uint8 4 50 2 //uint8 5 115 s for i, v := range str2 { fmt.Printf("%T ", v) fmt.Println(i, v, string(v)) } //int32 0 49 1 // int32 即 rune,代表取的是一个字符 //int32 1 27979 测 //int32 4 50 2 //int32 5 115 s // 利用切片合成字符串 byteSlice := []byte{0x43, 0x61, 0x66, 0xc3, 0xA9} strByByte := string(byteSlice) fmt.Println(byteSlice) //[67 97 102 195 169] fmt.Println(strByByte) //Café charlen := len(strByByte) charCount := utf8.RuneCountInString(strByByte) fmt.Println(charlen, charCount) //5 4 }
指针
1)指针是一种存储变量内存地址的变量
2)& 为取址符,可以取到后面变量的内存
3)*放在内存地址前面可以取到该内存地址所对应的值
4)指针也有类型,指定了类型就不能再指向其他类型了
5)*放在类型前面可以表示指向该类型的一个指针
指向什么类型就在什么类型前面加
*(
*int)
6)指针的零值是
nil
var a *int-->
nil
package main import "fmt" func main() { a := 10 fmt.Println(&a) // 1)& 为取址符,可以取到后面变量的内存地址 //0xc00000a0b8 fmt.Println(*&a) // 2)*放在内存地址前面可以取到该内存地址所对应的值 //10 b := &a // 等价于 var b *int = &a // 3)*放在类型前面可以表示指向该类型的一个指针 fmt.Println(b) //0xc00005e090 fmt.Println(*b) //10 //c := "dsadasd" //b = &c // 会直接报错,因为 b 指针在定义时就指向了 int 类型,现在要指向的是 string 类型,和他不是同一个类型了 var e *int fmt.Println(e) if e == nil{ fmt.Println("e == nil") } //<nil> //e == nil }
向函数传递指针参数
用了指针做参数传递,可以不考虑函数参数的传递是值传递还是引用传递(拿着内存地址,直接就能改了)
7)拿指针来传递参数,修改指针会将其指向的变量一同修改
package main import "fmt" func main() { a := 10 b := &a fmt.Println(a) //10 changePoint(b) // 利用指针去修改值 fmt.Println(a) //11 fmt.Println(a) //11 changeNum(*b) fmt.Println(a) //11 } func changePoint(point *int) { *point++ // 指针会更改指向内存地址上的值 } func changeNum(num int) { num++ }
传递数组指针 (最好是用切片)
8)指针也可以传递数组,但最好还是使用切片来传递数组
用切片做函数参数可以不用指定切片大小,而用指针做函数参数需要指定指针指向类型(数组类型需要指定大小)
package main import "fmt" func main() { a := [5]int{1, 2, 3, 4, 5} aPoint := &a ArgPoint(aPoint) //[999 2 3 4 5] aSlice := a[:] ArgSlice(aSlice) //[888 2 3 4 5] } func ArgPoint(point *[5]int) { // 如果使用 指针 做函数参数,需要指定指针所指定类型的大小,很不方便 (*point)[0] = 999 fmt.Println(*point) } func ArgSlice(slice []int) { // 如果是用 切片 做函数参数,则不需要关心该参数的大小 slice[0] = 888 fmt.Println(slice) } // 所以,我们优先选择用切片来当做函数参数,而不是指针!
9)Go 不支持指针运算
指针运算很危险,用不好会闪退,内存溢出等
指针数组、数组指针
package main import "fmt" func main() { a := 10 b := 20 // 指针数组 c := [2]*int{&a, &b} fmt.Println(c) //[0xc00000a0b8 0xc00000a0d0] // 数组指针 e := [2]int{1, 2} var f *[2]int f = &e fmt.Println(f) //&[1 2] //做了显示优化,让我们看起来方便点,其实还是地址 }
结构体
go 可以说有面向对象,也可以说没有面向对象(作者说,你觉得有就有,你觉得没有就没有)
1)结构体是一系列属性的集合(没有方法)
结构体零值
2)结构体是值类型
3)结构体的属性也是无序的
4)初始化可以指名道姓地传(指名道姓可以少传),也可以按位置传(必须所有属性都传)
package main import "fmt" //type 结构体名 struct { // 属性名1 属性名1的类型 // 属性名2 属性名2的类型 // 属性名3 属性名3的类型 //} type Person struct { name string //sex, age int // 是下面两行的简写 sex int age int } func main() { person := Person{} // 等价于 var person Person = Person{} fmt.Println(person) fmt.Println(person.name) fmt.Println(person.sex) //{ 0 0} // // 这是空字符串,不是没值(因为 string 类型的零值是 "" 空字符串) //0 var person2 Person = Person{name: "swb"} // 初始化可以指名道姓地传(指名道姓可以少传) var person3 Person = Person{"swb", 1, 18} // 也可以按位置传(必须所有属性都传) fmt.Println(person2) //{swb 0 0} fmt.Println(person3) //{swb 1 18} // 取属性、修改属性 fmt.Println(person2.name) //swb person2.age = 66 fmt.Println(person2.age) //66 }
匿名结构体
5)匿名结构体定义完了必须 立马实例化并赋值给一个对象
package main import "fmt" func main() { a := struct { name string age int }{"swb", 18} fmt.Println(a.name) //swb }
结构体指针
6)可以直接用地址点属性(官方做了处理)
package main import "fmt" type Person struct { name string //sex, age int // 是下面两行的简写 sex int age int } func main() { p := Person{name: "swb"} var pPoint *Person = &p fmt.Println(pPoint) //&{swb 0 0} // 这其实是一种 显示 优化,让我们知道内存地址存的是啥 fmt.Println((*pPoint).name) //swb fmt.Println(pPoint.name) // 结构体指针做了优化,可以直接用结构体指针 点 结构体属性 => pPoint.name //swb }
结构体匿名字段
package main import "fmt" // 结构体匿名字段 type Person struct { string int //age int // 如果有两个 int 类型的属性,可以这么弄 } func main() { //p := Person{"swb", 19} p := Person{string: "swb", int: 19} // 匿名字段的属性内名也可以用作 键 fmt.Println(p.string) //swb }
很奇葩,但是有用处,可以用作变量提升
表示字段没有名字
p := Person{}
结构体嵌套
package main import "fmt" // 结构体嵌套 type Person struct { name string sex, age int hobby Hobby } type Hobby struct { id int name string } func main() { p := Person{} fmt.Println(p) //{ 0 0 {0 }} // 零值状态,依旧是取该类型的零值 => {"" 0 0 {0 ""}} 字符串类型的零值是 "" 打印的时候,看不出来 p2 := Person{name: "swb", hobby: Hobby{id: 1, name: "学习"}} fmt.Println(p2.hobby.name) //学习 fmt.Println(p2) //{swb 0 0 {1 学习}} }
结构体嵌套 + 匿名字段
package main import "fmt" // 结构体嵌套 + 匿名字段 --> 变量提升 type Person struct { name string sex, age int Hobby } type Hobby struct { id int name string } func main() { p := Person{} fmt.Println(p) //{ 0 0 {0 }} // 零值状态,依旧是取该类型的零值 => {"" 0 0 {0 ""}} 字符串类型的零值是 "" 打印的时候,看不出来 p2 := Person{name: "swb", Hobby: Hobby{id: 1, name: "学习"}} fmt.Println(p2.Hobby.name) // 重名变量,不提升 //学习 fmt.Println(p2.id) // 可以直接取到 Hobby 中的 id,变量 id 被提升了 //1 fmt.Println(p2) //{swb 0 0 {1 学习}} }
7)变量提升过程中,如果有重名的,(重名的那个变量)就不提升了
(有点像 python 中的继承)
结构体相等比较
结构体之间可不可以比较要根据里面字段来的,里面字段都可以比较,那就可以比较(比如里面有切片字段,那就不可比较了)
作业
把 map 做成有序的
- (可以参考博客 python 3.6 字典),用切片来辅助实现
- 【Go入门教程4】变量(var),常量(const),内置基础类型(Boolean、数值 byte,int,rune、字符串、错误类型),分组,iota枚举,array(数值),slice(切片),map(字典),make/new操作,零值
- go 数组(array)、切片(slice)、map、结构体(struct)
- [Go] 复合类型(数组、切片、字典、结构体)变量的 初始化 及 注意事项
- Go 结构体、数组、字典和 json 字符串的相互转换方法
- [python 笔记2]列表、字符串、字典(list?string?map?)
- 【编程珠玑】第十五章--字符串:统计文本中单词数量(C++的Map&C的结构体实现&POJ2418)
- PAT乙级 1055. 集体照 (25) 结构体字符串字典排序,双端队列
- iOS菜鸟走过的路--01 字符串转为字典(其他格式类似)
- 第十六周项目2-用指针玩字符串(补充)
- C++第八周mooc在线测评—第8周 按址操作(2)——指针与数组、字符串、结构体,动态数组
- Go语言学习笔记之数组、数组切片和map
- [Go] 字典(map)
- 第十六周项目2-用指针玩字符串(补充)
- go结构体中String接口的实现为什么是结构体而不是指针
- 封送带字符串指针的结构体参数到非托管函数
- 封送带字符串指针的结构体参数到非托管函数
- Go(4[指针,Map])
- Go 结构体和指针
- C#调用C++ 平台调用P/Invoke 结构体--含有内置数据类型的一维、二维数组、字符串指针【六】
- 16年软件杯 & 字符串读入处理 & 结构体指针初始化 & 随机函数