AI智能
改变未来

7.(go语言)复合类型


7.复合类型

文章目录

  • 7.复合类型
  • 7.1 分类
  • 7.2 指针
  • 7.2.1 基本操作
  • 7.2.2 new函数
  • 7.2.3 指针做函数参数
  • 7.3 数组
    • 7.3.1 概述
    • 7.3.2 操作数组
    • 7.3.3在函数间传递数组
  • 7.4 slice
    • 7.4.1 概述
    • 7.4.2 切片的创建和初始化
    • 7.4.3 切片的操作
    • 7.4.3.1 切片截取
    • 7.4.3.2 切片和底层数组关系
  • 7.4.3.3 内建函数
  • 7.4.4 切片做函数参数
  • 7.5 map
    • 7.5.1概述
    • 7.5.2 创建和初始化
    • 7.5.2.1 map创建
    • 7.5.2.2 初始化
  • 7.5.3 常用操作
    • 7.5.3.1 赋值
    • 7.5.3.2 遍历
    • 7.5.3.3 删除
  • 7.5.4 map做函数参数
  • 7.6 结构体
    • 7.6.1 结构体类型
    • 7.6.2 结构体初始化
    • 7.6.2.1 普通变量
    • 7.6.2.2 指针变量
  • 7.6.3 结构体成员的使用
    • 7.6.3.1 普通变量
    • 7.6.3.2 指针变量
  • 7.6.4 结构体比较
  • 7.6.5 结构体作为函数参数
    • 7.6.5.1 值传递
    • 7.6.5.2 引用传递
  • 7.6.6 可见性
  • 7.1 分类

    类型 名称 长度 默认值 说明
    pointer 指针 nil
    array 数组 0
    slice 切片 nil 引⽤类型
    map 字典 nil 引⽤类型
    struct 结构体

    7.2 指针

    指针是一个代表着某个内存地址的值。这个内存地址往往是在内存中存储的另一个变量的值的起始位置。Go语言对指针的支持介于Java语言和C/C++语言之间,它既没有想Java语言那样取消了代码对指针的直接操作的能力,也避免了C/C++语言中由于对指针的滥用而造成的安全和可靠性问题。

    7.2.1 基本操作

    Go语言虽然保留了指针,但与其它编程语言不同的是:

    默认值 nil,没有 NULL 常量

    操作符 “&” 取变量地址, “*” 通过指针访问目标对象

    不支持指针运算,不支持 “->” 运算符,直接⽤ “.” 访问目标成员

    func main() {var a int = 10              //声明一个变量,同时初始化fmt.Printf(\"&a = %p\\n\", &a) //操作符 \"&\" 取变量地址var p *int = nil //声明一个变量p, 类型为 *int, 指针类型p = &afmt.Printf(\"p = %p\\n\", p)fmt.Printf(\"a = %d, *p = %d\\n\", a, *p)*p = 111 //*p操作指针所指向的内存,即为afmt.Printf(\"a = %d, *p = %d\\n\", a, *p)}

    7.2.2 new函数

    表达式new(T)将创建一个T类型的匿名变量,所做的是为T类型的新值分配并清零一块内存空间,然后将这块内存空间的地址作为结果返回,而这个结果就是指向这个新的T类型值的指针值,返回的指针类型为*T

    func main() {var p1 *intp1 = new(int)              //p1为*int 类型, 指向匿名的int变量fmt.Println(\"*p1 = \", *p1) //*p1 =  0p2 := new(int) //p2为*int 类型, 指向匿名的int变量*p2 = 111fmt.Println(\"*p2 = \", *p2) //*p1 =  111}

    我们只需使用new()函数,无需担心其内存的生命周期或怎样将其删除,因为Go语言的内存管理系统会帮我们打理一切。

    7.2.3 指针做函数参数

    func swap01(a, b int) {a, b = b, afmt.Printf(\"swap01 a = %d, b = %d\\n\", a, b)}func swap02(x, y *int) {*x, *y = *y, *x}func main() {a := 10b := 20//swap01(a, b) //值传递swap02(&a, &b) //变量地址传递fmt.Printf(\"a = %d, b = %d\\n\", a, b)}

    7.3 数组

    7.3.1 概述

    数组是指一系列同一类型数据的集合。数组中包含的每个数据被称为数组元素(element),一个数组包含的元素个数被称为数组的长度。

    数组⻓度必须是常量,且是类型的组成部分。 [2]int 和 [3]int 是不同类型。

    var n int = 10var a [n]int  //err, non-constant array bound nvar b [10]int //ok

    7.3.2 操作数组

    数组的每个元素可以通过索引下标来访问,索引下标的范围是从0开始到数组长度减1的位置。

    var a [10]intfor i := 0; i < 10; i++ {a[i] = i + 1fmt.Printf(\"a[%d] = %d\\n\", i, a[i])}//range具有两个返回值,第一个返回值是元素的数组下标,第二个返回值是元素的值for i, v := range a {fmt.Println(\"a[\", i, \"]=\", v)}

    内置函数 len(长度) 和 cap(容量) 都返回数组⻓度 (元素数量):

    a := [10]int{}fmt.Println(len(a), cap(a))//10 10

    初始化:

    a := [3]int{1, 2}           // 未初始化元素值为 0b := [...]int{1, 2, 3}      // 通过初始化值确定数组长度c := [5]int{2: 100, 4: 200} // 通过索引号初始化元素,未初始化元素值为 0fmt.Println(a, b, c)        //[1 2 0] [1 2 3] [0 0 100 0 200]//支持多维数组d := [4][2]int{{10, 11}, {20, 21}, {30, 31}, {40, 41}}e := [...][2]int{{10, 11}, {20, 21}, {30, 31}, {40, 41}} //第二维不能写\"...\"f := [4][2]int{1: {20, 21}, 3: {40, 41}}g := [4][2]int{1: {0: 20}, 3: {1: 41}}fmt.Println(d, e, f, g)

    相同类型的数组之间可以使用 == 或 != 进行比较,但不可以使用 < 或 >,也可以相互赋值:

    a := [3]int{1, 2, 3}b := [3]int{1, 2, 3}c := [3]int{1, 2}fmt.Println(a == b, b == c) //true falsevar d [3]intd = afmt.Println(d) //[1 2 3]

    7.3.3在函数间传递数组

    根据内存和性能来看,在函数间传递数组是一个开销很大的操作。在函数之间传递变量时,总是以值的方式传递的。如果这个变量是一个数组,意味着整个数组,不管有多长,都会完整复制,并传递给函数。

    func modify(array [5]int) {array[0] = 10 // 试图修改数组的第一个元素//In modify(), array values: [10 2 3 4 5]fmt.Println(\"In modify(), array values:\", array)}func main() {array := [5]int{1, 2, 3, 4, 5} // 定义并初始化一个数组modify(array)                  // 传递给一个函数,并试图在函数体内修改这个数组内容//In main(), array values: [1 2 3 4 5]fmt.Println(\"In main(), array values:\", array)}

    数组指针做函数参数:

    func modify(array *[5]int) {(*array)[0] = 10//In modify(), array values: [10 2 3 4 5]fmt.Println(\"In modify(), array values:\", *array)}func main() {array := [5]int{1, 2, 3, 4, 5} // 定义并初始化一个数组modify(&array)                 // 数组指针//In main(), array values: [10 2 3 4 5]fmt.Println(\"In main(), array values:\", array)}

    7.4 slice

    7.4.1 概述

    数组的长度在定义之后无法再次修改;数组是值类型,每次传递都将产生一份副本。显然这种数据结构无法完全满足开发者的真实需求。Go语言提供了数组切片(slice)来弥补数组的不足。

    切片并不是数组或数组指针,它通过内部指针和相关属性引⽤数组⽚段,以实现变⻓⽅案。

    slice并不是真正意义上的动态数组,而是一个引用类型。slice总是指向一个底层array,slice的声明也可以像array一样,只是不需要长度。

    7.4.2 切片的创建和初始化

    slice和数组的区别:声明数组时,方括号内写明了数组的长度或使用…自动计算长度,而声明slice时,方括号内没有任何字符。

    var s1 []int //声明切片和声明array一样,只是少了长度,此为空(nil)切片s2 := []int{}//make([]T, length, capacity) //capacity省略,则和length的值相同var s3 []int = make([]int, 0)s4 := make([]int, 0, 0)s5 := []int{1, 2, 3} //创建切片并初始化

    注意:make只能创建slice、map和channel,并且返回一个有初始值(非零)。

    7.4.3 切片的操作

    7.4.3.1 切片截取

    操作 含义
    s
    切片s中索引位置为n的项
    s[:] 从切片s的索引位置0到len(s)-1处所获得的切片
    s[low:] 从切片s的索引位置low到len(s)-1处所获得的切片
    s[:high] 从切片s的索引位置0到high处所获得的切片,len=high
    s[low:high] 从切片s的索引位置low到high处所获得的切片,len=high-low
    s[low:high:max] 从切片s的索引位置low到high处所获得的切片,len=high-low,cap=max-low
    len(s) 切片s的长度,总是<=cap(s)
    cap(s) 切片s的容量,总是>=len(s)

    示例说明:

    array := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
    操作 结果 len cap 说明
    array[:6:8] [0 1 2 3 4 5] 6 8 省略 low
    array[5:] [5 6 7 8 9] 5 5 省略 high、 max
    array[:3] [0 1 2] 3 10 省略 high、 max
    array[:] [0 1 2 3 4 5 6 7 8 9] 10 10 全部省略

    7.4.3.2 切片和底层数组关系

    s := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}s1 := s[2:5]       //[2 3 4]s1[2] = 100        //修改切片某个元素改变底层数组fmt.Println(s1, s) //[2 3 100] [0 1 2 3 100 5 6 7 8 9]s2 := s1[2:6] // 新切片依旧指向原底层数组 [100 5 6 7]s2[3] = 200fmt.Println(s2) //[100 5 6 200]fmt.Println(s) //[0 1 2 3 100 5 6 200 8 9]

    7.4.3.3 内建函数

    1) append

    append函数向 slice 尾部添加数据,返回新的 slice 对象:

    var s1 []int //创建nil切换//s1 := make([]int, 0)s1 = append(s1, 1)       //追加1个元素s1 = append(s1, 2, 3)    //追加2个元素s1 = append(s1, 4, 5, 6) //追加3个元素fmt.Println(s1)          //[1 2 3 4 5 6]s2 := make([]int, 5)s2 = append(s2, 6)fmt.Println(s2) //[0 0 0 0 0 6]s3 := []int{1, 2, 3}s3 = append(s3, 4, 5)fmt.Println(s3)//[1 2 3 4 5]

    append函数会智能地底层数组的容量增长,一旦超过原底层数组容量,通常以2倍容量重新分配底层数组,并复制原来的数据:

    func main() {s := make([]int, 0, 1)c := cap(s)for i := 0; i < 50; i++ {s = append(s, i)if n := cap(s); n > c {fmt.Printf(\"cap: %d -> %d\\n\", c, n)c = n}}/*cap: 1 -> 2cap: 2 -> 4cap: 4 -> 8cap: 8 -> 16cap: 16 -> 32cap: 32 -> 64*/}

    2) copy

    函数 copy 在两个 slice 间复制数据,复制⻓度以 len 小的为准,两个 slice 可指向同⼀底层数组。

    data := [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}s1 := data[8:]  //{8, 9}s2 := data[:5] //{0, 1, 2, 3, 4}copy(s2, s1)    // dst:s2, src:s1fmt.Println(s2)   //[8 9 2 3 4]fmt.Println(data) //[8 9 2 3 4 5 6 7 8 9]

    7.4.4 切片做函数参数

    func test(s []int) { //切片做函数参数s[0] = -1fmt.Println(\"test : \")for i, v := range s {fmt.Printf(\"s[%d]=%d, \", i, v)//s[0]=-1, s[1]=1, s[2]=2, s[3]=3, s[4]=4, s[5]=5, s[6]=6, s[7]=7, s[8]=8, s[9]=9,}fmt.Println(\"\\n\")}func main() {slice := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}test(slice)fmt.Println(\"main : \")for i, v := range slice {fmt.Printf(\"slice[%d]=%d, \", i, v)//slice[0]=-1, slice[1]=1, slice[2]=2, slice[3]=3, slice[4]=4, slice[5]=5, slice[6]=6, slice[7]=7, slice[8]=8, slice[9]=9,}fmt.Println(\"\\n\")}

    7.5 map

    7.5.1概述

    Go语言中的map(映射、字典)是一种内置的数据结构,它是一个无序的key—value对的集合,比如以身份证号作为唯一键来标识一个人的信息。


    map格式为:

    map[keyType]valueType

    在一个map里所有的键都是唯一的,而且必须是支持==和!=操作符的类型,切片、函数以及包含切片的结构类型这些类型由于具有引用语义,不能作为映射的键,使用这些类型会造成编译错误:

    dict := map[ []string ]int{} //err, invalid map key type []string

    map值可以是任意类型,没有限制。map里所有键的数据类型必须是相同的,值也必须如何,但键和值的数据类型可以不相同。

    注意:map是无序的,我们无法决定它的返回顺序,所以,每次打印结果的顺利有可能不同。

    7.5.2 创建和初始化

    7.5.2.1 map创建

    var m1 map[int]string  //只是声明一个map,没有初始化, 此为空(nil)mapfmt.Println(m1 == nil) //true//m1[1] = \"mike\" //err, panic: assignment to entry in nil map//m2, m3的创建方法是等价的m2 := map[int]string{}m3 := make(map[int]string)fmt.Println(m2, m3) //map[] map[]m4 := make(map[int]string, 10) //第2个参数指定容量fmt.Println(m4)                //map[]

    7.5.2.2 初始化

    //1、定义同时初始化var m1 map[int]string = map[int]string{1: \"mike\", 2: \"yoyo\"}fmt.Println(m1) //map[1:mike 2:yoyo]//2、自动推导类型 :=m2 := map[int]string{1: \"mike\", 2: \"yoyo\"}fmt.Println(m2)

    7.5.3 常用操作

    7.5.3.1 赋值

    m1 := map[int]string{1: \"mike\", 2: \"yoyo\"}m1[1] = \"xxx\"   //修改m1[3] = \"lily\"  //追加, go底层会自动为map分配空间fmt.Println(m1) //map[1:xxx 2:yoyo 3:lily]m2 := make(map[int]string, 10) //创建mapm2[0] = \"aaa\"m2[1] = \"bbb\"fmt.Println(m2)           //map[0:aaa 1:bbb]fmt.Println(m2[0], m2[1]) //aaa bbb

    7.5.3.2 遍历

    m1 := map[int]string{1: \"mike\", 2: \"yoyo\"}//迭代遍历1,第一个返回值是key,第二个返回值是valuefor k, v := range m1 {fmt.Printf(\"%d ----> %s\\n\", k, v)//1 ----> mike//2 ----> yoyo}//迭代遍历2,第一个返回值是key,第二个返回值是value(可省略)for k := range m1 {fmt.Printf(\"%d ----> %s\\n\", k, m1[k])//1 ----> mike//2 ----> yoyo}//判断某个key所对应的value是否存在, 第一个返回值是value(如果存在的话)value, ok := m1[1]fmt.Println(\"value = \", value, \", ok = \", ok) //value =  mike , ok =  truevalue2, ok2 := m1[3]fmt.Println(\"value2 = \", value2, \", ok2 = \", ok2) //value2 =   , ok2 =  false

    7.5.3.3 删除

    m1 := map[int]string{1: \"mike\", 2: \"yoyo\", 3: \"lily\"}//迭代遍历1,第一个返回值是key,第二个返回值是valuefor k, v := range m1 {fmt.Printf(\"%d ----> %s\\n\", k, v)//1 ----> mike//2 ----> yoyo//3 ----> lily}delete(m1, 2) //删除key值为3的mapfor k, v := range m1 {fmt.Printf(\"%d ----> %s\\n\", k, v)//1 ----> mike//3 ----> lily}

    7.5.4 map做函数参数

    在函数间传递映射并不会制造出该映射的一个副本,不是值传递,而是引用传递:

    func DeleteMap(m map[int]string, key int) {delete(m, key) //删除key值为3的mapfor k, v := range m {fmt.Printf(\"len(m)=%d, %d ----> %s\\n\", len(m), k, v)//len(m)=2, 1 ----> mike//len(m)=2, 3 ----> lily}}func main() {m := map[int]string{1: \"mike\", 2: \"yoyo\", 3: \"lily\"}DeleteMap(m, 2) //删除key值为3的mapfor k, v := range m {fmt.Printf(\"len(m)=%d, %d ----> %s\\n\", len(m), k, v)//len(m)=2, 1 ----> mike//len(m)=2, 3 ----> lily}}

    7.6 结构体

    7.6.1 结构体类型

    有时我们需要将不同类型的数据组合成一个有机的整体,如:一个学生有学号/姓名/性别/年龄/地址等属性。显然单独定义以上变量比较繁琐,数据不便于管理。

    结构体是一种聚合的数据类型,它是由一系列具有相同类型或不同类型的数据构成的数据集合。每个数据称为结构体的成员。

    7.6.2 结构体初始化

    7.6.2.1 普通变量

    type Student struct {id   intname stringsex  byteage  intaddr string}func main() {//1、顺序初始化,必须每个成员都初始化var s1 Student = Student{1, \"mike\", \'m\', 18, \"sz\"}s2 := Student{2, \"yoyo\", \'f\', 20, \"sz\"}//s3 := Studen4000t{2, \"tom\", \'m\', 20} //err, too few values in struct initializer//2、指定初始化某个成员,没有初始化的成员为零值s4 := Student{id: 2, name: \"lily\"}}

    7.6.2.2 指针变量

    type Student struct {id   intname stringsex  byteage  intaddr string}func main() {var s5 *Student = &Student{3, \"xiaoming\", \'m\', 16, \"bj\"}s6 := &Student{4, \"rocco\", \'m\', 3, \"sh\"}}

    7.6.3 结构体成员的使用

    7.6.3.1 普通变量

    //===============结构体变量为普通变量//1、打印成员var s1 Student = Student{1, \"mike\", \'m\', 18, \"sz\"}//结果:id = 1, name = mike, sex = m, age = 18, addr = szfmt.Printf(\"id = %d, name = %s, sex = %c, age = %d, addr = %s\\n\", s1.id, s1.name, s1.sex, s1.age, s1.addr)//2、成员变量赋值var s2 Students2.id = 2s2.name = \"yoyo\"s2.sex = \'f\'s2.age = 16s2.addr = \"guangzhou\"fmt.Println(s2) //{2 yoyo 102 16 guangzhou}

    7.6.3.2 指针变量

    //===============结构体变量为指针变量//3、先分配空间,再赋值s3 := new(Student)s3.id = 3s3.name = \"xxx\"fmt.Println(s3) //&{3 xxx 0 0 }//4、普通变量和指针变量类型打印var s4 Student = Student{4, \"yyy\", \'m\', 18, \"sz\"}fmt.Printf(\"s4 = %v, &s4 = %v\\n\", s4, &s4) //s4 = {4 yyy 109 18 sz}, &s4 = &{4 yyy 109 18 sz}var p *Student = &s4//p.成员 和(*p).成员 操作是等价的p.id = 5(*p).name = \"zzz\"fmt.Println(p, *p, s4) //&{5 zzz 109 18 sz} {5 zzz 109 18 sz} {5 zzz 109 18 sz}

    7.6.4 结构体比较

    如果结构体的全部成员都是可以比较的,那么结构体也是可以比较的,那样的话两个结构体将可以使用 == 或 != 运算符进行比较,但不支持 > 或 < 。

    func main() {s1 := Student{1, \"mike\", \'m\', 18, \"sz\"}s2 := Student{1, \"mike\", \'m\', 18, \"sz\"}fmt.Println(\"s1 == s2\", s1 == s2) //s1 == s2 truefmt.Println(\"s1 != s2\", s1 != s2) //s1 != s2 false}

    7.6.5 结构体作为函数参数

    7.6.5.1 值传递

    func printStudentValue(tmp Student) {tmp.id = 250//printStudentValue tmp =  {250 mike 109 18 sz}fmt.Println(\"printStudentValue tmp = \", tmp)}func main() {var s Student = Student{1, \"mike\", \'m\', 18, \"sz\"}printStudentValue(s)        //值传递,形参的修改不会影响到实参fmt.Println(\"main s = \", s) //main s =  {1 mike 109 18 sz}}

    7.6.5.2 引用传递

    func printStudentPointer(p *Student) {p.id = 250//printStudentPointer p =  &{250 mike 109 18 sz}fmt.Println(\"printStudentPointer p = \", p)}func main() {var s Student = Student{1, \"mike\", \'m\', 18, \"sz\"}printStudentPointer(&s)     //引用(地址)传递,形参的修改会影响到实参fmt.Println(\"main s = \", s) //main s =  {250 mike 109 18 sz}}

    7.6.6 可见性

    Go语言对关键字的增加非常吝啬,其中没有private、 protected、 public这样的关键字。

    要使某个符号对其他包(package)可见(即可以访问),需要将该符号定义为以大写字母

    开头。

    目录结构:


    test.go示例代码如下:

    //test.gopackage test//student01只能在本文件件引用,因为首字母小写type student01 struct {Id   intName string}//Student02可以在任意文件引用,因为首字母大写type Student02 struct {Id   intname string}

    main.go示例代码如下:

    // main.gopackage mainimport (\"fmt\"\"test\" //导入test包)func main() {//s1 := test.student01{1, \"mike\"} //err, cannot refer to unexported name test.student01//err, implicit assignment of unexported field \'name\' in test.Student02 literal//s2 := test.Student02{2, \"yoyo\"}//fmt.Println(s2)var s3 test.Student02 //声明变量s3.Id = 1             //ok//s3.name = \"mike\"  //err, s3.name undefined (cannot refer to unexported field or method name)fmt.Println(s3)}
    赞(0) 打赏
    未经允许不得转载:爱站程序员基地 » 7.(go语言)复合类型