日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

《Go语言圣经》学习笔记 第三章 基础数据类型

發布時間:2024/4/11 编程问答 38 豆豆
生活随笔 收集整理的這篇文章主要介紹了 《Go语言圣经》学习笔记 第三章 基础数据类型 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

《Go語言圣經》學習筆記 第三章 基礎數據類型


目錄

  • 整型
  • 浮點數
  • 復數
  • 布爾型
  • 字符串
  • 常量
  • 注:學習《Go語言圣經》筆記,PDF點擊下載,建議看書。
    Go語言小白學習筆記,書上的內容照搬,大佬看了勿噴,以后熟悉了會總結成自己的讀書筆記。


    1. 整型

  • Go語言的數值類型包括幾種不同大小的整形數、 浮點數和復數。 每種數值類型都決定了對應的大小范圍和是否支持正負符號。 讓我們先從整形數類型開始介紹。
  • Go語言同時提供了有符號和無符號類型的整數運算。 這里有int8、 int16、 int32和int64四種截然不同大小的有符號整形數類型, 分別對應8、 16、 32、 64bit大小的有符號整形數, 與此對應的是uint8、 uint16、 uint32和uint64四種無符號整形數類型。
  • 這里還有兩種一般對應特定CPU平臺機器字大小的有符號和無符號整數int和uint; 其中int是應用最廣泛的數值類型。 這兩種類型都有同樣的大小, 32或64bit, 但是我們不能對此做任何的假設; 因為不同的編譯器即使在相同的硬件。
  • Unicode字符rune類型是和int32等價的類型, 通常用于表示一個Unicode碼點。 這兩個名稱可以互換使用。 同樣byte也是uint8類型的等價類型, byte類型一般用于強調數值是一個原始的數據而不是一個小的整數。
  • 最后, 還有一種無符號的整數類型uintptr, 沒有指定具體的bit大小但是足以容納指針。 uintptr類型只有在底層編程是才需要, 特別是Go語言和C語言函數庫或操作系統接口相交互的地方。 我們將在第十三章的unsafe包相關部分看到類似的例子。
  • 不管它們的具體大小, int、 uint和uintptr是不同類型的兄弟類型。 其中int和int32也是不同的類型, 即使int的大小也是32bit, 在需要將int當作int32類型的地方需要一個顯式的類型轉換操作, 反之亦然。
  • 其中有符號整數采用2的補碼形式表示, 也就是最高bit位用作表示符號位, 一個n-bit的有符號數的值域是從?2^(n-1) 到2^(n-1) ? 1。 無符號整數的所有bit位都用于表示非負數, 值域是0到2^(n-1) ? 1。 例如, int8類型整數的值域是從-128到127, 而uint8類型整數的值域是從0到255。
  • 下面是Go語言中關于算術運算、 邏輯運算和比較運算的二元運算符, 它們按照先級遞減的順序的排列:
  • 二元運算符有五種優先級。 在同一個優先級, 使用左優先結合規則, 但是使用括號可以明確優先順序, 使用括號也可以用于提升優先級, 例如 mask & (1 << 28) 。
  • 對于上表中前兩行的運算符, 例如+運算符還有一個與賦值相結合的對應運算符+=, 可以用于簡化賦值語句。
  • 算術運算符+、 -、 * 和 / 可以適用與于整數、 浮點數和復數, 但是取模運算符%僅用于整數間的運算。 對于不同編程語言, %取模運算的行為可能并不相同。 在Go語言中, %取模運算符的符號和被取模數的符號總是一致的, 因此 -5%3 和 -5%-3 結果都是-2。 除法運算符 / 的行為則依賴于操作數是否為全為整數, 比如 5.0/4.0 的結果是1.25, 但是5/4的結果是1, 因為整數除法會向著0方向截斷余數。
  • 如果一個算術運算的結果, 不管是有符號或者是無符號的, 如果需要更多的bit位才能正確表示的話, 就說明計算結果是溢出了。 超出的高位的bit位部分將被丟棄。 如果原始的數值是有符號類型, 而且最左邊的bit為是1的話, 那么最終結果可能是負的, 例如int8的例子:
  • 兩個相同的整數類型可以使用下面的二元比較運算符進行比較; 比較表達式的結果是布爾類
    型。
  • 事實上, 布爾型、 數字類型和字符串等基本類型都是可比較的, 也就是說兩個相同類型的值可以用==和!=進行比較。 此外, 整數、 浮點數和字符串可以根據比較結果排序。 許多其它類型的值可能是不可比較的, 因此也就可能是不可排序的。 對于我們遇到的每種類型, 我們要保證規則的一致性。
  • 這里是一元的加法和減法運算符:
  • 對于整數, +x是0+x的簡寫, -x則是0-x的簡寫; 對于浮點數和復數, +x就是x, -x則是x 的負數。
  • Go語言還提供了以下的bit位操作運算符, 前面4個操作運算符并不區分是有符號還是無符號數:
  • 位操作運算符 ^ 作為二元運算符時是按位異或( XOR) , 當用作一元運算符時表示按位取反; 也就是說, 它返回一個每個bit位都取反的數。 位操作運算符 &^ 用于按位置零( ANDNOT) : 表達式 z = x &^ y 結果z的bit位為0, 如果對應y中bit位為1的話, 否則對應的bit位等于x相應的bit位的值。
  • 下面的代碼演示了如何使用位操作解釋uint8類型值的8個獨立的bit位。 它使用了Printf函數的%b參數打印二進制格式的數字; 其中%08b中08表示打印至少8個字符寬度, 不足的前綴部分用0填充。
  • 在 x<<n 和 x>>n 移位運算中, 決定了移位操作bit數部分必須是無符號數; 被操作的x數可以是有符號或無符號數。 算術上, 一個 x<<n 左移運算等價于乘以2 , 一個 x>>n 右移運算等價于除以2 。
  • 左移運算用零填充右邊空缺的bit位, 無符號數的右移運算也是用0填充左邊空缺的bit位, 但是有符號數的右移運算會用符號位的值填充左邊空缺的bit位。 因為這個原因, 最好用無符號運算, 這樣你可以將整數完全當作一個bit位模式處理。
  • 盡管Go語言提供了無符號數和運算, 即使數值本身不可能出現負數我們還是傾向于使用有符號的int類型, 就像數組的長度那樣, 雖然使用uint無符號類型似乎是一個更合理的選擇。 事實上, 內置的len函數返回一個有符號的int, 我們可以像下面例子那樣處理逆序循環。
  • 另一個選擇對于上面的例子來說將是災難性的。 如果len函數返回一個無符號數, 那么i也將是無符號的uint類型, 然后條件 i >= 0 則永遠為真。 在三次迭代之后, 也就是 i == 0 時, i–語句將不會產生-1, 而是變成一個uint類型的最大值( 可能是2^64 ? 1) , 然后medals[i]表達式將發生運行時panic異常 , 也就是試圖訪問一個slice范圍以外的元素。
  • 出于這個原因, 無符號數往往只有在位運算或其它特殊的運算場景才會使用, 就像bit集合、分析二進制文件格式或者是哈希和加密操作等。 它們通常并不用于僅僅是表達非負數量的場合。
  • 一般來說, 需要一個顯式的轉換將一個值從一種類型轉化位另一種類型, 并且算術和邏輯運算的二元操作中必須是相同的類型。 雖然這偶爾會導致需要很長的表達式, 但是它消除了所有和類型相關的問題, 而且也使得程序容易理解。
  • 在很多場景, 會遇到類似下面的代碼通用的錯誤:
  • 當嘗試編譯這三個語句時, 將產生一個錯誤信息:
  • 這種類型不匹配的問題可以有幾種不同的方法修復, 最常見方法是將它們都顯式轉型為一個常見類型:
  • 如2.5節所述, 對于每種類型T, 如果轉換允許的話, 類型轉換操作T(x)將x轉換為T類型。 許多整形數之間的相互轉換并不會改變數值; 它們只是告訴編譯器如何解釋這個值。 但是對于將一個大尺寸的整數類型轉為一個小尺寸的整數類型, 或者是將一個浮點數轉為整數, 可能會改變數值或丟失精度:
  • 浮點數到整數的轉換將丟失任何小數部分, 然后向數軸零方向截斷。 你應該避免對可能會超出目標類型表示范圍的數值類型轉換, 因為截斷的行為可能依賴于具體的實現:
  • 任何大小的整數字面值都可以用以0開始的八進制格式書寫, 例如0666; 或用以0x或0X開頭的十六進制格式書寫, 例如0xdeadbeef。 十六進制數字可以用大寫或小寫字母。 如今八進制數據通常用于POSIX操作系統上的文件訪問權限標志, 十六進制數字則更強調數字值的bit位模式。
  • 當使用fmt包打印一個數值時, 我們可以用%d、 %o或%x參數控制輸出的進制格式, 就像下面的例子:
  • 請注意fmt的兩個使用技巧。 通常Printf格式化字符串包含多個%參數時將會包含對應相同數量的額外操作數, 但是%之后的 [1] 副詞告訴Printf函數再次使用第一個操作數。 第二, %后的 # 副詞告訴Printf在用%o、 %x或%X輸出時生成0、 0x或0X前綴。
  • 字符面值通過一對單引號直接包含對應字符。 最簡單的例子是ASCII中類似’a’寫法的字符面值, 但是我們也可以通過轉義的數值來表示任意的Unicode碼點對應的字符, 馬上將會看到這樣的例子。
  • 字符使用 %c 參數打印, 或者是用 %q 參數打印帶單引號的字符:

  • 2. 浮點數

  • 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個十進制數的精度; 通常應該優先使用float64類型, 因為float32類型的累計計算誤差很容易擴散,并且float32能精確表示的正整數并不是很大( 譯注: 因為float32的有效bit位只有23個, 其它的bit位用于指數和符號; 當整數大于23bit能表達的范圍時, float32的表示將出現誤差) :

  • 浮點數的字面值可以直接寫小數部分, 像這樣:

  • 小數點前面或后面的數字都可能被省略( 例如.707或1.) 。 很小或很大的數最好用科學計數法書寫, 通過e或E來指定指數部分:

  • 用Printf函數的%g參數打印浮點數, 將采用更緊湊的表示形式打印, 并提供足夠的精度, 但是對應表格的數據, 使用%e( 帶指數) 或%f的形式打印可能更合適。 所有的這三個打印形式都可以指定打印的寬度和控制打印精度。

  • 上面代碼打印e的冪, 打印精度是小數點后三個小數精度和8個字符寬度:

  • math包中除了提供大量常用的數學函數外, 還提供了IEEE754浮點數標準中定義的特殊值的創建和測試: 正無窮大和負無窮大, 分別用于表示太大溢出的數字和除零的結果; 還有NaN非數, 一般用于表示無效的除法操作結果0/0或Sqrt(-1).

  • 函數math.IsNaN用于測試一個數是否是非數NaN, math.NaN則返回非數對應的值。 雖然可以用math.NaN來表示一個非法的結果, 但是測試一個結果是否是非數NaN則是充滿風險的, 因為NaN和任何數都是不相等的( 譯注: 在浮點數中, NaN、 正無窮大和負無窮大都不是唯一的, 每個都有非常多種的bit模式表示) :

  • 如果一個函數返回的浮點數結果可能失敗, 最好的做法是用單獨的標志報告失敗, 像這樣:

  • 接下來的程序演示了通過浮點計算生成的圖形。 它是帶有兩個參數的z = f(x, y)函數的三維形式, 使用了可縮放矢量圖形( SVG) 格式輸出, SVG是一個用于矢量線繪制的XML標準。 圖3.1顯示了sin?/r函數的輸出圖形, 其中r是sqrt(xx+yy)。

  • gopl.io/ch3/surface

    // Surface computes an SVG rendering of a 3-D surface function. package mainimport ("fmt""math" )const (width, height = 600, 320 // canvas size in pixelscells = 100 // number of grid cellsxyrange = 30.0 // axis ranges (-xyrange..+xyrange)xyscale = width / 2 / xyrange // pixels per x or y unitzscale = height * 0.4 // pixels per z unitangle = math.Pi / 6 // angle of x, y axes (=30°) )var sin30, cos30 = math.Sin(angle), math.Cos(angle) // sin(30°), cos(30°)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) {// Find point (x,y) at corner of cell (i,j).x := xyrange * (float64(i)/cells - 0.5)y := xyrange * (float64(j)/cells - 0.5)// Compute surface height z.z := f(x, y)// Project (x,y,z) isometrically onto 2-D SVG canvas (sx,sy).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) // distance from (0,0)return math.Sin(r) / r }
  • 要注意的是corner函數返回了兩個結果, 分別對應每個網格頂點的坐標參數。

  • 要解釋這個程序是如何工作的需要一些基本的幾何學知識, 但是我們可以跳過幾何學原理,因為程序的重點是演示浮點數運算。 程序的本質是三個不同的坐標系中映射關系, 如圖3.2所示。 第一個是100x100的二維網格, 對應整數整數坐標(i,j), 從遠處的(0, 0)位置開始。 我們從遠處向前面繪制, 因此遠處先繪制的多邊形有可能被前面后繪制的多邊形覆蓋。

  • 第二個坐標系是一個三維的網格浮點坐標(x,y,z), 其中x和y是i和j的線性函數, 通過平移轉換位網格單元的中心, 然后用xyrange系數縮放。 高度z是函數f(x,y)的值。

  • 第三個坐標系是一個二維的畫布, 起點(0,0)在左上角。 畫布中點的坐標用(sx, sy)表示。 我們使用等角投影將三維點

  • (x,y,z)投影到二維的畫布中。 畫布中從遠處到右邊的點對應較大的x值和較大的y值。 并且畫布中x和y值越大, 則對應的z值越小。 x和y的垂直和水平縮放系數來自30度角的正弦和余弦值。z的縮放系數0.4, 是一個任意選擇的參數。

  • 對于二維網格中的每一個網格單元, main函數計算單元的四個頂點在畫布中對應多邊形ABCD的頂點, 其中B對應(i,j)頂點位置, A、 C和D是其它相鄰的頂點, 然后輸出SVG的繪制指令。


  • 3. 復數

  • Go語言提供了兩種精度的復數類型: complex64和complex128, 分別對應float32和float64兩種浮點數精度。 內置的complex函數用于構建復數, 內建的real和imag函數分別返回復數的實部和虛部:

  • 如果一個浮點數面值或一個十進制整數面值后面跟著一個i, 例如3.141592i或2i, 它將構成一個復數的虛部, 復數的實部是0:

  • 在常量算術規則下, 一個復數常量可以加到另一個普通數值常量( 整數或浮點數、 實部或虛部) , 我們可以用自然的方式書寫復數, 就像1+2i或與之等價的寫法2i+1。 上面x和y的聲明語句還可以簡化:

  • 復數也可以用==和!=進行相等比較。 只有兩個復數的實部和虛部都相等的時候它們才是相等的( 譯注: 浮點數的相等比較是危險的, 需要特別小心處理精度問題) 。

  • math/cmplx包提供了復數處理的許多函數, 例如求復數的平方根函數和求冪函數。

  • 下面的程序使用complex128復數算法來生成一個Mandelbrot圖像。

  • gopl.io/ch3/mandelbrot

    // Mandelbrot emits a PNG image of the Mandelbrot fractal. 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)// Image point (px, py) represents complex value z.img.Set(px, py, mandelbrot(z))}}png.Encode(os.Stdout, img) // NOTE: ignoring errors }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 }//!-// Some other interesting functions: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} }// f(x) = x^4 - 1 // // z' = z - f(z)/f'(z) // = z - (z^4 - 1) / (4 * z^3) // = z - (z - 1/z^3) / 4 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圖像每個點的兩個嵌套的循環對應-2到+2區間的復數平面。 程序反復測試每個點對應復數值平方值加一個增量值對應的點是否超出半徑為2的圓。 如果超過了, 通過根據預設置的逃逸迭代次數對應的灰度顏色來代替。 如果不是, 那么該點屬于Mandelbrot集合, 使用黑色顏色標記。 最終程序將生成的PNG格式分形圖像圖像輸出到標準輸出, 如圖3.3所示。


  • 4. 布爾型

  • 一個布爾類型的值只有兩種: true和false。 if和for語句的條件部分都是布爾類型的值, 并且==和<等比較操作也會產生布爾型的值。 一元操作符! 對應邏輯非操作, 因此 !true 的值為 false , 更羅嗦的說法是 (!true==false)==true , 雖然表達方式不一樣, 不過我們一般會采用簡潔的布爾表達式, 就像用x來表示 x==true 。
  • 布爾值可以和&&( AND) 和||( OR) 操作符結合, 并且可能會有短路行為: 如果運算符左邊值已經可以確定整個布爾表達式的值, 那么運算符右邊的值將不在被求值, 因此下面的表達式總是安全的:
  • 其中s[0]操作如果應用于空字符串將會導致panic異常。
  • 因為 && 的優先級比 || 高( 助記: && 對應邏輯乘法, || 對應邏輯加法, 乘法比加法優先級要高) , 下面形式的布爾表達式是不需要加小括弧的:
  • 布爾值并不會隱式轉換為數字值0或1, 反之亦然。 必須使用一個顯式的if語句輔助轉換:
  • 如果需要經常做類似的轉換, 包裝成一個函數會更方便:
  • 數字到布爾型的逆轉換則非常簡單, 不過為了保持對稱, 我們也可以包裝一個函數:

  • 5. 字符串

  • 一個字符串是一個不可改變的字節序列。 字符串可以包含任意的數據, 包括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個字節。

  • 同樣, 如果索引超出字符串范圍或者j小于i的話將導致panic異常

  • 不管i還是j都可能被忽略, 當它們被忽略時將采用0作為開始位置, 采用len(s)作為結束的位置。

  • 其中+操作符將兩個字符串鏈接構造一個新字符串:

  • 字符串可以用==和<進行比較; 比較通過逐個字節比較完成的, 因此比較的結果是字符串自然編碼的順序。

  • 字符串的值是不可變的: 一個字符串包含的字節序列永遠不會被改變, 當然我們也可以給一個字符串變量分配一個新字符串值。 可以像下面這樣將一個字符串追加到另一個字符串:

  • 這并不會導致原始的字符串值被改變, 但是變量s將因為+=語句持有一個新的字符串值, 但是t依然是包含原先的字符串值。

  • 因為字符串是不可修改的, 因此嘗試修改字符串內部數據的操作也是被禁止的:

  • 不變性意味如果兩個字符串共享相同的底層數據的話也是安全的, 這使得復制任何長度的字符串代價是低廉的。 同樣, 一個字符串s和對應的子字符串切片s[7:]的操作也可以安全地共享相同的內存, 因此字符串切片操作代價也是低廉的。 在這兩種情況下都沒有必要分配新的內存。 圖3.4演示了一個字符串和兩個字串共享相同的底層數據。

  • 1. 字符串面值

  • 字符串值也可以用字符串面值方式編寫, 只要將一系列字節序列包含在雙引號即可:"hello world"
  • 因為Go語言源文件總是用UTF8編碼, 并且Go語言的文本字符串也以UTF8編碼的方式處理,因此我們可以將Unicode碼點也寫到字符串面值中。
  • 在一個雙引號包含的字符串面值中, 可以用以反斜杠 \ 開頭的轉義序列插入任意的數據。 下面的換行、 回車和制表符等是常見的ASCII控制代碼的轉義方式:
  • 符號含義
    \a響鈴
    \b退格
    \f換頁
    \n換行
    \r回車
    \t制表符
    \v垂直制表符
    單引號 (只用在 ‘’’ 形式的rune符號面值中)
    "雙引號 (只用在 “…” 形式的字符串面值中)
    \反斜杠
  • 可以通過十六進制或八進制轉義在字符串面值包含任意的字節。 一個十六進制的轉義形式是\xhh, 其中兩個h表示十六進制數字( 大寫或小寫都可以) 。 一個八進制轉義形式是\ooo, 包含三個八進制的o數字( 0到7) , 但是不能超過 \377 ( 譯注: 對應一個字節的范圍, 十進制為255) 。 每一個單一的字節表達一個特定的值。 稍后我們將看到如何將一個Unicode碼點寫到字符串面值中。
  • 一個原生的字符串面值形式是 … , 使用反引號 代替雙引號。 在原生的字符串面值中, 沒有轉義操作; 全部的內容都是字面的意思, 包含退格和換行, 因此一個程序中的原生字符串面值可能跨越多行( 注: 在原生字符串面值內部是無法直接寫 字符的, 可以用八進制或十六進制轉義或+"```"鏈接字符串常量完成) 。 唯一的特殊處理是會刪除回車以保證在所有平臺上的值都是一樣的, 包括那些把回車也放入文本文件的系統( 譯注: Windows系統會把回車和換行一起放入文本文件中) 。
  • 原生字符串面值用于編寫正則表達式會很方便, 因為正則表達式往往會包含很多反斜杠。 原生字符串面值同時被廣泛應用于HTML模板、 JSON面值、 命令行提示信息以及那些需要擴展到多行的場景。
  • 2. Unicode

  • 在很久以前, 世界還是比較簡單的, 起碼計算機世界就只有一個ASCII字符集: 美國信息交換標準代碼。 ASCII, 更準確地說是美國的ASCII, 使用7bit來表示128個字符: 包含英文字母的大小寫、 數字、 各種標點符號和設置控制符。 對于早期的計算機程序來說, 這些就足夠了,但是這也導致了世界上很多其他地區的用戶無法直接使用自己的符號系統。 隨著互聯網的發展, 混合多種語言的數據變得很常見( 譯注: 比如本身的英文原文或中文翻譯都包含了ASCII、 中文、 日文等多種語言字符) 。 如何有效處理這些包含了各種語言的豐富多樣的文本數據呢?
  • 答案就是使用Unicode( http://unicode.org ) , 它收集了這個世界上所有的符號系統, 包括重音符號和其它變音符號, 制表符和回車符, 還有很多神秘的符號, 每個符號都分配一個唯一的Unicode碼點, Unicode碼點對應Go語言中的rune整數類型( 譯注: rune是int32等價類型) 。
  • 我們可以將一個符文序列表示為一個int32序列。 這種編碼方式叫UTF-32或UCS-4, 每個Unicode碼點都使用同樣的大小32bit來表示。 這種方式比較簡單統一, 但是它會浪費很多存儲空間, 因為大數據計算機可讀的文本是ASCII字符, 本來每個ASCII字符只需要8bit或1字節就能表示。 而且即使是常用的字符也遠少于65,536個, 也就是說用16bit編碼方式就能表達常用字符。 但是, 還有其它更好的編碼方法嗎?
  • 3. UTF-8

  • UTF8是一個將Unicode碼點編碼為字節序列的變長編碼。 UTF8編碼由Go語言之父KenThompson和Rob Pike共同發明的, 現在已經是Unicode的標準。 UTF8編碼使用1到4個字節來表示每個Unicode碼點, ASCII部分字符只使用1個字節, 常用字符部分使用2或3個字節表示。 每個符號編碼后第一個字節的高端bit位用于表示總共有多少編碼個字節。 如果第一個字節的高端bit為0, 則表示對應7bit的ASCII字符, ASCII字符每個字符依然是一個字節, 和傳統的ASCII編碼兼容。 如果第一個字節的高端bit是110, 則說明需要2個字節; 后續的每個高端bit都以10開頭。 更大的Unicode碼點也是采用類似的策略處理。
  • 變長的編碼無法直接通過索引來訪問第n個字符, 但是UTF8編碼獲得了很多額外的優點。 首先UTF8編碼比較緊湊, 完全兼容ASCII碼, 并且可以自動同步: 它可以通過向前回朔最多2個字節就能確定當前字符編碼的開始字節的位置。 它也是一個前綴編碼, 所以當從左向右解碼時不會有任何歧義也并不需要向前查看( 譯注: 像GBK之類的編碼, 如果不知道起點位置則可能會出現歧義) 。 沒有任何字符的編碼是其它字符編碼的子串, 或是其它編碼序列的字串, 因此搜索一個字符時只要搜索它的字節編碼序列即可, 不用擔心前后的上下文會對搜索結果產生干擾。 同時UTF8編碼的順序和Unicode碼點的順序一致, 因此可以直接排序UTF8編碼序列。 同時因為沒有嵌入的NUL(0)字節, 可以很好地兼容那些使用NUL作為字符串結尾的編程語言。
  • Go語言的源文件采用UTF8編碼, 并且Go語言處理UTF8編碼的文本也很出色。 unicode包提供了諸多處理rune字符相關功能的函數( 比如區分字母和數組, 或者是字母的大寫和小寫轉換等) , unicode/utf8包則提供了用于rune字符序列的UTF8編碼和解碼的功能。
  • 有很多Unicode字符很難直接從鍵盤輸入, 并且還有很多字符有著相似的結構; 有一些甚至是不可見的字符( 譯注: 中文和日文就有很多相似但不同的字) 。 Go語言字符串面值中的Unicode轉義字符讓我們可以通過Unicode碼點輸入特殊的字符。 有兩種形式: \uhhhh對應16bit的碼點值, \Uhhhhhhhh對應32bit的碼點值, 其中h是一個十六進制數字; 一般很少需要使用32bit的形式。 每一個對應碼點的UTF8編碼。 例如: 下面的字母串面值都表示相同的值:
  • 上面三個轉義序列都為第一個字符串提供替代寫法, 但是它們的值都是相同的。
  • Unicode轉義也可以使用在rune字符中。 下面三個字符是等價的:
  • 對于小于256碼點值可以寫在一個十六進制轉義字節中, 例如’\x41’對應字符’A’, 但是對于更大的碼點則必須使用\u或\U轉義形式。 因此, '\xe4\xb8\x96’并不是一個合法的rune字符, 雖然這三個字節對應一個有效的UTF8編碼的碼點。
  • 得益于UTF8編碼優良的設計, 諸多字符串操作都不需要解碼操作。 我們可以不用解碼直接測試一個字符串是否是另一個字符串的前綴:
  • 或者是后綴測試:
  • 或者是包含子串測試 :
  • 對于UTF8編碼后文本的處理和原始的字節處理邏輯是一樣的。 但是對應很多其它編碼則并不是這樣的。 ( 上面的函數都來自strings字符串處理包, 真實的代碼包含了一個用哈希技術優化的Contains 實現。 )
  • 另一方面, 如果我們真的關心每個Unicode字符, 我們可以使用其它處理方式。 考慮前面的第一個例子中的字符串, 它包混合了中西兩種字符。 圖3.5展示了它的內存表示形式。 字符串包含13個字節, 以UTF8形式編碼, 但是只對應9個Unicode字符:
  • 為了處理這些真實的字符, 我們需要一個UTF8解碼器。 unicode/utf8包提供了該功能, 我們可以這樣使用:
  • 每一次調用DecodeRuneInString函數都返回一個r和長度, r對應字符本身, 長度對應r采用UTF8編碼后的編碼字節數目。 長度可以用于更新第i個字符在字符串中的字節索引位置。 但是這種編碼方式是笨拙的, 我們需要更簡潔的語法。 幸運的是, Go語言的range循環在處理字符串的時候, 會自動隱式解碼UTF8字符串。 下面的循環運行如圖3.5所示; 需要注意的是對于非ASCII, 索引更新的步長將超過1個字節。

  • 我們可以使用一個簡單的循環來統計字符串中字符的數目, 像這樣:

  • 總結

    以上是生活随笔為你收集整理的《Go语言圣经》学习笔记 第三章 基础数据类型的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。