数码相机

当前位置:今晚六彩开奖号码结果 > 数码相机 > 基本数据结构,Go语言极简教程

基本数据结构,Go语言极简教程

来源:http://www.amonstervacation.com 作者:今晚六彩开奖号码结果 时间:2019-08-22 12:44

图片 1

本文内容是本人对Go语言的变量、常量、数组、切片、映射、结构体的备忘录,记录了关键的相关知识点,以供翻查。

数组

文中如有错误的地方请大家指出,以免误导!转摘本文也请注明出处:Go语言备忘录:基本数据结构,多谢!**

Go语言中数组的声明语法为[n]DataType,n表示数组长度,DataType表示数组元素的数据类型。跟c、java以及c#等语言不同,Go语言将数组长度写在类型的前面。

参考书籍《The Go Programming Language》、《GoIn Action》、《Go语言学习笔记》等

图片 2

目录:

Go语言中数组有以下几个特性:数组是固定长度,创建完不能再追加元素。

  1. 变量
  2. 常量
  3. 数组
  4. 切片
  5. 映射
  6. 结构体

不同长度的数组元素类型相同也不可以直接赋值。

一、变量

图片 3

  • 变量是一段或多段用来存储数据的内存;
  • 变量总是有固定的数据类型,类型决定了所占内存的长度和存储格式;
  • 编译后的代码使用变量的内存地址来访问数据,而不是变量名;
  • 简短变量声明只能在函数内声明,var声明方式则无限制(但一般用于声明未显式初始化的变量);
  • 声明同一作用域中的同名变量时,将回退为赋值,即重用该变量(必须至少有一个新变量定义);
  • 而声明不同作用域的同名变量则为重新定义;

数组是值类型,赋值或作为函数实参传递时均会进行值拷贝,因此当数组长度较大时可以用slice代替。

图片 4

var q intvar y = 453var (    n,m = 134,"srf"    n1,m1 int )func f1() {    n,m := 25,"sss"     n,m1 := 34,"yyy"    fmt.Println    n = n 5 //赋值表达式中,首先计算右值    //“_”空标识符用来临时规避编译器对未使用变量和导入包的错误检查    if _,ok := add1;ok {        fmt.Println    }}func add1 (int, bool) {    return n 1,true}

slice

  

正因为数组有着上述诸多的不足之处,因此Go语言提供了另一种内置的数据类型slice。slice的定义和数组非常相似var s=[]int,区别是数组需要在[]指定长度或...,而slice[]中不需要指定长度。

二、常量、枚举

slice的操作make()创建新的slice并追n个元素,元素的初始值为该数据类型的零值。append()向slice追加一个元素,并返回新的slice。使用slice[n:n]产生子slice, 子slice与原slice引用同一个底层数组。len()返回slice的长度,实际元素的数量。cap()返回slice的容量,容量的大小受slice扩容机制的影响并不总等于实际元素的数量。copy()将源slice元素复制给目标slice。可以通过for range遍历slice。

  • 常量是一个不可改变的值,它可以为字面量,或编译器能计算出结果的表达式。未使用的常量不会引起编译错误;
  • 在常量组中如不指定类型和初始值,则与上一行非空常量右值相同;
  • 常量会被编译器在预处理阶段直接展开,作为指令数据使用,所以无法取常量的地址;

图片 5

slice的结构

const i = 5const (    x byte = 1    x1    x2       //x1,x2均为1    s = "abs"    s1       //s1=“abc”)const (    _,_ int = iota,iota*3 //0,0*3 忽略值,并显式指定类型为int    k1,k2             //1,1*3    l1,l2             //2,2*3    o1,o2 = 5,6       //中断iota自增    r1,r2             //5,6  同上一行    e1,e2 = iota,iota*3 //5,5*3  恢复iota自增,按行递增)//枚举type color byteconst (    blue color = iota    red    green)func main() {    t:= blue    fmt.Println //0    //fmt.Println //错误:无法对常量取地址 cannot take the address of i}

slice是一个结构体,源代码如下:

  

图片 6

三、数组

slice结构有三个字段:array、len以及cap。array字段是一个指针,指向存储实际元素的数组。len表示slice当前的元素数量。cap表示slice当前的容量。

数组是切片和映射的基础数据结构。数组是值类型,在赋值和传递数组时将拷贝整个数组。

图片 7

数组是一个长度固定的数据类型,存储着一段具有相同数据类型元素的连续内存块。

slice作为变量赋值或函数实参传递,虽然对slice本身进行了值拷贝,但指向的底层数组还是同一个,因此函数对slice的修改也会影响到原slice,可以把slice看成是引用类型。

因为数组占用的内存是连续分配的,所以对数组元素的查询、修改等操作速度很快。

图片 8

声明数组的方式:

slice的扩容机制

  • var array1 [5]int
  • array1 := [5]int{3,5,6,3,2}
  • array1 := [...]int{3,4,7,8,1} //根据数组字面量中元素的个数来确定数组的长度
  • array1 := [5]int{0:3,3:5,4:8} //只初始化指定索引的元素,其余元素保持零值
  • array1 := [...]int{1,2,9:32}

slice一般按照当前容量的两倍来扩容,即1,2,4,8,16这样规律。

数组元素的类型可以为任何内置类型,也可以是某种结构类型,也可以是指针类型。

图片 9

数组变量的类型包括数组长度和元素的类型,只有两部分都相同的数组才可相互赋值。

slice的子切片

多维数组:数组本身只有一个维度,只能通过组合多个数组来创建多维数组;内置函数len、cap均返回第一维度的长度

slice可以通过slice[n:n]方式来创建子的slice,需要注意的是子slice与原slice引用的是同一个数组,因此修改或append子slice会引起原slice的修改。

  • var array [4][2]int
  • array := [4][2]int{2:{20,21},3:{41,25}}
  • array := [4][2]int{2:{1:21},3:{0:41}}
  • array := [...][4]int{{2,3},{4,5}} //仅第一维度允许使用“...”
  • array[2][1] = 10

图片 10

在函数间传递数组:由于在函数间传递变量时,传递的总是变量的值的副本,因为数组是值类型,所以在赋值和传递数组变量时将拷贝整个数组!在定义函数时,对于较大的数据类型应该把参数设计为指针类型,这样在调用函数时,只需在栈上分配给每个指针8字节的内存,但这意味着会改变指针指向的值,其实大部分情况下应该使用切片类型,而不是数组。

map

注意:因为切片的底层数组可能会在堆上分配内存,对于小数组在栈上拷贝的消耗可能比make代价小;

Go语言内置的字典类型是map,格式为map[K]T,其中K可以是任意可比较类型。map和slice一样也是引用类型。

四、切片slice

创建map

  • 切片slice是引用类型,它内部通过指针引用一个底层数组,并设定相关属性将数据的读写操作限定在指定区域。

图片 11

map的操作通过make函数创建map实例。通过下标map[K]方式读取、新增或修改map元素。len()函数返回mapKV的数量。可以通过for range来遍历map,但顺序不能保证。delete删除元素,即使对应的Key不存在也不会引起程序错误。

//切片本身是个只读对象,工作机制类似数组指针的一种包装type slice struct{    array unsafe.Pointer    len int //可读写的元素数量    cap int //所引用数组片段的真实长度}

图片 12

创建和初始化:

map的注意事项内置的map是非并发安全的,多个goroutine并发读写时会引起程序异常。标准包中的sync.Map结构体是并发安全的。如果map的值是结构体类型,那么修改应采用整个值替换的方式而不是单独修改结构体的某个字段。

  • slice1 := make( []string, 5 ) //创建一个长度、容量都为5的string类型的切片
  • slice1 := make( []string, 3, 5 ) //创建一个长度为3,容量为5的string类型的切片
  • slice2 := []string{ "red","blue","green" } //长度和容量均为3的切片
  • slice2 := []int{ 99:1 } //长度和容量均为100,并初始化第100个元素为1

图片 13

再次切片reslice:以开始和结束原切片的索引位置确定所引用的数组片段,不支持反向索引,实际范围是一个右半开区间
假设原切片slice容量为k,新切片newSlice为原切片的索引 i 元素位置开始,在原切片的容量范围内取值

  • newSlice := slice[ i : j ] //长度为j-i,容量为k-i
  • newSlice := slice[ i : j : n ] //限制新切片的容量为n-i(第三个参数n-1表示新切片可扩展到的最后一个可见的底层数组部分的元素索引,这样就达到了限制容量的目的,注意:n必须>=j)
  • 新切片无法访问它所指向的底层数组的第一个元素之前的部分(第一个索引之前的部分)
  • 例子:ss:=[]int{10,20,30,40,50} newss:=ss[2:4:5] //newss为[30,40],容量为3
  • 新切片和旧切片指向同一个底层数组;
//利用reslice实现一个栈式结构(也可将stack定义为一个类型)var stack = make([]int,0,5)func push error {n:=lenif n == cap {return errors.New("stack is full")}stack = stack[:n 1] //新的stack增加了一个可访问元素stack[n]stack[n]=xreturn nil}func pop() (int, error) {n:=lenif n == 0 {return 0,errors.New("stack is empty")}x:=stack[n-1]stack = stack[:n-1] //新的stack减少了一个可访问元素stack[n-1]return x,nil}func main() {for i := 0; i < 7; i   {fmt.Printf("push %d: %v,%vn",i,push}for i := 0; i < 7; i   {x,err:=pop()fmt.Printf("push %d: %v,%vn",x,err,stack)}}

切片的长度可以按需自动增长或缩小:

  • 动态增长是通过append()函数实现的
  • 缩小则是通过对它再次切片来实现,通过再次切片获得的新切片将和原切片共享底层数组,它们的指针指向同一个底层数组。

由于切片只是引用了底层数组,底层数组的数据并不属于切片本身,所以一个切片只需要24字节的内存:指针字段8字节、长度字段8字节、容量字段8字节。所以在函数之间直接传递切片是高效的,只需分配24字节的栈内存。

nil切片和空切片:

  • nil切片:只声明,但未初始化的切片,如var slice1 []int,nil切片可用来描述一个不存在的切片
  • 空切片:长度和容量均为0的切片,创建空切片时未对底层数组元素分配任何内存,可用来表示空集合,如slice1 := make( []int, 0 ),slice2 := []int{}
  • 对nil切片和空切片调用内置函数append、len、cap的效果一样

append()函数:
slice = append(slice, elem1, elem2) //一次可追加多个值
slice = append(slice, anotherSlice...) //使用“...”将anotherSlice的所有元素追加到slice里

  • 当slice还有可用的容量时,append()操作将可用的元素合并到切片的长度,并对其赋值,最后返回一个全新的切片(和旧切片共享同一个底层数组);
  • 如果slice的容量不足时,append()操作会创建一个新的底层数组,并将被引用的旧值复制到新数组里,然后追加新的值;
  • 原切片容量不足时,且小于1000,则新切片的容量为原容量的2倍,若大于1000,则容量的增长因子变为1.25;
  • 由于容量不足时,append操作会返回一个具有自己独立的底层数组的新切片,即与原切片不共享同一底层数组,对新切片元素的修改将不会影响原切片的底层数组,技巧:在创建切片时设置长度等于容量,这样就可以强制在第一次append操作时创建新的底层数组,达到与原数组分离的目的,如newSlice := oldSlice[2:3:3]

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

  • 切片的迭代如:for index, value := range slice{ .... },index为当前迭代到的索引位置,value是从slice的副本中取值,index和value变量的内存地址是不变的,只是指向的值在不断更新。
  • len函数可返还切片的长度、cap函数可返还切片的容量
  • 多维切片:切片和数组一样,本身是一维的,可以组合多个切片来形成多维切片,如:slice := [][]int{ {12},{34,23} },slice[0]为{12},slice[1]为{34,23}
  • 注意:如果切片长时间引用大数组中很小的片段,则应该复制出所需数据,新建独立切片,以便原大数组内存可被及时回收;

五、映射map

映射map:是一个存储键值对的无序集合,它能基于键快速检索数据,键就像索引一样,指向与该键关联的值;

映射是无序的,每次迭代它时顺序可能不一样,因为映射内部使用了散列表;

映射的散列表包含一组桶,每个桶里存储着一部分键值对;

映射内部使用了两个数组:

  • 第一个数组:存储着用于选择桶的散列键的高八位值,该数组用于区分每个键值对要存在哪个桶里;
  • 第二个数组:每个桶里都有一个字节数组,先依次存储了该桶里的所有键,之后存储了该桶的所有值;

在存储、删除、查找映射的键值对时,会把指定的键传给映射的散列函数,该函数把键转换为一个散列值,然后把该散列值与第一个数组比对来选择哪个桶,再到桶里的字节数组里查找对应的键和值;

创建和初始化映射:

  • dict1 := make(map[string]int) //空映射,等同于dict1 := map[string]int{}
    dict1 := make(map[string]int, 1000) //预先分配足够内存空间,有助于提升性能
    dict2 := map[string]int{"srf":143,"robt":342}
  • 映射的键:只能是能用“==”做比较的类型,但不可以是切片、函数、以及包含切片的类型,因为他们具有引用语义。而映射的值则可以是任意类型;
  • nil映射是只声明而未初始化的映射,无法直接使用,如var dict map[string]int。空映射则可以直接使用;
  • map类型的零值是nil,也就是没有引用任何哈希表。在向map存数据前必须先创建map,即:引用一个哈希表。

如果要用map存储大量小对象,应该直接存储为值类型,而非指针类型,有助于减少需要扫描的对象数量,大幅缩短垃圾回收时间;

从映射中取值:

  • value := dict2["srf"] //键存在时返回对应的值,不存在时返回类型的零值
  • value, ok := dict2["srf"] //ok为键是否存在的布尔标志
    if ok { ...... }
  • map中的元素并不是一个变量,我们不能对map的元素进行取址操作(因为map可能会在动态增长时重新分配内存),因此无法直接修改value成员,而应该通过临时变量来修改,或把值定义为指针类型:
m := users[int]user{    1:{"srf",25}}//m[1].age  =1 //错误,无法设置值u := m[1]u.age =1m[1] = u

遍历映射:

  • for key := range dict2 { ..... } //只接收键
  • for key, value := range dict2 { ...... } //同时接收键和值
  • 遍历映射时,可以添加、删除成员
  • 遍历映射的键值对时的顺序是随机,若要有序的获得映射的键值对,则需要先遍历出映射的键存到一个切片中,然后排序该切片,最后遍历该切片,按切片中元素的顺序去映射中取对应的值

delete(dict2,"srf") 从映射中删除指定的键值对;

运行时会对映射的并发操作做出检测,对映射的操作只能同步进行(同一时刻只能有一个任务在操作映射),否则会导致进程崩溃。可用读写锁sync.RWMutex实现同步,避免读写操作同时进行:

func main() {var lock sync.RWMutexm:=make(map[string]int)go func() {for {lock.Lock()m["a"]  = 1lock.Unlock()  //不能用defertime.Sleep(time.Microsecond)}}()go func() {for {lock.RLock()_ = m["b"]lock.RUnlock()time.Sleep(time.Microsecond)}}()select {} //阻止进程退出}
  • 在函数间传递映射与传递切片一样,传递的只是映射本身的副本,而不会复制映射所引用的所有底层数据结构,对该映射副本所做的修改将会反映到所有对这个映射的引用。
  • 多维映射:即值为映射类型的映射。使用时应注意,作为值的映射也需要初始化后才能使用,如:
    var m1 = make(map[int]map[string]string)
    m1[13]= map[string]string{"srf":"yes"}
  • 判断两个map是否相等的函数:
func equal(x, y map[string]int) bool {    if len != len {        return false    }    for k, xv := range x {        if yv, ok := y[k]; !ok || yv != xv {            return false        }    }    return true}
  • 用map来表示字符串的集合set:
m:=make(map[string]bool)if !m["srf"] {    m["srf"] = true}

六、结构体

  • 结构体struct是一种复合类型,由多个不同类型的命名字段系列打包而成;
  • 字段名必须唯一,可用“_”补位,支持使用自身的指针类型成员(这可以让我们创建递归的数据结构,比如链表和树结构等);
type node struct{    _ int    id int `账号`    next *node}
  • 结构体类型信息包括:字段名、字段标签、排列顺序,只有三者全部相同才可认为是同一类型;
  • 可按顺序初始化全部字段,但建议使用命名方式初始化部分或全部字段(可忽视字段的定义顺序,便于结构体成员顺序的修改、扩充);
  • 结构体的比较:只有当结构体的所有成员都是可比较的,那么该结构体才是可比较的
  • 可直接定义一个匿名的结构体类型,并赋值给一个变量,或用作字段的类型(匿名结构体字段无法用字面量形式直接初始化,需要“.”语法来初始化其成员)
u := struct{    name string}type file struct{    name string    attr struct{        owner int        perm int    }}f := file{name:"test.dat"}f.attr.owner = 1f.attr.perm = 0755
  • 空结构:struct{},长度为0,不分配内存,它和其它所有“长度”为0的对象通常都指向runtime.zerobase变量(即它们都指向同一个变量);空结构类型经常作为通道元素的类型,用于事件通知;

匿名字段:即没有指定显式的名称,只有类型的字段:

  • 编译器将隐式地以类型名作为字段名称;
  • 外层的结构体不仅获得了匿名成员类型的所有成员,而且也获得了该类型全部的导出的方法;
  • 可直接引用嵌入类型字段的成员,但在以字面量语法初始化时须显式初始化它的整个结构;
  • 匿名字段的成员的数据类型必须是命名的类型或指向一个命名的类型的指针,不能是接口指针和多级指针;
  • 不能将基础类型和其指针类型同时作为匿名字段
  • 字段重名处理:优先使用外层字段(内层字段被遮蔽了,只能通过完全限定名来访问),对于多个相同层级的同名字段也必须通过完全限定名来访问,否则编译器无法确定目标;

字段标签:用来对字段进行描述的元数据,它虽然不属于数据成员,但却是类型信息的组成部分;在运行期,可用反射来获取字段的标签信息,它常被用作格式检验、数据库关系映射等;标准库reflect.StructTag提供了分/解析标签的功能;

type user struct{    name string `昵称`    sex byte `性别`}func main(){    u:=user{"TOM",1}    v:=reflect.ValueOf    t:=v.Type()        for i,n:=0,t.NumField();i<n;i  {        fmt.Printf("%s: %vn", t.Field.Tag, v.Field    }}
  • 不管结构体有多少个字段,它的内存总是一次性分配的,各字段在相邻的地址空间按定义顺序排列(包含嵌入字段的所有 成员)。对于引用类型、字符串、指针,结构内存中只包含其基本数据。
  • 结构体在分配内存时,会进行内存对齐处理(根据所有字段中最长的基础类型宽度为标准),唯一例外是编译器把空结构类型字段作为最后一个字段时的长度视为1来做对齐处理。
  • 内存对齐与硬件平台、以及访问效率有关(CPU在访问自然对齐的数据时需要的读周期更少,还可避免拼接数据)

本文由今晚六彩开奖号码结果发布于数码相机,转载请注明出处:基本数据结构,Go语言极简教程

关键词: