生活随笔
收集整理的這篇文章主要介紹了
《Go语言圣经》学习笔记 第三章 基础数据类型
小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.
《Go語(yǔ)言圣經(jīng)》學(xué)習(xí)筆記 第三章 基礎(chǔ)數(shù)據(jù)類型
目錄
整型浮點(diǎn)數(shù)復(fù)數(shù)布爾型字符串常量
注:學(xué)習(xí)《Go語(yǔ)言圣經(jīng)》筆記,PDF點(diǎn)擊下載,建議看書。
Go語(yǔ)言小白學(xué)習(xí)筆記,書上的內(nèi)容照搬,大佬看了勿噴,以后熟悉了會(huì)總結(jié)成自己的讀書筆記。
1. 整型
Go語(yǔ)言的數(shù)值類型包括幾種不同大小的整形數(shù)、 浮點(diǎn)數(shù)和復(fù)數(shù)。 每種數(shù)值類型都決定了對(duì)應(yīng)的大小范圍和是否支持正負(fù)符號(hào)。 讓我們先從整形數(shù)類型開始介紹。Go語(yǔ)言同時(shí)提供了有符號(hào)和無(wú)符號(hào)類型的整數(shù)運(yùn)算。 這里有int8、 int16、 int32和int64四種截然不同大小的有符號(hào)整形數(shù)類型, 分別對(duì)應(yīng)8、 16、 32、 64bit大小的有符號(hào)整形數(shù), 與此對(duì)應(yīng)的是uint8、 uint16、 uint32和uint64四種無(wú)符號(hào)整形數(shù)類型。這里還有兩種一般對(duì)應(yīng)特定CPU平臺(tái)機(jī)器字大小的有符號(hào)和無(wú)符號(hào)整數(shù)int和uint; 其中int是應(yīng)用最廣泛的數(shù)值類型。 這兩種類型都有同樣的大小, 32或64bit, 但是我們不能對(duì)此做任何的假設(shè); 因?yàn)椴煌木幾g器即使在相同的硬件。Unicode字符rune類型是和int32等價(jià)的類型, 通常用于表示一個(gè)Unicode碼點(diǎn)。 這兩個(gè)名稱可以互換使用。 同樣byte也是uint8類型的等價(jià)類型, byte類型一般用于強(qiáng)調(diào)數(shù)值是一個(gè)原始的數(shù)據(jù)而不是一個(gè)小的整數(shù)。最后, 還有一種無(wú)符號(hào)的整數(shù)類型uintptr, 沒有指定具體的bit大小但是足以容納指針。 uintptr類型只有在底層編程是才需要, 特別是Go語(yǔ)言和C語(yǔ)言函數(shù)庫(kù)或操作系統(tǒng)接口相交互的地方。 我們將在第十三章的unsafe包相關(guān)部分看到類似的例子。不管它們的具體大小, int、 uint和uintptr是不同類型的兄弟類型。 其中int和int32也是不同的類型, 即使int的大小也是32bit, 在需要將int當(dāng)作int32類型的地方需要一個(gè)顯式的類型轉(zhuǎn)換操作, 反之亦然。其中有符號(hào)整數(shù)采用2的補(bǔ)碼形式表示, 也就是最高bit位用作表示符號(hào)位, 一個(gè)n-bit的有符號(hào)數(shù)的值域是從?2^(n-1) 到2^(n-1) ? 1。 無(wú)符號(hào)整數(shù)的所有bit位都用于表示非負(fù)數(shù), 值域是0到2^(n-1) ? 1。 例如, int8類型整數(shù)的值域是從-128到127, 而uint8類型整數(shù)的值域是從0到255。下面是Go語(yǔ)言中關(guān)于算術(shù)運(yùn)算、 邏輯運(yùn)算和比較運(yùn)算的二元運(yùn)算符, 它們按照先級(jí)遞減的順序的排列:
二元運(yùn)算符有五種優(yōu)先級(jí)。 在同一個(gè)優(yōu)先級(jí), 使用左優(yōu)先結(jié)合規(guī)則, 但是使用括號(hào)可以明確優(yōu)先順序, 使用括號(hào)也可以用于提升優(yōu)先級(jí), 例如 mask & (1 << 28) 。對(duì)于上表中前兩行的運(yùn)算符, 例如+運(yùn)算符還有一個(gè)與賦值相結(jié)合的對(duì)應(yīng)運(yùn)算符+=, 可以用于簡(jiǎn)化賦值語(yǔ)句。算術(shù)運(yùn)算符+、 -、 * 和 / 可以適用與于整數(shù)、 浮點(diǎn)數(shù)和復(fù)數(shù), 但是取模運(yùn)算符%僅用于整數(shù)間的運(yùn)算。 對(duì)于不同編程語(yǔ)言, %取模運(yùn)算的行為可能并不相同。 在Go語(yǔ)言中, %取模運(yùn)算符的符號(hào)和被取模數(shù)的符號(hào)總是一致的, 因此 -5%3 和 -5%-3 結(jié)果都是-2。 除法運(yùn)算符 / 的行為則依賴于操作數(shù)是否為全為整數(shù), 比如 5.0/4.0 的結(jié)果是1.25, 但是5/4的結(jié)果是1, 因?yàn)檎麛?shù)除法會(huì)向著0方向截?cái)嘤鄶?shù)。如果一個(gè)算術(shù)運(yùn)算的結(jié)果, 不管是有符號(hào)或者是無(wú)符號(hào)的, 如果需要更多的bit位才能正確表示的話, 就說(shuō)明計(jì)算結(jié)果是溢出了。 超出的高位的bit位部分將被丟棄。 如果原始的數(shù)值是有符號(hào)類型, 而且最左邊的bit為是1的話, 那么最終結(jié)果可能是負(fù)的, 例如int8的例子:
兩個(gè)相同的整數(shù)類型可以使用下面的二元比較運(yùn)算符進(jìn)行比較; 比較表達(dá)式的結(jié)果是布爾類
型。
事實(shí)上, 布爾型、 數(shù)字類型和字符串等基本類型都是可比較的, 也就是說(shuō)兩個(gè)相同類型的值可以用==和!=進(jìn)行比較。 此外, 整數(shù)、 浮點(diǎn)數(shù)和字符串可以根據(jù)比較結(jié)果排序。 許多其它類型的值可能是不可比較的, 因此也就可能是不可排序的。 對(duì)于我們遇到的每種類型, 我們要保證規(guī)則的一致性。這里是一元的加法和減法運(yùn)算符:
對(duì)于整數(shù), +x是0+x的簡(jiǎn)寫, -x則是0-x的簡(jiǎn)寫; 對(duì)于浮點(diǎn)數(shù)和復(fù)數(shù), +x就是x, -x則是x 的負(fù)數(shù)。Go語(yǔ)言還提供了以下的bit位操作運(yùn)算符, 前面4個(gè)操作運(yùn)算符并不區(qū)分是有符號(hào)還是無(wú)符號(hào)數(shù):
位操作運(yùn)算符 ^ 作為二元運(yùn)算符時(shí)是按位異或( XOR) , 當(dāng)用作一元運(yùn)算符時(shí)表示按位取反; 也就是說(shuō), 它返回一個(gè)每個(gè)bit位都取反的數(shù)。 位操作運(yùn)算符 &^ 用于按位置零( ANDNOT) : 表達(dá)式 z = x &^ y 結(jié)果z的bit位為0, 如果對(duì)應(yīng)y中bit位為1的話, 否則對(duì)應(yīng)的bit位等于x相應(yīng)的bit位的值。下面的代碼演示了如何使用位操作解釋uint8類型值的8個(gè)獨(dú)立的bit位。 它使用了Printf函數(shù)的%b參數(shù)打印二進(jìn)制格式的數(shù)字; 其中%08b中08表示打印至少8個(gè)字符寬度, 不足的前綴部分用0填充。
在 x<<n 和 x>>n 移位運(yùn)算中, 決定了移位操作bit數(shù)部分必須是無(wú)符號(hào)數(shù); 被操作的x數(shù)可以是有符號(hào)或無(wú)符號(hào)數(shù)。 算術(shù)上, 一個(gè) x<<n 左移運(yùn)算等價(jià)于乘以2 , 一個(gè) x>>n 右移運(yùn)算等價(jià)于除以2 。左移運(yùn)算用零填充右邊空缺的bit位, 無(wú)符號(hào)數(shù)的右移運(yùn)算也是用0填充左邊空缺的bit位, 但是有符號(hào)數(shù)的右移運(yùn)算會(huì)用符號(hào)位的值填充左邊空缺的bit位。 因?yàn)檫@個(gè)原因, 最好用無(wú)符號(hào)運(yùn)算, 這樣你可以將整數(shù)完全當(dāng)作一個(gè)bit位模式處理。盡管Go語(yǔ)言提供了無(wú)符號(hào)數(shù)和運(yùn)算, 即使數(shù)值本身不可能出現(xiàn)負(fù)數(shù)我們還是傾向于使用有符號(hào)的int類型, 就像數(shù)組的長(zhǎng)度那樣, 雖然使用uint無(wú)符號(hào)類型似乎是一個(gè)更合理的選擇。 事實(shí)上, 內(nèi)置的len函數(shù)返回一個(gè)有符號(hào)的int, 我們可以像下面例子那樣處理逆序循環(huán)。
另一個(gè)選擇對(duì)于上面的例子來(lái)說(shuō)將是災(zāi)難性的。 如果len函數(shù)返回一個(gè)無(wú)符號(hào)數(shù), 那么i也將是無(wú)符號(hào)的uint類型, 然后條件 i >= 0 則永遠(yuǎn)為真。 在三次迭代之后, 也就是 i == 0 時(shí), i–語(yǔ)句將不會(huì)產(chǎn)生-1, 而是變成一個(gè)uint類型的最大值( 可能是2^64 ? 1) , 然后medals[i]表達(dá)式將發(fā)生運(yùn)行時(shí)panic異常 , 也就是試圖訪問(wèn)一個(gè)slice范圍以外的元素。出于這個(gè)原因, 無(wú)符號(hào)數(shù)往往只有在位運(yùn)算或其它特殊的運(yùn)算場(chǎng)景才會(huì)使用, 就像bit集合、分析二進(jìn)制文件格式或者是哈希和加密操作等。 它們通常并不用于僅僅是表達(dá)非負(fù)數(shù)量的場(chǎng)合。一般來(lái)說(shuō), 需要一個(gè)顯式的轉(zhuǎn)換將一個(gè)值從一種類型轉(zhuǎn)化位另一種類型, 并且算術(shù)和邏輯運(yùn)算的二元操作中必須是相同的類型。 雖然這偶爾會(huì)導(dǎo)致需要很長(zhǎng)的表達(dá)式, 但是它消除了所有和類型相關(guān)的問(wèn)題, 而且也使得程序容易理解。在很多場(chǎng)景, 會(huì)遇到類似下面的代碼通用的錯(cuò)誤:
當(dāng)嘗試編譯這三個(gè)語(yǔ)句時(shí), 將產(chǎn)生一個(gè)錯(cuò)誤信息:
這種類型不匹配的問(wèn)題可以有幾種不同的方法修復(fù), 最常見方法是將它們都顯式轉(zhuǎn)型為一個(gè)常見類型:
如2.5節(jié)所述, 對(duì)于每種類型T, 如果轉(zhuǎn)換允許的話, 類型轉(zhuǎn)換操作T(x)將x轉(zhuǎn)換為T類型。 許多整形數(shù)之間的相互轉(zhuǎn)換并不會(huì)改變數(shù)值; 它們只是告訴編譯器如何解釋這個(gè)值。 但是對(duì)于將一個(gè)大尺寸的整數(shù)類型轉(zhuǎn)為一個(gè)小尺寸的整數(shù)類型, 或者是將一個(gè)浮點(diǎn)數(shù)轉(zhuǎn)為整數(shù), 可能會(huì)改變數(shù)值或丟失精度:
浮點(diǎn)數(shù)到整數(shù)的轉(zhuǎn)換將丟失任何小數(shù)部分, 然后向數(shù)軸零方向截?cái)唷?你應(yīng)該避免對(duì)可能會(huì)超出目標(biāo)類型表示范圍的數(shù)值類型轉(zhuǎn)換, 因?yàn)榻財(cái)嗟男袨榭赡芤蕾囉诰唧w的實(shí)現(xiàn):
任何大小的整數(shù)字面值都可以用以0開始的八進(jìn)制格式書寫, 例如0666; 或用以0x或0X開頭的十六進(jìn)制格式書寫, 例如0xdeadbeef。 十六進(jìn)制數(shù)字可以用大寫或小寫字母。 如今八進(jìn)制數(shù)據(jù)通常用于POSIX操作系統(tǒng)上的文件訪問(wèn)權(quán)限標(biāo)志, 十六進(jìn)制數(shù)字則更強(qiáng)調(diào)數(shù)字值的bit位模式。當(dāng)使用fmt包打印一個(gè)數(shù)值時(shí), 我們可以用%d、 %o或%x參數(shù)控制輸出的進(jìn)制格式, 就像下面的例子:
請(qǐng)注意fmt的兩個(gè)使用技巧。 通常Printf格式化字符串包含多個(gè)%參數(shù)時(shí)將會(huì)包含對(duì)應(yīng)相同數(shù)量的額外操作數(shù), 但是%之后的 [1] 副詞告訴Printf函數(shù)再次使用第一個(gè)操作數(shù)。 第二, %后的 # 副詞告訴Printf在用%o、 %x或%X輸出時(shí)生成0、 0x或0X前綴。字符面值通過(guò)一對(duì)單引號(hào)直接包含對(duì)應(yīng)字符。 最簡(jiǎn)單的例子是ASCII中類似’a’寫法的字符面值, 但是我們也可以通過(guò)轉(zhuǎn)義的數(shù)值來(lái)表示任意的Unicode碼點(diǎn)對(duì)應(yīng)的字符, 馬上將會(huì)看到這樣的例子。字符使用 %c 參數(shù)打印, 或者是用 %q 參數(shù)打印帶單引號(hào)的字符:
2. 浮點(diǎn)數(shù)
Go語(yǔ)言提供了兩種精度的浮點(diǎn)數(shù), float32和float64。 它們的算術(shù)規(guī)范由IEEE754浮點(diǎn)數(shù)國(guó)際標(biāo)準(zhǔn)定義, 該浮點(diǎn)數(shù)規(guī)范被所有現(xiàn)代的CPU支持。
這些浮點(diǎn)數(shù)類型的取值范圍可以從很微小到很巨大。 浮點(diǎn)數(shù)的范圍極限值可以在math包找到。 常量math.MaxFloat32表示float32能表示的最大數(shù)值, 大約是 3.4e38; 對(duì)應(yīng)的math.MaxFloat64常量大約是1.8e308。 它們分別能表示的最小值近似為1.4e-45和4.9e-324。
一個(gè)float32類型的浮點(diǎn)數(shù)可以提供大約6個(gè)十進(jìn)制數(shù)的精度, 而float64則可以提供約15個(gè)十進(jìn)制數(shù)的精度; 通常應(yīng)該優(yōu)先使用float64類型, 因?yàn)閒loat32類型的累計(jì)計(jì)算誤差很容易擴(kuò)散,并且float32能精確表示的正整數(shù)并不是很大( 譯注: 因?yàn)閒loat32的有效bit位只有23個(gè), 其它的bit位用于指數(shù)和符號(hào); 當(dāng)整數(shù)大于23bit能表達(dá)的范圍時(shí), float32的表示將出現(xiàn)誤差) :
浮點(diǎn)數(shù)的字面值可以直接寫小數(shù)部分, 像這樣:
小數(shù)點(diǎn)前面或后面的數(shù)字都可能被省略( 例如.707或1.) 。 很小或很大的數(shù)最好用科學(xué)計(jì)數(shù)法書寫, 通過(guò)e或E來(lái)指定指數(shù)部分:
用Printf函數(shù)的%g參數(shù)打印浮點(diǎn)數(shù), 將采用更緊湊的表示形式打印, 并提供足夠的精度, 但是對(duì)應(yīng)表格的數(shù)據(jù), 使用%e( 帶指數(shù)) 或%f的形式打印可能更合適。 所有的這三個(gè)打印形式都可以指定打印的寬度和控制打印精度。
上面代碼打印e的冪, 打印精度是小數(shù)點(diǎn)后三個(gè)小數(shù)精度和8個(gè)字符寬度:
math包中除了提供大量常用的數(shù)學(xué)函數(shù)外, 還提供了IEEE754浮點(diǎn)數(shù)標(biāo)準(zhǔn)中定義的特殊值的創(chuàng)建和測(cè)試: 正無(wú)窮大和負(fù)無(wú)窮大, 分別用于表示太大溢出的數(shù)字和除零的結(jié)果; 還有NaN非數(shù), 一般用于表示無(wú)效的除法操作結(jié)果0/0或Sqrt(-1).
函數(shù)math.IsNaN用于測(cè)試一個(gè)數(shù)是否是非數(shù)NaN, math.NaN則返回非數(shù)對(duì)應(yīng)的值。 雖然可以用math.NaN來(lái)表示一個(gè)非法的結(jié)果, 但是測(cè)試一個(gè)結(jié)果是否是非數(shù)NaN則是充滿風(fēng)險(xiǎn)的, 因?yàn)镹aN和任何數(shù)都是不相等的( 譯注: 在浮點(diǎn)數(shù)中, NaN、 正無(wú)窮大和負(fù)無(wú)窮大都不是唯一的, 每個(gè)都有非常多種的bit模式表示) :
如果一個(gè)函數(shù)返回的浮點(diǎn)數(shù)結(jié)果可能失敗, 最好的做法是用單獨(dú)的標(biāo)志報(bào)告失敗, 像這樣:
接下來(lái)的程序演示了通過(guò)浮點(diǎn)計(jì)算生成的圖形。 它是帶有兩個(gè)參數(shù)的z = f(x, y)函數(shù)的三維形式, 使用了可縮放矢量圖形( SVG) 格式輸出, SVG是一個(gè)用于矢量線繪制的XML標(biāo)準(zhǔn)。 圖3.1顯示了sin?/r函數(shù)的輸出圖形, 其中r是sqrt(xx+yy)。
gopl.io/ch3/surface
package mainimport ("fmt""math"
)const (width, height = 600, 320 cells = 100 xyrange = 30.0 xyscale = width / 2 / xyrange zscale = height * 0.4 angle = math.Pi / 6
)var sin30, cos30 = math.Sin(angle), math.Cos(angle) func main() {fmt.Printf("<svg xmlns='http://www.w3.org/2000/svg' "+"style='stroke: grey; fill: white; stroke-width: 0.7' "+"width='%d' height='%d'>", width, height)for i := 0; i < cells; i++ {for j := 0; j < cells; j++ {ax, ay := corner(i+1, j)bx, by := corner(i, j)cx, cy := corner(i, j+1)dx, dy := corner(i+1, j+1)fmt.Printf("<polygon points='%g,%g %g,%g %g,%g %g,%g'/>\n",ax, ay, bx, by, cx, cy, dx, dy)}}fmt.Println("</svg>")
}func corner(i, j int) (float64, float64) {x := xyrange * (float64(i)/cells - 0.5)y := xyrange * (float64(j)/cells - 0.5)z := f(x, y)sx := width/2 + (x-y)*cos30*xyscalesy := height/2 + (x+y)*sin30*xyscale - z*zscalereturn sx, sy
}func f(x, y float64) float64 {r := math.Hypot(x, y) return math.Sin(r) / r
}
要注意的是corner函數(shù)返回了兩個(gè)結(jié)果, 分別對(duì)應(yīng)每個(gè)網(wǎng)格頂點(diǎn)的坐標(biāo)參數(shù)。
要解釋這個(gè)程序是如何工作的需要一些基本的幾何學(xué)知識(shí), 但是我們可以跳過(guò)幾何學(xué)原理,因?yàn)槌绦虻闹攸c(diǎn)是演示浮點(diǎn)數(shù)運(yùn)算。 程序的本質(zhì)是三個(gè)不同的坐標(biāo)系中映射關(guān)系, 如圖3.2所示。 第一個(gè)是100x100的二維網(wǎng)格, 對(duì)應(yīng)整數(shù)整數(shù)坐標(biāo)(i,j), 從遠(yuǎn)處的(0, 0)位置開始。 我們從遠(yuǎn)處向前面繪制, 因此遠(yuǎn)處先繪制的多邊形有可能被前面后繪制的多邊形覆蓋。
第二個(gè)坐標(biāo)系是一個(gè)三維的網(wǎng)格浮點(diǎn)坐標(biāo)(x,y,z), 其中x和y是i和j的線性函數(shù), 通過(guò)平移轉(zhuǎn)換位網(wǎng)格單元的中心, 然后用xyrange系數(shù)縮放。 高度z是函數(shù)f(x,y)的值。
第三個(gè)坐標(biāo)系是一個(gè)二維的畫布, 起點(diǎn)(0,0)在左上角。 畫布中點(diǎn)的坐標(biāo)用(sx, sy)表示。 我們使用等角投影將三維點(diǎn)
(x,y,z)投影到二維的畫布中。 畫布中從遠(yuǎn)處到右邊的點(diǎn)對(duì)應(yīng)較大的x值和較大的y值。 并且畫布中x和y值越大, 則對(duì)應(yīng)的z值越小。 x和y的垂直和水平縮放系數(shù)來(lái)自30度角的正弦和余弦值。z的縮放系數(shù)0.4, 是一個(gè)任意選擇的參數(shù)。
對(duì)于二維網(wǎng)格中的每一個(gè)網(wǎng)格單元, main函數(shù)計(jì)算單元的四個(gè)頂點(diǎn)在畫布中對(duì)應(yīng)多邊形ABCD的頂點(diǎn), 其中B對(duì)應(yīng)(i,j)頂點(diǎn)位置, A、 C和D是其它相鄰的頂點(diǎn), 然后輸出SVG的繪制指令。
3. 復(fù)數(shù)
Go語(yǔ)言提供了兩種精度的復(fù)數(shù)類型: complex64和complex128, 分別對(duì)應(yīng)float32和float64兩種浮點(diǎn)數(shù)精度。 內(nèi)置的complex函數(shù)用于構(gòu)建復(fù)數(shù), 內(nèi)建的real和imag函數(shù)分別返回復(fù)數(shù)的實(shí)部和虛部:
如果一個(gè)浮點(diǎn)數(shù)面值或一個(gè)十進(jìn)制整數(shù)面值后面跟著一個(gè)i, 例如3.141592i或2i, 它將構(gòu)成一個(gè)復(fù)數(shù)的虛部, 復(fù)數(shù)的實(shí)部是0:
在常量算術(shù)規(guī)則下, 一個(gè)復(fù)數(shù)常量可以加到另一個(gè)普通數(shù)值常量( 整數(shù)或浮點(diǎn)數(shù)、 實(shí)部或虛部) , 我們可以用自然的方式書寫復(fù)數(shù), 就像1+2i或與之等價(jià)的寫法2i+1。 上面x和y的聲明語(yǔ)句還可以簡(jiǎn)化:
復(fù)數(shù)也可以用==和!=進(jìn)行相等比較。 只有兩個(gè)復(fù)數(shù)的實(shí)部和虛部都相等的時(shí)候它們才是相等的( 譯注: 浮點(diǎn)數(shù)的相等比較是危險(xiǎn)的, 需要特別小心處理精度問(wèn)題) 。
math/cmplx包提供了復(fù)數(shù)處理的許多函數(shù), 例如求復(fù)數(shù)的平方根函數(shù)和求冪函數(shù)。
下面的程序使用complex128復(fù)數(shù)算法來(lái)生成一個(gè)Mandelbrot圖像。
gopl.io/ch3/mandelbrot
package mainimport ("image""image/color""image/png""math/cmplx""os"
)func main() {const (xmin, ymin, xmax, ymax = -2, -2, +2, +2width, height = 1024, 1024)img := image.NewRGBA(image.Rect(0, 0, width, height))for py := 0; py < height; py++ {y := float64(py)/height*(ymax-ymin) + yminfor px := 0; px < width; px++ {x := float64(px)/width*(xmax-xmin) + xminz := complex(x, y)img.Set(px, py, mandelbrot(z))}}png.Encode(os.Stdout, img)
}func mandelbrot(z complex128) color.Color {const iterations = 200const contrast = 15var v complex128for n := uint8(0); n < iterations; n++ {v = v*v + zif cmplx.Abs(v) > 2 {return color.Gray{255 - contrast*n}}}return color.Black
}func acos(z complex128) color.Color {v := cmplx.Acos(z)blue := uint8(real(v)*128) + 127red := uint8(imag(v)*128) + 127return color.YCbCr{192, blue, red}
}func sqrt(z complex128) color.Color {v := cmplx.Sqrt(z)blue := uint8(real(v)*128) + 127red := uint8(imag(v)*128) + 127return color.YCbCr{128, blue, red}
}
func newton(z complex128) color.Color {const iterations = 37const contrast = 7for i := uint8(0); i < iterations; i++ {z -= (z - 1/(z*z*z)) / 4if cmplx.Abs(z*z*z*z-1) < 1e-6 {return color.Gray{255 - contrast*i}}}return color.Black
}
用于遍歷1024x1024圖像每個(gè)點(diǎn)的兩個(gè)嵌套的循環(huán)對(duì)應(yīng)-2到+2區(qū)間的復(fù)數(shù)平面。 程序反復(fù)測(cè)試每個(gè)點(diǎn)對(duì)應(yīng)復(fù)數(shù)值平方值加一個(gè)增量值對(duì)應(yīng)的點(diǎn)是否超出半徑為2的圓。 如果超過(guò)了, 通過(guò)根據(jù)預(yù)設(shè)置的逃逸迭代次數(shù)對(duì)應(yīng)的灰度顏色來(lái)代替。 如果不是, 那么該點(diǎn)屬于Mandelbrot集合, 使用黑色顏色標(biāo)記。 最終程序?qū)⑸傻腜NG格式分形圖像圖像輸出到標(biāo)準(zhǔn)輸出, 如圖3.3所示。
4. 布爾型
一個(gè)布爾類型的值只有兩種: true和false。 if和for語(yǔ)句的條件部分都是布爾類型的值, 并且==和<等比較操作也會(huì)產(chǎn)生布爾型的值。 一元操作符! 對(duì)應(yīng)邏輯非操作, 因此 !true 的值為 false , 更羅嗦的說(shuō)法是 (!true==false)==true , 雖然表達(dá)方式不一樣, 不過(guò)我們一般會(huì)采用簡(jiǎn)潔的布爾表達(dá)式, 就像用x來(lái)表示 x==true 。布爾值可以和&&( AND) 和||( OR) 操作符結(jié)合, 并且可能會(huì)有短路行為: 如果運(yùn)算符左邊值已經(jīng)可以確定整個(gè)布爾表達(dá)式的值, 那么運(yùn)算符右邊的值將不在被求值, 因此下面的表達(dá)式總是安全的:
其中s[0]操作如果應(yīng)用于空字符串將會(huì)導(dǎo)致panic異常。因?yàn)?&& 的優(yōu)先級(jí)比 || 高( 助記: && 對(duì)應(yīng)邏輯乘法, || 對(duì)應(yīng)邏輯加法, 乘法比加法優(yōu)先級(jí)要高) , 下面形式的布爾表達(dá)式是不需要加小括弧的:
布爾值并不會(huì)隱式轉(zhuǎn)換為數(shù)字值0或1, 反之亦然。 必須使用一個(gè)顯式的if語(yǔ)句輔助轉(zhuǎn)換:
如果需要經(jīng)常做類似的轉(zhuǎn)換, 包裝成一個(gè)函數(shù)會(huì)更方便:
數(shù)字到布爾型的逆轉(zhuǎn)換則非常簡(jiǎn)單, 不過(guò)為了保持對(duì)稱, 我們也可以包裝一個(gè)函數(shù):
5. 字符串
一個(gè)字符串是一個(gè)不可改變的字節(jié)序列。 字符串可以包含任意的數(shù)據(jù), 包括byte值0, 但是通
常是用來(lái)包含人類可讀的文本。 文本字符串通常被解釋為采用UTF8編碼的Unicode碼點(diǎn)
( rune) 序列, 我們稍后會(huì)詳細(xì)討論這個(gè)問(wèn)題。
內(nèi)置的len函數(shù)可以返回一個(gè)字符串中的字節(jié)數(shù)目( 不是rune字符數(shù)目) , 索引操作s[i]返回第i個(gè)字節(jié)的字節(jié)值, i必須滿足0 ≤ i< len(s)條件約束
如果試圖訪問(wèn)超出字符串索引范圍的字節(jié)將會(huì)導(dǎo)致panic異常:
第i個(gè)字節(jié)并不一定是字符串的第i個(gè)字符, 因?yàn)閷?duì)于非ASCII字符的UTF8編碼會(huì)要兩個(gè)或多個(gè)字節(jié)。 我們先簡(jiǎn)單說(shuō)下字符的工作方式。
子字符串操作s[i:j]基于原始的s字符串的第i個(gè)字節(jié)開始到第j個(gè)字節(jié)( 并不包含j本身) 生成一個(gè)新字符串。 生成的新字符串將包含j-i個(gè)字節(jié)。
同樣, 如果索引超出字符串范圍或者j小于i的話將導(dǎo)致panic異常
不管i還是j都可能被忽略, 當(dāng)它們被忽略時(shí)將采用0作為開始位置, 采用len(s)作為結(jié)束的位置。
其中+操作符將兩個(gè)字符串鏈接構(gòu)造一個(gè)新字符串:
字符串可以用==和<進(jìn)行比較; 比較通過(guò)逐個(gè)字節(jié)比較完成的, 因此比較的結(jié)果是字符串自然編碼的順序。
字符串的值是不可變的: 一個(gè)字符串包含的字節(jié)序列永遠(yuǎn)不會(huì)被改變, 當(dāng)然我們也可以給一個(gè)字符串變量分配一個(gè)新字符串值。 可以像下面這樣將一個(gè)字符串追加到另一個(gè)字符串:
這并不會(huì)導(dǎo)致原始的字符串值被改變, 但是變量s將因?yàn)?#43;=語(yǔ)句持有一個(gè)新的字符串值, 但是t依然是包含原先的字符串值。
因?yàn)樽址遣豢尚薷牡?#xff0c; 因此嘗試修改字符串內(nèi)部數(shù)據(jù)的操作也是被禁止的:
不變性意味如果兩個(gè)字符串共享相同的底層數(shù)據(jù)的話也是安全的, 這使得復(fù)制任何長(zhǎng)度的字符串代價(jià)是低廉的。 同樣, 一個(gè)字符串s和對(duì)應(yīng)的子字符串切片s[7:]的操作也可以安全地共享相同的內(nèi)存, 因此字符串切片操作代價(jià)也是低廉的。 在這兩種情況下都沒有必要分配新的內(nèi)存。 圖3.4演示了一個(gè)字符串和兩個(gè)字串共享相同的底層數(shù)據(jù)。
1. 字符串面值
字符串值也可以用字符串面值方式編寫, 只要將一系列字節(jié)序列包含在雙引號(hào)即可:"hello world"
因?yàn)镚o語(yǔ)言源文件總是用UTF8編碼, 并且Go語(yǔ)言的文本字符串也以UTF8編碼的方式處理,因此我們可以將Unicode碼點(diǎn)也寫到字符串面值中。在一個(gè)雙引號(hào)包含的字符串面值中, 可以用以反斜杠 \ 開頭的轉(zhuǎn)義序列插入任意的數(shù)據(jù)。 下面的換行、 回車和制表符等是常見的ASCII控制代碼的轉(zhuǎn)義方式:
符號(hào)含義
| \a | 響鈴 |
| \b | 退格 |
| \f | 換頁(yè) |
| \n | 換行 |
| \r | 回車 |
| \t | 制表符 |
| \v | 垂直制表符 |
| ’ | 單引號(hào) (只用在 ‘’’ 形式的rune符號(hào)面值中) |
| " | 雙引號(hào) (只用在 “…” 形式的字符串面值中) |
| \ | 反斜杠 |
可以通過(guò)十六進(jìn)制或八進(jìn)制轉(zhuǎn)義在字符串面值包含任意的字節(jié)。 一個(gè)十六進(jìn)制的轉(zhuǎn)義形式是\xhh, 其中兩個(gè)h表示十六進(jìn)制數(shù)字( 大寫或小寫都可以) 。 一個(gè)八進(jìn)制轉(zhuǎn)義形式是\ooo, 包含三個(gè)八進(jìn)制的o數(shù)字( 0到7) , 但是不能超過(guò) \377 ( 譯注: 對(duì)應(yīng)一個(gè)字節(jié)的范圍, 十進(jìn)制為255) 。 每一個(gè)單一的字節(jié)表達(dá)一個(gè)特定的值。 稍后我們將看到如何將一個(gè)Unicode碼點(diǎn)寫到字符串面值中。一個(gè)原生的字符串面值形式是 … , 使用反引號(hào) 代替雙引號(hào)。 在原生的字符串面值中, 沒有轉(zhuǎn)義操作; 全部的內(nèi)容都是字面的意思, 包含退格和換行, 因此一個(gè)程序中的原生字符串面值可能跨越多行( 注: 在原生字符串面值內(nèi)部是無(wú)法直接寫 字符的, 可以用八進(jìn)制或十六進(jìn)制轉(zhuǎn)義或+"```"鏈接字符串常量完成) 。 唯一的特殊處理是會(huì)刪除回車以保證在所有平臺(tái)上的值都是一樣的, 包括那些把回車也放入文本文件的系統(tǒng)( 譯注: Windows系統(tǒng)會(huì)把回車和換行一起放入文本文件中) 。原生字符串面值用于編寫正則表達(dá)式會(huì)很方便, 因?yàn)檎齽t表達(dá)式往往會(huì)包含很多反斜杠。 原生字符串面值同時(shí)被廣泛應(yīng)用于HTML模板、 JSON面值、 命令行提示信息以及那些需要擴(kuò)展到多行的場(chǎng)景。
2. Unicode
在很久以前, 世界還是比較簡(jiǎn)單的, 起碼計(jì)算機(jī)世界就只有一個(gè)ASCII字符集: 美國(guó)信息交換標(biāo)準(zhǔn)代碼。 ASCII, 更準(zhǔn)確地說(shuō)是美國(guó)的ASCII, 使用7bit來(lái)表示128個(gè)字符: 包含英文字母的大小寫、 數(shù)字、 各種標(biāo)點(diǎn)符號(hào)和設(shè)置控制符。 對(duì)于早期的計(jì)算機(jī)程序來(lái)說(shuō), 這些就足夠了,但是這也導(dǎo)致了世界上很多其他地區(qū)的用戶無(wú)法直接使用自己的符號(hào)系統(tǒng)。 隨著互聯(lián)網(wǎng)的發(fā)展, 混合多種語(yǔ)言的數(shù)據(jù)變得很常見( 譯注: 比如本身的英文原文或中文翻譯都包含了ASCII、 中文、 日文等多種語(yǔ)言字符) 。 如何有效處理這些包含了各種語(yǔ)言的豐富多樣的文本數(shù)據(jù)呢?答案就是使用Unicode( http://unicode.org ) , 它收集了這個(gè)世界上所有的符號(hào)系統(tǒng), 包括重音符號(hào)和其它變音符號(hào), 制表符和回車符, 還有很多神秘的符號(hào), 每個(gè)符號(hào)都分配一個(gè)唯一的Unicode碼點(diǎn), Unicode碼點(diǎn)對(duì)應(yīng)Go語(yǔ)言中的rune整數(shù)類型( 譯注: rune是int32等價(jià)類型) 。我們可以將一個(gè)符文序列表示為一個(gè)int32序列。 這種編碼方式叫UTF-32或UCS-4, 每個(gè)Unicode碼點(diǎn)都使用同樣的大小32bit來(lái)表示。 這種方式比較簡(jiǎn)單統(tǒng)一, 但是它會(huì)浪費(fèi)很多存儲(chǔ)空間, 因?yàn)榇髷?shù)據(jù)計(jì)算機(jī)可讀的文本是ASCII字符, 本來(lái)每個(gè)ASCII字符只需要8bit或1字節(jié)就能表示。 而且即使是常用的字符也遠(yuǎn)少于65,536個(gè), 也就是說(shuō)用16bit編碼方式就能表達(dá)常用字符。 但是, 還有其它更好的編碼方法嗎?
3. UTF-8
UTF8是一個(gè)將Unicode碼點(diǎn)編碼為字節(jié)序列的變長(zhǎng)編碼。 UTF8編碼由Go語(yǔ)言之父KenThompson和Rob Pike共同發(fā)明的, 現(xiàn)在已經(jīng)是Unicode的標(biāo)準(zhǔn)。 UTF8編碼使用1到4個(gè)字節(jié)來(lái)表示每個(gè)Unicode碼點(diǎn), ASCII部分字符只使用1個(gè)字節(jié), 常用字符部分使用2或3個(gè)字節(jié)表示。 每個(gè)符號(hào)編碼后第一個(gè)字節(jié)的高端bit位用于表示總共有多少編碼個(gè)字節(jié)。 如果第一個(gè)字節(jié)的高端bit為0, 則表示對(duì)應(yīng)7bit的ASCII字符, ASCII字符每個(gè)字符依然是一個(gè)字節(jié), 和傳統(tǒng)的ASCII編碼兼容。 如果第一個(gè)字節(jié)的高端bit是110, 則說(shuō)明需要2個(gè)字節(jié); 后續(xù)的每個(gè)高端bit都以10開頭。 更大的Unicode碼點(diǎn)也是采用類似的策略處理。
變長(zhǎng)的編碼無(wú)法直接通過(guò)索引來(lái)訪問(wèn)第n個(gè)字符, 但是UTF8編碼獲得了很多額外的優(yōu)點(diǎn)。 首先UTF8編碼比較緊湊, 完全兼容ASCII碼, 并且可以自動(dòng)同步: 它可以通過(guò)向前回朔最多2個(gè)字節(jié)就能確定當(dāng)前字符編碼的開始字節(jié)的位置。 它也是一個(gè)前綴編碼, 所以當(dāng)從左向右解碼時(shí)不會(huì)有任何歧義也并不需要向前查看( 譯注: 像GBK之類的編碼, 如果不知道起點(diǎn)位置則可能會(huì)出現(xiàn)歧義) 。 沒有任何字符的編碼是其它字符編碼的子串, 或是其它編碼序列的字串, 因此搜索一個(gè)字符時(shí)只要搜索它的字節(jié)編碼序列即可, 不用擔(dān)心前后的上下文會(huì)對(duì)搜索結(jié)果產(chǎn)生干擾。 同時(shí)UTF8編碼的順序和Unicode碼點(diǎn)的順序一致, 因此可以直接排序UTF8編碼序列。 同時(shí)因?yàn)闆]有嵌入的NUL(0)字節(jié), 可以很好地兼容那些使用NUL作為字符串結(jié)尾的編程語(yǔ)言。Go語(yǔ)言的源文件采用UTF8編碼, 并且Go語(yǔ)言處理UTF8編碼的文本也很出色。 unicode包提供了諸多處理rune字符相關(guān)功能的函數(shù)( 比如區(qū)分字母和數(shù)組, 或者是字母的大寫和小寫轉(zhuǎn)換等) , unicode/utf8包則提供了用于rune字符序列的UTF8編碼和解碼的功能。有很多Unicode字符很難直接從鍵盤輸入, 并且還有很多字符有著相似的結(jié)構(gòu); 有一些甚至是不可見的字符( 譯注: 中文和日文就有很多相似但不同的字) 。 Go語(yǔ)言字符串面值中的Unicode轉(zhuǎn)義字符讓我們可以通過(guò)Unicode碼點(diǎn)輸入特殊的字符。 有兩種形式: \uhhhh對(duì)應(yīng)16bit的碼點(diǎn)值, \Uhhhhhhhh對(duì)應(yīng)32bit的碼點(diǎn)值, 其中h是一個(gè)十六進(jìn)制數(shù)字; 一般很少需要使用32bit的形式。 每一個(gè)對(duì)應(yīng)碼點(diǎn)的UTF8編碼。 例如: 下面的字母串面值都表示相同的值:
上面三個(gè)轉(zhuǎn)義序列都為第一個(gè)字符串提供替代寫法, 但是它們的值都是相同的。Unicode轉(zhuǎn)義也可以使用在rune字符中。 下面三個(gè)字符是等價(jià)的:
對(duì)于小于256碼點(diǎn)值可以寫在一個(gè)十六進(jìn)制轉(zhuǎn)義字節(jié)中, 例如’\x41’對(duì)應(yīng)字符’A’, 但是對(duì)于更大的碼點(diǎn)則必須使用\u或\U轉(zhuǎn)義形式。 因此, '\xe4\xb8\x96’并不是一個(gè)合法的rune字符, 雖然這三個(gè)字節(jié)對(duì)應(yīng)一個(gè)有效的UTF8編碼的碼點(diǎn)。得益于UTF8編碼優(yōu)良的設(shè)計(jì), 諸多字符串操作都不需要解碼操作。 我們可以不用解碼直接測(cè)試一個(gè)字符串是否是另一個(gè)字符串的前綴:
或者是后綴測(cè)試:
或者是包含子串測(cè)試 :
對(duì)于UTF8編碼后文本的處理和原始的字節(jié)處理邏輯是一樣的。 但是對(duì)應(yīng)很多其它編碼則并不是這樣的。 ( 上面的函數(shù)都來(lái)自strings字符串處理包, 真實(shí)的代碼包含了一個(gè)用哈希技術(shù)優(yōu)化的Contains 實(shí)現(xiàn)。 )另一方面, 如果我們真的關(guān)心每個(gè)Unicode字符, 我們可以使用其它處理方式。 考慮前面的第一個(gè)例子中的字符串, 它包混合了中西兩種字符。 圖3.5展示了它的內(nèi)存表示形式。 字符串包含13個(gè)字節(jié), 以UTF8形式編碼, 但是只對(duì)應(yīng)9個(gè)Unicode字符:
為了處理這些真實(shí)的字符, 我們需要一個(gè)UTF8解碼器。 unicode/utf8包提供了該功能, 我們可以這樣使用:
每一次調(diào)用DecodeRuneInString函數(shù)都返回一個(gè)r和長(zhǎng)度, r對(duì)應(yīng)字符本身, 長(zhǎng)度對(duì)應(yīng)r采用UTF8編碼后的編碼字節(jié)數(shù)目。 長(zhǎng)度可以用于更新第i個(gè)字符在字符串中的字節(jié)索引位置。 但是這種編碼方式是笨拙的, 我們需要更簡(jiǎn)潔的語(yǔ)法。 幸運(yùn)的是, Go語(yǔ)言的range循環(huán)在處理字符串的時(shí)候, 會(huì)自動(dòng)隱式解碼UTF8字符串。 下面的循環(huán)運(yùn)行如圖3.5所示; 需要注意的是對(duì)于非ASCII, 索引更新的步長(zhǎng)將超過(guò)1個(gè)字節(jié)。
我們可以使用一個(gè)簡(jiǎn)單的循環(huán)來(lái)統(tǒng)計(jì)字符串中字符的數(shù)目, 像這樣:
總結(jié)
以上是生活随笔為你收集整理的《Go语言圣经》学习笔记 第三章 基础数据类型的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。