您好,登錄后才能下訂單哦!
今天小編給大家分享一下Golang無類型常量問題怎么解決的相關知識點,內容詳細,邏輯清晰,相信大部分人都還太了解這方面的知識,所以分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后有所收獲,下面我們一起來了解一下吧。
這個問題是很常見的:
package main import "fmt" type S string const ( A S = "a" B = "b" C = "c" ) func output(s S) { fmt.Println(s) } func main() { output(A) output(B) output(C) }
這段代碼能正常編譯并運行,能有什么問題?這里我就要提示你一下了,B和C的類型是什么?
你會說他們都是S類型,那你就犯了第一個錯誤,我們用發射看看:
fmt.Println(reflect.TypeOf(any(A))) fmt.Println(reflect.TypeOf(any(B))) fmt.Println(reflect.TypeOf(any(C)))
輸出是:
main.S
string
string
驚不驚喜意不意外,常量的類型是由等號右邊的值推導出來的(iota是例外,但只能處理整型相關的),除非你顯式指定了類型。
所以在這里B和C都是string。
那真正的問題來了,正如我在這篇所說的,從原類型新定義的類型是獨立的類型,不能隱式轉換和賦值給原類型。
所以這樣的代碼就是錯的:
func output(s S) { fmt.Println(s) } func main() { var a S = "a" output(a) }
編譯器會報錯。然而我們最開始的復現代碼是沒有報錯的:
const ( A S = "a" B = "b" C = "c" ) func output(s S) { fmt.Println(s) }
output函數只接受S類型的值,但我們的B和C都是string類型的,為什么這里可以編譯通過還正常運行了呢?
這就要說到golang的坑點之一——無類型常量了。
這個好理解,定義常量時沒指定類型,那就是無類型常量,比如:
const ( A S = "a" B = "b" C = "c" )
這里A顯式指定了類型,所以不是無類型常量;而B和C沒有顯式指定類型,所以就是無類型常量(untyped constant)。
無類型常量有一些特性和其他有類型的常量以及變量不一樣,得單獨講講。
正如下面的代碼里我們看到的:
const ( A = "a" B = 1 C = 1.0 ) func main() { fmt.Println(reflect.TypeOf(any(A))) // string fmt.Println(reflect.TypeOf(any(B))) // int fmt.Println(reflect.TypeOf(any(C))) // float64 }
雖說我們沒給這些常量指定某個類型,但他們還是有自己的類型,和初始化他們的字面量的默認類型相應,比如整數字面量是int,字符串字面量是string等等。
但只有一種情況下他們才會表現出自己的默認類型,也就是在上下文中沒法推斷出這個常量現在應該是什么類型的時候,比如賦值給空接口。
這個名字不好,是我根據它的表現起的,官方的名字叫Representability,直譯過來是“代表性”。
看下這個例子:
const delta = 1 // untyped constant, default type is int var num int64 num += delta
如果我們把const換成var,代碼無法編譯,會爆出這種錯誤:invalid operation: num + delta (mismatched types int64 and int)。
但為什么常量可以呢?這就是Representability或者說類型自動匹配在搗鬼。
按照官方的解釋:如果一個無類型常量的值是一個類型T的有效值,那么這個常量的類型就可以是類型T。
舉個例子,int8類型的所有合法的值是[-128, 127),那么只要值在這個范圍內的整數常量,都可以被轉換成int8。
字符串類型同理,所有用字符串初始化的無類型常量都可以轉換成字符串以及那些基于字符串創建的新類型。
這就解釋了開頭那段代碼為什么沒問題:
type S string const ( A S = "a" B = "b" C = "c" ) func output(s S) { fmt.Println(s) } func main() { output(A) // A 本來就是 S,自然沒問題 output(B) // B 是無類型常量,默認類型string,可以表示成 S,沒問題 output(C) // C 是無類型常量,默認類型string,可以表示成 S,沒問題 // 下面的是有問題的,因為類型自動匹配不會發生在無類型常量和字面量以外的地方 // s := "string" // output(s) }
也就是說,在有明確給出類型的上下文里,無類型常量會嘗試去匹配那個目標類型T,如果常量的值符合目標類型的要求,常量的類型就會變成目標類型T。例子里的delta的類型就會自動變成int64類型。
我沒有去找為什么golang會這么設計,在c++、rust和Java里常量的類型就是從初始化表達式推導或顯式指定的那個類型。
一個猜測是golang的設計初衷想讓常量的行為表現和字面量一樣。除了兩者都有的類型自動匹配,另一個有力證據是golang里能作為常量的只有那些能做字面類型的類型(字符串、整數、浮點數、復數)。
無類型常量的類型自動匹配會帶來很有限的好處,以及很惡心的坑。
便利只有一個,可以少些幾次類型轉換,考慮下面的例子:
const factor = 2 var result int64 = int64(num) * factor / ( (a + b + c) / factor )
這樣復雜的計算表達式在數據分析和圖像處理的代碼里是很常見的,如果我們沒有自動類型匹配,那么就需要顯式轉換factor的類型,光是想想就覺得煩人,所以我也就不寫顯式類型轉換的例子了。
有了無類型常量,這種表達式的書寫就沒那么折磨了。
說完聊勝于無的好處,下面來看看坑。
一種常見的在golang中模擬enum的方法如下:
type ConfigType string const ( CONFIG_XML ConfigType = "XML" CONFIG_JSON = "JSON" )
發現上面的問題了嗎,沒錯,只有CONFIG_XML是ConfigType類型的!
但因為無類型常量有自動類型匹配,所以你的代碼目前為止運行起來一點問題也沒有,這也導致你沒發現這個缺陷,直到:
// 給enum加個方法,現在要能獲取常量的名字,以及他們在配置數組里的index type ConfigType string func (c ConfigType) Name() string { switch c { case CONFIG_XML: return "XML" case CONFIG_JSON: return "JSON" } return "invalid" } func (c ConfigType) Index() int { switch c { case CONFIG_XML: return 0 case CONFIG_JSON: return 1 } return -1 }
目前為止一切安好,然后代碼炸了:
fmt.Println(CONFIG_XML.Name()) fmt.Println(CONFIG_JSON.Name()) // !!! error
編譯器不樂意,它說:CONFIG_JSON.Name undefined (type untyped string has no field or method Name)。
為什么呢,因為上下文里沒明確指定類型,fmt.Println的參數要求都是any,所以這里用了無類型常量的默認類型。當然在其他地方也一樣,CONFIG_JSON.Name()這個表達式是無法推斷出CONFIG_JSON要匹配成什么類型的。
這一切只是因為你少寫了一個類型。
這還只是第一個坑,實際上因為只要是目標類型可以接受的值,就可以賦值給目標類型,那么出現這種代碼也不奇怪:
const NET_ERR_MESSAGE = "site is unreachable" func doWithConfigType(t ConfigType) doWithConfigType(CONFIG_JSON) doWithConfigType(NET_ERR_MESSAGE) // WTF???
一不小心就能把錯得離譜的參數傳進去,如果你沒想到這點而做好防御的話,生產事故就理你不遠了。
第一個坑還可以通過把常量定義寫全每個都加上類型來避免,第二個就只能靠防御式編程湊活了。
以上就是“Golang無類型常量問題怎么解決”這篇文章的所有內容,感謝各位的閱讀!相信大家閱讀完這篇文章都有很大的收獲,小編每天都會為大家更新不同的知識,如果還想學習更多的知識,請關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。