1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        深入理解 Go 語言的類型

        共 7728字,需瀏覽 16分鐘

         ·

        2021-01-09 21:25

        點擊上方藍(lán)色“Go語言中文網(wǎng)”關(guān)注,每天一起學(xué) Go

        當(dāng)我使用 C/C++ 編寫代碼時,理解類型(type)是非常有必要的。如果不理解類型,你就會在編譯或者運行代碼的時候,碰到一大堆麻煩。無論什么語言,類型都涉及到了編程語法的方方面面。加強(qiáng)對于類型和指針的理解,對于提高編程水平十分關(guān)鍵。本文會主要講解類型。

        我們首先來看看這幾個字節(jié)的內(nèi)存:

        FFE4FFE3FFE2FFE1
        00000000110010110110010100001010

        請問地址 FFE1 上字節(jié)的值是多少?如果你試圖回答一個結(jié)果,那就是錯的。為什么?因為我還沒有告訴你這個字節(jié)表示什么。我還沒有告訴你類型信息。

        如果我說上述字節(jié)表示一個數(shù)字會怎么樣呢?你可能會回答 10,那么你又錯了。為什么?因為當(dāng)我說這是數(shù)字的時候,你認(rèn)為我是指十進(jìn)制的數(shù)字。

        基數(shù)(number base):

        所有編號系統(tǒng)(numbering system)要發(fā)揮作用,都要有一個基(base)。從你出生的時候開始,人們就教你用基數(shù) 10 來數(shù)數(shù)了。這可能是因為我們大多數(shù)人都有 10 個手指和 10 個腳趾。另外,用基數(shù) 10 來進(jìn)行數(shù)學(xué)計算也很自然。

        基定義了編號系統(tǒng)所包含的符號數(shù)?;鶖?shù) 10 會有 10 個不同的符號,用以表示我們可以計量的無限事物。基數(shù) 10 的編號系統(tǒng)為 0、1、2、3、4、5、6、7、8、9。一旦超過了 9,我們需要增加數(shù)的長度。例如,10、100 和 1000。

        在計算機(jī)領(lǐng)域,我們還一直使用其他兩種基。第一種是基數(shù) 2(或二進(jìn)制數(shù)),例如上圖所表示的位。第二種是基數(shù) 16(或十六進(jìn)制數(shù)),例如上圖中表示的地址。

        在二進(jìn)制編號系統(tǒng)(基數(shù) 2)中,只有兩種符號,即 0 和 1。

        在十六進(jìn)制數(shù)字系統(tǒng)(基數(shù) 16)中,有 16 個符號,這些符號分別是:0、1、2、3、4、5、6、7、8、9、A、B、C、D、E、F。

        如果桌上有些蘋果,那些蘋果可以用任何編號系統(tǒng)來表示。我們可以說這里有:

        • 10010001 個蘋果(使用 2 作為基數(shù))
        • 145 個蘋果(使用 10 作為基數(shù))
        • 91 個蘋果(使用 16 作為基數(shù))

        所有答案都正確,只要給定了正確的基。

        注意每個編號系統(tǒng)表示那些蘋果所需要的符號數(shù)?;鶖?shù)越大,編號系統(tǒng)的效率就越高。

        對于計算機(jī)地址、IP 地址和顏色代碼,使用 16 作為基數(shù),就顯得很有價值。

        看看用三種基,來分別表示 HTML 的顏色(“白”)的數(shù)字:

        • 使用 2 作為基數(shù):1111 1111 1111 1111 1111 1111(24 個字符)
        • 使用 10 作為基數(shù):16777215(10 個字符)
        • 使用 16 作為基數(shù):FFFFFF(6 個字符)

        你會選擇哪個編號系統(tǒng)來表示顏色呢?

        現(xiàn)在,如果我告訴你,地址 FFE1 處的字節(jié)表示一個基數(shù)為 10 的數(shù)字,你回答 10,這就正確了。

        類型提供了兩條信息,你和編譯器都需要它來執(zhí)行我們剛剛經(jīng)歷過的練習(xí)。

        1. 要查看的內(nèi)存數(shù)量(以字節(jié)為單位)
        2. 這些字節(jié)的表示

        Go 語言提供了以下基本數(shù)字類型:

        無符號整數(shù)

        uint8, uint16, uint32, uint64

        有符號整數(shù)

        int8, int16, int32, int64

        實數(shù)

        float32, float64

        預(yù)聲明整數(shù)

        uint, int, uintptr

        這些關(guān)鍵字提供了所有的類型信息。

        uint8 包含一個基為 10 的數(shù)字,用 1 個存儲字節(jié)表示。uint8 的值從 0 到 255。

        int32 包含一個基為 10 的數(shù)字,用 4 個存儲字節(jié)表示。int32 的值從 -2147483648 到 2147483647。

        預(yù)聲明整數(shù)會根據(jù)你構(gòu)建代碼時的體系結(jié)構(gòu)來進(jìn)行映射。在 64 位操作系統(tǒng)上,int 將映射到 int64,而在 32 位系統(tǒng)上,它將映射到 int32。

        所有存儲在內(nèi)存中的內(nèi)容都解析為某種數(shù)字類型。在 Go 中,字符串只是一系列 uint8 類型,并包含了一些規(guī)則,用于關(guān)聯(lián)這些字節(jié)和識別字符串的結(jié)尾位置。

        在 Go 中,指針就是 uintptr 類型。同樣地,基于操作系統(tǒng)的體系結(jié)構(gòu),它將映射為 uint32 或者 uint64。Go 為指針創(chuàng)建了一個特殊的類型。在過去,許多 C 程序員在編寫代碼時,會認(rèn)為指針值總能符合 unsigned int。隨著時間的推移,語言和體系結(jié)構(gòu)不斷升級,最終這不再是對的了。由于地址變得比預(yù)先聲明的 unsigned int 更大,很多代碼都出錯了。

        結(jié)構(gòu)體類型只是很多類型的組合,而這些類型也最終會解析為數(shù)字類型。

        type?Example?struct{
        ????BoolValue?bool
        ????IntValue??int16
        ????FloatValue?float32
        }

        該結(jié)構(gòu)體表示一個復(fù)雜類型。它表示 7 個字節(jié),有三種不同的數(shù)字表示。bool 有 1 個字節(jié),int16 有 2 個字節(jié),而 float32 有 4 個字節(jié)。但是,這個結(jié)構(gòu)體最終在內(nèi)存中分配了 8 個字節(jié)。

        為了最大限度地減少內(nèi)存碎片整理(memory defragmentation),分配內(nèi)存時都會將內(nèi)存邊界對齊。要確定 Go 在體系結(jié)構(gòu)上所用的對齊邊界(alignment boundary),你可以運行 unsafe.Alignof 函數(shù)。Go 在 64 位 Darwin 平臺的對齊邊界是 8 個字節(jié)。因此在 Go 確定我們結(jié)構(gòu)體的內(nèi)存分配時,它將填充字節(jié)以確保最終占用的內(nèi)存是 8 的倍數(shù)。編譯器會決定在哪里添加填充。

        如果你想要學(xué)習(xí)更多有關(guān)結(jié)構(gòu)體成員對齊和填充的知識,請查看下面的鏈接:

        http://www.geeksforgeeks.org/structure-member-alignment-padding-and-data-packing/

        下面的程序會顯示對于 Example 結(jié)構(gòu)體類型,Go 向內(nèi)存所插入的填充:

        package?main

        import?(
        ????"fmt"
        ????"unsafe"
        )

        type?Example?struct?{
        ????BoolValue?bool
        ????IntValue?int16
        ????FloatValue?float32
        }

        func?main()?{
        ????example?:=?&Example{
        ????????BoolValue:??true,
        ????????IntValue:???10,
        ????????FloatValue:?3.141592,
        ????}

        ????exampleNext?:=?&Example{
        ????????BoolValue:??true,
        ????????IntValue:???10,
        ????????FloatValue:?3.141592,
        ????}

        ????alignmentBoundary?:=?unsafe.Alignof(example)

        ????sizeBool?:=?unsafe.Sizeof(example.BoolValue)
        ????offsetBool?:=?unsafe.Offsetof(example.BoolValue)

        ????sizeInt?:=?unsafe.Sizeof(example.IntValue)
        ????offsetInt?:=?unsafe.Offsetof(example.IntValue)

        ????sizeFloat?:=?unsafe.Sizeof(example.FloatValue)
        ????offsetFloat?:=?unsafe.Offsetof(example.FloatValue)

        ????sizeBoolNext?:=?unsafe.Sizeof(exampleNext.BoolValue)
        ????offsetBoolNext?:=?unsafe.Offsetof(exampleNext.BoolValue)

        ????fmt.Printf("Alignment?Boundary:?%d\n",?alignmentBoundary)

        ????fmt.Printf("BoolValue?=?Size:?%d?Offset:?%d?Addr:?%v\n",
        ????????sizeBool,?offsetBool,?&example.BoolValue)

        ????fmt.Printf("IntValue?=?Size:?%d?Offset:?%d?Addr:?%v\n",
        ????????sizeInt,?offsetInt,?&example.IntValue)

        ????fmt.Printf("FloatValue?=?Size:?%d?Offset:?%d?Addr:?%v\n",
        ????????sizeFloat,?offsetFloat,?&example.FloatValue)

        ????fmt.Printf("Next?=?Size:?%d?Offset:?%d?Addr:?%v\n",
        ????????sizeBoolNext,?offsetBoolNext,?&exampleNext.BoolValue)
        }

        輸出如下所示:

        Alignment?Boundary:?8
        BoolValue??=?Size:?1??Offset:?0??Addr:?0x21015b018
        IntValue???=?Size:?2??Offset:?2??Addr:?0x21015b01a
        FloatValue?=?Size:?4??Offset:?4??Addr:?0x21015b01c
        Next???????=?Size:?1??Offset:?0??Addr:?0x21015b020

        該結(jié)構(gòu)體類型的對齊邊界的確是 8 字節(jié)。

        Size 大小值表示某字段讀寫時所用的內(nèi)存。不出所料,該值與字段的類型信息相一致。

        Offset 偏移值表示字段的開始位置,在內(nèi)存占用中的字節(jié)序號。

        Addr 地址值表示每個字段開始在內(nèi)存占用中所處的位置。

        我們可以看到,Go 在 BoolValueIntValue 字段之間填充了 1 個字節(jié)。偏移值和兩個地址之差是 2 個字節(jié)。你還可以看到,下一個內(nèi)存分配時是從結(jié)構(gòu)體最后的字段處分配 4 個字節(jié)。

        我們讓結(jié)構(gòu)體只有一個 bool 字段(1 字節(jié)),來證實 8 字節(jié)對齊法則。

        package?main

        import?(
        ????"fmt"
        ????"unsafe"
        )

        type?Example?struct?{
        ????BoolValue?bool
        }

        func?main()?{
        ????example?:=?&Example{
        ????????BoolValue:??true,
        ????}

        ????exampleNext?:=?&Example{
        ????????BoolValue:??true,
        ????}

        ????alignmentBoundary?:=?unsafe.Alignof(example)

        ????sizeBool?:=?unsafe.Sizeof(example.BoolValue)
        ????offsetBool?:=?unsafe.Offsetof(example.BoolValue)

        ????sizeBoolNext?:=?unsafe.Sizeof(exampleNext.BoolValue)
        ????offsetBoolNext?:=?unsafe.Offsetof(exampleNext.BoolValue)

        ????fmt.Printf("Alignment?Boundary:?%d\n",?alignmentBoundary)

        ????fmt.Printf("BoolValue?=?Size:?%d?Offset:?%d?Addr:?%v\n",
        ????????sizeBool,?offsetBool,?&example.BoolValue)

        ????fmt.Printf("Next?=?Size:?%d?Offset:?%d?Addr:?%v\n",
        ????????sizeBoolNext,?offsetBoolNext,?&exampleNext.BoolValue)
        }

        其輸出如下:

        Alignment?Boundary:?8
        BoolValue?=?Size:?1?Offset:?0?Addr:?0x21015b018
        Next??????=?Size:?1?Offset:?0?Addr:?0x21015b020

        把兩個地址相減,你將看到兩種結(jié)構(gòu)體類型分配之間存在 8 個字節(jié)的間隙。此外,這一次的內(nèi)存分配從上一示例相同的地址開始。為了保持對齊邊界,Go 向結(jié)構(gòu)體填充了 7 個字節(jié)。

        無論如何填充,Size 值實際上表示我們可以為每個字段讀寫的內(nèi)存大小。

        我們只能在使用數(shù)字類型時,才能操作內(nèi)存,通過賦值運算符(=)可以做到這一點。為了方便,Go 創(chuàng)建了一些可以支持賦值運算符的復(fù)雜類型。這些類型有字符串、數(shù)組和切片。要查看這些類型的完整列表,請查看此文檔:http://golang.org/ref/spec#Types。

        這些復(fù)雜類型其實對底層數(shù)字類型進(jìn)行了抽象,我們可以在各種復(fù)雜類型的實現(xiàn)發(fā)現(xiàn)這一點。在這種情況下,這些復(fù)雜類型可以像數(shù)字類型那樣直接讀取內(nèi)存。

        Go 是一種類型安全的語言。這意味著,編譯器將始終強(qiáng)制賦值運算符的兩邊類型保持相似。這非常重要,因為這會防止我們錯誤地讀取內(nèi)存。

        假設(shè)我們想做下面的事。如果你試圖編譯代碼,你會得到一個錯誤。

        type?Example?struct{
        ????BoolValue?bool
        ????IntValue??int16
        ????FloatValue?float32
        }

        example?:=?&Example{
        ????BoolValue:??true,
        ????IntValue:???10,
        ????FloatValue:?3.141592,
        }

        var?pointer?*int32
        pointer?=?*int32(&example.IntValue)
        *pointer?=?20

        我試圖獲取 IntValue 字段(2 個字節(jié))的內(nèi)存地址,并把它存儲在類型為 int32 的指針上。接下來,我試圖用指針,向內(nèi)存地址寫入一個 4 個字節(jié)的整數(shù)。如果可以使用該指針,那么我就會違反 IntValue 字段的類型規(guī)則,并在此過程中破壞內(nèi)存。

        FFE8FFE7FFE6FFE5FFE4FFE3FFE2FFE1
        0003.140100true
        pointer
        FFE3
        FFE8FFE7FFE6FFE5FFE4FFE3FFE2FFE1
        00000200true

        根據(jù)上面的內(nèi)存占用情況,指針將在 FFE3 和 FFE6 之間的 4 個字節(jié)中寫入 20。IntValue 的值將如預(yù)期的那樣變?yōu)?20,但 FloatValue 的值現(xiàn)在等于 0。想象一下,寫入這些字節(jié)超出了該結(jié)構(gòu)體的內(nèi)存分配,并且開始破壞應(yīng)用的其他區(qū)域的內(nèi)存。隨之而來的錯誤會是隨機(jī)、不可預(yù)測的

        Go 編譯器會一直保證內(nèi)存對齊和轉(zhuǎn)型是安全的。

        在下面一個轉(zhuǎn)型的示例中,編譯器會報錯

        ackage?main

        import?(
        ????"fmt"
        )

        //?Create?a?new?type
        type?int32Ext?int32

        func?main()?{
        ????//?Cast?the?number?10?to?a?value?of?type?Jill
        ????var?jill?int32Ext?=?10

        ????//?Assign?the?value?of?jill?to?jack
        ????//?**?cannot?use?jill?(type?int32Ext)?as?type?int32?in?assignment?**
        ????var?jack?int32?=?jill

        ????//?Assign?the?value?of?jill?to?jack?by?casting
        ????//?**?the?compiler?is?happy?**
        ????var?jack?int32?=?int32(jill)

        ????fmt.Printf("%d\n",?jack)
        }

        首先,我們在系統(tǒng)中新建了一個 int32Ext 類型,并告訴編譯器該類型表示一個 int32。接下來,我們創(chuàng)建了一個名為 jill 的新變量,將其賦值為 10。編譯器允許這個賦值操作,因為數(shù)字類型在賦值運算符的右側(cè)。編譯器知道賦值是安全的。

        現(xiàn)在,我們嘗試創(chuàng)建第二個變量,名為 jack,其類型為 int32,我們將 jill 賦值給 jack。在這里,編譯器會拋出錯誤:

        cannot?use?jill?(type?int32Ext)?as?type?int32?in?assignment

        編譯器認(rèn)為 jill 的類型是 int32Ext,不會對賦值的安全性作出任何假設(shè)。

        現(xiàn)在我們使用強(qiáng)制轉(zhuǎn)換,編譯器允許賦值,并如預(yù)期打印出值來。當(dāng)我們執(zhí)行轉(zhuǎn)型時,編譯器會檢查賦值的安全性。在這里,編譯器確定了這是相同類型的值,于是允許賦值操作。

        對于某些讀者來說,這似乎很基礎(chǔ),但它是使用任何編程語言的基石。即使類型是經(jīng)過抽象的,你也是在操作內(nèi)存,你應(yīng)該知道你究竟在做些什么。

        有了這些基礎(chǔ),我們才可以在 Go 中討論指針,然后將參數(shù)傳遞給函數(shù)。

        像往常一樣,我希望這篇文章,能夠幫助你了解一些可能存在的盲區(qū)。


        via: https://www.ardanlabs.com/blog/2013/07/understanding-type-in-go.html

        作者:William Kennedy[1]譯者:Noluye[2]校對:polaris1119[3]

        本文由 GCTT[4] 原創(chuàng)編譯,Go 中文網(wǎng)[5] 榮譽(yù)推出

        參考資料

        [1]

        William Kennedy: https://github.com/ardanlabs/gotraining

        [2]

        Noluye: https://github.com/Noluye

        [3]

        polaris1119: https://github.com/polaris1119

        [4]

        GCTT: https://github.com/studygolang/GCTT

        [5]

        Go 中文網(wǎng): https://studygolang.com/



        推薦閱讀


        福利

        我為大家整理了一份從入門到進(jìn)階的Go學(xué)習(xí)資料禮包,包含學(xué)習(xí)建議:入門看什么,進(jìn)階看什么。關(guān)注公眾號 「polarisxu」,回復(fù) ebook 獲??;還可以回復(fù)「進(jìn)群」,和數(shù)萬 Gopher 交流學(xué)習(xí)。

        瀏覽 35
        點贊
        評論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報
        評論
        圖片
        表情
        推薦
        點贊
        評論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            一区二区三区免费播放 | 国产精品重口调教系列 | 激情五月深爱五月 | 久久久精品视频在线 | 成人视频在线观看免费无码 | 国产成a人亚洲精v | 啊啊啊操我视频 | 内涵av入口 | 奶水人妻羽月希电复活 | 台湾成人在线 |