分享
  1. 首页
  2. 文章

Golang 学习笔记二 字典 字符串 结构体

懒皮 · · 1324 次点击 · · 开始浏览
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

一、字典

《快学 Go 语言》第 6 课 —— 字典
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]
二、字符串

《快学 Go 语言》第 7 课 —— 字符串

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 转为字符串时,重新创造了一个新的字符串。

三、结构体

《快学 Go 语言》第 8 课 —— 结构体
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 学习笔记二 字典 字符串 结构体

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

关注微信
1324 次点击
暂无回复
添加一条新回复 (您需要 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传

用户登录

没有账号?注册
(追記) (追記ここまで)

今日阅读排行

    加载中
(追記) (追記ここまで)

一周阅读排行

    加载中

关注我

  • 扫码关注领全套学习资料 关注微信公众号
  • 加入 QQ 群:
    • 192706294(已满)
    • 731990104(已满)
    • 798786647(已满)
    • 729884609(已满)
    • 977810755(已满)
    • 815126783(已满)
    • 812540095(已满)
    • 1006366459(已满)
    • 692541889

  • 关注微信公众号
  • 加入微信群:liuxiaoyan-s,备注入群
  • 也欢迎加入知识星球 Go粉丝们(免费)

给该专栏投稿 写篇新文章

每篇文章有总共有 5 次投稿机会

收入到我管理的专栏 新建专栏