您好,登錄后才能下訂單哦!
Go語言中的程序實體包括變量、常量、函數、結構體和接口。Go語言是靜態類型的編程語言,所以我們在聲明變量或常量的時候都需要指定它們類型,或者給予足夠的信息以使Go語言能夠推導出它們的類型。
聲明變量的方式:
var name string
var name = "Adam"
name := "Bob" // 短變量聲明
第三種短變量聲明只能在函數體內部使用。
類型推斷
后兩種在聲明的同時還進行了賦值,沒有顯示的指定類型,而是利用了Go的類型推斷。
代碼重構
我們通常把“不改變某個程序與外界的任何交互方式和規則,而只改變其內部實現”的代碼修改方式,叫做對該程序的重構。
代碼塊
在Go語言中,代碼塊一般就是一個由花括號括起來的區域,里面可以包含表達式和語句。
Go語言本身以及我們編寫的代碼共同形成了一個非常大的代碼塊,也叫全域代碼塊。
空代碼塊
func main() {}
變量重聲明,對已經聲明過的變量再次聲明:
重聲明只在短變量聲明中出現,并且是多個變量的聲明中出現。給新的變量賦值,給舊的變量賦新值。
變量重聲明,允許我們再使用短變量聲明時不用理會被賦值的多個變量中是否有包含舊變量。好處是寫代碼時的便利。
package main
import (
"os"
"io"
"fmt"
)
func main() {
n1, err := io.WriteString(os.Stdout, "Test1")
if err != nil {
fmt.Printf("ERROR: %v\n", err)
}
fmt.Println("寫入字節(byte)數:", n1)
n2, err := io.WriteString(os.Stdout, "測試2") // 對err進行了重聲明
if err != nil {
fmt.Printf("ERROR: %v\n", err)
}
fmt.Println("寫入字節(byte)數:", n2)
n2, err = io.WriteString(os.Stdout, "測試三") // 這里都是舊變量,沒有新變量,所以用的是賦值=
if err != nil {
fmt.Printf("ERROR: %v\n", err)
}
fmt.Println("寫入字節(byte)數:", n2)
}
使用關鍵字var和短變量聲明,都可以實現對變量的“聲明并賦值”。
前者可以被用在任何地方,而后者只能被用在函數或者其他更小的代碼塊中。
前者無法對已有的變量進行聲明,就是無法處理新舊變量混在一起的情況。可以使用后者的變量重聲明實現。
共同點是,都是基于“類型推斷”。
一個程序實體的作用域總是會被限制在某個代碼塊中。而這個作用域最大的用處,就是對程序實體的訪問權限的控制。對“高內聚,低耦合”這種程序設計思想的實踐恰恰可以從這里開始。
變量重名的示例:
package main
import "fmt"
var block = "package"
func main() {
block := "function"
{
block := "inner"
fmt.Printf("block here is %s\n", block)
}
fmt.Printf("block here is %s\n", block)
}
/* 執行結果
PS H:\Go\src\Go36\article05\example01> go run main.go
block here is inner
block here is function
PS H:\Go\src\Go36\article05\example01>
*/
上面的代碼中有4個代碼塊:
在后3個代碼塊中都聲明了一個block的變量,賦值為不同的字符串。
聲明重名的變量是無法編譯通過的,但是這是對同一代碼塊內部而言的。上面的例子中是在不同的代碼塊中進行的聲明。
引用變量時的查找過程
首先,會在當前代碼塊中查找變量。不包含任何的子代碼塊。
其次,如果當前代碼塊沒有什么此變量名,一層一層往上層的代碼塊查找。
最后,如果都找不到,則編譯器會報錯。
不同代碼塊的變量可以重名,并且類型也可以不同。必要時,在使用之前,要先對變量的類型進行檢查。
下面代碼中的container變量,雖然類型不同,但是都可以使用下標[0]、[1]、[2],獲取到值:
package main
import "fmt"
var container = []string{"ZERO", "ONE", "TWO"}
func main() {
container := map[int]string{0: "zero", 1: "one", 2: "two"}
fmt.Println(container[0], container[1], container[2])
}
如果,要判斷變量的類型,就要使用“類型斷言”表達式。
語法:x.(T)。
其中的x代表要被判斷類型的那個值。T是要判斷的類型。針對上面示例中的類型斷言:
value, ok := interface{}(container).([]string)
上面是一條賦值語句,賦值符號的右邊,就是一個類型斷言表達式。
先把變量container的值轉換為空接口的值interface{}(container)。然后再判斷他的類型是否為后面.()中的類型。
有2個返回值,value和ok。ok是布爾類型,代碼類型判斷的結果:
不接收ok
這里ok也是可以沒有的:
value := interface{}(container).([]string)
這樣的話,如果類型不對,就是引發異常panic。
轉為空接口的語法
在Go語言中,interface{}代表空接口。任何類型的值都可以很方便地被轉換成空接口的值,語法:interface{}(x)。
一對不包裹任何東西的花括號,除了可以代表空的代碼塊之外,還可以用于表示不包含任何內容的數據結構(或者說數據類型)。
字面量
小括號中[]string是一個類型字面量。所謂類型字面量,就是用來表示數據類型本身的若干個字符。
比如:string是表示字符串類型的字面量,uint8是表示8位無符號整數類型的字面量。
修改開始的示例,在打印前,先對變量的類型進行判斷,只有map或切片類型才進行打印:
package main
import "fmt"
var container = []string{"ZERO", "ONE", "TWO"}
func main() {
container := map[int]string{0: "zero", 1: "one", 2: "two"}
// 打印之前先要做判斷,只有map或者切片類型才能通過
_, ok1 := interface{}(container).([]string)
_, ok2 := interface{}(container).(map[int]string)
if !(ok1 || ok2) {
fmt.Printf("ERROR: 類型斷言失敗 %T\n", container)
return
}
fmt.Println(container[0], container[1], container[2])
}
另外還有一種switch語句的實現形式:
package main
import "fmt"
var container = []string{"ZERO", "ONE", "TWO"}
func main() {
container := map[int]string{0: "zero", 1: "one", 2: "two"}
switch v := interface{}(container).(type) {
case []string:
fmt.Println("[]string:", v)
case map[int]string:
fmt.Println("map[int]string:", v)
default:
fmt.Printf("ERROR: 類型斷言失敗 %T\n", container)
return
}
}
類型轉換表達式的語法:T(x)。
其中的x可以是一個變量,也可以是一個代表值的字面量(比如1.23和struct{}),還可以是一個表達式。如果是表達式,表達式的結果只能是一個值。
x被叫做源值,它的類型就是源類型。T代表的類型是目標類型。
對于整數類型值、整數常量之間的類型轉換,原則上只要源值在目標類型的可表示范圍內就是合法的。
上面說的只是語法上合法,但是轉換后的結果可能是可坑。比如,如果源整數類型的可表示范圍大,而目標類型的可表示范圍小:
package main
import "fmt"
func main() {
var srcInt = int16(-255) // 1111111100000001
dstInt := int8(srcInt) // 00000001,簡單粗暴的截掉最前面的8位
fmt.Println(srcInt, dstInt)
}
/* 執行結果
PS H:\Go\src\Go36\article06\example04> go run main.go
-255 1
PS H:\Go\src\Go36\article06\example04>
*/
在計算機系統中,數值一律用補碼來表示和存儲。原因在于,使用補碼,可以將符號位和數值域統一處理;同時,加法和減法也可以統一處理。補碼就是原碼的各位求反再加1。比如-255:
原碼: 1000 0000 1111 1111
反碼: 1111 1111 0000 0000 最高位是符號位,不反轉。
補碼: 1111 1111 0000 0001
類型轉換的很簡單粗暴,直接把最高的8位截掉,并不處理符號位,結果就是0000 0001,所以轉換后的值就變成1了。
浮點類型轉換
如果把浮點數轉換為整數,則小數部分會被全部截掉:
package main
import "fmt"
func main() {
var x = float64(1.9999999)
y := int(x)
fmt.Println(x, y)
}
/* 執行結果
PS H:\Go\src\Go36\article06\example05> go run main.go
1.9999999 1
PS H:\Go\src\Go36\article06\example05>
*/
直接把一個整數值轉換為一個string類型的值是可行的。但是,被轉換的整數值應該是一個有效的Unicode碼點,否則轉換的結果將會是"?"。字符'?'的Unicode碼點是U+FFFD。它是Unicode標準中定義的Replacement Character,專用于替換那些未知的、不被認可的以及無法展示的字符。無效的碼點有很多,如果自己要搞一個測試,那么就用-1吧:
package main
import "fmt"
func main() {
fmt.Println(string(-1)) // 一個無效的Unicode碼點
fmt.Println(string(65)) // 字符A
fmt.Println(string(24464)) // 中文
}
一個值在從string類型轉為[]byte類型時,其中UTF-8編碼的字符串會被拆分成零散、獨立的字節。這樣只有ASCII碼的那部分字符是一個字節代碼一個字符的。而其他字符,比如中文(UTF-8里中文字符用3個字節表示),會被拆開成3個字節。而且由于UTF-8的長度是可變的,這樣還要想辦法判斷那幾個字節應該是一個字符。
可以轉為[]rune類型,這樣轉換時,每個字符會被拆開成一個個的Unicode字符。
package main
import "fmt"
func main() {
s := "你好"
s1 := []byte(s)
fmt.Println(s1)
s2 := []rune(s)
fmt.Println(s2)
for _, v := range(s1) {
fmt.Print(string(v)) // 亂碼
}
fmt.Println()
for _, v := range(s2) {
fmt.Print(string(v))
}
fmt.Println()
}
/* 執行結果
PS H:\Go\src\Go36\article06\example07> go run main.go
[228 189 160 229 165 189]
[20320 22909]
?? ?¥?
你好
PS H:\Go\src\Go36\article06\example07>
*/
別名類型聲明與類型再定義之間的區別,以及由此帶來的它們的值在類型轉換、判等、比較和賦值操作方面的不同。
可以用關鍵字type聲明自定義的各種類型。比如,可以聲明別名類型:
type MyString = string
上面的聲明語句表示,MyString是string類型的別名類型。別名類型與其源類型除了在名稱上以外,都是完全相同的。別名類型主要是為了代碼重構而存在的。
Go語言的基本類型中就存在兩個別名類型。byte是uint8的別名類型,而rune是int32的別名類型。
另外一種聲明:
type MyString2 string // 注意,這里沒有等號
這種方式也可以被叫做對類型的再定義。這里MyString2是一個新的類型,和string是不同的類型。string可以被稱為MyString2的潛在類型。
潛在類型相同的不同類型的值之間是可以進行類型轉換的。因此,MyString2類型的值與string類型的值可以使用類型轉換表達式進行互轉。
但是,[]MyStrings 和 []string 是不同的潛在類型,不能做類型轉換。
另外,即使是相同的潛在類型,也不能進行判等或比較,變量之間不能賦值。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。