學習筆記-go程序實體

Go 語言中的程序實體包括:變量、常量、函數、結構體和接口。
Go 語言是靜態類型的編程語言,因此咱們在聲明變量或常量的時候,都須要指定它們的類型,或者給予足夠的信息,這樣纔可讓 Go 語言可以推導出它們的類型,在 Go 語言中,變量的類型能夠是其預約義的那些類型,也能夠是程序自定義的函數、結構體或接口。常量的合法類型很少,只能是那些 Go 語言預約義的基本類型。它的聲明方式也更簡單一些。golang

一、問題:聲明變量有幾種方式?

package main

import (
    "flag"
    "fmt"
)

func main() {
    var name string    //var name string這種聲明變量name的方式         // [1]
    flag.StringVar(&name, "name", "everyone", "The greeting object.") // [2]

    // 方式1。
    //var name = flag.String("name", "everyone", "The greeting object.")

    // 方式2。
    //name := flag.String("name", "everyone", "The greeting object.")

    flag.Parse()
    fmt.Printf("Hello, %v!\n", name)

    // 適用於方式1和方式2。
    //fmt.Printf("Hello, %v!\n", *name)
}

第一種方式中的代碼在聲明變量name的同時,還爲它賦了值,而這時聲明中並無顯式指定name的類型。這裏利用了 Go 語言自身的類型推斷,而省去了對該變量的類型的聲明。編程

把被調用的函數由flag.StringVar改成flag.String,傳參的列表也須要隨之修改,這是爲了[1]和[2]處代碼合併的準備工做。c#

注意,flag.String函數返回的結果值的類型是string而不是string。類型string表明的是字符串的指針類型,而不是字符串類型。所以,這裏的變量name表明的是一個指向字符串值的指針。數據結構

咱們能夠經過操做符把這個指針指向的字符串值取出來了。所以,在這種狀況下,那個被用來打印內容的函數調用就須要微調一下,把其中的參數name改成name,即:fmt.Printf("Hello, %v!\n", *name)。編程語言

第二種方式與第一種方式很是相似,它基於第一種方式的代碼,賦值符號=右邊的代碼不動,左邊只留下name,再把=變成:=ide

學習筆記-go程序實體

二、 上面列出了幾種方式,區別在哪裏?

var name = flag.String("name", "everyone", "The greeting object.")

第一種方式中的代碼在聲明變量name的同時,還爲它賦了值,而這時聲明中並無顯式指定name的類型。函數

這裏利用了 Go 語言自身的類型推斷,而省去了對該變量的類型的聲明。學習

簡單地說,類型推斷是一種編程語言在編譯期自動解釋表達式類型的能力。什麼是表達式?詳細的解釋你能夠參看 Go 語言規範中的表達式https://golang.google.cn/ref/spec#Expressions 和表達式語句https://golang.google.cn/ref/spec#Expression_statements 章節網站

表達式類型就是對錶達式進行求值後獲得結果的類型。Go 語言中的類型推斷是很簡約的,這也是 Go 語言總體的風格。ui

它只能用於對變量或常量的初始化,就像上述回答中描述的那樣。對flag.String函數的調用其實就是一個調用表達式,而這個表達式的類型是*string,即字符串的指針類型。

這也是調用flag.String函數後獲得結果的類型。隨後,Go 語言把這個調用了flag.String函數的表達式類型,直接做爲了變量name的類型,這就是「推斷」一詞所指代的操做了。

name := flag.String("name", "everyone", "The greeting object.")

至於第二種方式所用的短變量聲明,實際上就是 Go 語言的類型推斷再加上一點點語法糖。

咱們只能在函數體內部使用短變量聲明。在編寫if、for或switch語句的時候,咱們常常把它安插在初始化子句中,並用來聲明一些臨時的變量。而相比之下,第一種方式更加通用,它能夠被用在任何地方。

三、 Go 語言的類型推斷能夠帶來哪些好處?

先看一段代碼:

package main

import (
    "flag"
    "fmt"
)

func main() {
    var name = getTheFlag()
    flag.Parse()
    fmt.Printf("Hello, %v!\n", *name)
}

func getTheFlag() *string {
    return flag.String("name", "everyone", "The greeting object.")
}

//上面函數的實現也能夠是這樣的。
//func getTheFlag() *int {
//  return flag.Int("num", 1, "The number of greeting object.")
//}
go run demo8.go -name huaihe
Hello, huaihe!

name能不能是數字呢?

package main

import (
    "flag"
    "fmt"
)

func main() {
    var name = getTheFlag()
    flag.Parse()
    fmt.Printf("Hello, %v!\n", *name)
}

// func getTheFlag() *string {
//  return flag.String("name", "everyone", "The greeting object.")
// }

//上面函數的實現也能夠是這樣的。
func getTheFlag() *int {
    return flag.Int("name", 1, "The number of greeting object.")
}

name輸出已是一個數字了

go run demo8.go -name=2 
Hello, 2!

咱們能夠用getTheFlag函數包裹(或者說包裝)那個對flag.String函數的調用,並把其結果直接做爲getTheFlag函數的結果,結果的類型是*string。

這樣一來,var name =右邊的表達式,能夠變爲針對getTheFlag函數的調用表達式了。這其實是對「聲明並賦值name變量的那行代碼」的重構。

一般把不改變某個程序與外界的任何交互方式和規則,而只改變其內部實現」的代碼修改方式,叫作對該程序的重構。重構的對象能夠是一行代碼、一個函數、一個功能模塊,甚至一個軟件系統。

好了,在準備工做作完以後,你會發現,你能夠隨意改變getTheFlag函數的內部實現,及其返回結果的類型,而不用修改main函數中的任何代碼。

這個命令源碼文件依然能夠經過編譯,而且構建和運行也都不會有問題。也許你能感受獲得,這是一個關於程序靈活性的質變。

咱們不顯式地指定變量name的類型,使得它能夠被賦予任何類型的值。也就是說,變量name的類型能夠在其初始化時,由其餘程序動態地肯定。

在你改變getTheFlag函數的結果類型以後,Go 語言的編譯器會在你再次構建該程序的時候,自動地更新變量name的類型。

經過這種類型推斷,你能夠體驗到動態類型編程語言所帶來的一部分優點,即程序靈活性的明顯提高。但在那些編程語言中,這種提高能夠說是用程序的可維護性和運行效率換來的。

Go 語言是靜態類型的,因此一旦在初始化變量時肯定了它的類型,以後就不可能再改變。這就避免了在後面維護程序時的一些問題。另外,請記住,這種類型的肯定是在編譯期完成的,所以不會對程序的運行效率產生任何影響。

總結:
Go 語言的類型推斷能夠明顯提高程序的靈活性,使得代碼重構變得更加容易,同時又不會給代碼的維護帶來額外負擔(實際上,它偏偏能夠避免散彈式的代碼修改),更不會損失程序的運行效率。

四、變量的重聲明是什麼意思?有什麼功能?

變量聲明。經過使用它,咱們能夠對同一個代碼塊中的變量進行重聲明。

說到了代碼塊,我先來解釋一下它。在 Go 語言中,代碼塊通常就是一個由花括號括起來的區域,裏面能夠包含表達式和語句。Go 語言自己以及咱們編寫的代碼共同造成了一個很是大的代碼塊,也叫全域代碼塊。
這主要體如今,只要是公開的全局變量,均可以被任何代碼所使用。相對小一些的代碼塊是代碼包,一個代碼包能夠包含許多子代碼包,因此這樣的代碼塊也能夠很大。
接下來,每一個源碼文件也都是一個代碼塊,每一個函數也是一個代碼塊,每一個if語句、for語句、switch語句和select語句都是一個代碼塊。甚至,switch或select語句中的case子句也都是獨立的代碼塊。走個極端,我就在main函數中寫一對緊挨着的花括號算不算一個代碼塊?固然也算,這甚至還有個名詞,叫「空代碼塊」。

變量重聲明的前提條件以下:

因爲變量的類型在其初始化時就已經肯定了,因此對它再次聲明時賦予的類型必須與其本來的類型相同,不然會產生編譯錯誤。

變量的重聲明只可能發生在某一個代碼塊中。若是與當前的變量重名的是外層代碼塊中的變量,那麼就是另一種含義了。

變量的重聲明只有在使用短變量聲明時纔會發生,不然也沒法經過編譯。若是要在此處聲明全新的變量,那麼就應該使用包含關鍵字var的聲明語句,可是這時就不能與同一個代碼塊中的任何變量有重名了

被「聲明並賦值」的變量必須是多個,而且其中至少有一個是新的變量。這時咱們才能夠說對其中的舊變量進行了重聲明。

變量重聲明其實算是一個語法糖(或者叫便利措施)。它容許咱們在使用短變量聲明時不用理會被賦值的多個變量中是否包含舊變量。能夠想象,若是不這樣會多寫很多代碼。

package main

import (
    "fmt"
    "io"
    "os"
)

func main() {
    var err error
    n, err := io.WriteString(os.Stdout, "Hello, everyone!\n") // 這裏對`err`進行了重聲明。
    if err != nil {
        fmt.Printf("Error: %v\n", err)
    }
    fmt.Printf("%d byte(s) were written.\n", n)
}

使用短變量聲明對新變量n和舊變量err進行了「聲明並賦值」,這時也是對後者的重聲明。

在本篇中,咱們聚焦於最基本的 Go 語言程序實體:變量。並詳細解說了變量聲明和賦值的基本方法,及其背後的重要概念和知識。咱們使用關鍵字var和短變量聲明,均可以實現對變量的「聲明並賦值」。

這兩種方式各有千秋,有着各自的特色和適用場景。前者能夠被用在任何地方,然後者只能被用在函數或者其餘更小的代碼塊中。

不過,經過前者咱們沒法對已有的變量進行重聲明,也就是說它沒法處理新舊變量混在一塊兒的狀況。不過它們也有一個很重要的共同點,即:基於類型推斷,Go 語言的類型推斷只應用在了對變量或常量的初始化方面。

五、若是一個變量與其外層代碼塊中的變量重名會出現什麼情況?

package main

import "fmt"

var block = "package"

func main() {
    block := "function"
    {
        block := "inner"
        fmt.Printf("The block is %s.\n", block)
    }
    fmt.Printf("The block is %s.\n", block)
}

執行結果:

go run demo10.go 
The block is inner.
The block is function.

程序實體的訪問權限有三種:包級私有的、模塊級私有的和公開的,包級私有和模塊級私有訪問權限對應的都是代碼包代碼塊,公開的訪問權限對應的是全域代碼塊。

這個命令源碼文件中有四個代碼塊,它們是:全域代碼塊、main包表明的代碼塊、main函數表明的代碼塊,以及在main函數中的一個用花括號包起來的代碼塊。後三個代碼塊中分別聲明瞭一個名爲block的變量,並分別把字符串值"package"、"function"和"inner"賦給了它們。此外,我在後兩個代碼塊的最後分別嘗試用fmt.Printf函數打印出「The block is %s.」。這裏的「%s」只是爲了佔位,程序會用block變量的實際值替換掉。

首先,代碼引用變量的時候總會最優先查找當前代碼塊中的那個變量。注意,這裏的「當前代碼塊」僅僅是引用變量的代碼所在的那個代碼塊,並不包含任何子代碼塊。

其次,若是當前代碼塊中沒有聲明以此爲名的變量,那麼程序會沿着代碼塊的嵌套關係,從直接包含當前代碼塊的那個代碼塊開始,一層一層地查找。

通常狀況下,程序會一直查到當前代碼包表明的代碼塊。若是仍然找不到,那麼 Go 語言的編譯器就會報錯了。

從做用域的角度也能夠說,雖然經過var block = "package"聲明的變量做用域是整個main代碼包,可是在main函數中,它卻被那兩個同名的變量「屏蔽」了。

雖然main函數首先聲明的block的做用域,是整個main函數,可是在最內層的那個代碼塊中,它倒是不可能被引用到的。
最內層的{ }代碼塊會使用當前代碼塊{ }的變量block := "inner",因此第一次打印The block is inner.。
最內層代碼塊中的block也不可能被該塊以外的main代碼引用到,因此第二行打印「The block is function.」

六、不一樣代碼塊中的重名變量與變量重聲明中的變量區別到底在哪兒?

學習筆記-go程序實體

方便描述,把不一樣代碼塊中的重名變量叫作「可重名變量」。注意,在同一個代碼塊中不容許出現重名的變量,這違背了 Go 語言的語法。

(1)變量重聲明中的變量必定是在某一個代碼塊內的。注意,這裏的「某一個代碼塊內」並不包含它的任何子代碼塊,不然就變成了「多個代碼塊之間」。而可重名變量指的正是在多個代碼塊之間由相同的標識符表明的變量。
(2)變量重聲明是對同一個變量的屢次聲明,這裏的變量只有一個。而可重名變量中涉及的變量確定是有多個的。
(3)不論對變量重聲明多少次,其類型必須始終一致,具體聽從它第一次被聲明時給定的類型。而可重名變量之間不存在相似的限制,它們的類型能夠是任意的。
(4)若是可重名變量所在的代碼塊之間,存在直接或間接的嵌套關係,那麼它們之間必定會存在「屏蔽」的現象。可是這種現象絕對不會在變量重聲明的場景下出現。

既然可重名變量的類型能夠是任意的,那麼當它們之間存在「屏蔽」時你就更須要注意了。不一樣類型的值大都有着不一樣的特性和用法。當你在某一種類型的值上施加只有在其餘類型值上才能作的操做時,Go 語言編譯器必定會告訴你:「這不能夠」。

看個例子,兩個都叫作container的變量,分別位於main包代碼塊和main函數代碼塊。main包代碼塊中的變量是切片(slice)類型的,另外一個是字典(map)類型的。在main函數的最後,我試圖打印出container變量的值中索引爲1的那個元素:

package main

import "fmt"

var container = []string{"zero", "one", "two"}

func main() {
    container := map[int]string{0: "zero", 1: "one", 2: "two"}
    fmt.Printf("The element is %q.\n", container[1])
}
go run demo11.go 
The element is "one".

若是修改下代碼,把:

package main

import "fmt"

var container = []string{"zero", "one", "two"}

func main() {
    container := map[int]string{0: "zero", 1: "1", 2: "two"} //這裏 修改1:"one" 爲 1:"1"
    fmt.Printf("The element is %q.\n", container[1])
}

輸出是1,說明代碼執行使用的內層{ }代碼塊中的變量。

go run demo11.go 
The element is "1".

七、怎樣在打印其中元素以前,正確判斷變量container的類型?

答案是使用「類型斷言」表達式。具體怎麼寫呢?

value, ok := interface{}(container).([]string)

學習筆記-go程序實體

賦值語句的賦值符號的右邊,是一個類型斷言表達式,它包括了用來把container變量的值轉換爲空接口值的interface{}(container)。以及一個用於判斷前者的類型是否爲切片類型 []string 的 .([]string)。

這個表達式的結果能夠被賦給兩個變量,在這裏由value和ok表明。變量ok是布爾(bool)類型的,它將表明類型判斷的結果,true或false。

若是是true,那麼被判斷的值將會被自動轉換爲[]string類型的值,並賦給變量value,不然value將被賦予nil(即「空」)。

順便提一下,這裏的ok也能夠沒有。也就是說,類型斷言表達式的結果,能夠只被賦給一個變量,在這裏是value。可是這樣的話,當判斷爲否時就會引起異常。

類型斷言表達式的語法形式是x.(T)。其中的x表明要被判斷類型的值。這個值當下的類型必須是接口類型的,不過具體是哪一個接口類型實際上是無所謂的。因此,當這裏的container變量類型不是任何的接口類型時,咱們就須要先把它轉成某個接口類型的值。

若是container是某個接口類型的,那麼這個類型斷言表達式就能夠是container.([]string)。這樣看是否是清晰一些了?

interface{}表明空接口,任何類型都是它的實現類型。我在下個模塊,會再講接口及其實現類型的問題。如今你只要知道,任何類型的值均可以很方便地被轉換成空接口的值就好了。

你可能會對這裏的{}產生疑惑,爲何在關鍵字interface的右邊還要加上這個東西?

請記住,一對不包裹任何東西的花括號,除了能夠表明空的代碼塊以外,還能夠用於表示不包含任何內容的數據結構(或者說數據類型)。

好比你從此確定會遇到的struct{},它就表明了不包含任何字段和方法的、空的結構體類型。而空接口interface{}則表明了不包含任何方法定義的、空的接口類型。固然了,對於一些集合類的數據類型來講,{}還能夠用來表示其值不包含任何元素,好比空的切片值[]string{},以及空的字典值map[int]string{}。

最右邊看。圓括號中[]string是一個類型字面量。所謂類型字面量,就是用來表示數據類型自己的若干個字符。

好比,string是表示字符串類型的字面量,uint8是表示 8 位無符號整數類型的字面量。

再複雜一些的就是咱們剛纔提到的[]string,用來表示元素類型爲string的切片類型,以及map[int]string,用來表示鍵類型爲int、值類型爲string的字典類型。

八、 你認爲類型轉換規則中有哪些值得注意的地方?

首先,對於整數類型值、整數常量之間的類型轉換,原則上只要源值在目標類型的可表示範圍內就是合法的。好比,之因此uint8(255)能夠把無類型的常量255轉換爲uint8類型的值,是由於255在 [0, 255] 的範圍內。但須要特別注意的是,源整數類型的可表示範圍較大,而目標類型的可表示範圍較小的狀況,好比把值的類型從int16轉換爲int8。請看下面這段代碼:

var srcInt = int16(-255)
dstInt := int8(srcInt)

變量srcInt的值是int16類型的-255,而變量dstInt的值是由前者轉換而來的,類型是int8。int16類型的可表示範圍可比int8類型大了很多。
問題是,dstInt的值是多少?首先你要知道,整數在 Go 語言以及計算機中都是以補碼的形式存儲的。這主要是爲了簡化計算機對整數的運算過程。補碼其實就是原碼各位求反再加 1。好比,int16類型的值-255的補碼是1111111100000001。若是咱們把該值轉換爲int8類型的值,那麼 Go 語言會把在較高位置(或者說最左邊位置)上的 8 位二進制數直接截掉,從而獲得00000001。又因爲其最左邊一位是0,表示它是個正整數,以及正整數的補碼就等於其原碼,因此dstInt的值就是1。
必定要記住,當整數值的類型的有效範圍由寬變窄時,只需在補碼形式下截掉必定數量的高位二進制數便可。
相似的快刀斬亂麻規則還有:當把一個浮點數類型的值轉換爲整數類型值時,前者的小數部分會被所有截掉。

第二,雖然直接把一個整數值轉換爲一個string類型的值是可行的,但值得關注的是,被轉換的整數值應該能夠表明一個有效的 Unicode 代碼點,不然轉換的結果將會是"�"(僅由高亮的問號組成的字符串值)。

字符'�'的 Unicode 代碼點是U+FFFD。它是 Unicode 標準中定義的 Replacement Character,專用於替換那些未知的、不被承認的以及沒法展現的字符。我確定不會去問「哪一個整數值轉換後會獲得哪一個字符串」,這太變態了!可是我會寫下:

string(-1)

並詢問會獲得什麼?這但是徹底不一樣的問題啊。因爲-1確定沒法表明一個有效的 Unicode 代碼點,因此獲得的總會是"�"。在實際工做中,咱們在排查問題時可能會遇到�,你須要知道這多是因爲什麼引發的。

第三個知識點是關於string類型與各類切片類型之間的互轉的。
你先要理解的是,一個值在從string類型向[]byte類型轉換時表明着以 UTF-8 編碼的字符串會被拆分紅零散、獨立的字節。除了與 ASCII 編碼兼容的那部分字符集,以 UTF-8 編碼的某個單一字節是沒法表明一個字符的。

string([]byte{'\xe4', '\xbd', '\xa0', '\xe5', '\xa5', '\xbd'}) // 你好

好比,UTF-8 編碼的三個字節\xe四、\xbd和\xa0合在一塊兒才能表明字符'你',而\xe五、\xa5和\xbd合在一塊兒才能表明字符'好'。

其次,一個值在從string類型向[]rune類型轉換時表明着字符串會被拆分紅一個個 Unicode 字符。

string([]rune{'\u4F60', '\u597D'}) // 你好

當你真正理解了 Unicode 標準及其字符集和編碼方案以後,上面這些內容就會顯得很容易了。什麼是 Unicode 標準?我會首先推薦你去它的http://www.unicode.org/ 官方網站一探究竟。

九、什麼是別名類型?什麼是潛在類型?

咱們能夠用關鍵字type聲明自定義的各類類型。固然了,這些類型必須在 Go 語言基本類型和高級類型的範疇以內。在它們當中,有一種被叫作「別名類型」的類型。咱們能夠像下面這樣聲明它:

type MyString = string

這條聲明語句表示,MyString是string類型的別名類型。顧名思義,別名類型與其源類型的區別恐怕只是在名稱上,它們是徹底相同的。源類型與別名類型是一對概念,是兩個對立的稱呼。別名類型主要是爲了代碼重構而存在的

Go 語言內建的基本類型中就存在兩個別名類型。byte是uint8的別名類型,而rune是int32的別名類型。

必定要注意,若是我這樣聲明:

type MyString2 string // 注意,這裏沒有等號。

MyString2和string就是兩個不一樣的類型了。這裏的MyString2是一個新的類型,不一樣於其餘任何類型。這種方式也能夠被叫作對類型的再定義。咱們剛剛把string類型再定義成了另一個類型MyString2。
學習筆記-go程序實體
對於這裏的類型再定義來講,string能夠被稱爲MyString2的潛在類型。潛在類型的含義是,某個類型在本質上是哪一個類型。
潛在類型相同的不一樣類型的值之間是能夠進行類型轉換的。所以,MyString2類型的值與string類型的值可使用類型轉換表達式進行互轉。

但對於集合類的類型[]MyString2與[]string來講這樣作倒是不合法的,由於[]MyString2與[]string的潛在類型不一樣,分別是[]MyString2和[]string。另外,即便兩個不一樣類型的潛在類型相同,它們的值之間也不能進行判等或比較,它們的變量之間也不能賦值。

package main

import (
    "fmt"
)

var container = []string{"zero", "one", "two"}

func main() {
    container := map[int]string{0: "zero", 1: "one", 2: "two"}

    // 方式1。
    _, ok1 := interface{}(container).([]string)
    _, ok2 := interface{}(container).(map[int]string)
    if !(ok1 || ok2) {
        fmt.Printf("Error: unsupported container type: %T\n", container)
        return
    }
    fmt.Printf("The element is %q. (container type: %T)\n",
        container[1], container)

    // 方式2。
    elem, err := getElement(container)
    if err != nil {
        fmt.Printf("Error: %s\n", err)
        return
    }
    fmt.Printf("The element is %q. (container type: %T)\n",
        elem, container)
}

func getElement(containerI interface{}) (elem string, err error) {
    switch t := containerI.(type) {
    case []string:
        elem = t[1]
    case map[int]string:
        elem = t[1] //這裏若是改成t[0],輸出是zero
    default:
        err = fmt.Errorf("unsupported container type: %T", containerI)
        return
    }
    return
}
go run demo12.go 
The element is "one". (container type: map[int]string)
The element is "one". (container type: map[int]string)
package main

import (
    "fmt"
)

func main() {
    // 重點1的示例。
    var srcInt = int16(-255)
    // 請注意,之因此要執行uint16(srcInt),是由於只有這樣才能獲得全二進制的表示。
    // 例如,fmt.Printf("%b", srcInt)將打印出"-11111111",後者是負數符號再加上srcInt的絕對值的補碼。
    // 而fmt.Printf("%b", uint16(srcInt))纔會打印出srcInt原值的補碼"1111111100000001"。
    fmt.Printf("The complement of srcInt: %b (%b)\n",
        uint16(srcInt), srcInt)
    dstInt := int8(srcInt)
    fmt.Printf("The complement of dstInt: %b (%b)\n",
        uint8(dstInt), dstInt)
    fmt.Printf("The value of dstInt: %d\n", dstInt)
    fmt.Println()

    // 重點2的示例。
    fmt.Printf("The Replacement Character: %s\n", string(-1))
    fmt.Printf("The Unicode codepoint of Replacement Character: %U\n", '�')
    fmt.Println()

    // 重點3的示例。
    srcStr := "你好"
    fmt.Printf("The string: %q\n", srcStr)
    fmt.Printf("The hex of %q: %x\n", srcStr, srcStr)
    fmt.Printf("The byte slice of %q: % x\n", srcStr, []byte(srcStr))
    fmt.Printf("The string: %q\n", string([]byte{'\xe4', '\xbd', '\xa0', '\xe5', '\xa5', '\xbd'}))
    fmt.Printf("The rune slice of %q: %U\n", srcStr, []rune(srcStr))
    fmt.Printf("The string: %q\n", string([]rune{'\u4F60', '\u597D'}))
}
go run demo13.go 
The complement of srcInt: 1111111100000001 (-11111111)
The complement of dstInt: 1 (1)
The value of dstInt: 1

The Replacement Character: �
The Unicode codepoint of Replacement Character: U+FFFD

The string: "你好"
The hex of "你好": e4bda0e5a5bd
The byte slice of "你好": e4 bd a0 e5 a5 bd
The string: "你好"
The rune slice of "你好": [U+4F60 U+597D]
The string: "你好"
package main

import "fmt"

func main() {
    // 示例1。
    {
        type MyString = string
        str := "BCD"
        myStr1 := MyString(str)
        myStr2 := MyString("A" + str)
        fmt.Printf("%T(%q) == %T(%q): %v\n",
            str, str, myStr1, myStr1, str == myStr1)
        fmt.Printf("%T(%q) > %T(%q): %v\n",
            str, str, myStr2, myStr2, str > myStr2)
        fmt.Printf("Type %T is the same as type %T.\n", myStr1, str)

        strs := []string{"E", "F", "G"}
        myStrs := []MyString(strs)
        fmt.Printf("A value of type []MyString: %T(%q)\n",
            myStrs, myStrs)
        fmt.Printf("Type %T is the same as type %T.\n", myStrs, strs)
        fmt.Println()
    }
    // 示例2。
    {
        type MyString string
        str := "BCD"
        myStr1 := MyString(str)
        myStr2 := MyString("A" + str)
        _ = myStr2
        //fmt.Printf("%T(%q) == %T(%q): %v\n",
        //  str, str, myStr1, myStr1, str == myStr1) // 這裏的判等不合法,會引起編譯錯誤。
        //fmt.Printf("%T(%q) > %T(%q): %v\n",
        //  str, str, myStr2, myStr2, str > myStr2) // 這裏的比較不合法,會引起編譯錯誤。
        fmt.Printf("Type %T is different from type %T.\n", myStr1, str)

        strs := []string{"E", "F", "G"}
        var myStrs []MyString
        //myStrs := []MyString(strs) // 這裏的類型轉換不合法,會引起編譯錯誤。
        //fmt.Printf("A value of type []MyString: %T(%q)\n",
        //  myStrs, myStrs)
        fmt.Printf("Type %T is different from type %T.\n", myStrs, strs)
        fmt.Println()
    }
    // 示例3。
    {
        type MyString1 = string
        type MyString2 string
        str := "BCD"
        myStr1 := MyString1(str)
        myStr2 := MyString2(str)
        myStr1 = MyString1(myStr2)
        myStr2 = MyString2(myStr1)

        myStr1 = str
        //myStr2 = str // 這裏的賦值不合法,會引起編譯錯誤。
        //myStr1 = myStr2 // 這裏的賦值不合法,會引起編譯錯誤。
        //myStr2 = myStr1 // 這裏的賦值不合法,會引起編譯錯誤。
    }
}
go run demo14.go 
string("BCD") == string("BCD"): true
string("BCD") > string("ABCD"): true
Type string is the same as type string.
A value of type []MyString: []string(["E" "F" "G"])
Type []string is the same as type []string.

Type main.MyString is different from type string.
Type []main.MyString is different from type []string.