• 复合数据类型介绍


    复合数据类型介绍

    一、什么是复合数据类型

    基本数据类型是Go语言世界中的原子,以不同的方式组合基本数据类型得到的就是复合数据类型。复合类型是通过组合基础类型,来表达更加复杂的数据结构,即使用其他类型定义的类型,因而复合类型又称之为派生类型,数据类型分为值类型与引用类型。

    二、值类型与引用类型

    1. 数值型复合类型:

      • 数组(array)

      • 结构体(struct)

    2. 引用型复合类型:

      • 1 指针(pointer)
      • 2 切片(slice)
      • 3 字典(map)
      • 4 通道(chan)
      • 函数也属于引用类型

    三、值类型

    基础类型(数字、字符串、字符、布尔)以及数值型复合类型都属于值类型

    对于值类型,即便我们在声明的时候都没有为其初始化值,那么编译器会依据它们的零值为它们申请好内存空间(分配于栈上),所以对于值类型我们都不必费心为其申请内存空间

    var a int       //int类型默认值为 0
    var b string    //string类型默认值为 空字串
    var c bool      //bool类型默认值为false
    var d [2]int    //数组默认值为[0 0]
    fmt.Println(&a) //默认已经分配内存地址,所以我们可以直接使用&操作,取到的是一个合法的内存地址,而非nil
    

    值类型的变量赋值操作,如 j = i ,实际上是在内存中将 i 的值进行了拷贝,如果修改某个变量的值i=3,不会影响另一个

    var a =10   //定义变量a
    b := a      //将a的值赋值给b
    b = 101     //修改b的值,此时不会影响a
    fmt.Printf("值%v,内存地址%p
    ",a,&a)   //a的值是10,a的内存地址是0xc0000aa058
    fmt.Printf("值%v,内存地址%p
    ",b,&b)   //b的值是101,b的内存地址是0xc0000aa070
    
    
    var c =[3]int{1,2,3}    //定义一个长度为3的int类型的数组
    d := c      //将数组c赋值给d
    d[1] = 100  //修改数组d中索引为1的值为100
    fmt.Printf("值%v,内存地址%p
    ",c,&c)   //c的值是[1 2 3],c的内存地址是0xc0000ae090
    fmt.Printf("值%v,内存地址%p
    ",d,&d)   //d的值是[1 100 3],d的内存地址是0xc0000ae0a8
    

    值10,内存地址0xc0000aa058
    值101,内存地址0xc0000aa070
    值[1 2 3],内存地址0xc0000ae090
    值[1 100 3],内存地址0xc0000ae0a8

    img

    三、引用类型

    引用型复合类型(指针、切片、字典、通道)、函数、接口类型都属于引用类型。

    对于引用类型,如果我们在声明的时候没有为其初始化“值”,那么默认零值为nil,nil代表空,编译器不会为其分配内存,而引用类型存在的意义在于引用了一个已经存在的内存空间,所以对于引用类型我们必须为其申请好内存才可以使用,需要用到函数new()和make()。

    new()与make()的异同

    相同点:
    1. 都是针对引用类型的内存空间分配操作
    2. 分配的内存都在堆上
    3. 第一个参数都是一个类型,而不是一个值

    不同点:

    1. make 只用于引用类型map, slice, channel的内存分配,返回的还是这三个引用类型本身,而new返回的是一个指针
    2. new多用于匿名变量的空指针操作,如new(int),new(string), new(array)等,而且内存置为零,此外,大多数场景下new并不常用
    

    引用类型的变量赋值操作,如 j = i ,当然也是在内存中将 i 的值拷贝给了j,但是因为引用类型的变量直接存放的就是一个内存地址值(这个地址值指向的空间存的才是值),即i与j都是同一个地址。所以通过i或j修改对应内存地址空间中的值,另外一个也会修改。

    var a = []int{1, 2, 3, 4, 5}
    b := a    //此时a,b都指向了内存中的[1 2 3 4 5]的地址
    b[1] = 10 //相当于修改同一个内存地址,所以a的值也会改变
    
    c := make([]int, 5, 5) //切片的初始化
    copy(c, a)             //将切片a拷贝到c
    c[1] = 20              //copy是拷贝值、创建了新的底层数组,所以a不会改变
    
    fmt.Printf("值a: %v,内存地址%p
    ", a, &a)
    fmt.Printf("值b: %v,内存地址%p
    ", b, &b) 
    fmt.Printf("值c: %v,内存地址%p
    ", c, &c) 
    d := &a                           //将a的内存地址赋值给d,取值用*d
    a[1] = 11
    fmt.Printf("值是d: %v,内存地址%p
    ", *d, d) //d的值是[1 11 3 4 5],d的内存地址是0xc420084060
    fmt.Printf("值是a: %v,内存地址%p
    ", a, &a) //a的值是[1 11 3 4 5],a的内存地址是0xc420084060
    

    值a: [1 10 3 4 5],内存地址0xc42000a180
    值b: [1 10 3 4 5],内存地址0xc000004090
    值c: [1 20 3 4 5],内存地址0xc0000040a8
    值是d: [1 11 3 4 5],内存地址0xc000004078
    值是a: [1 11 3 4 5],内存地址0xc000004078

    a,b底层数组是一样的,即操作的都是同一个底层数组,但是上层切片不同,所以内存地址不一样。

    img

    引用类型的零值是nil,而nil只能与nil做相等性判断

    // 例
    var s []int    // len(s) == 0, s == nil
    s = nil        // len(s) == 0, s == nil
    s = []int(nil) // len(s) == 0, s == nil
    s = []int{}    // len(s) == 0, s != nil 此时不等于nil,而该切片仍然是空,所以如果你需要测试一个slice是否是空的,使用len(s) == 0来判断,而不应该用s == nil来判断
    
    // 思考题
    var a []string        //声明一个字符串切片,初始零值为?
    fmt.Printf("%#v
    ",a)  //  []string(nil)
    fmt.Println(a == nil) //true
    
    var b []int           // 声明一个整型切片,初始零值为?
    fmt.Printf("%#v
    ",b) // []int(nil)
    fmt.Println(b == nil) //true
    
    var c = []int{}       //声明一个整型切片并初始,化初始零值为?
    fmt.Printf("%#v
    ",c)  // []int{}
    fmt.Println(c == nil) //false
    

    关于函数

    函数的参数传递机制是将实参的值拷贝一份给形参.只不过这个实参的值有可能是地址, 有可能是数据.

    所以, 函数传参的传递其实本质全都是值传递,只不过该值有可能是数据(通常被简称为”值传递“),也有可能是地址(通常被简称为”引用传递“).

    在当下的阶段,必将由程序员来主导,甚至比以往更甚。
  • 相关阅读:
    逻辑最复杂的MVVM模式实现
    剧本:博客园之天外飞仙
    本博客开始偏转方向,开始研究UDP在WCF下的实现
    Prism研究 目录
    Q & A category in Prism forums, with some answers and samples of mine.
    我眼中的SOA,以及在实际项目中的应用经验
    数据结构 C#描述 第三章 (更新)
    数据结构 C#描述 第四章
    数据结构 C#描述 第七章 (第一部分)
    数据结构 C#描述
  • 原文地址:https://www.cnblogs.com/randysun/p/15412681.html
Copyright © 2020-2023  润新知