Go開發 之 基礎語法(數據類型、類型轉換、運算符的優先級)

1、數據類型

1.1、數值類型

數值類型分爲以下幾種:整數、浮點數、複數。

1.1.1、整型(整數類型)

Go語言提供了有符號和無符號的整數類型,其中包括 int8、int16、int32 和 int64 四種大小截然不同的有符號整數類型,分別對應 8、16、32、64 bit(二進制位)大小的有符號整數,與此對應的是 uint8、uint16、uint32 和 uint64 四種無符號整數類型。

用來表示 Unicode 字符的 rune 類型和 int32 類型是等價的,通常用於表示一個 Unicode 碼點。這兩個名稱可以互換使用。同樣,byte 和 uint8 也是等價類型,byte 類型一般用於強調數值是一個原始的數據而不是一個小的整數。最後,還有一種無符號的整數類型 uintptr,它沒有指定具體的 bit 大小但是足以容納指針。

儘管在某些特定的運行環境下 int、uint 和 uintptr 的大小可能相等,但是它們依然是不同的類型,比如 int 和 int32,雖然 int 類型的大小也可能是 32 bit,但是在需要把 int 類型當做 int32 類型使用的時候必須顯示的對類型進行轉換,反之亦然。

1.1.2、浮點型(小數類型)

Go語言提供了兩種精度的浮點數 float32 和 float64。
一個 float32 類型的浮點數可以提供大約 6 個十進制數的精度,而 float64 則可以提供約 15 個十進制數的精度,通常應該優先使用 float64 類型,因爲 float32 類型的累計計算誤差很容易擴散,並且 float32 能精確表示的正整數並不是很大。例如:

var f float32 = 16777216 // 1 << 24
fmt.Println(f == f+1)    // "true"!

浮點數在聲明的時候可以只寫整數部分或者小數部分,像下面這樣:

const e = .71828 // 0.71828
const f = 1.     // 1

很小或很大的數最好用科學計數法書寫,通過 e 或 E 來指定指數部分:

const Avogadro = 6.02214129e23  // 阿伏伽德羅常數
const Planck   = 6.62606957e-34 // 普朗克常數

用 Printf 函數打印浮點數時可以使用“%f”來控制保留幾位小數,例如:

fmt.Printf("%f\n", math.Pi)		//3.141593
fmt.Printf("%.2f\n", math.Pi)	//3.14

1.1.3、複數

複數是由兩個浮點數表示的,其中一個表示實部(real),一個表示虛部(imag)。Go語言中複數的類型有兩種,分別是 complex128(64 位實數和虛數)和 complex64(32 位實數和虛數),其中 complex128 爲複數的默認類型。

複數的值由三部分組成 RE + IMi,其中 RE 是實數部分,IM 是虛數部分,RE 和 IM 均爲 float 類型,而最後的 i 是虛數單位。
聲明覆數的語法格式如下所示:

var name complex128 = complex(x, y)

其中 name 爲複數的變量名,complex128 爲複數的類型,“=”後面的 complex 爲Go語言的內置函數用於爲複數賦值,x、y 分別表示構成該複數的兩個 float64 類型的數值,x 爲實部,y 爲虛部。

上面的聲明語句也可以簡寫爲下面的形式:

name := complex(x, y)

對於一個複數z := complex(x, y),可以通過Go語言的內置函數real(z)來獲得該複數的實部,也就是 x;通過imag(z)獲得該複數的虛部,也就是 y。例如:

var x complex128 = complex(1, 2) // 1+2i
var y complex128 = complex(3, 4) // 3+4i
fmt.Println(x*y)                 // "(-5+10i)"
fmt.Println(real(x*y))           // "-5"
fmt.Println(imag(x*y))           // "10"

複數也可以用==!=進行相等比較,只有兩個複數的實部和虛部都相等的時候它們纔是相等的。
Go語言內置的 math/cmplx 包中提供了很多操作複數的公共方法,實際操作中建議大家使用複數默認的 complex128 類型,因爲這些內置的包中都使用 complex128 類型作爲參數。

1.2、bool類型(布爾類型)

一個布爾類型的值只有兩種:true 或 false。if 和 for 語句的條件部分都是布爾類型的值,並且==<等比較操作也會產生布爾型的值。

一元操作符!對應邏輯非操作,因此!true的值爲 false,更復雜一些的寫法是(!true == false) == true,實際開發中我們應儘量採用比較簡潔的布爾表達式,就像用 x 來表示x==true

Go語言對於值之間的比較有非常嚴格的限制,只有兩個相同類型的值纔可以進行比較,如果值的類型是接口(interface),那麼它們也必須都實現了相同的接口。如果其中一個值是常量,那麼另外一個值可以不是常量,但是類型必須和該常量類型相同。如果以上條件都不滿足,則必須將其中一個值的類型轉換爲和另外一個值的類型相同之後纔可以進行比較。

布爾值可以和 &&(AND)和 ||(OR)操作符結合,並且有短路行爲,如果運算符左邊的值已經可以確定整個布爾表達式的值,那麼運算符右邊的值將不再被求值,因此下面的表達式總是安全的:

s != "" && s[0] == 'x'	// 其中 s[0] 操作如果應用於空字符串將會導致 panic 異常。

1.3、字符串類型

字符串是一種值類型,且值不可變,即創建某個文本後將無法再次修改這個文本的內容,更深入地講,字符串是字節的定長數組。

1.3.1、定義字符串

可以使用雙引號""來定義字符串,字符串中可以使用轉義字符來實現換行、縮進等效果,常用的轉義字符包括:\n:換行符、\r:回車符、\t:tab 鍵、\u 或 \U:Unicode 字符、\:反斜槓自身。
例如:

var str = "C語言中文網\nGo語言教程"

字符串的內容(純字節)可以通過標準索引法來獲取,在方括號[]內寫入索引,索引從 0 開始計數:

  • 字符串 str 的第 1 個字節:str[0]
  • 第 i 個字節:str[i - 1]
  • 最後 1 個字節:str[len(str)-1]

需要注意的是,這種轉換方案只對純 ASCII 碼的字符串有效。
注意:獲取字符串中某個字節的地址屬於非法行爲,例如 &str[i]。

1.3.2、字符串拼接符“+”

兩個字符串 s1 和 s2 可以通過 s := s1 + s2 拼接在一起。將 s2 追加到 s1 尾部並生成一個新的字符串 s。

可以通過下面的方式來對代碼中多行的字符串進行拼接:

str := "Beginning of the string " + "second part of the string"

提示:因爲編譯器會在行尾自動補全分號,所以拼接字符串用的加號“+”必須放在第一行末尾。

也可以使用“+=”來對字符串進行拼接:

s := "hel" + "lo,"
s += "world!"
fmt.Println(s) //輸出 “hello, world!”

1.3.3、字符串實現基於 UTF-8 編碼

Go語言中字符串的內部實現使用 UTF-8 編碼,通過 rune 類型,可以方便地對每個 UTF-8 字符進行訪問。當然,Go語言也支持按照傳統的 ASCII 碼方式逐字符進行訪問。

1.3.4、定義多行字符串

在Go語言中,使用雙引號書寫字符串的方式是字符串常見表達方式之一,被稱爲字符串字面量(string literal),這種雙引號字面量不能跨行,如果想要在源碼中嵌入一個多行字符串時,就必須使用`反引號,代碼如下:
在這裏插入圖片描述
代碼運行結果:
在這裏插入圖片描述

在 `間的所有代碼均不會被編譯器識別,而只是作爲字符串的一部分。

1.4、字符類型(byte和rune)

1.4.1、概念

字符串中的每一個元素叫做“字符”,在遍歷或者單個獲取字符串元素時可以獲得字符。
字符有以下兩種:

  • 一種是 uint8 類型,或者叫 byte 型,代表了 ASCII 碼的一個字符。
  • 另一種是 rune 類型,代表一個 UTF-8 字符,當需要處理中文、日文或者其他複合字符時,則需要用到 rune 類型。rune 類型等價於 int32 類型。

下面的寫法是等效的:

var ch byte = 65var ch byte = '\x41'      // \x 總是緊跟着長度爲 2 的 16 進制數

另外一種可能的寫法是 \後面緊跟着長度爲 3 的八進制數,例如 \377。

Go語言同樣支持 Unicode(UTF-8),因此字符同樣稱爲 Unicode 代碼點或者 runes,並在內存中使用 int 來表示。在文檔中,一般使用格式 U+hhhh 來表示,其中 h 表示一個 16 進制數。

在書寫 Unicode 字符時,需要在 16 進制數之前加上前綴\u或者\U。因爲 Unicode 至少佔用 2 個字節,所以我們使用 int16 或者 int 類型來表示。如果需要使用到 4 字節,則使用\u前綴,如果需要使用到 8 個字節,則使用\U前綴。

var ch int = '\u0041'
var ch2 int = '\u03B2'
var ch3 int = '\U00101234'
fmt.Printf("%d - %d - %d\n", ch, ch2, ch3) // integer: 65 - 946 - 1053236
fmt.Printf("%c - %c - %c\n", ch, ch2, ch3) // character: A - β - r
fmt.Printf("%X - %X - %X\n", ch, ch2, ch3) // UTF-8 bytes: 41 - 3B2 - 101234
fmt.Printf("%U - %U - %U", ch, ch2, ch3)   // UTF-8 code point: U+0041 - U+03B2 - U+101234

1.4.2、UTF-8 和 Unicode 的區別

Unicode 與 ASCII 類似,都是一種字符集。

字符集爲每個字符分配一個唯一的 ID,我們使用到的所有字符在 Unicode 字符集中都有一個唯一的 ID,例如上面例子中的 a 在 Unicode 與 ASCII 中的編碼都是 97。漢字“你”在 Unicode 中的編碼爲 20320,在不同國家的字符集中,字符所對應的 ID 也會不同。而無論任何情況下,Unicode 中的字符的 ID 都是不會變化的。

UTF-8 是編碼規則,將 Unicode 中字符的 ID 以某種方式進行編碼,UTF-8 的是一種變長編碼規則,從 1 到 4 個字節不等。編碼規則如下:

  • 0xxxxxx 表示文字符號 0~127,兼容 ASCII 字符集。
  • 從 128 到 0x10ffff 表示其他字符。

根據這個規則,拉丁文語系的字符編碼一般情況下每個字符佔用一個字節,而中文每個字符佔用 3 個字節。

廣義的 Unicode 指的是一個標準,它定義了字符集及編碼規則,即 Unicode 字符集和 UTF-8、UTF-16 編碼等。

2、類型轉換

2.1、類型轉換的概念

類型 B 的值 = 類型 B(類型 A 的值)

在必要以及可行的情況下,一個類型的值可以被轉換成另一種類型的值。由於Go語言不存在隱式類型轉換,因此所有的類型轉換都必須顯式的聲明:

a := 5.0
b := int(a)

浮點數在轉換爲整型時,會將小數部分去掉,只保留整數部分。

2.2、字符串和數值類型的相互轉換

開發時往往需要對一些常用的數據類型進行轉換,如 string、int、int64、float 等數據類型之間的轉換,Go語言中的 strconv 包爲我們提供了字符串和基本數據類型之間的轉換功能。strconv 包中常用的函數包括 Atoi()、Itia()、parse 系列函數、format 系列函數、append 系列函數等。

2.2.1、string 與 int 類型之間的轉換

字符串和整型之間的轉換是我們平時編程中使用的最多的,下面就來介紹一下具體的操作。

  • Itoa():整型轉字符串

Itoa() 函數用於將 int 類型數據轉換爲對應的字符串類型,函數簽名如下func Itoa(i int) string,如:

func main() {
    num := 100
    str := strconv.Itoa(num)
    fmt.Printf("type:%T value:%#v\n", str, str) // type:string value:"100"
}
  • Atoi():字符串轉整型

Atoi() 函數用於將字符串類型的整數轉換爲 int 類型,函數簽名func Atoi(s string) (i int, err error)。通過函數簽名可以看出 Atoi() 函數有兩個返回值,i 爲轉換成功的整型,err 在轉換成功是爲空轉換失敗時爲相應的錯誤信息,如:

func main() {
    str1 := "110"
    str2 := "s100"
    num1, err := strconv.Atoi(str1)
    if err != nil {
        fmt.Printf("%v 轉換失敗!", str1)
    } else {
        fmt.Printf("type:%T value:%#v\n", num1, num1)
    }
    num2, err := strconv.Atoi(str2)
    if err != nil {
        fmt.Printf("%v 轉換失敗!", str2)
    } else {
        fmt.Printf("type:%T value:%#v\n", num2, num2)
    }
}

結果:

type:int value:110
s100 轉換失敗!

2.2.2、Parse 系列函數

Parse 系列函數用於將字符串轉換爲指定類型的值,其中包括 ParseBool()、ParseFloat()、ParseInt()、ParseUint()。

2.2.2.1、ParseBool()

ParseBool() 函數用於將字符串轉換爲 bool 類型的值,它只能接受 1、0、t、f、T、F、true、false、True、False、TRUE、FALSE,其它的值均返回錯誤,函數簽名:func ParseBool(str string) (value bool, err error)
示例如下:

func main() {
    str1 := "110"
    boo1, err := strconv.ParseBool(str1)
    if err != nil {
        fmt.Printf("str1: %v\n", err)
    } else {
        fmt.Println(boo1)
    }
    str2 := "t"
    boo2, err := strconv.ParseBool(str2)
    if err != nil {
        fmt.Printf("str2: %v\n", err)
    } else {
        fmt.Println(boo2)
    }
}

結果如下:

str1: strconv.ParseBool: parsing "110": invalid syntax
true

2.2.2.2、ParseInt()

ParseInt() 函數用於返回字符串表示的整數值(可以包含正負號),函數簽名:func ParseInt(s string, base int, bitSize int) (i int64, err error)。參數說明:

  • base 指定進制,取值範圍是 2 到 36。如果 base 爲 0,則會從字符串前置判斷,“0x”是 16 進制,“0”是 8 進制,否則是 10 進制。
  • bitSize 指定結果必須能無溢出賦值的整數類型,0、8、16、32、64 分別代表 int、int8、int16、int32、int64。
  • 返回的 err 是 *NumErr 類型的,如果語法有誤,err.Error = ErrSyntax,如果結果超出類型範圍 err.Error = ErrRange。

示例代碼如下:

func main() {
    str := "-11"
    num, err := strconv.ParseInt(str, 10, 0)
    if err != nil {
        fmt.Println(err)
    } else {
        fmt.Println(num)
    }
}

運行結果如下:

-11

2.2.2.3、ParseUnit()

ParseUint() 函數的功能類似於 ParseInt() 函數,但 ParseUint() 函數不接受正負號,用於無符號整型,函數簽名:func ParseUint(s string, base int, bitSize int) (n uint64, err error)。示例代碼如下:

func main() {
    str := "11"
    num, err := strconv.ParseUint(str, 10, 0)
    if err != nil {
        fmt.Println(err)
    } else {
        fmt.Println(num)
    }
}

運行結果如下:

11

2.2.2.4、ParseFloat()

ParseFloat() 函數用於將一個表示浮點數的字符串轉換爲 float 類型,函數簽名: func ParseFloat(s string, bitSize int) (f float64, err error)。參數說明:

  • 如果 s 合乎語法規則,函數會返回最爲接近 s 表示值的一個浮點數(使用 IEEE754 規範舍入)。
  • bitSize 指定了返回值的類型,32 表示 float32,64 表示 float64;
  • 返回值 err 是 *NumErr 類型的,如果語法有誤 err.Error=ErrSyntax,如果返回值超出表示範圍,返回值 f 爲 ±Inf,err.Error= ErrRange。

示例代碼如下:

func main() {
    str := "3.1415926"
    num, err := strconv.ParseFloat(str, 64)
    if err != nil {
        fmt.Println(err)
    } else {
        fmt.Println(num)
    }
}

運行結果如下:

3.1415926

Parse 系列函數都有兩個返回值,第一個返回值是轉換後的值,第二個返回值爲轉化失敗的錯誤信息。

2.2.3、Format 系列函數

Format 系列函數實現了將給定類型數據格式化爲字符串類型的功能,其中包括 FormatBool()、FormatInt()、FormatUint()、FormatFloat()。

2.2.3.1、FormatBool()

FormatBool() 函數可以一個 bool 類型的值轉換爲對應的字符串類型,函數簽名:func FormatBool(b bool) string。示例代碼如下:

func main() {
    num := true
    str := strconv.FormatBool(num)
    fmt.Printf("type:%T,value:%v\n ", str, str)
}

運行結果如下:

type:string,value:true

2.2.3.2、FormatInt()

FormatInt() 函數用於將整型數據轉換成指定進制並以字符串的形式返回,函數簽名: func FormatInt(i int64, base int) string。其中,參數 i 必須是 int64 類型,參數 base 必須在 2 到 36 之間,返回結果中會使用小寫字母“a”到“z”表示大於 10 的數字。示例代碼如下:

func main() {
    var num int64 = 100
    str := strconv.FormatInt(num, 16)
    fmt.Printf("type:%T,value:%v\n ", str, str)
}

運行結果如下:

type:string,value:64

2.2.3.3、FormatUint()

FormatUint() 函數與 FormatInt() 函數的功能類似,但是參數 i 必須是無符號的 uint64 類型,函數簽名: func FormatUint(i uint64, base int) string。示例代碼如下:

func main() {
    var num uint64 = 110
    str := strconv.FormatUint(num, 16)
    fmt.Printf("type:%T,value:%v\n ", str, str)
}

運行結果如下:

type:string,value:6e

2.2.3.4、FormatFloat()

FormatFloat() 函數用於將浮點數轉換爲字符串類型,函數簽名:func FormatFloat(f float64, fmt byte, prec, bitSize int) string。參數說明:

  • bitSize 表示參數 f 的來源類型(32 表示 float32、64 表示 float64),會據此進行舍入。
  • fmt 表示格式,可以設置爲“f”表示 -ddd.dddd、“b”表示 -ddddp±ddd,指數爲二進制、“e”表示 -d.dddde±dd 十進制指數、“E”表示 -d.ddddE±dd 十進制指數、“g”表示指數很大時用“e”格式,否則“f”格式、“G”表示指數很大時用“E”格式,否則“f”格式。
  • prec 控制精度(排除指數部分):當參數 fmt 爲“f”、“e”、“E”時,它表示小數點後的數字個數;當參數 fmt 爲“g”、“G”時,它控制總的數字個數。如果 prec 爲 -1,則代表使用最少數量的、但又必需的數字來表示 f。

示例代碼如下:

func main() {
    var num float64 = 3.1415926
    str := strconv.FormatFloat(num, 'E', -1, 64)
    fmt.Printf("type:%T,value:%v\n ", str, str)
}

運行結果如下:

type:string,value:3.1415926E+00

2.2.4、Append 系列函數

Append 系列函數用於將指定類型轉換成字符串後追加到一個切片中,其中包含 AppendBool()、AppendFloat()、AppendInt()、AppendUint()。

Append 系列函數和 Format 系列函數的使用方法類似,只不過是將轉換後的結果追加到一個切片中。

示例代碼如下:

package main
import (
    "fmt"
    "strconv"
)
func main() {
    // 聲明一個slice
    b10 := []byte("int (base 10):")
  
    // 將轉換爲10進制的string,追加到slice中
    b10 = strconv.AppendInt(b10, -42, 10)
    fmt.Println(string(b10))
    b16 := []byte("int (base 16):")
    b16 = strconv.AppendInt(b16, -42, 16)
    fmt.Println(string(b16))
}

運行結果如下:

int (base 10):-42
int (base 16):-2a

3、運算符的優先級

運算符是用來在程序運行時執行數學或邏輯運算的,在Go語言中,一個表達式可以包含多個運算符,當表達式中存在多個運算符時,就會遇到優先級的問題,此時應該先處理哪個運算符呢?這個就由Go語言運算符的優先級來決定的。
比如對於下面的表達式:

var a, b, c int = 16, 4, 2
d := a + b*c

對於表達式a + b * c,如果按照數學規則推導,應該先計算乘法,再計算加法;b * c的結果爲 8,a + 8的結果爲 24,所以 d 最終的值也是 24。實際上Go語言也是這樣處理的,先計算乘法再計算加法,和數據中的規則一樣,讀者可以親自驗證一下。

先計算乘法後計算加法,說明乘法運算符的優先級比加法運算符的優先級高。所謂優先級,就是當多個運算符出現在同一個表達式中時,先執行哪個運算符。

Go語言有幾十種運算符,被分成十幾個級別,有的運算符優先級不同,有的運算符優先級相同,請看下錶。

優先級 分類 運算符 結合性
1 逗號運算符 , 從左到右
2 賦值運算符 =、+=、-=、*=、/=、 %=、 >=、 <<=、&=、^=、|= 從右到左
3 邏輯或 || 從左到右
4 邏輯與 && 從左到右
5 按位或 | 從左到右
6 按位異或 ^ 從左到右
7 按位與 & 從左到右
8 相等/不等 ==、!= 從左到右
9 關係運算符 <、<=、>、>= 從左到右
1 位移運算符 <<、>> 從左到右
1 加法/減法 +、- 從左到右
1 乘法/除法/取餘 *(乘號)、/、% 從左到右
1 單目運算符 !、*(指針)、& 、++、–、+(正號)、-(負號) 從右到左
1 後綴運算符 ( )、[ ]、-> 從左到右

注意:優先級值越大,表示優先級越高。

一下子記住所有運算符的優先級並不容易,還好Go語言中大部分運算符的優先級和數學中是一樣的,大家在以後的編程過程中也會逐漸熟悉起來。如果實在搞不清,可以加括號。

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