Go的研習筆記-day3(以Java的視角學習Go)

原文鏈接:https://github.com/unknwon/the-way-to-go_ZH_CN/blob/master/eBook/04.9.md

Go的語法:

一、文件名、關鍵字與標識符
Go 的源文件以 .go 爲後綴名存儲在計算機中,這些文件名均由小寫字母組成,如 scanner.go 。如果文件名由多個部分組成,則使用下劃線 _ 對它們進行分隔,如 scanner_test.go 。文件名不包含空格或其他特殊字符。
一個源文件可以包含任意多行的代碼,Go 本身沒有對源文件的大小進行限制。
你會發現在 Go 代碼中的幾乎所有東西都有一個名稱或標識符。另外,Go 語言也是區分大小寫的,這與 C 家族中的其它語言相同。有效的標識符必須以字母(可以使用任何 UTF-8 編碼的字符或 _)開頭,然後緊跟着 0 個或多個字符或 Unicode 數字,如:X56、group1、_x23、i、өԑ12
以下是無效的標識符:

  • 1ab(以數字開頭)
  • case(Go 語言的關鍵字)
  • a+b(運算符是不允許的)
    _ 本身就是一個特殊的標識符,被稱爲空白標識符。它可以像其他標識符那樣用於變量的聲明或賦值(任何類型都可以賦值給它),但任何賦給這個標識符的值都將被拋棄,因此這些值不能在後續的代碼中使用,也不可以使用這個標識符作爲變量對其它變量進行賦值或運算。
    在編碼過程中,你可能會遇到沒有名稱的變量、類型或方法。雖然這不是必須的,但有時候這樣做可以極大地增強代碼的靈活性,這些變量被統稱爲匿名變量。
    下面列舉了 Go 代碼中會使用到的 25 個關鍵字或保留字:
Java中經常用的 go使用的Java無的(除goto)
break select
default map
import go
interface struct
case chan
for fallthrough
else range
continue type
package goto
switch defer
return func
if const
var

之所以刻意地將 Go 代碼中的關鍵字保持的這麼少,是爲了簡化在編譯過程第一步中的代碼解析。和其它語言一樣,關鍵字不能夠作標識符使用。
除了以上介紹的這些關鍵字,Go 語言還有 36 個預定義標識符,其中包含了基本類型的名稱和一些基本的內置函數在這裏插入圖片描述
程序一般由關鍵字、常量、變量、運算符、類型和函數組成。
程序中可能會使用到這些分隔符:括號 (),中括號 [] 和大括號 {}。
程序中可能會使用到這些標點符號:.、,、;、: 和 …。
程序的代碼通過語句來實現結構化。每個語句不需要像 C 家族中的其它語言一樣以分號 ; 結尾,因爲這些工作都將由 Go 編譯器自動完成。
如果你打算將多個語句寫在同一行,它們則必須使用 ; 人爲區分

二、Go程序的基本結構和要素

package main

import "fmt"

func main() {
	fmt.Println("hello, world")
}
  • 包的概念、導入與可見性
  • 一個包package可以有多個.go的文件與Java類似一個包下多個.java文件。道理相通。
  • 在源文件中非註釋的第一行指明這個文件屬於哪個包,如:package main。package main表示一個可獨立執行的程序,每個 Go 應用程序都包含一個名爲 main 的包。這一步驟是必須的。
  • 一個應用程序可以包含不同的包,而且即使你只使用 main 包也不必把所有的代碼都寫在一個巨大的文件裏:你可以用一些較小的文件,並且在每個文件非註釋的第一行都使用 package main 來指明這些文件都屬於 main 包。如果你打算編譯包名不是爲 main 的源文件,如 pack1,編譯後產生的對象文件將會是 pack1.a 而不是可執行程序。另外要注意的是,所有的包名都應該使用小寫字母
  • 標準庫
    在 Go 的安裝文件裏包含了一些可以直接使用的包,即標準庫。在 Windows 下,標準庫的位置在 Go 根目錄下的子目錄 pkg\windows_386 中;在 Linux 下,標準庫在 Go 根目錄下的子目錄 pkg\linux_amd64 中(如果是安裝的是 32 位,則在 linux_386 目錄中)。一般情況下,標準包會存放在 $ GOROOT/pkg/$ GOOS_$GOARCH/ 目錄下。
    Go 的標準庫包含了大量的包(如:fmt 和 os),但是你也可以創建自己的包。
    如果想要構建一個程序,則包和包內的文件都必須以正確的順序進行編譯。包的依賴關係決定了其構建順序。
    屬於同一個包的源文件必須全部被一起編譯,一個包即是編譯時的一個單元,因此根據慣例,每個目錄都只包含一個包。
    如果對一個包進行更改或重新編譯,所有引用了這個包的客戶端程序都必須全部重新編譯。
    Go 中的包模型採用了顯式依賴關係的機制來達到快速編譯的目的,編譯器會從後綴名爲 .o 的對象文件(需要且只需要這個文件)中提取傳遞依賴類型的信息。
    如果 A.go 依賴 B.go,而 B.go 又依賴 C.go:
    編譯 C.go, B.go, 然後是 A.go.
    爲了編譯 A.go, 編譯器讀取的是 B.o 而不是 C.o.
    這種機制對於編譯大型的項目時可以顯著地提升編譯速度。
    每一段代碼只會被編譯一次
    一個 Go 程序是通過 import 關鍵字將一組包鏈接在一起。
    import “fmt” 告訴 Go 編譯器這個程序需要使用 fmt 包(的函數,或其他元素),fmt 包實現了格式化 IO(輸入/輸出)的函數。包名被封閉在半角雙引號 “” 中。如果你打算從已編譯的包中導入並加載公開聲明的方法,不需要插入已編譯包的源代碼。
    如果需要多個包,它們可以被分別導入:
    import “fmt”
    import “os”
    或:
    import “fmt”; import “os”
    但是還有更短且更優雅的方法(被稱爲因式分解關鍵字,該方法同樣適用於 const、var 和 type 的聲明或定義):
    import (
    “fmt”
    “os”
    )
    它甚至還可以更短的形式,但使用 gofmt 後將會被強制換行:
    import (“fmt”; “os”)
    當你導入多個包時,最好按照字母順序排列包名,這樣做更加清晰易讀。
    如果包名不是以 . 或 / 開頭,如 “fmt” 或者 “container/list”,則 Go 會在全局文件進行查找;如果包名以 ./ 開頭,則 Go 會在相對目錄中查找;如果包名以 / 開頭(在 Windows 下也可以這樣使用),則會在系統的絕對路徑中查找。
    導入包即等同於包含了這個包的所有的代碼對象。
    除了符號 _,包中所有代碼對象的標識符必須是唯一的,以避免名稱衝突。但是相同的標識符可以在不同的包中使用,因爲可以使用包名來區分它們。
  • 可見性規則
    當標識符(包括常量、變量、類型、函數名、結構字段等等)以一個大寫字母開頭,如:Group1,那麼使用這種形式的標識符的對象就可以被外部包的代碼所使用(客戶端程序需要先導入這個包),這被稱爲導出(像面嚮對象語言中的 public);標識符如果以小寫字母開頭,則對包外是不可見的,但是他們在整個包的內部是可見並且可用的(像面嚮對象語言中的 private )。
    (大寫字母可以使用任何 Unicode 編碼的字符,比如希臘文,不僅僅是 ASCII 碼中的大寫字母)。
    因此,在導入一個外部包後,能夠且只能夠訪問該包中導出的對象。
    假設在包 pack1 中我們有一個變量或函數叫做 Thing(以 T 開頭,所以它能夠被導出),那麼在當前包中導入 pack1 包,Thing 就可以像面嚮對象語言那樣使用點標記來調用:pack1.Thing(pack1 在這裏是不可以省略的)。
    因此包也可以作爲命名空間使用,幫助避免命名衝突(名稱衝突):兩個包中的同名變量的區別在於他們的包名,例如 pack1.Thing 和 pack2.Thing。
    你可以通過使用包的別名來解決包名之間的名稱衝突,或者說根據你的個人喜好對包名進行重新設置,如:import fm “fmt”。
    package main
    import fm “fmt” // alias3
    func main() {
    fm.Println(“hello, world”)
    }
  • 注意:清除無用的包,Java中經常由於導入多個未使用的包需要清除加快編譯速度以及代碼整潔程度。go語言直接會將引入未使用的包以報錯信息編譯不通過。
  • 包的分級聲明和初始化
    你可以在使用 import 導入包之後定義或聲明 0 個或多個常量(const)、變量(var)和類型(type),這些對象的作用域都是全局的(在本包範圍內),所以可以被本包中所有的函數調用,然後聲明一個或多個函數(func)。
  • 函數
    func 聲明方法函數類似js的function或者Java中的自定義方法名稱。程序調用如果有init()方法會先執行init方法,再調用main方法。main函數與Java函數main方法一樣沒有返回值類型。否則報錯。函數通過大括號括起來,左大括號必須與方法聲明放在同一行(編譯器強制規定)。多個參數可以使用逗號分隔開,與Java和js一樣。但是區別是類型在後,參數在前相比於Java而言。
    Java的方法select(String a,String b); go的方法select(a string,b string)
    只有當某個函數需要被外部包調用的時候才使用大寫字母開頭,並遵循 Pascal 命名法;否則就遵循駱駝命名法,即第一個單詞的首字母小寫,其餘單詞的首字母大寫。
  • 調試
    Print 和 Println 這兩個函數也支持使用變量,如:fmt.Println(arr)。如果沒有特別指定,它們會以默認的打印格式將變量 arr 輸出到控制檯。程序正常退出的代碼爲 0 即 Program exited with code 0;如果程序因爲異常而被終止,則會返回非零值,如:1。這個數值可以用來測試是否成功執行一個程序。
  • 註釋
    與Java中的一樣,以 // 開頭的單行註釋。多行註釋也叫塊註釋,均已以 /* 開頭,並以 */ 結尾,且不可以嵌套使用,多行註釋一般用於包的文檔描述或註釋成塊的代碼片段。
    每一個包應該有相關注釋,在 package 語句之前的塊註釋將被默認認爲是這個包的文檔說明,其中應該提供一些相關信息並對整體功能做簡要的介紹。一個包可以分散在多個文件中,但是只需要在其中一個進行註釋說明即可。當開發人員需要了解包的一些情況時,自然會用 godoc 來顯示包的文檔說明,在首行的簡要註釋之後可以用成段的註釋來進行更詳細的說明,而不必擁擠在一起。另外,在多段註釋之間應以空行分隔加以區分。
    幾乎所有全局作用域的類型、常量、變量、函數和被導出的對象都應該有一個合理的註釋。如果這種註釋(稱爲文檔註釋)出現在函數前面,例如函數 Abcd,則要以 “Abcd…” 作爲開頭。
  • 類型
    變量(或常量)包含數據,這些數據可以有不同的數據類型,簡稱類型。使用 var 聲明的變量的值會自動初始化爲該類型的零值。類型定義了某個變量的值的集合與可對其進行操作的集合。
    類型可以是基本類型,如:int、float、bool、string;結構化的(複合的),如:struct、array、slice、map、channel;只描述類型的行爲的,如:interface。
    結構化的類型沒有真正的值,它使用 nil 作爲默認值(在 Objective-C 中是 nil,在 Java 中是 null,在 C 和 C++ 中是NULL或 0)。值得注意的是,Go 語言中不存在類型繼承。
    使用 type 關鍵字可以定義你自己的類型,你可能想要定義一個結構體
    使用var方式聲明變量
    多個類型需要定義,可以使用因式分解關鍵字的方式
    type (
    IZ int
    FZ float64
    STR string
    )
    每個值都必須在經過編譯後屬於某個類型(編譯器必須能夠推斷出所有值的類型),因爲 Go 語言是一種靜態類型語言。
  • 函數的返回值
這一點與Java不一樣,Java中經常以如下方式作爲返回
public Object  getReutrnType(){
return new Ojbect();
}

go語言返回值
func FunctionName (a typea, b typeb) typeFunc{
return var
}
而且go語言可以多個返回值
func Atoi(s string) (i int, err error){
return var1, var2
}

go的基本結構以及go程序的執行啓動順序

  • package的聲明
  • import其他包的導入
  • 常量const,變量var,類型type的定義和聲明
  • init函數(如果存在每個包都會首先執行該函數)
  • main包,則需要定義main函數
  • 定義其餘函數或者類型返回的方法,然後按照main函數中先後調用順序定義相關函數,如果很多函數,則按照字母順序來排序。
package main

import (
   "fmt"
)

const c = "C"

var v int = 5

type T struct{}

func init() { // initialization of package
}

func main() {
   var a int
   Func1()
   // ...
   fmt.Println(a)
}

func (t T) Method1() {
   //...
}

func Func1() { // exported function Func1
   //...
}

Go 程序的執行(程序啓動)順序如下:
1、按順序導入所有被 main 包引用的其它包,然後在每個包中執行如下流程:
2、如果該包又導入了其它的包,則從第一步開始遞歸執行,但是每個包只會被導入一次。
3、然後以相反的順序在每個包中初始化常量和變量,如果該包含有 init 函數的話,則調用該函數。
4、在完成這一切之後,main 也執行同樣的過程,最後調用 main 函數開始執行程序。

類型轉換
類型 B 的值 = 類型 B(類型 A 的值)
在必要以及可行的情況下,一個類型的值可以被轉換成另一種類型的值。由於 Go 語言不存在隱式類型轉換,因此所有的轉換都必須顯式說明,就像調用一個函數一樣(類型在這裏的作用可以看作是一種函數):
valueOfTypeB = typeB(valueOfTypeA)
在定義正確的情況下轉換成功,例如從一個取值範圍較小的類型轉換到一個取值範圍較大的類型(例如將 int16 轉換爲 int32)。當從一個取值範圍較大的轉換到取值範圍較小的類型時(例如將 int32 轉換爲 int16 或將 float32 轉換爲 int),會發生精度丟失(截斷)的情況。當編譯器捕捉到非法的類型轉換時會引發編譯時錯誤,否則將引發運行時錯誤。

Go命名規範
與Java這裏也有不太相同,go追求的是簡潔,乾淨,可讀。所以命名不建議太冗餘或者下劃線分隔。有必要可以大小寫混合。因爲其使用包名作爲了限定符。

三、常量
常量使用關鍵字 const 定義,用於存儲不會改變的數據。
存儲在常量中的數據類型只可以是布爾型、數字型(整數型、浮點型和複數)和字符串型。
常量的定義格式:const identifier [type] = value

在 Go 語言中,你可以省略類型說明符 [type],因爲編譯器可以根據變量的值來推斷其類型。
顯式類型定義: const b string = “abc”
隱式類型定義: const b = “abc”
未定義類型的常量會在必要時刻根據上下文來獲得相關類型
var n int
f(n + 5) // 無類型的數字型常量 “5” 它的類型在這裏變成了 int
我覺得這點有點類似Java中根據上下文推斷是否需要指令重排序進行優化。

常量的值必須是能夠在編譯時就能夠確定的;因爲是一門靜態語言。
正確的做法:const c1 = 2/3
錯誤的做法:const c2 = getNumber() // 引發構建錯誤: getNumber() used as value
因爲在編譯期間自定義函數均屬於未知,因此無法用於常量的賦值,但內置函數可以使用,如:len()。
數字型的常量是沒有大小和符號的,並且可以使用任何精度而不會導致溢出:

反斜槓 \ 可以在常量表達式中作爲多行的連接符使用。
const Ln2= 0.693147180559945309417232121458\
			176568075500134360255254120680009
const Log2E= 1/Ln2 // this is a precise reciprocal
const Billion = 1e9 // float constant
const hardEight = (1 << 100) >> 97

當常量賦值給一個精度過小的數字型變量時,可能會因爲無法正確表達常量所代表的數值而導致溢出,這會在編譯期間就引發錯誤。另外,常量也允許使用並行賦值的形式:

const beef, two, c = "eat", 2, "veg"
const Monday, Tuesday, Wednesday, Thursday, Friday, Saturday = 1, 2, 3, 4, 5, 6
const (
	Monday, Tuesday, Wednesday = 1, 2, 3
	Thursday, Friday, Saturday = 4, 5, 6
)

常量還可以用作枚舉:

const (
	Unknown = 0
	Female = 1
	Male = 2
)
如果使用iota表達的話則可以表達如下
const (
	Unknown = iota
	Female = iota
	Male = iota
)
iota 也可以用在表達式中,如:iota + 50。在每遇到一個新的常量塊或單個常量聲明時, iota 都會重置爲 0( 簡單地講,每遇到一次 const 關鍵字,iota 就重置爲 0 )。

四、變量(除了類型在後聲明,其他都類似相同。)
聲明變量的一般形式是使用 var 關鍵字:var identifier type
var str string
你也可以改寫成這種形式:
var (
a int
b bool
str string
)
這種因式分解關鍵字的寫法一般用於聲明全局變量。
當一個變量被聲明之後,系統自動賦予它該類型的零值:int 爲 0,float 爲 0.0,bool 爲 false,string 爲空字符串,指針爲 nil。記住,所有的內存在 Go 中都是經過初始化的。
變量的命名規則遵循駱駝命名法,即首個單詞小寫,每個新單詞的首字母大寫,例如:numShips 和 startDate。
一個變量(常量、類型或函數)在程序中都有一定的作用範圍,稱之爲作用域。如果一個變量在函數體外聲明,則被認爲是全局變量,可以在整個包甚至外部包(被導出後)使用,不管你聲明在哪個源文件裏或在哪個源文件裏調用該變量。
在函數體內聲明的變量稱之爲局部變量,它們的作用域只在函數體內,參數和返回值變量也是局部變量。
儘管變量的標識符必須是唯一的,但你可以在某個代碼塊的內層代碼塊中使用相同名稱的變量,則此時外部的同名變量將會暫時隱藏(結束內部代碼塊的執行後隱藏的外部同名變量又會出現,而內部同名變量則被釋放),你任何的操作都只會影響內部代碼塊的局部變量。
變量可以編譯期間就被賦值,賦值給變量使用運算符等號 =,當然你也可以在運行時對變量進行賦值操作。但是 Go 編譯器的智商已經高到可以根據變量的值來自動推斷其類型,這有點像 Ruby 和 Python 這類動態語言,只不過它們是在運行時進行推斷,而 Go 是在編譯時就已經完成推斷過程。
全局變量中聲明變量 var a int64 =1;如果是局部變量可以改爲 a:=1;

值類型和引用類型
內存在計算機中使用一堆箱子來表示,這些箱子被稱爲 “ 字 ”。根據不同的處理器以及操作系統類型,所有的字都具有 32 位(4 字節)或 64 位(8 字節)的相同長度;所有的字都使用相關的內存地址來進行表示(以十六進制數表示)。所有像 int、float、bool 和 string 這些基本類型都屬於值類型,使用這些類型的變量直接指向存在內存中的值
在這裏插入圖片描述
另外,像數組和結構這些複合類型也是值類型。
當使用等號 = 將一個變量的值賦值給另一個變量時,如:j = i,實際上是在內存中將 i 的值進行了拷貝:
在這裏插入圖片描述
你可以通過 &i 來獲取變量 i 的內存地址,值類型的變量的值存儲在棧中。這個時候你類比Java感受到了什麼?我們的基本變量類型其實就對應着go語言的值類型。
內存地址會根據機器的不同而有所不同,甚至相同的程序在不同的機器上執行後也會有不同的內存地址。因爲每臺機器可能有不同的存儲器佈局,並且位置分配也可能不同。
更復雜的數據通常會需要使用多個字,這些數據一般使用引用類型保存。一個引用類型的變量 r1 存儲的是 r1 的值所在的內存地址(數字),或內存地址中第一個字所在的位置。
在這裏插入圖片描述
這個內存地址被稱之爲指針,這個指針實際上也被存在另外的某一個字中。
同一個引用類型的指針指向的多個字可以是在連續的內存地址中(內存佈局是連續的),這也是計算效率最高的一種存儲形式;也可以將這些字分散存放在內存中,每個字都指示了下一個字所在的內存地址。
當使用賦值語句 r2 = r1 時,只有引用(地址)被複制。如果 r1 的值被改變了,那麼這個值的所有引用都會指向被修改後的內容,在這個例子中,r2 也會受到影響
在 Go 語言中,指針屬於引用類型,其它的引用類型還包括 slices,maps和 channel。被引用的變量會存儲在堆中,以便進行垃圾回收,且比棧擁有更大的內存空間。這樣的話你如果類比Java的話會發現這裏強調的指針其實類型Java對象,而對象的傳遞也是引用的傳遞。Java對象同樣保存在堆內存中,以便進行垃圾回收。這裏也可以看到是Go語言也充分利用了Java的一些特性比如垃圾回收機制。

打印
函數 Printf 可以在 fmt 包外部使用,這是因爲它以大寫字母 P 開頭,該函數主要用於打印輸出到控制檯。通常使用的格式化字符串作爲第一個參數:
func Printf(format string, list of variables to be printed)
函數 fmt.Sprintf 與 Printf 的作用是完全相同的,不過前者將格式化後的字符串以返回值的形式返回給調用者
:=賦值操作符
初始化聲明:使用操作符 := 可以高效地創建一個新的變量
注意事項
如果在相同的代碼塊中,我們不可以再次對於相同名稱的變量使用初始化聲明,例如:a := 20 就是不被允許的,編譯器會提示錯誤 no new variables on left side of :=,但是 a = 20 是可以的,因爲這是給相同的變量賦予一個新的值。
如果你在定義變量 a 之前使用它,則會得到編譯錯誤 undefined: a
如果你聲明瞭一個局部變量卻沒有在相同的代碼塊中使用它,同樣會得到編譯錯誤,例如下面這個例子當中的變量 a:
func main() {
var a string = “abc”
fmt.Println(“hello, world”)
}
此外,單純地給 a 賦值也是不夠的,這個值必須被使用,所以使用 fmt.Println(“hello, world”, a) 會移除錯誤。
但是全局變量是允許聲明但不使用。
並行 或 同時 賦值
a, b, c := 5, 7, "abc"右邊的這些值以相同的順序賦值給左邊的變量,所以 a 的值是 5, b 的值是 7,c 的值是 “abc”。

init函數
變量除了可以在全局聲明中初始化,也可以在 init 函數中初始化。這是一類非常特殊的函數,它不能夠被人爲調用,而是在每個包完成初始化後自動執行,並且執行優先級比 main 函數高。
每個源文件都只能包含一個 init 函數。初始化總是以單線程執行,並且按照包的依賴關係順序執行。
一個可能的用途是在開始執行程序之前對數據進行檢驗或修復,以保證程序狀態的正確性。

五、 基本類型和運算符
一元運算符只可以用於一個值的操作(作爲後綴),而二元運算符則可以和兩個值或者操作數結合(作爲中綴)。
只有兩個類型相同的值纔可以和二元運算符結合,另外要注意的是,Go 是強類型語言,因此不會進行隱式轉換,任何不同類型之間的轉換都必須顯式說明Go 不存在像 C 和 Java 那樣的運算符重載,表達式的解析順序是從左至右。

  • 布爾類型bool
    布爾型的值只可以是常量 true 或者 false。
    兩個類型相同的值可以使用相等 == 或者不等 != 運算符來進行比較並獲得一個布爾型的值。
    當相等運算符兩邊的值是完全相同的值的時候會返回 true,否則返回 false,並且只有在兩個的值的類型相同的情況下纔可以使用。
var aVar = 10
aVar == 5 -> false
aVar == 10 -> true

當不等運算符兩邊的值是不同的時候會返回 true,否則返回 false。

var aVar = 10
aVar != 5 -> true
aVar != 10 -> false

Go 對於值之間的比較有非常嚴格的限制,只有兩個類型相同的值纔可以進行比較,如果值的類型是接口(interface),它們也必須都實現了相同的接口。如果其中一個值是常量,那麼另外一個值的類型必須和該常量類型相兼容的。如果以上條件都不滿足,則其中一個值的類型必須在被轉換爲和另外一個值的類型相同之後纔可以進行比較。
布爾型的常量和變量也可以通過和邏輯運算符(非 !、和 &&、或 ||)結合來產生另外一個布爾值,這樣的邏輯語句就其本身而言,並不是一個完整的 Go 語句。
邏輯值可以被用於條件結構中的條件語句,以便測試某個條件是否滿足。另外,和 &&、或 || 與相等 == 或不等 != 屬於二元運算符,而非 ! 屬於一元運算符。在接下來的內容中,我們會使用 T 來代表條件符合的語句,用 F 來代表條件不符合的語句。
在 Go 語言中,&& 和 || 是具有快捷性質的運算符,當運算符左邊表達式的值已經能夠決定整個表達式的值的時候(&& 左邊的值爲 false,|| 左邊的值爲 true),運算符右邊的表達式將不會被執行。利用這個性質,如果你有多個條件判斷,應當將計算過程較爲複雜的表達式放在運算符的右側以減少不必要的運算。
而這些運算其實類比Java語言也是相同的。

數字類型

  • 整型int和浮點型float
    Go 語言支持整型和浮點型數字,並且原生支持複數,其中位的運算採用補碼(詳情參見 二的補碼 頁面)
    Go 也有基於架構的類型,例如:int、uint 和 uintptr
    這些類型的長度都是根據運行程序所在的操作系統類型所決定的:
    int 和 uint 在 32 位操作系統上,它們均使用 32 位(4 個字節),在 64 位操作系統上,它們均使用 64 位(8 個字節)。
    uintptr 的長度被設定爲足夠存放一個指針即可。
    Go 語言中沒有 float 類型。(Go語言中只有 float32 和 float64)沒有double類型。
    與操作系統架構無關的類型都有固定的大小,並在類型的名稱中就可以看出來。
  • 整數:
    int8(-128 -> 127)
    int16(-32768 -> 32767)
    int32(-2,147,483,648 -> 2,147,483,647)
    int64(-9,223,372,036,854,775,808 -> 9,223,372,036,854,775,807)
  • 無符號整數:
    uint8(0 -> 255)
    uint16(0 -> 65,535)
    uint32(0 -> 4,294,967,295)
    uint64(0 -> 18,446,744,073,709,551,615)
  • 浮點型(IEEE-754 標準):
    float32(± 1e-45 -> ± 3.4 * 1e38)
    float64(± 5 * 1e-324 -> 107 * 1e308)

int 型是計算最快的一種類型。
整型的零值爲 0,浮點型的零值爲 0.0。
float32 精確到小數點後 7 位,float64 精確到小數點後 15 位。由於精確度的緣故,你在使用 == 或者 != 來比較浮點數時應當非常小心。你最好在正式使用前測試對於精確度要求較高的運算。
你應該儘可能地使用 float64,因爲 math 包中所有有關數學運算的函數都會要求接收這個類型。
你可以通過增加前綴 0 來表示 8 進制數(如:077),增加前綴 0x 來表示 16 進制數(如:0xFF),以及使用 e 來表示 10 的連乘(如: 1e3 = 1000,或者 6.022e23 = 6.022 x 1e23)。
你可以使用 a := uint64(0) 來同時完成類型轉換和賦值操作,這樣 a 的類型就是 uint64。

位運算
位運算只能用於整數類型的變量,且需當它們擁有等長位模式時
%b 是用於表示位的格式化標識符。

  • 二元運算符
  • 按位與 &
  1 & 1 -> 1
  1 & 0 -> 0
  0 & 1 -> 0
  0 & 0 -> 0
  • 按位或 |
  1 | 1 -> 1
  1 | 0 -> 1
  0 | 1 -> 1
  0 | 0 -> 0
  • 按位異或 ^
 1 ^ 1 -> 0
  1 ^ 0 -> 1
  0 ^ 1 -> 1
  0 ^ 0 -> 0
  • 位清除 &^:將指定位置上的值設置爲 0
  • 一元運算符
  • 按位補足 ^
    該運算符與異或運算符一同使用,即 m^x,對於無符號 x 使用“全部位設置爲 1”,對於有符號 x 時使用 m=-1
    ^10 = -01 ^ 10 = -11
  • 位左移 <<:
    用法:bitP << n。
    bitP 的位向左移動 n 位,右側空白部分使用 0 填充;如果 n 等於 2,則結果是 2 的相應倍數,即 2 的 n 次方。例如:
    1 << 10 // 等於 1 KB
    1 << 20 // 等於 1 MB
    1 << 30 // 等於 1 GB
  • 位右移 >>:
    用法:bitP >> n。
    bitP 的位向右移動 n 位,左側空白部分使用 0 填充;如果 n 等於 2,則結果是當前值除以 2 的 n 次方。
  • [ ]位左移常見實現存儲單位的用例
    使用位左移與 iota 計數配合可優雅地實現存儲單位的常量枚舉:
    type ByteSize float64
    const (
    _ = iota // 通過賦值給空白標識符來忽略值
    KB ByteSize = 1<<(10*iota)
    MB
    GB
    TB
    PB
    EB
    ZB
    YB
    )
    在通訊中使用位左移表示標識的用例
    type BitFlag int
    const (
    Active BitFlag = 1 << iota // 1 << 0 == 1
    Send // 1 << 1 == 2
    Receive // 1 << 2 == 4
    )
    flag := Active | Send // == 3
  • 邏輯運算符
    Go 中擁有以下邏輯運算符:==、!=、<、<=、>、>=。
    它們之所以被稱爲邏輯運算符是因爲它們的運算結果總是爲布爾值 bool
  • 算術運算符
    常見可用於整數和浮點數的二元運算符有 +、-、* 和 /。
    (相對於一般規則而言,Go 在進行字符串拼接時允許使用對運算符 + 的重載,但 Go 本身不允許開發者進行自定義的運算符重載)
    / 對於整數運算而言,結果依舊爲整數,例如:9 / 4 -> 2。
    取餘運算符只能作用於整數:9 % 4 -> 1。
    整數除以 0 可能導致程序崩潰,將會導致運行時的恐慌狀態(如果除以 0 的行爲在編譯時就能被捕捉到,則會引發編譯錯誤);
    浮點數除以 0.0 會返回一個無窮盡的結果,使用 +Inf 表示
  • 隨機數
    rand 包實現了僞隨機數的生成。
  • 運算符優先級
    二元運算符的運算方向均是從左至右。下表列出了所有運算符以及它們的優先級,由上至下代表優先級由高到低:
    在這裏插入圖片描述
    通過使用括號來臨時提升某個表達式的整體運算優先級。
  • 類型別名
    在 type TZ int 中,TZ 就是 int 類型的新名稱(用於表示程序中的時區),然後就可以使用 TZ 來操作 int 類型的數據。
  • 字符類型
    byte 類型是 uint8 的別名,對於只佔用 1 個字節的傳統 ASCII 編碼的字符來說,完全沒有問題。例如:var ch byte = ‘A’;字符使用單引號括起來。
    格式化說明符 %c 用於表示字符;當和字符配合使用時,%v 或 %d 會輸出用於表示該字符的整數;%U 輸出格式爲 U+hhhh 的字符串。
    包 unicode 包含了一些針對測試字符的非常有用的函數(其中 ch 代表字符):
    判斷是否爲字母:unicode.IsLetter(ch)
    判斷是否爲數字:unicode.IsDigit(ch)
    判斷是否爲空白符號:unicode.IsSpace(ch)

六、字符串
字符串是 UTF-8 字符的一個序列(當字符爲 ASCII 碼時則佔用 1 個字節,其它字符根據需要佔用 2-4 個字節)。UTF-8 是被廣泛使用的編碼格式,是文本文件的標準編碼,其它包括 XML 和 JSON 在內,也都使用該編碼。由於該編碼對佔用字節長度的不定性,Go 中的字符串裏面的字符也可能根據需要佔用 1 至 4 個字節,這與其它語言如 C++、Java 或者 Python 不同(Java 始終使用 2 個字節)。Go 這樣做的好處是不僅減少了內存和硬盤空間佔用,同時也不用像其它語言那樣需要對使用 UTF-8 字符集的文本進行編碼和解碼
字符串是一種值類型,且值不可變,即創建某個文本後你無法再次修改這個文本的內容;更深入地講,字符串是字節的定長數組。

  • Go 支持以下 2 種形式的字面值:
  • 解釋字符串:
    該類字符串使用雙引號括起來,其中的相關的轉義字符將被替換,這些轉義字符包括:
    \n:換行符
    \r:回車符
    \t:tab 鍵
    \u 或 \U:Unicode 字符
    \:反斜槓自身
  • 非解釋字符串:
    該類字符串使用反引號括起來,支持換行,例如:
    This is a raw string \n 中的 \n\ 會被原樣輸出。

和 C/C++不一樣,Go 中的字符串是根據長度限定,而非特殊字符\0。
string 類型的零值爲長度爲零的字符串,即空字符串 “”。
一般的比較運算符(==、!=、<、<=、>=、>)通過在內存中按字節比較來實現字符串的對比。你可以通過函數 len() 來獲取字符串所佔的字節長度,例如:len(str)
對純 ASCII 碼的字符串,字符串 str 的第 1 個字節:str[0],最後 1 個字節:str[len(str)-1]

== 注意事項 獲取字符串中某個字節的地址的行爲是非法的,例如:&str[i]==

字符串拼接符 +
str := "Beginning of the string " +
“second part of the string”
s := “hel” + “lo,”
s += “world!”
fmt.Println(s) //輸出 “hello, world!”

這裏也和Java相通,唯一優化點Java可以通過StringBuilder或者StringBuffer類拼接

strings和strconv包
作爲一種基本數據結構,每種語言都有一些對於字符串的預定義處理函數。Go 中使用 strings 包來完成對字符串的主要操作。這樣類似Java中的string的自己帶的方法

  • HasPrefix 判斷字符串 s 是否以 prefix 開頭:
    strings.HasPrefix(s, prefix string) bool
  • HasSuffix 判斷字符串 s 是否以 suffix 結尾:
    strings.HasSuffix(s, suffix string) bool
  • Contains 判斷字符串 s 是否包含 substr:
    strings.Contains(s, substr string) bool
  • 判斷子字符串或字符在父字符串中出現的位置(索引)
    Index 返回字符串 str 在字符串 s 中的索引(str 的第一個字符的索引),-1 表示字符串 s 不包含字符串 str:
    strings.Index(s, str string) int
    LastIndex 返回字符串 str 在字符串 s 中最後出現位置的索引(str 的第一個字符的索引),-1 表示字符串 s 不包含字符串 str:
    strings.LastIndex(s, str string) int
    如果需要查詢非 ASCII 編碼的字符在父字符串中的位置,建議使用以下函數來對字符進行定位:
    strings.IndexRune(s string, r rune) int
  • 字符串替換
    Replace 用於將字符串 str 中的前 n 個字符串 old 替換爲字符串 new,並返回一個新的字符串,如果 n = -1 則替換所有字符串 old 爲字符串 new:
    strings.Replace(str, old, new, n) string
  • 統計字符串出現次數
    Count 用於計算字符串 str 在字符串 s 中出現的非重疊次數:
    strings.Count(s, str string) int
  • 重複字符串
    Repeat 用於重複 count 次字符串 s 並返回一個新的字符串:
    strings.Repeat(s, count int) string
  • 修改字符串大小寫
    ToLower 將字符串中的 Unicode 字符全部轉換爲相應的小寫字符:
    strings.ToLower(s) string
    ToUpper 將字符串中的 Unicode 字符全部轉換爲相應的大寫字符:
    strings.ToUpper(s) string
  • 修剪字符串
    你可以使用 strings.TrimSpace(s) 來剔除字符串開頭和結尾的空白符號;如果你想要剔除指定字符,則可以使用 strings.Trim(s, “cut”) 來將開頭和結尾的 cut 去除掉。該函數的第二個參數可以包含任何字符,如果你只想剔除開頭或者結尾的字符串,則可以使用 TrimLeft 或者 TrimRight 來實現。
  • 分割字符串
    strings.Fields(s) 將會利用 1 個或多個空白符號來作爲動態長度的分隔符將字符串分割成若干小塊,並返回一個 slice,如果字符串只包含空白符號,則返回一個長度爲 0 的 slice。
    strings.Split(s, sep) 用於自定義分割符號來對指定字符串進行分割,同樣返回 slice。因爲這 2 個函數都會返回 slice,所以習慣使用 for-range 循環來對其進行處理
  • 拼接 slice 到字符串
    Join 用於將元素類型爲 string 的 slice 使用分割符號來拼接組成一個字符串:
    strings.Join(sl []string, sep string) string
package main

import (
	"fmt"
	"strings"
)

func main() {
	str := "The quick brown fox jumps over the lazy dog"
	sl := strings.Fields(str)
	fmt.Printf("Splitted in slice: %v\n", sl)
	for _, val := range sl {
		fmt.Printf("%s - ", val)
	}
	fmt.Println()
	str2 := "GO1|The ABC of Go|25"
	sl2 := strings.Split(str2, "|")
	fmt.Printf("Splitted in slice: %v\n", sl2)
	for _, val := range sl2 {
		fmt.Printf("%s - ", val)
	}
	fmt.Println()
	str3 := strings.Join(sl2,";")
	fmt.Printf("sl2 joined by ;: %s\n", str3)
}
輸出結果:
Splitted in slice: [The quick brown fox jumps over the lazy dog]
The - quick - brown - fox - jumps - over - the - lazy - dog -
Splitted in slice: [GO1 The ABC of Go 25]
GO1 - The ABC of Go - 25 -
sl2 joined by ;: GO1;The ABC of Go;25
  • 從字符串中讀取內容
    函數 strings.NewReader(str) 用於生成一個 Reader 並讀取字符串中的內容,然後返回指向該 Reader 的指針,從其它類型讀取內容的函數還有:
    Read() 從 []byte 中讀取內容。
    ReadByte() 和 ReadRune() 從字符串中讀取下一個 byte 或者 rune。
  • 字符串與其它類型的轉換
    與字符串相關的類型轉換都是通過 strconv 包實現的。
    該包包含了一些變量用於獲取程序運行的操作系統平臺下 int 類型所佔的位數,如:strconv.IntSize。
    任何類型 T 轉換爲字符串總是成功的。
    針對從數字類型轉換到字符串,Go 提供了以下函數:
    strconv.Itoa(i int) string 返回數字 i 所表示的字符串類型的十進制數。
    strconv.FormatFloat(f float64, fmt byte, prec int, bitSize int) string 將 64 位浮點型的數字轉換爲字符串,其中 fmt 表示格式(其值可以是 ‘b’、‘e’、‘f’ 或 ‘g’),prec 表示精度,bitSize 則使用 32 表示 float32,用 64 表示 float64。
    將字符串轉換爲其它類型 tp 並不總是可能的,可能會在運行時拋出錯誤 parsing “…”: invalid argument。
    針對從字符串類型轉換爲數字類型,Go 提供了以下函數:
    strconv.Atoi(s string) (i int, err error) 將字符串轉換爲 int 型。
    strconv.ParseFloat(s string, bitSize int) (f float64, err error) 將字符串轉換爲 float64 型。
    利用多返回值的特性,這些函數會返回 2 個值,第 1 個是轉換後的結果(如果轉換成功),第 2 個是可能出現的錯誤,因此,我們一般使用以下形式來進行從字符串到其它類型的轉換:
    val, err = strconv.Atoi(s)

八、時間和日期
time 包爲我們提供了一個數據類型 time.Time(作爲值使用)以及顯示和測量時間和日期的功能函數。
當前時間可以使用 time.Now() 獲取,或者使用 t.Day()、t.Minute() 等等來獲取時間的一部分;你甚至可以自定義時間格式化字符串,例如: fmt.Printf("%02d.%02d.%4d\n", t.Day(), t.Month(), t.Year()) 將會輸出 21.07.2011。
Duration 類型表示兩個連續時刻所相差的納秒數,類型爲 int64。Location 類型映射某個時區的時間,UTC 表示通用協調世界時間。
包中的一個預定義函數 func (t Time) Format(layout string) string 可以根據一個格式化字符串來將一個時間 t 轉換爲相應格式的字符串,你可以使用一些預定義的格式,如:time.ANSIC 或 time.RFC822。
一般的格式化設計是通過對於一個標準時間的格式化描述來展現的,這聽起來很奇怪,但看下面這個例子你就會一目瞭然:
fmt.Println(t.Format(“02 Jan 2006 15:04”))

九、指針
程序在內存中存儲它的值,每個內存塊(或字)有一個地址,通常用十六進制數表示,如:0x6b0820 或 0xf84001d7f0。
Go 語言的取地址符是 &,放到一個變量前使用就會返回相應變量的內存地址
下面的代碼片段可能輸出 An integer: 5, its location in memory: 0x6b0820(這個值隨着你每次運行程序而變化)
var i1 = 5
fmt.Printf(“An integer: %d, it’s location in memory: %p\n”, i1, &i1)
這個地址可以存儲在一個叫做指針的特殊數據類型中,在本例中這是一個指向 int 的指針,即 i1:此處使用 *int 表示。如果我們想調用指針 intP,我們可以這樣聲明它:
var intP *int
然後使用 intP = &i1 是合法的,此時 intP 指向 i1。
(指針的格式化標識符爲 %p)
intP 存儲了 i1 的內存地址;它指向了 i1 的位置,它引用了變量 i1。
一個指針變量可以指向任何一個值的內存地址 它指向那個值的內存地址,在 32 位機器上佔用 4 個字節,在 64 位機器上佔用 8 個字節,並且與它所指向的值的大小無關。當然,可以聲明指針指向任何類型的值來表明它的原始性或結構性;你可以在指針類型前面加上 * 號(前綴)來獲取指針所指向的內容,這裏的 * 號是一個類型更改器。使用一個指針引用一個值被稱爲間接引用。
當一個指針被定義後沒有分配到任何變量時,它的值爲 nil。
一個指針變量通常縮寫爲 ptr。
注意事項
在書寫表達式類似 var p type 時,切記在 * 號和指針名稱間留有一個空格,因爲 - var ptype 是語法正確的,但是在更復雜的表達式中,它容易被誤認爲是一個乘法表達式!
符號 * 可以放在一個指針前,如 *intP,那麼它將得到這個指針指向地址上所存儲的值;這被稱爲反引用(或者內容或者間接引用)操作符;另一種說法是指針轉移。
對於任何一個變量 var, 如下表達式都是正確的:var == *(&var)。
c = *p++ 在 Go 語言的代碼中是不合法的。
指針的一個高級應用是你可以傳遞一個變量的引用(如函數的參數),這樣不會傳遞變量的拷貝。指針傳遞是很廉價的,只佔用 4 個或 8 個字節。當程序在工作中需要佔用大量的內存,或很多變量,或者兩者都有,使用指針會減少內存佔用和提高效率。被指向的變量也保存在內存中,直到沒有任何指針指向它們,所以從它們被創建開始就具有相互獨立的生命週期。
另一方面(雖然不太可能),由於一個指針導致的間接引用(一個進程執行了另一個地址),指針的過度頻繁使用也會導致性能下降。
指針也可以指向另一個指針,並且可以進行任意深度的嵌套,導致你可以有多級的間接引用,但在大多數情況這會使你的代碼結構不清晰。

原文鏈接:https://github.com/unknwon/the-way-to-go_ZH_CN/blob/master/eBook/04.9.md

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章