【go语言基础】-数据操作

1. 数组

1. 特征

静态语言的数组特征: 1.大小确定 2.类型一致

只能取值,修改值

2.定义

 

// 3种方式,声明,初始,省略号

// 变量arr1类型为[5]int
var arr1 [5]int

// 变量arr2类型为[3]int,同时初始化赋值
var arr2 [5]int = [5]int{1,2,3}

// 让编译器自己数,结果为[3]int
arr3 := [...]int{1,2,3}

// 错误例子,因为[3]int和[4]int是两种类型
arr3 := [4]int{1,2,3,4}

 

  • 数组长度是数组类型的一部分[3]int[5]int就是两种类型。
  • 数组长度必须是常量表达式,即这个表达式的值在编译阶段就可以确定。

 

package main

import (
    "fmt"
)

func printArray(toPrint [5]string) {
    //[]string这是切片类型
    toPrint[0] = "bobby"
    fmt.Println(toPrint)
}

func main() {
    //go语言中的数组和python的list可以对应起来理解,slice和python的list更像
    //静态语言中的数组: 1. 大小确定 2. 类型一致
    //数组的申明
    //var courses [10] string
    //var courses = [5]string{"django", "scrapy", "tornado"}
    course := [5]string{"django", "scrapy", "tornado"}
    //静态语言要求严格, 动态语言是一门动态类型的

    //1. 修改值, 取值: 删除值, 添加某一个值, 数组一开始就要指定大小
    //取值, 修改值
    fmt.Println(course[0])
    //修改值
    course[0] = "django3"
    fmt.Println(course)

    //数组的另一种创建方式
    //var a [4] float32
    var a = [4]float32{1.0}
    fmt.Println(a)

    var c = [5] int{'A', 'B'}
    fmt.Println(c)

    //首次接触到了省略号
    d := [...]int{1,2,3,4,5}
    fmt.Println(d)

    e := [5]int{4:100}
    fmt.Println(e)
    
    f := [...]int{0:1, 4:1, 9:100}
    fmt.Println(f)

    //数组操作第一种场景: 求长度
    fmt.Println(len(f))
    //数组操作第二种场景: 遍历数组

    for i, value := range course {
        fmt.Println(i, value)
    }

    //使用for range求和
    sum := 0
    for _, value := range f {
        sum += value
    }
    fmt.Println(sum)

    //使用for语句也可以遍历数组
    sum = 0
    for i := 0; i<len(course); i++{
        sum += f[i]
    }
    fmt.Println(sum)

    //数组是值类型
    courseA := [3]string{"django", "scrapy", "tornado"}
    courseB := [...]string{"django1", "scrapy1", "tornado1", "python+go", "asyncio"}
    //courseA和courseB应该是同一种类型, 都是数组类型
    //在go语言中,courseA和courseB都是数组,但是不是同一种类型
    fmt.Printf("%T\n", courseA)
    fmt.Printf("%T\n", courseB)
    //如果courseA和courseB是一种类型的话 为什么前面要加一个数组, 长度不一样的数组类型是不一样
    //正是基于这些,在go语言中函数传递参数的时候,数组作为参数 实际调用的时候是值传递
    printArray(courseB)
    fmt.Println(courseB)
}

3. 操作

求长度

 

arr1 := [...]{1,2,3}
lenth := len(arr1)

遍历数组

 

package main

import "fmt"

func main() {

   f := [...]int{0: 1, 9: 100}

   for i, v := range f {
      fmt.Println(i, v)
   }

   // 使用for range求和
   sum := 0
   for _, v := range f {
      sum += v
   }
   fmt.Println(sum)

   // 使用for语句也可以遍历数组
   sum2 := 0
   for i := 0; i < len(f); i++ {
      sum2 += f[i]
   }
   fmt.Println(sum2)
}

(4)数组的类型

courseA := [3]string{"a", "b", "c"}
courseB := [...]string{"a1", "b1", "c1", "d1"}

// 在go语言中,courseA和courseB都是数组,但是不是同一种类型

package main

import "fmt"

func main() {

    courseA := [3]string{"a", "b", "c"}
    courseB := [...]string{"a1", "b1", "c1", "d1"}
    //    在go语言中,courseA和courseB都是数组,但是不是同一种类型
    fmt.Printf("%T\n", courseA)
    fmt.Printf("%T\n", courseB)
    //[3]string
    //[4]string

}

2. 切片

1.切片定义

 

Go 数组的长度不可改变,在特定场景中这样的集合就不太适用,Go中提供了一种灵活,功能强悍的内置类型切片("动态数组"),与数组相比切片的长度是不固定的,可以追加元素,在追加时可能使切片的容量增大

 

Go的切片类型为处理同类型数据序列提供一个方便而高效的方式。 切片有些类似于其他语言中的数组,但是有一些不同寻常的特性。

 

package main

import "fmt"

func main() {
//1切片初始化第一种
var name = []string{"a", "b", "c"}
fmt.Println(name)
fmt.Printf("%T", name) // []string

//2.切片初始化另一种 make
courses := make([]string, 5)
fmt.Printf("%T", courses) // []string
fmt.Println(len(courses)) //5

// 3. 通过数组变成一个切片
var course1 = [5]string{"a", "b", "c", "d", "f"}
subCourse := course1[1:4] //切片
fmt.Printf("%T", subCourse) // []string
 //python中的用法叫切片,go语言中的切片是一种数据结构


// 4. 使用new
subCourse1 := *new([]int)
fmt.Println(subCourse1)

}
//数组的传递是值传递,切片是引用传递,会改变原值
// slice对标的是python 的lIst

注意:

 数组的传递是值传递,切片是引用传递。

slice对标的是python 的lIst

 

2. 切片操作 

package main

import "fmt"

func main() {
    // slice的操作
    // 1.对数据进行取值
    var course1 = [5]string{ "a" ,  "b" ,  "c" ,  "d" ,  "f" }
    subCourse := course1[1:4]  //切片
    subCourse2 := subCourse[1:2]
    fmt.Printf( "%T, %v\n" , subCourse, subCourse)    // []string, [b c d]
    fmt.Printf( "%T, %v\n" , subCourse2, subCourse2)  // []string, [b c d]

    //2.增加值append
    var name = []string{ "a" ,  "b" ,  "c" }
    name = append(name,  "e" )
    fmt.Printf( "%T, %v\n" , name, name)  // []string, [b c d]

    //    3.append增加多个参数
    name = append(name,  "g" ,  "i" ,  "p" )
    fmt.Printf( "%T, %v\n" , name, name)  // []string, [b c d]

    //    4.拷贝copy
    var subCourse3 = []string{ "a" ,  "b" ,  "c" }
    subCourse3 = append(subCourse3,  "e" )
    fmt.Printf( "%T, %v\n" , subCourse3, subCourse3)  // []string, [b c d]
    //拷贝的时候,目标对象长度需要设置好,要不然可能拷贝的不完整
    //subCourse4 := make([]string, 2)
    subCourse4 := make([]string, len(subCourse3))
    copy(subCourse4, subCourse3)
    fmt.Println(subCourse4)

    //    5.合并
    appendCourse := []string{ "a" ,  "b" ,  "c" }
    var subCourse10 = []string{ "e" ,  "f" ,  "g" }
    subCourse10 = append(subCourse10, appendCourse...)  //使用...是函数传递规则决定的
    fmt.Println(subCourse10)

    //    6.删除
    deleteCourse := [5]string{ "a" ,  "b" ,  "c" ,  "d" ,  "f" }
    courseSlice := deleteCourse[:]  //变成切片
    courseSlice = append(courseSlice[:1], courseSlice[2:]...)   //取巧做法,但是实际就是这么做
    fmt.Println(courseSlice)
//    这样可以把B删除

}

//数组的传递是值传递,切片是引用传递,会改变原值
// slice对标的是python 的lIst

 slice扩容机制

append 函数的参数长度可变,因此可以追加多个值到 slice 中,还可以用 ... 传入 slice,直接追加一个切片。

 

append函数返回值是一个新的slice,Go编译器不允许调用了 append 函数后不使用返回值。

 

使用 append 可以向 slice 追加元素,实际上是往底层数组添加元素。但是底层数组的长度是固定的,如果索引 len-1 所指向的元素已经是底层数组的最后一个元素,就没法再添加了。

 

这时,slice 会迁移到新的内存位置,新底层数组的长度也会增加,这样就可以放置新增的元素。同时,为了应对未来可能再次发生的 append 操作,新的底层数组的长度,也就是新 slice 的容量是留了一定的 buffer 的。否则,每次添加元素的时候,都会发生迁移,成本太高。

 

 3. GO语言的slice的原理

Go 中切片扩容的策略是这样的:
首先判断,如果新申请容量(cap)大于2倍的旧容量(old.cap),最终容量(newcap)就是新申请的容量(cap)
否则判断,如果旧切片的长度小于1024,则最终容量(newcap)就是旧容量(old.cap)的两倍,即(newcap=doublecap)
否则判断,如果旧切片长度大于等于1024,则最终容量(newcap)从旧容量(old.cap)开始循环增加原来的 1/4,即(newcap=old.cap,for {newcap += newcap/4})直到最终容量(newcap)大于等于新申请的容量(cap),即(newcap >= cap)
如果最终容量(cap)计算值溢出,则最终容量(cap)就是新申请容量(cap)

也就是说
如果小于1024 扩容的速度是2倍 如果大于了1024 扩容的速度就是1.25

    //python和go语言的slice区别 玩出花样 go的slice更像是python的list, go语言的底层是基于数组实现的 python的list狄岑逛夜市基于数组实现的
    //slice进行的操作都会影响原来的数组, slice更像是一个指针 本身不存值

    //slice的原理,因为很多底层的知识相对来说很多时候并不难而是需要花费比较多的时间去慢慢理解
    //1. 第一个现象
    a := make([]int, 0)
    b := []int{1, 2, 3}
    fmt.Println(copy(a, b))
    fmt.Println(a)
    //不会去扩展a的空间
    //2. 第二个现象
    c := b[:]
    //c[0] = 8
    fmt.Println(b)
    fmt.Println(c)

    //3. 第三个现象
    c = append(c, 9)
    fmt.Println(b) //append函数没有影响到原来的数组
    fmt.Println(c)
    //这就是因为产生了扩容机制,扩容机制一旦产生 这个时候切片就会指向新的内存地址
    c[0] = 8
    fmt.Println(b)
    fmt.Println(c) //为什么append函数之后再调用c[0]=8不会影响到原来的数组
    //4. 第四个现象
    fmt.Println(len(c))
    fmt.Println(cap(c)) //cap指的是容量 长度和容量这两个概念
    //切片底层是使用数组实现的,既要使用数组 又要满足动态的功能 怎么实现?
    //假设有一个值 实际上申请数组的时候可能是两个,如果后续要增加数据那么就直接添加到数据的结尾,这个时候我不要额外重新申请
    //切片有不同的初始化方式
    //1. 使用make方法初始化 len和cap是多少. 不会有多余的预留空间
    d := make([]int, 0)
    fmt.Printf("len=%d, cap=%d\n", len(d), cap(d))

    //2. 通过数组取切片
    data := [10]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
    slice := data[2:4]
    newSlice := data[3:6]
    for index, value := range slice {
        fmt.Println(index, value)
    }
    fmt.Printf("len=%d, cap=%d\n", len(slice), cap(slice))
    fmt.Printf("len=%d, cap=%d\n", len(newSlice), cap(newSlice))

    //3.
    slice2 := []int{1, 2, 3}
    fmt.Printf("len=%d, cap=%d\n", len(slice2), cap(slice2))

    //切片扩容问题, 扩容阶段会影响速度, python的list中底层实际上也是数组,也会面临动态扩容的问题,python的list中数据类型可以不一致
    oldSlice := make([]int, 0)
    fmt.Printf("len=%d, cap=%d\n", len(oldSlice), cap(oldSlice))
    oldSlice = append(oldSlice, 1)
    fmt.Printf("len=%d, cap=%d\n", len(oldSlice), cap(oldSlice))
    oldSlice = append(oldSlice, 2)
    fmt.Printf("len=%d, cap=%d\n", len(oldSlice), cap(oldSlice))
    oldSlice = append(oldSlice, 3)
    fmt.Printf("len=%d, cap=%d\n", len(oldSlice), cap(oldSlice))
    oldSlice = append(oldSlice, 4)
    oldSlice = append(oldSlice, 5)
    oldSlice = append(oldSlice, 4)
    oldSlice = append(oldSlice, 5)
    oldSlice = append(oldSlice, 4)
    oldSlice = append(oldSlice, 5)
    fmt.Printf("len=%d, cap=%d\n", len(oldSlice), cap(oldSlice))
当make遇到了append容易出现的坑
s1 := make([]int, 0)
s1 = append(s1, 6)
//对于很多初学者来说我们期望的是只有一个数字就是6
fmt.Println(s1)
//很多人对make函数产生了一个假象 ,s1 := make([]int, 5) 好比是python的 s1 = []

 

3. map(字典)

定义和操作

package main

import "fmt"

func main() {
    //go中的map ->python中的dict
    //go语言中的map的key和value类型申明的就要指明
    //1. 字面值
    m1 := map[string]string{
        "m1": "v1",
    }
    fmt.Printf("%v\n", m1)

    //2. make函数 make函数可以创建slice 可以创建map
    m2 := make(map[string]string) //创建时,里面不添加元素
    m2["m2"] = "v2"
    fmt.Printf("%v\n", m2)

    //3. 定义一个空的map
    m3 := map[string]string{}
    fmt.Printf("%v\n", m3)

    //map中的key 不是所有的类型都支持,改类型需要支持 == 或者 != 操作
    //int rune
    //a := []int{1,2,3}
    //b := []int{1,2,3}
    //var m1 map[[]int]string

    //支持的
    //a := [3]int{1,2,3}
    //b := [3]int{1,2,3}
    //if a == b {
    //
    //}
    //map的基本操作
    m := map[string]string{
        "a": "va",
        "b": "vb",
        "d": "",
    }

    //1. 进行增加,修改
    m["c"] = "vc"
    m["b"] = "vb1"
    fmt.Printf("%v\n", m)

    //查询,你返回空的字符串到底是没有获取到还是值本身就是这样空字符串呢
    v, ok := m["d"]
    if ok {
        fmt.Println("找到了", v)
    } else {
        fmt.Println("没找到")
    }
    fmt.Println(v, ok)

    //删除,删除不存在的值不会报错
    delete(m, "a")
    delete(m, "e")
    delete(m, "a")
    fmt.Printf("%v", m)

    //遍历
    for k, v := range m {
        fmt.Println(k, v)
    }

    //go语言中也有一个list 就是数据结构中提到的链表
    //指针 //为什么指针在java python等很多语言中不存在
}

 

 

 

 

 

 

  

 

 

posted @ 2022-05-15 16:08  wanghhhh  阅读(70)  评论(0)    收藏  举报