復合數據類型介紹


復合數據類型介紹

一、什么是復合數據類型

基本數據類型是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\n",a,&a)   //a的值是10,a的內存地址是0xc0000aa058
fmt.Printf("值%v,內存地址%p\n",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\n",c,&c)   //c的值是[1 2 3],c的內存地址是0xc0000ae090
fmt.Printf("值%v,內存地址%p\n",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\n", a, &a)
fmt.Printf("值b: %v,內存地址%p\n", b, &b) 
fmt.Printf("值c: %v,內存地址%p\n", c, &c) 
d := &a                           //將a的內存地址賦值給d,取值用*d
a[1] = 11
fmt.Printf("值是d: %v,內存地址%p\n", *d, d) //d的值是[1 11 3 4 5],d的內存地址是0xc420084060
fmt.Printf("值是a: %v,內存地址%p\n", 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\n",a)  //  []string(nil)
fmt.Println(a == nil) //true

var b []int           // 聲明一個整型切片,初始零值為?
fmt.Printf("%#v\n",b) // []int(nil)
fmt.Println(b == nil) //true

var c = []int{}       //聲明一個整型切片並初始,化初始零值為?
fmt.Printf("%#v\n",c)  // []int{}
fmt.Println(c == nil) //false

關於函數

函數的參數傳遞機制是將實參的值拷貝一份給形參.只不過這個實參的值有可能是地址, 有可能是數據.

所以, 函數傳參的傳遞其實本質全都是值傳遞,只不過該值有可能是數據(通常被簡稱為”值傳遞“),也有可能是地址(通常被簡稱為”引用傳遞“).


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM