Go 語言中的程序實體包括:變量、常量、函數、結構體和接口。
Go 語言是靜態類型的編程語言,所以我們在聲明變量或常量的時候,都需要指定它們的類型,或者給予足夠的信息,這樣才可以讓 Go 語言能夠推導出它們的類型,在 Go 語言中,變量的類型可以是其預定義的那些類型,也可以是程序自定義的函數、結構體或接口。常量的合法類型不多,只能是那些 Go 語言預定義的基本類型。它的聲明方式也更簡單一些。
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]處代碼合并的準備工作。
注意,flag.String函數返回的結果值的類型是string而不是string。類型string代表的是字符串的指針類型,而不是字符串類型。因此,這里的變量name代表的是一個指向字符串值的指針。
我們可以通過操作符把這個指針指向的字符串值取出來了。因此,在這種情況下,那個被用來打印內容的函數調用就需要微調一下,把其中的參數name改為name,即:fmt.Printf("Hello, %v!\n", *name)。
第二種方式與第一種方式非常類似,它基于第一種方式的代碼,賦值符號=右邊的代碼不動,左邊只留下name,再把=變成:=
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 語言整體的風格。
它只能用于對變量或常量的初始化,就像上述回答中描述的那樣。對flag.String函數的調用其實就是一個調用表達式,而這個表達式的類型是*string,即字符串的指針類型。
這也是調用flag.String函數后得到結果的類型。隨后,Go 語言把這個調用了flag.String函數的表達式類型,直接作為了變量name的類型,這就是“推斷”一詞所指代的操作了。
name := flag.String("name", "everyone", "The greeting object.")
至于第二種方式所用的短變量聲明,實際上就是 Go 語言的類型推斷再加上一點點語法糖。
我們只能在函數體內部使用短變量聲明
。在編寫if、for或switch語句的時候,我們經常把它安插在初始化子句中,并用來聲明一些臨時的變量。而相比之下,第一種方式更加通用,它可以被用在任何地方。
先看一段代碼:
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函數中寫一對緊挨著的花括號算不算一個代碼塊?當然也算,這甚至還有個名詞,叫“空代碼塊”。
變量重聲明的前提條件如下:
變量重聲明其實算是一個語法糖(或者叫便利措施)。它允許我們在使用短變量聲明時不用理會被賦值的多個變量中是否包含舊變量??梢韵胂?,如果不這樣會多寫不少代碼。
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 語言的語法。
(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".
答案是使用“類型斷言”表達式。具體怎么寫呢?
value, ok := interface{}(container).([]string)
賦值語句的賦值符號的右邊,是一個類型斷言表達式,它包括了用來把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{}代表空接口,任何類型都是它的實現類型。我在下個模塊,會再講接口及其實現類型的問題?,F在你只要知道,任何類型的值都可以很方便地被轉換成空接口的值就行了。
你可能會對這里的{}產生疑惑,為什么在關鍵字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 編碼的三個字節\xe4、\xbd和\xa0合在一起才能代表字符'你',而\xe5、\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。
對于這里的類型再定義來說,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.
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。