Go 語言的基本數據類型
0)變量聲明
var 變量名字 類型 = 表達式
例:
var num int = 10
其中“類型”或“= 表達式”兩個部分可以省略其中的一個。
- 1)根據初始化表達式來推導類型信息
- 2)默認值初始化為0。
例:
var num int // var num int = 0 var num = 10 // var num int = 10
1)整型
1.1)整型類型
類型名稱 | 有無符號 | bit數 |
---|---|---|
int8 | Yes | 8 |
int16 | Yes | 16 |
int32 | Yes | 32 |
int64 | Yes | 64 |
uint8 | No | 8 |
uint16 | No | 16 |
uint32 | No | 32 |
uint64 | No | 64 |
int | Yes | 等於cpu位數 |
uint | No | 等於cpu位數 |
rune | Yes | 與 int32 等價 |
byte | No | 與 uint8 等價 |
uintptr | No | - |
rune 類型是 Unicode 字符類型,和 int32 類型等價,通常用於表示一個 Unicode 碼點。rune 和 int32 可以互換使用。
byte 是uint8類型的等價類型,byte類型一般用於強調數值是一個原始的數據而不是 一個小的整數。
uintptr 是一種無符號的整數類型,沒有指定具體的bit大小但是足以容納指針。 uintptr類型只有在底層編程是才需要,特別是Go語言和C語言函數庫或操作系統接口相交互的地方。
不管它們的具體大小,int、uint和uintptr是不同類型的兄弟類型。其中int和int32也是 不同的類型, 即使int的大小也是32bit,在需要將int當作int32類型的地方需要一個顯式 的類型轉換操作,反之亦然。
有符號整數采用 2 的補碼形式表示,也就是最高 bit 位用作表示符號位,一個 n bit 的有 符號數的值域是從 -2^{n-1} 到 2^{n-1}−1。例如,int8類型整數的值域是從-128 到 127, 而uint8類型整數的值域是從0到255。
1.2)整型運算
二元運算符:算術運算、邏輯運算和比較運算,運算符優先級從上到下遞減順序排列
* / % << >> & &^ + - | ^ == != < <= > >= && ||
在同一個優先級,使用左優先結合規則,但是使用括號可以明確優先順序。
算術運算符+、-、*和/可以適用與於整數、浮點數和復數,但是取模運算符%僅用於整數間的運算。 % 取模運算符的符號和被取模數的符號總是一致的。除法運算符/的行為則依賴於操作數是否 全為整數,比如5.0/4.0的結果是1.25,但是5/4的結果是1,因為整數除法會向着0方向截斷余數。
兩個相同的整數類型可以使用下面的二元比較運算符進行比較;比較表達式的結果是布爾類型。
== equal to != not equal to < less than <= less than or equal to > greater than >= greater than or equal to
個算術運算的結果,無論有無符號,超出的高位的bit位部分將被丟棄。如果原始的數值是有符號類型,而且最左邊 的bit為是1的話,那么最終結果可能是負的。
布爾型、數字類型和字符串等基本類型都是可比較的,也就是說兩個相同類型的值可以用 == 和 != 進行比較。
一元的加法和減法運算符:
+ 一元加法 (無效果)
- 負數
bit位操作運算符:
符號 | 操作 | 操作數是否區分符號 |
---|---|---|
& | 位運算 AND | No |
| | 位運算 OR | No |
^ | 位運算 XOR | No |
&^ | 位清空 (AND NOT) | No |
<< | 左移 | Yes |
>> | 右移 | Yes |
注意 位操作運算符^作為二元運算符時是按位異或(XOR),當用作一元運算符時表示按位取反。
位操作運算符&^用於按位置零(AND NOT):對於表達式z = x &^ y, 如果對應y中某位bit位為 0 的話,結果z的對應的bit位等於x相應的bit位的值,否則 z 對應的bit位為0。
x << n 和x >> n 的右操作數(n)必須為無符號數。
操作 | 含義 | -- |
---|---|---|
<< | 左移 | 左移運算用零填充右邊空缺的bit位 |
>> | 右移 | 無符號數的右移運算用0填充左邊空缺的bit位,有符號數的右移運算用符號位的值填充左邊空缺的bit位 |
一般來說,需要一個顯式的轉換將一個值從一種類型轉化位另一種類型,並且算術和邏輯運算的二元操 作中必須是相同的類型。雖然這偶爾會導致需要很長的表達式,但是它消除了所有和類型相關的問題, 而且也使得程序容易理解。
許多整形數之 間的相互轉換並不會改變數值;它們只是告訴編譯器如何解釋這個值。但是對於將一個大尺寸的整數類 型轉為一個小尺寸的整數類型,或者是將一個浮點數轉為整數,可能會改變數值或丟失精度。 浮點數到整數的轉換將丟失任何小數部分,然后向數軸零方向截斷。
任何大小的整數字面值都可以用以0開始的八進制格式書寫,例如0666;或用以0x或0X開頭的十六進制格 式書寫,例如0xdeadbeef。十六進制數字可以用大寫或小寫字母。
1.3)浮點數
Go語言提供了兩種精度的浮點數,float32和float64。它們的算術規范由IEEE754浮點數國際標准定義, 該浮點數規范被所有現代的CPU支持。
這些浮點數類型的取值范圍可以從很微小到很巨大。浮點數的范圍極限值可以在math包找到。常量 math.MaxFloat32表示float32能表示的最大數值,大約是 3.4e38;對應的math.MaxFloat64常量大約是 1.8e308。它們分別能表示的最小值近似為1.4e-45和4.9e-324。
一個float32類型的浮點數可以提供大約6個十進制數的精度,而float64則可以提供約15個十進制數的精度。
函數math.IsNaN用於測試一個數是否是非數NaN,math.NaN則返回非數對應的值。雖然可以用math.NaN來 表示一個非法的結果,但是測試一個結果是否是非數NaN則是充滿風險的,因為NaN和任何數都是不相等的。 如果一個函數返回的浮點數結果可能失敗,最好的做法是用單獨的標志報告失敗。
nan := math.NaN() fmt.Println(nan == nan, nan < nan, nan > nan) // "false false false"
1.4)復數
Go語言提供了兩種精度的復數類型:complex64和complex128,分別對應float32和float64兩種浮點數精度。內置的complex函數用於構建復數,內建的real和imag函數分別返回復數的實部和虛部。
z := x + yi x = real(z) y = imag(z)
復數也可以用==和!=進行相等比較。只有兩個復數的實部和虛部都相等的時候它們才是相等的。 math/cmplx包提供了復數處理的許多函數,例如求復數的平方根函數和求冪函數。
1.5)布爾型
一個布爾類型的值只有兩種:true和false。if和for語句的條件部分都是布爾類型的值,並且==和<等比 較操作也會產生布爾型的值。一元操作符!對應邏輯非操作,因此!true的值為false。
布爾值可以和&&(AND)和||(OR)操作符結合,並且可能會有短路行為:如果運算符左邊值已經可以確 定整個布爾表達式的值,那么運算符右邊的值將不在被求值
布爾值並不會隱式轉換為數字值0或1,反之亦然。必須使用一個顯式的if語句輔助轉換。
1.6)字符串
一個字符串是一個不可改變的字節序列。字符串可以包含任意的數據,包括byte值0,但是通常是用來包 含人類可讀的文本。文本字符串通常被解釋為采用UTF8編碼的Unicode碼點(rune)序列。
內置的len函數可以返回一個字符串中的字節數目(不是rune字符數目),索引操作s[i]返回第i個字節 的字節值,i必須滿足0 <= i < len(s)條件約束。如果試圖訪問超出字符串索引范圍的字節將會導致panic異常。
第i個字節並不一定是字符串的第i個字符,因為對於非ASCII字符的UTF8編碼會要兩個或多個字節。
子字符串 操作s[i:j]基於原始的s字符串的第i個字節開始到第j個字節(並不包含j本身)生成一個新字 符串。生成的新字符串將包含j-i個字節。不管i還是j都可能被忽略,當它們被忽略時將采用0作為開始位置,采用len(s)作為結束的位置。
其中+操作符將兩個字符串鏈接構造一個新字符串。
字符串可以用==和<進行比較;比較通過逐個字節比較完成的,因此比較的結果是字符串自然編碼的順 序。
字符串值也可以用字符串面值方式編寫,只要將一系列字節序列包含在雙引號即可。
字符串的值是不可變的:一個字符串包含的字節序列永遠不會被改變,當然我們也可以給一個字符串變 量分配一個新字符串值。
s := "left foot" t := s s += ", right foot"
這並不會導致原始的字符串值被改變,但是變量s將因為+=語句持有一個新的字符串值,但是t依然是包 含原先的字符串值。
在一個雙引號包含的字符串面值中,可以用以反斜杠\開頭的轉義序列插入任意的數據。
符號 | 含義 |
---|---|
\a | 響鈴 |
\b | 退格 |
\f | 換頁 |
\n | 換行 |
\r | 回車 |
\t | 制表符 |
\v | 垂直制表符 |
\' | 單引號 (只用在 '\'' 形式的rune符號面值中) |
\" | 雙引號 (只用在 "..." 形式的字符串面值中) |
\\ | 反斜杠 |
可以通過十六進制或八進制轉義在字符串面值包含任意的字節。一個十六進制的轉義形式是\xhh,其中兩個h表示十六進制數字(大寫或小寫都可以)。一個八進制轉義形式是\ooo,包含三個八進制的o數字(0到7),但是不能超過\377(\377為十進制的255)。每一個單一的字節表達一個特定的值。
原生字符串
一個原生的字符串面值形式是...,使用反引號代替雙引號。在原生的字符串面值中,沒有轉義操作;全部的內容都是字面的意思,包含退格和換行,因此一個程序中的原生字符串面值可能跨越多行。唯一的特殊處理是會刪除回車以保證在所有平台上的值都是一樣的。
Unicode
Unicode( http://unicode.org )收集了這個世界上所有的符號系統,包括重音符號和其它變音符號,制表符和回車符,還有很多神秘的符號,每個符號都分配一個唯一的Unicode碼點,Unicode碼點對應Go語言中的rune整數類型。 通用的表示一個Unicode碼點的數據類型是int32,也就是Go語言中rune對應的類型;它的同義詞rune符文正是這個意思。
UTF-8
UTF8是一個將Unicode碼點編碼為字節序列的變長編碼。UTF8編碼使用1到4個字節來表示每個Unicode碼點,ASCII部分字符只使用1個字節,常用字符部分使用2或3個字節表示。每個符號編碼后第一個字節的高端bit位用於表示總共有多少編碼個字節。如果第一個字節的高端bit為0,則表示對應7bit的ASCII字符,ASCII字符每個字符依然是一個字節,和傳統的ASCII編碼兼容。如果第一個字節的高端bit是110,則說明需要2個字節;后續的每個高端bit都以10開頭。更大的Unicode碼點也是采用類似的策略處理。
0xxxxxxx runes 0-127 (ASCII) 110xxxxx 10xxxxxx 128-2047 (values <128 unused) 1110xxxx 10xxxxxx 10xxxxxx 2048-65535 (values <2048 unused) 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx 65536-0x10ffff (other values unused)
Go語言的源文件采用UTF8編碼,並且Go語言處理UTF8編碼的文本也很出色。unicode包提供了諸多處理 rune字符相關功能的函數(比如區分字母和數組,或者是字母的大寫和小寫轉換等),unicode/utf8包 則提供了用於rune字符序列的UTF8編碼和解碼的功能。
Go語言字符串面值中的Unicode轉義字符讓我們可以通過Unicode碼點輸入特殊的字符。有兩種形式:\uhhhh對應16bit的碼點值,\Uhhhhhhhh對應32bit的碼點值,其中h是一個十六進制數字。每一個對應碼點的UTF8編碼。
例如:下面的字母串面值都表示相同的值:
"世界" "\xe4\xb8\x96\xe7\x95\x8c" "\u4e16\u754c" "\U00004e16\U0000754c"
Unicode轉義也可以使用在rune字符中。下面三個字符是等價的: '世' '\u4e16' '\U00004e16'
對於小於256碼點值可以寫在一個十六進制轉義字節中,例如'\x41'對應字符'A',但是對於更大的碼點則必須使用\u或\U轉義形式。因此,'\xe4\xb8\x96'並不是一個合法的rune字符,雖然這三個字節對應一個有效的UTF8編碼的碼點。
得益於UTF8編碼優良的設計,諸多字符串操作都不需要解碼操作。我們可以不用解碼直接測試一個字符串是否是另一個字符串的前綴、是后綴、或者是包含子串測試。對於UTF8編碼后文本的處理和原始的字節處理邏輯是一樣的。
每一個UTF8字符解碼,不管是顯式地調用utf8.DecodeRuneInString解碼或是在range循環中隱式地解碼,如果遇到一個錯誤的UTF8編碼輸入,將生成一個特別的Unicode字符'\uFFFD',在印刷中這個符號通常是一個黑色六角或鑽石形狀,里面包含一個白色的問號(?)。
字符串和Byte切片
標准庫中有四個包對字符串處理尤為重要:bytes、strings、strconv和unicode包。
strings包提供了許多如字符串的查詢、替換、比較、截斷、拆分和合並等功能。
bytes包也提供了很多類似功能的函數,但是針對和字符串有着相同結構的[]byte類型。 因為字符串是只讀的,因此逐步構建字符串會導致很多分配和復制。在這種情況下,使用 bytes.Buffer 類型將會更有效。
strconv包提供了布爾型、整型數、浮點數和對應字符串的相互轉換,還提供了雙引號轉義相關的轉換。
unicode包提供了IsDigit、IsLetter、IsUpper和IsLower等類似功能。
每個函數有一個單一的rune類型的參數,然后返回一個布爾值。而像ToUpper和ToLower之類的轉換函數將用於rune字符的大小寫轉換。所有的這些函數都是遵循Unicode標准定義的字母、數字等分類規范。 strings 包也有類似的函數,它們是ToUpper和ToLower,將原始字符串的每個字符都做相應的轉換,然后返回新的字符串。
一個字符串是包含的只讀字節數組,一旦創建,是不可變的。相比之下,一個字節slice的元素則可以自由地修改。
字符串和字節slice之間可以相互轉換:
s := "abc" b := []byte(s) s2 := string(b)
將一個字節slice轉到字符串的string(b)操作則是構造一個字符串拷貝,以確保s2字符串是只讀的。
為了避免轉換中不必要的內存分配,bytes包和strings同時提供了許多實用函數。下面是strings包中的 六個函數:
func Contains(s, substr string) bool func Count(s, sep string) int func Fields(s string) []string func HasPrefix(s, prefix string) bool func Index(s, sep string) int func Join(a []string, sep string) strings
bytes包中也對應的六個函數:
func Contains(b, subslice []byte) bool func Count(s, sep []byte) int func Fields(s []byte) [][]byte func HasPrefix(s, prefix []byte) bool func Index(s, sep []byte) int func Join(s [][]byte, sep []byte) []byte
它們之間唯一的區別是字符串類型參數被替換成了字節slice類型的參數。
bytes包還提供了Buffer類型用於字節slice的緩存。一個Buffer開始是空的,但是隨着string、byte或[]byte等類型數據的寫入可以動態增長,一個bytes.Buffer變量並不需要初始化,因為零值也是有效的。
當向bytes.Buffer添加任意字符的UTF8編碼時,最好使用bytes.Buffer的WriteRune方法,但是 WriteByte方法對於寫入類似'['和']'等ASCII字符效率會更高。
字符串和數字的轉換
除了字符串、字符、字節之間的轉換,字符串和數值之間的轉換也比較常見。由strconv包提供這類轉換功能。
幾個常用函數:
函數名 | 功能 |
---|---|
strconv.Itoa() | 整數到ASCII |
strconv.FormatInt() | 用不同的進制格式化數字 |
strconv.FormatUint() | 用不同的進制格式化數字 |
strconv.Atoi() | 將一個字符串解析為整數 |
strconv.ParseInt() | 將一個字符串解析為整數 |
注:
ParseInt函數的第三個參數是用於指定整型數的大小;例如16表示int16,0則表示int。在任何情況下, 返回的結果y總是int64類型,你可以通過強制類型轉換將它轉為更小的整數類型。
有時候也會使用fmt.Scanf來解析輸入的字符串和數字,特別是當字符串和數字混合在一行的時候,它可 以靈活處理不完整或不規則的輸入。
1.7)常量
常量表達式的值在編譯期計算,而不是在運行期。每種常量的潛在類型都是基礎類型:boolean、string 或數字。 一個常量的聲明語句定義了常量的名字,和變量的聲明語法類似,常量的值不可修改,這樣可以防止在 運行期被意外或惡意的修改。
和變量聲明一樣,可以批量聲明多個常量;這比較適合聲明一組相關的常量:
const ( e = 2.71828182845904523536028747135266249775724709369995957496696763 pi = 3.14159265358979323846264338327950288419716939937510582097494459 )
常量間的所有算術運算、邏輯運算和比較運算的結果也是常量,對常量的類型轉換操作或以下函數調用 都是返回常量結果:len、cap、real、imag、complex和unsafe.Sizeof。
因為它們的值是在編譯期就確定的,因此常量可以是構成類型的一部分,例如用於指定數組類型的長 度:
const IPv4Len = 4 // parseIPv4 parses an IPv4 address (d.d.d.d). func parseIPv4(s string) IP { var p [IPv4Len]byte // ... }
一個常量的聲明也可以包含一個類型和一個值,但是如果沒有顯式指明類型,那么將從右邊的表達式推斷類型。
iota 常量生成器
iota常量生成器初始化,它用於生成一組以相似規則初始化的常量,但是不用每行都 寫一遍初始化表達式。在一個const聲明語句中,在第一個聲明的常量所在的行,iota將會被置為0,然 后在每一個有常量聲明的行加一。
type Weekday int const ( Sunday Weekday = iota Monday Tuesday Wednesday Thursday Friday Saturday )
或者下面的例子:
type Flags uint const ( FlagUp Flags = 1 << iota // is up FlagBroadcast // supports broadcast access capability FlagLoopback // is a loopback interface FlagPointToPoint // belongs to a point-to-point link FlagMulticast // supports multicast access capability )
隨着iota的遞增,每個常量對應表達式1 << iota,是連續的2的冪,分別對應一個bit位置。
無類型常量
許多常量並沒有一個明確的基礎類型。編譯器為這些沒有明確的基礎類型的數字常量提供比基礎類型更高精度的算術運算;你可以認為至少有256bit的運算精度。這里有六種未明確類型的常量類型,分別是無類型的布爾型、無類型的整數、無類型的字符、無類型的浮點數、無類型的復數、無類型的字符串。
通過延遲明確常量的具體類型,無類型的常量不僅可以提供更高的運算精度,而且可以直接用於更多的 表達式而不需要顯式的類型轉換。
只有常量可以是無類型的。當一個無類型的常量被賦值給一個變量的時候,或者是語句中右邊表達式含有明確類型的值,如果轉換合法的話,無類型的常量將會被隱式轉換為對應的類型。
無論是隱式或顯式轉換,將一種類型轉換為另一種類型都要求目標可以表示原始值。對於浮點數和復數,可能會有舍入處理。
對於一個沒有顯式類型的變量聲明語法(包括短變量聲明語法),無類型的常量會被隱式轉為默認的變量類型。
注意默認類型是規則的:無類型的整數常量默認轉換為int,對應不確定的內存大小,但是浮點數和復數常量則默認轉換為float64和complex128。Go語言本身並沒有不確定內存大小的浮點數和復數類型,而且如果不知道浮點數類型的話將很難寫出正確的數值算法。
如果要給變量一個不同的類型,我們必須顯式地將無類型的常量轉化為所需的類型,或給聲明的變量指 定明確的類型。
參考:《Go語言程序設計》(The Go Programming Language)