字符串

字符串是不可变字节(byte)序列,其本身是一个复合结构。

type stringStruct struct{str unsafe.Pointerlen int
}

头部指针指向字节数组,但没有NULL结尾。默认以utf-8编码存储Unicode字符,字面量里允许使用十六进制,八进制和UFT编码格式。字符串默认值不是nil是“”。

  • 使用“ ‘ ” 定义不做转义处理的原始字符串,支持跨行(\n)。

  • 支持“!= ,==,< ,>,=,+=”操作符

  • 允许以索引访问字节数组(非符号),但不能获取元素地址。

  • 以切片语法(起始和结束索引号)返回子串时,其内部依旧指向原字节数组。

转换

要修改字符串,需将其转换为可变类型([]rune或[]byte),待完成后在转换回来。不管怎样转换,都需要重新分配内存,并复制数据。

用append函数,可将string直接追加到[]byte内。

考虑到字符串只读特征,转换是复制数据到新分配内存是可以理解的。

编译器会为某些场合进行专门优化,避免额外分配内存和复制操作:

  • 将[]byte 转换为string key,去map[string]查询的时候。
  • 将string转换为[]byte,进行for range 迭代时,直接取字节赋值给局部变量。

性能

除类型转化外,动态构建字符串也容易造成性能问题。

用加法操作符拼接字符串时,每次都须重新分配内存。造成性能的浪费。

改进思路就是预先分配足够的内存空间。常用方式是用string.Join函数,会统计所有参数长度,并一次完成内存分配操作。

Unicode

类型rune 专门用来存储Unicode码点(code point),是int32的别名,相当于USC-4/UTF-32编码格式。使用单引号的字面量,其默认类型就是rune。

除了[]rune外,还可以直接在rune, byte,sting间进行转换。

标准库unicode里提供了丰富的操作函数。验证函数外,还可以用RuneCountInString代替len返回准确的Unicode字符数量。

数组

定义数组类型时,数组长度必须时非负整形常量表达式,长度是类型组成部分。元素类型相同,长度不同的数组不属于同一类型。

初始化方式

func main() {var a [5]intb := [3]int{12,5}c := [4]int{1 , 3: 10}d := [...]int{1,2,3}e := [...]int{1, 5: 1}fmt.Println(a,b,c,d,e)}

对于结构等复合类型,可省略元素初始化类型标签。

func main() {type user struct {name stringage string}d := [...]user{{"zz","ll"},{"jj","14"},}fmt.Println(d)
}

在定义多维数组时,仅第一维允许使用[…]

内置函数len()和cap()都返回第一维度长度。

如元素类型支持“== !=”操作符,那么数组也支持该操作。

指针

要分清指针数组和数组指针的区别。指针数组时指元素为指针类型的数组,数组指针式获取数组变量的地址。

func main() {x , y := 110 , 120a := [...]*int{&x,&y}p := &afmt.Printf("%T,%v\n",a,a)fmt.Printf("%T,%v\n",p,p)}

可获取任意元素地址。数组指针可直接用来操作元素。

复制

Go数组是值类型,复制和传参操作都会复制整个数组数据。

如果需要,可改用指针或切片,以避免数据复制。

切片

切片(slice)本身并非动态数组或数组指针。切片内部通过指针引用底层数组,设定相关属性将数据读写操作限定在指定区域内。

type slice struyct {array unsafe.Pointerlen int cap int
}

切片本身是个只读对象,其工作机制类似于数组指针的一种包装。

初始化

可基于数组或数组指针创建切片,以开始和结束索引位置确定引用的数组片段。不支持反向索引,实际范围是一个右半开区间。

切片名 := 数组名[起始索引:结束索引:最大索引]// 起始索引省略,就是从数组起始位置开始
// 结束索引省略,就是到数组最后索引结束
// 最大索引省略,就是默认是数组的最大索引。
结构体中的 len = 结束索引-起始索引cap = 最大索引-起始索引

**属性cap表示切片所引用数组片段的真实长度,len用于限定可读的写元素数量。**数组必须是addressable(可寻址的),否则会引发错误。

和数组一样,切片同样使用索引号访问元素内容,起始索引为0,而非对应的底层数组的真实索引位置。

可直接创建切片对象,无须预先准备数组。因为是引用类型,须使用make函数或显示初始化语句,它会自动完成底层数组内存分配。

func main() {s1 := make([]int,3,5) //指定len,cap底层数组初始化为零值s2 := make([]int,3) //省略cap 和len相等s3 := []int{10,20,5:90}fmt.Println(s1,len(s1),cap(s1))fmt.Println(s2,len(s2),cap(s2))fmt.Println(s3,len(s3),cap(s3))
}

切片类型不支持比较操作,就算元素类型支持也不行,仅能判断是否为nil.

切片可获取元素地址,但不能向数组那样直接用指针访问元素内容。

如果元素类型也是切片可实现类似交错数组功能。

func main() {x := [][]int{{1,2},{10,20,30},{100},}fmt.Println(x[1])x[2] = append(x[2], 110,120)fmt.Println(x[2])
}

切片只是很小的结构体对象,用来代替数组传参可避免复制开销。make函数允许在运行期动态指定数组长度,绕开了数组类型必须使用编译器

并非所有时候都适合切片代替数组,因为切片底层数组可能会在堆上分配内存。而且小数组在栈上拷贝的消耗未必就比make大。

reslice

将切片视作[cap]slice数据源,根据此创建新切片对象。不能超出cap,但不受len限制。

新建切片对象依旧指向原底层数组,也就是说修改堆所有关联的切片可见。

可借助replace实现栈数据结构。

func main() {stack := make([]int, 0, 5)//入栈push := func(x int) error {n := len(stack)if n == cap(stack) {return errors.New("stack is full")}stack = stack[:n+1]stack[n] = xreturn nil}//出栈pop := func() (int, error) {n := len(stack)if n == 0 {return 0, errors.New("stack is empty")}x := stack[n-1]stack = stack[:n-1]return x, nil}for i := 0; i < 7; i++ {fmt.Printf("push %d: %v , %v \n", i, push(i), stack)}for i := 0; i < 7; i++ {x, err := pop()fmt.Printf("pop: %d,%v %v\n", x, err, stack)}
}

append

向切片尾部(slice[len])添加数据,返回新的切片对象。

数据被追加到原底层数组。如超出cap限制,则为新的切片对象重新分配数组。

注意:

  • 是超出底层cap限制,而非底层数组长度限制,应为cap可小于数组长度。
  • 新分配数组长度是原cap的2倍,而非原数组的2倍。
  • 并非总是2倍,对于较大的切片,会尝试扩容1/4,节约内存。
  • 向nil切片追加数据时,会为其分配底层数组内存。

应为存在重新分配底层数组的缘故,在某些场合建议预留足够多的空间,避免中途内存分配和数据复制开销。

copy

在两个切片对象间复制数据,允许指向同一底层数组,允许目标区间重叠。最终复制长度以较短的切片长度(len)为准。

copy函数以第一字符串为拷贝目标,返回值是拷贝了几个字符。

如果切片长时间内引用大数组中很小的片段,那么建议新建切片,复制出所需要数据,以便原数组内存可被及时回收。

字典

字典(哈希表)是一种使用频率极高的数据结构。Go语言将其作为语言内置类型,从运行时层面进行优化,以便获取更高效的性能。

作为无序键值对集合,字典要求key必须时支持相等运算符(==,!=)数据类型。

字典时引用类型,使用make函数或初始化表达语句来创建。

m := make(map[key类型]value类型)
m[key] = valuem2 := map[key类型]value类型{key : value
}

访问不存在的键值,默认返回零值,不会引发错误。

对字典进行迭代,每次返回的键值次序都不相同。

函数len返回当前键值对数量,cap不支持字典类型。因内存访问安全和哈希算法等原因,字典被设计成“not addressable”(不可访问),故不能直接修改value成员。

正确做法是返回整个value,待修改后在设置字典键值值,或直接用指针类型。

func main() {m := map[int]user{1: {"zxt", 18},}//设置整个valueu := m[1]u.age += 1m[1] = ufmt.Println(m)m2 := map[int]*user{2: &user{"zz", 10},}m2[2].age++fmt.Println(m2[2])
}

不能对nil字典进行写操作,但却内读。内容为空的字典,于nil是不同的。

安全

在迭代期间删除或新增键值是安全的。运行时会对字典并发操作作出检测。如果某个任务正在对字典进行写操作,那么其他任务就不能对该字典执行兵法操作(读,写,删除),否则会导致进程崩溃。

可用sync.RWMutex实现同步,避免读写操作同时进行。

性能

字典对象本身就是指针包装,传参时无须再次取地址。

在创建时预先准备足够空间有助于提升性能,减少扩张时内存分配和重新哈希操作。

m := make(map[int]int,1024) // 预先准备足够的空间

对于海量小对象,应直接用字典存储键值数据拷贝,而非指针。着有助于减少需要扫描的对象数量,大幅度缩短垃圾回收时间。字典不会收缩内存,所以适当替换成新对象时必要的。

结构

结构体(struct)将多个不同类型命名字段(field)序列打包成一个复合类型。

字段名必须唯一,可用“—”补位,支持使用自身指针类型成员。字段名,排列顺序属性类型组成部分。除堆起处理外,编译器不会优化,调整内存布局。

初始化

可按顺序序列化全部字段,或使用命名方式初始化指定字段。

type zxr struct{name stringage int
}z := zxt{"ll",18} //按照顺讯序列化全部字段
// 按照命名方式初始化指定指定字段
x := zxt{name: "ss"age: 8
}

推荐用命名初始化。这样在扩充结构字段或调整字段顺序是,不会导致初始化错误。

可直接定义匿名结构类型变量。或用作字段类型。但因缺少类型标识,在作为字段类型是无法直接初始化,稍显麻烦。

只有在所有字段类型全部支持是,才可做相等操作。

可以使用指针直接操作结构体字段,但不能是多级指针。

空结构体

空结构体(struct{})是指没有字段的结构体类型。无论是其自身,还是作为数组元素类型,长度都是零。

尽管没有分配数组内存,但依然可以操作元素,对应切片len,cap属性也正常。

这类“长度”为零的对象通常都指向runtime.zerobase。

空结构可作为通道元素类型,用于事件通知。

匿名字段

所谓匿名字段(anonymous field),是指没有名字,仅有类型的字段,也被称为嵌入类型。

从编译器角度看,这只是隐式地一类型名作为字段名字。可直接引用匿名字段的成员,但初始化时必须当作独立字段。

如嵌入其他包中的类型,则隐式字段名字不包括包名。

不仅仅是结构体,除接口指针和多级指针以外的任何命名类型都可作为匿名字段。

未命名类型没有名字标识,自然无法做为匿名字段。

虽然可以想普通字段以牙膏访问匿名字段成员,但会存在重名问题。默认情况下,编译器从当前现实命名字段开始,逐步向内查找匿名字段成语昂。如果匿名字段成员被外层相同名字段遮蔽,那么必须使用显示字段名。

如果多个相同层次的匿名字段成员,就只能泗洪显式字段名访问,因为编译器无法确定目标。

Go语言不是传统面向对象编程语言,或者仅实现了最小面向对象机制。匿名嵌入不是继承,无法实现多态。

字段标签

字段标签(tag)并不是注释,而是用来对字段进行描述的元数据。字段标签不属于数据成员,但却是类型的组成部分。

在运行期,可以用反射获取标签信息。通常被用作格式校验,数据库关系映射。

type user struct{name string '名字'sex byte '性别'
}

内存布局

不管结构体包含多少字段,其内存总是一次性分配的,各字段在相邻的地址空间按定义顺序排列。

对于引用类型,字符串和指针,结构内存中只包含其基本(头部)数据。

在分配内存是,字段须做对齐处理,通常以所有字段中最长的基础类型宽度为标准。

参考资料:
《Go语言学习笔记》 雨痕

数据结构进阶(Go语言)相关推荐

  1. 数据结构(C语言版) 第 三 章 栈与队列 知识梳理 + 作业习题详解

    目录 一.栈 0.栈的基本概念 1.栈的实现 2.栈与递归 3.Hanoi塔问题 二.队列 0.队列的基本概念 1.队列的实现 2.循环队列 2.1循环队列的相关条件和公式: 3.链队列 4.链队列完 ...

  2. 数据结构(C语言版) 第二章 线性表 知识梳理+作业习题详解

    目录 一.线性表顺序存储结构(顺序表) 0.线性表的基本概念 1.样例引入:多项式相加 二.线性表链式存储结构(链表) 0.链表的基本概念 1.前插法代码实例 2.链表尾插法完整代码附带各种操作 三. ...

  3. 从kernel源码进阶C语言

    从kernel源码进阶C语言 第一章 关于学习和技能提升 第二章 常见的宏定义深度分析  2.1 ARRAY_SIZE(arr)宏深度解析  2.2 max(x, y)宏深度解析  2.3 conta ...

  4. 【编程书库】入门+进阶C语言,这几本就够了!

    相信不用我说你也知道,C语言是一种非常流行.简单且灵活的通用编程语言,被广泛应用于各种应用程序中,在TOIBE编程语言榜单中,C语言自从5月份超越Java登上第一后,就稳坐第一的宝座. 今天就和大家分 ...

  5. 静态树表查找算法及C语言实现,数据结构算法C语言实现(三十二)--- 9.1静态查找表...

    一.简述 静态查找表又分为顺序表.有序表.静态树表和索引表.以下只是算法的简单实现及测试,不涉及性能分析. 二.头文件 /** author:zhaoyu date:2016-7-12 */ #inc ...

  6. 资料分享:送你一本《数据结构(C语言版)》电子书!

    要想写出可复用.可扩展.易维护.灵活性好的代码,「数据结构」这一关必须要过啊! 在数据结构与算法的众多教材中,奉为经典的当属清华大学严蔚敏老师的著作.很多学校也选择这本书作为考研指定教材. 正在学习数 ...

  7. 资料分享:送你一本《数据结构(C#语言版)》电子书!

    对于信息类专业的学生而言,数据结构与算法是一门必修的课程.只有学好这门课程,熟练掌握线性表.栈.队列.树.图等基本结构,以及在这些结构上的各种算法,才能利用计算机去解决实际问题. 如何学好这门课程呢, ...

  8. 数据结构(C语言版) 第 八 章 排序 知识梳理 + 习题详解

    目录 一.归并排序 二.交换排序 1.快速排序 2.冒泡排序 三.插入排序 1.直接插入排序(基于顺序查找) 2.折半插入排序(基于折半查找) 3.希尔排序(基于逐趟缩小增量) 四.选择排序 0.直接 ...

  9. 数据结构(C语言版) 第 六 章 图 知识梳理 + 习题详解

    目录 一. 图的基本定义和术语 一.图的基本概念 1.度 2.连通 (1)连通图 (2)强连通/强连通图 3.回路 4.完全图 二.图的三种存储结构 1.邻接矩阵表示法 2.邻接表(链式)表示法 3. ...

最新文章

  1. 3704对象关闭时_VB中“对象关闭时,不允许操作”解决方案 3704 錯誤 | 学步园...
  2. opencv图像和二维数组相互转换
  3. 从用户的视角看待网页设计(一)
  4. 一篇搞定异常: Exception
  5. HarmonyOS之深入解析线程间的通信
  6. java类与对象实验报告心得体会_第四周课程总结与实验报告(Java简单类与对象)...
  7. 量化投资交易 vn.py
  8. 快微音频课程小程序v3.8.4+前端
  9. python学习交流 - 匿名函数
  10. 转 Spring是如何管理Hibernate和Struts的(二)
  11. 2019最烂密码榜单出炉,教你设置神级密码!
  12. php怎么画五星红旗,php基于GD库画五星红旗的方法_php技巧
  13. 苹果蓝牙耳机平替哪个好用?商务通话蓝牙耳机推荐
  14. html图片闪光效果,CSS实现的一闪而过的图片闪光效果
  15. Jquery插件包-jqwidgets
  16. AutomationAnywhere(AA)实现读取Excel文件
  17. 为什么快捷指令无法将媒体转换为文本_全知乎最全!iOS“捷径(快捷指令)”应用进阶教程 (附入门教程链接)...
  18. 2014年数学建模国赛A题(嫦娥三号软着陆轨道设计与控制策略)优秀论文.doc
  19. stable_sort的含义
  20. K8S(02)管理核心资源的三种基本方法

热门文章

  1. 华为云与计算机,华为云电脑和达龙云电脑
  2. 美团java后台面经
  3. 安卓7.0以后如何开启手电筒
  4. Java-web css笔记
  5. 51单片机的延时子程序
  6. star- Transformer
  7. 一个包含学生信息的顺序表
  8. python 斗鱼弹幕的爬取一(selenium)
  9. 微信小程序--音乐播放器案例
  10. __builtin_offsetof()