内容简介:1.make如果你可以预知字典内部键值对的数量,那么还可以给 make 函数传递一个整数值,通知运行时提前分配好相应的内存。这样可以避免字典在长大的过程中要经历的多次扩容操作。
一、字典
1.make
func main() { var m map[int]string = make(map[int]string) fmt.Println(m, len(m)) } ---------- map[] 0
如果你可以预知字典内部键值对的数量,那么还可以给 make 函数传递一个整数值,通知运行时提前分配好相应的内存。这样可以避免字典在长大的过程中要经历的多次扩容操作。
var m = make(map[int]string, 16)
2.初始化
func main() { var m map[int]string = map[int]string{ 90: "优秀", 80: "良好", 60: "及格", // 注意这里逗号不可缺少,否则会报语法错误 } fmt.Println(m, len(m)) } --------------- map[90:优秀 80:良好 60:及格] 3
3.读写
func main() { var fruits = map[string]int { "apple": 2, "banana": 5, "orange": 8, } // 读取元素 var score = fruits["banana"] fmt.Println(score) // 增加或修改元素 fruits["pear"] = 3 fmt.Println(fruits) // 删除元素 delete(fruits, "pear") fmt.Println(fruits) } ----------------------- 5 map[apple:2 banana:5 orange:8 pear:3] map[orange:8 apple:2 banana:5]
删除操作时,如果对应的 key 不存在,delete 函数会静默处理。遗憾的是 delete 函数没有返回值,你无法直接得到 delete 操作是否真的删除了某个元素。这时候必须使用字典的特殊语法,如下
func main() { var fruits = map[string]int { "apple": 2, "banana": 5, "orange": 8, } var score, ok = fruits["durin"] if ok { fmt.Println(score) } else { fmt.Println("durin not exists") } fruits["durin"] = 0 score, ok = fruits["durin"] if ok { fmt.Println(score) } else { fmt.Println("durin still not exists") } } ------------- durin not exists 0
4.遍历
这个和数组一样的
func main() { var fruits = map[string]int { "apple": 2, "banana": 5, "orange": 8, } for name, score := range fruits { fmt.Println(name, score) } for name := range fruits { fmt.Println(name) } } ------------ orange 8 apple 2 banana 5 apple banana orange
奇怪的是,Go 语言的字典没有提供诸于 keys() 和 values() 这样的方法,意味着如果你要获取 key 列表,就得自己循环一下,如下
func main() { var fruits = map[string]int { "apple": 2, "banana": 5, "orange": 8, } var names = make([]string, 0, len(fruits)) var scores = make([]int, 0, len(fruits)) for name, score := range fruits { names = append(names, name) scores = append(scores, score) } fmt.Println(names, scores) } ---------- [apple banana orange] [2 5 8]
二、字符串
image.png
1.按字节遍历
func main() { var s = "嘻哈china" for i:=0;i<len(s);i++ { fmt.Printf("%x ", s[i]) } } ----------- e5 98 bb e5 93 88 63 68 69 6e 61
2.按字符 rune 遍历
package main import "fmt" func main() { var s = "嘻哈china" for codepoint, runeValue := range s { fmt.Printf("%d %d ", codepoint, int32(runeValue)) } } ----------- 0 22075 3 21704 6 99 7 104 8 105 9 110 10 97
对字符串进行 range 遍历,每次迭代出两个变量 codepoint 和 runeValue。codepoint 表示字符起始位置,runeValue 表示对应的 unicode 编码(类型是 rune)。
3.字符串是只读的
你可以使用下标来读取字符串指定位置的字节,但是你无法修改这个位置上的字节内容。如果你尝试使用下标赋值,编译器在语法上直接拒绝你。
package main func main() { var s = "hello" s[0] = 'H' } -------- ./main.go:5:7: cannot assign to s[0]
4.字节切片和字符串的相互转换
在使用 Go 语言进行网络编程时,经常需要将来自网络的字节流转换成内存字符串,同时也需要将内存字符串转换成网络字节流。Go 语言直接内置了字节切片和字符串的相互转换语法。
package main import "fmt" func main() { var s1 = "hello world" var b = []byte(s1) // 字符串转字节切片 var s2 = string(b) // 字节切片转字符串 fmt.Println(b) fmt.Println(s2) } -------- [104 101 108 108 111 32 119 111 114 108 100] hello world
从节省内存的角度出发,你可能会认为字节切片和字符串的底层字节数组是共享的。但是事实不是这样的,底层字节数组会被拷贝。如果内容很大,那么转换操作是需要一定成本的。
那为什么需要拷贝呢?因为字节切片的底层数组内容是可以修改的,而字符串的底层字节数组是只读的,如果共享了,就会导致字符串的只读属性不再成立。
5.修改字符串
无法直接修改每一个字符元素
angel := "Heros never die" angleBytes := []byte(angel) for i := 5; i <= 10; i++ { angleBytes[i] = ' ' } fmt.Println(string(angleBytes))
字符串不可变有很多好处,如天生线程安全,大家使用的都是只读对象,无须加锁;再者,方便内存共享,而不必使用写时复制(Copy On Write)等技术;字符串 hash 值也只需要制作一份。所以说,代码中实际修改的是 []byte,[]byte 在 Go 语言中是可变的,本身就是一个切片。在完成了对 []byte 操作后,在第 9 行,使用 string() 将 []byte 转为字符串时,重新创造了一个新的字符串。
三、结构体
1.结构体类型的定义
结构体和其它高级语言里的「类」比较类似。下面我们使用结构体语法来定义一个「圆」型
type Circle struct { x int y int Radius int }
Circle 结构体内部有三个变量,分别是圆心的坐标以及半径。特别需要注意是结构体内部变量的大小写,首字母大写是公开变量,首字母小写是内部变量,分别相当于类成员变量的 Public 和 Private 类别。内部变量只有属于同一个 package(简单理解就是同一个目录)的代码才能直接访问。
2.创建
func main() { var c Circle = Circle { x: 100, y: 100, Radius: 50, // 注意这里的逗号不能少 } fmt.Printf("%+v\n", c) } ---------- {x:100 y:100 Radius:50}
可以只指定部分字段的初值,甚至可以一个字段都不指定,那些没有指定初值的字段会自动初始化为相应类型的「零值」。
func main() { var c1 Circle = Circle { Radius: 50, } var c2 Circle = Circle {} fmt.Printf("%+v\n", c1) fmt.Printf("%+v\n", c2) } ---------- {x:0 y:0 Radius:50} {x:0 y:0 Radius:0}
结构体的第二种创建形式是不指定字段名称来顺序字段初始化,需要显示提供所有字段的初值,一个都不能少。这种形式称之为「顺序形式」。 var c Circle = Circle {100, 100, 50}
结构体变量创建的第三种形式,使用全局的 new() 函数来创建一个「零值」结构体,所有的字段都被初始化为相应类型的零值。 var c *Circle = new(Circle)
注意 new() 函数返回的是指针类型。
第四种创建形式,这种形式也是零值初始化,就数它看起来最不雅观。 var c Circle
3.零值结构体和 nil 结构体
nil 结构体是指结构体指针变量没有指向一个实际存在的内存。这样的指针变量只会占用 1 个指针的存储空间,也就是一个机器字的内存大小。
var c *Circle = nil
而零值结构体是会实实在在占用内存空间的,只不过每个字段都是零值。如果结构体里面字段非常多,那么这个内存空间占用肯定也会很大。
4.结构体的拷贝
func main() { var c1 Circle = Circle {Radius: 50} var c2 Circle = c1 fmt.Printf("%+v\n", c1) fmt.Printf("%+v\n", c2) c1.Radius = 100 fmt.Printf("%+v\n", c1) fmt.Printf("%+v\n", c2) var c3 *Circle = &Circle {Radius: 50} var c4 *Circle = c3 fmt.Printf("%+v\n", c3) fmt.Printf("%+v\n", c4) c3.Radius = 100 fmt.Printf("%+v\n", c3) fmt.Printf("%+v\n", c4) } --------------- {x:0 y:0 Radius:50} {x:0 y:0 Radius:50} {x:0 y:0 Radius:100} {x:0 y:0 Radius:50} &{x:0 y:0 Radius:50} &{x:0 y:0 Radius:50} &{x:0 y:0 Radius:100} &{x:0 y:0 Radius:100}
5.无处不在的结构体
通过观察 Go 语言的底层源码,可以发现所有的 Go 语言内置的高级数据结构都是由结构体来完成的。
切片头的结构体形式如下,它在 64 位机器上将会占用 24 个字节
type slice struct { array unsafe.Pointer // 底层数组的地址 len int // 长度 cap int // 容量 }
字符串头的结构体形式,它在 64 位机器上将会占用 16 个字节
type string struct { array unsafe.Pointer // 底层数组的地址 len int }
字典头的结构体形式
type hmap struct { count int ... buckets unsafe.Pointer // hash桶地址 ... }
6.结构体的参数传递
函数调用时参数传递结构体变量,Go 语言支持值传递,也支持指针传递。值传递涉及到结构体字段的浅拷贝,指针传递会共享结构体内容,只会拷贝指针地址,规则上和赋值是等价的。下面我们使用两种传参方式来编写扩大圆半径的函数。
package main import "fmt" type Circle struct { x int y int Radius int } func expandByValue(c Circle) { c.Radius *= 2 } func expandByPointer(c *Circle) { c.Radius *= 2 } func main() { var c = Circle {Radius: 50} expandByValue(c) fmt.Println(c) expandByPointer(&c) fmt.Println(c) } --------- {0 0 50} {0 0 100}
从上面的输出中可以看到通过值传递,在函数里面修改结构体的状态不会影响到原有结构体的状态,函数内部的逻辑并没有产生任何效果。通过指针传递就不一样。
7.结构体方法
Go 语言不是面向对象的语言,它里面不存在类的概念,结构体正是类的替代品。类可以附加很多成员方法,结构体也可以。
package main import "fmt" import "math" type Circle struct { x int y int Radius int } // 面积 func (c Circle) Area() float64 { return math.Pi * float64(c.Radius) * float64(c.Radius) } // 周长 func (c Circle) Circumference() float64 { return 2 * math.Pi * float64(c.Radius) } func main() { var c = Circle {Radius: 50} fmt.Println(c.Area(), c.Circumference()) // 指针变量调用方法形式上是一样的 var pc = &c fmt.Println(pc.Area(), pc.Circumference()) } ----------- 7853.981633974483 314.1592653589793 7853.981633974483 314.1592653589793
Go 语言不喜欢类型的隐式转换,所以需要将整形显示转换成浮点型,不是很好看,不过这就是 Go 语言的基本规则,显式的代码可能不够简洁,但是易于理解。
Go 语言的结构体方法里面没有 self 和 this 这样的关键字来指代当前的对象,它是用户自己定义的变量名称,通常我们都使用单个字母来表示。
Go 语言的方法名称也分首字母大小写,它的权限规则和字段一样,首字母大写就是公开方法,首字母小写就是内部方法,只能归属于同一个包的代码才可以访问内部方法。
结构体的值类型和指针类型访问内部字段和方法在形式上是一样的。这点不同于 C++ 语言,在 C++ 语言里,值访问使用句点 . 操作符,而指针访问需要使用箭头 -> 操作符。
8.关于GO如何实现面对对象的继承、多态,是个有趣的话题。参考 go是面向对象语言吗?
以上所述就是小编给大家介绍的《Golang 学习笔记二 字典 字符串 结构体》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- Python基础-列表、元组、字典、字符串
- Go 实现字符串全排列字典序排列详解
- 少说话多写代码之Python学习011——字典的格式化字符串
- 【Python—字典的用法】创建字典的3种方法
- Python 字典(Dictionary)
- python 数据类型 ----字典
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。