golang 數據一   (字符串、數組和數組指針)

從如下幾個方面介紹GO語言的數據

1. 字符串
2. 數組
3. 切片
4. 字典
5. 結構

字符串

Go語言中的字符串是由一組不可變的字節(byte)序列組成從源碼文件中看出其本身是一個複合結構

string.go 
type stringStruct struct {
    str unsafe.Pointer    
    len int
}

字符串中的每個字節都是以UTF-8編碼存儲的Unicode字符字符串的頭部指針指向字節數組的開始但是沒有NULL或'\0'結尾標誌。 表示方式很簡單用雙引號("")或者反引號(``)它們的區別是

  1. 雙引號之間的轉義符會被轉義而反引號之間的轉義符保持不變

  2. 反引號支持跨行編寫而雙引號則不可以


{
    println("hello\tgo")    //輸出hello    go
    println(`hello\tgo`)    //輸出hello\tgo
}

{
    println( "hello 
    go" )//syntax error: unexpected semicolon or newline, expecting comma or )

    println(`hello                                                                                                                                                                                                 
        go`) //可以編譯通過
}
輸出
hello 
    go

在前面類型的章節中描述過字符串的默認值是""而不是nil,比如

var s string
println( s == "" )  //true
println( s == nil ) //invalid operation: s == nil (mismatched types string and nil)

Go字符串支持 "+ , += , == , != , < , >" 六種運算符

Go字符串允許用索引號訪問字節數組(非字符)但不能獲取元素的地址比如

{    
    var a = "hello"
    println(a[0])       //輸出 104
    println(&a[1])      //cannot take the address of a[1]
}

Go字符串允許用切片的語法返回子串(起始和結束索引號)比如

var a = "0123456"                                                                                                                                                                                              
println(a[:3])      //0,1,2
println(a[1:3])     //1,2
println(a[3:])      //3,4,5,6

日常開發中經常會有遍歷字符串的場景比如

{
    var a = "Go語言"
    for i:=0;i < len(a);i++{                //以byte方式按字節遍歷
        fmt.Printf("%d: [%c]\n", i, a[i])
    }
    for i, v := range a{                    //以rune方式遍歷                                                                                               
        fmt.Printf("%d: [%c]\n", i, v)
    }
}
輸出    
0: [G]
1: [o]
2: [è]
3: [ˉ]
4: [-]
5: [è]
6: [¨]
7: []
0: [G]
1: [o]
2: [語]
5: [言]

在Go語言中字符串的底層使用的是不可以改變的byte數組存的所以在用byte輪詢方式時每次得到的只有一個byte而中文字符則是佔3個byte的。rune採用計算字符串長度的方式與byte方式不同比如

println(utf8.RuneCountInString(a)) // 結果爲  4
println(len(a)) // 結果爲 8

所以如果想要獲得期待的那種結果的話需要先將字符串a轉換爲rune切片再使用內置的len函數比如:

{
    r := []rune(a)
    for i:= 0;i < len(r);i++{
        fmt.Printf("%d: [%c]\n", i, r[i])
    }
}

所以在遍歷或處理的字符串的情況下如果其中存在中文儘量使用rune方式處理。

轉換

前面講過不能修改原字符串如果修改的話需要將字符串轉換成[]byte或[]rune , 然後在轉換回來比如

{
    var a = "hello go"                                                                                                       
    a[1] = 'd'              //cannot assign to a[1]
}
{
    var a = "hello go"
    bs := []byte(a)
    ...                                                                                                                                                                                                            
    s2 := string(bs)

    rs := []rune(a)
    ...
    s3 := string(rs)
}

Go語言支持用"+"運算符進行字符串拼接但是每次拼接都需要重新分配內存如果頻繁構造一個很長的字符串則性能影響就會很大比如

func test1()string{
    var s string
    for i:= 0;i < 1000 ;i++{
        s += "a" 
    }   
    return s
}

func Benchmark_test1(b *testing.B){
    for i:= 0;i < b.N; i++{
        test1()                                                                                                                                                                                                    
    }   
}
輸出
# go test str1_b_test.go  -bench="test1" -benchmem
Benchmark_test1-2   	    5000	    227539 ns/op	  530338 B/op	     999 allocs/op

常用的改進方法是預分配足夠的內存空間然後使用strings.Join函數該函數會統計出所有參數的長度並一次性完成內存分配操作改進一下上面的代碼

func test()string{
    s := make([]string,1000)
    for i:= 0;i < 1000 ;i++{
        s[i] = "a" 
    }   
    return strings.Join(s,"")
}
func Benchmark_test(b *testing.B){
    for i:= 0;i < b.N; i++{
        test()
    }   
}
輸出
# go test -v b_test.go  -bench="test1" -benchmem
Benchmark_test1-2   	  200000	     10765 ns/op	    2048 B/op	       2 allocs/op

在日常開發中可以使用fmt.Sprintf函數來格式化和拼接較少的字符串操作比如

{
    a := 10010
    as := fmt.Sprintf("%d",a)
    fmt.Printf("%T , %v\n",as,as)
}

數組

數組是內置(build-in)類型是一組存放相同類型數據的集合數組的數據類型是由存儲的元素類型和數組的長度共同決定的,即使元素類型相同但是長度不同數組也不屬於同一類型。數組初始化之後長度是固定無法修改的數組也支持邏輯判斷運算符 "==","="定義方式如下

{
    var a [10]int
    var b [20]int
    println(a == b)  //invalid operation: a == b (mismatched types [10]int and [20]int)
}

數組的初始化相對靈活下標索引值從0開始支持按索引位置初始化對於未初始化的數組編譯器將給以默認值。

{
    var a[4] int                //元素初始化爲0
    b := [4] int{0,1}           //未初始化的元素將被初始化爲0
    c := [4] int{0, 2: 3}       //可指定索引位置初始化
    d := [...]int{0,1,2}        //編譯器根據初始化值數量來確定數組的長度
    e := [...]int{1, 3:3}       //支持索引位置初始化但數組長度與其無關
    
    type user struct{
        name string
        age int 
    }   

    d := [...] user{            //複合數據類型數組可省略元素初始化類型標籤
        {"a",1},
        {"b",2},
    }
}

定義多維數組時只有數組的第一維度允許使用 "..."

{
    x := [2]int{2,2}
    a := [2][2]int{{1,2},{2,2}}
    b := [...][2]int{{2,3},{2,2},{3,3}}
    c := [...][2][2]int{{ {2,3},{2,2} },{{3,3},{4,4}} }
}

計算數組長度時無論使用內置的len還是cap返回的都是第一維度的長度比如

{
    fmt.Println(x, len(x), cap(x))    
    fmt.Println(a, len(a), cap(x))
    fmt.Println(b, len(b), cap(x))
    fmt.Println(c, len(c), cap(x))
}
輸出
[2 2] 2 2
[[1 2] [2 2]] 2 2
[[2 3] [2 2] [3 3]] 3 2
[[[2 3] [2 2]] [[3 3] [4 4]]] 2 2

數組指針&指針數組

數組除了可以存放具體類型的數據也可以存放指針比如

{ 
    x, y := 10, 20
    a := [...]*int{&x, &y}      //指針數組 
    p := &a                     //數組的指針
}

數組複製

Go語言數組是值(非引用)類型所以在賦值和參數傳遞過程中都會複製整個數組數據比如:

func test(x [2]int){
    fmt.Printf("x:= %p,%v\n", &x, x)
}

func main(){ 
    a := [2] int{1, 2}
    test(a)                     //傳參過程中完全複製
    var b [2]int
    b = a                       //賦值過程中完全複製
    fmt.Printf("a:= %p,%v\n", &a, a)
    fmt.Printf("b:= %p,%v\n", &b, b)                                                                                                                                                                               
}
輸出
x:= 0xc42000a330,[1 2]
a:= 0xc42000a320,[1 2]
b:= 0xc42000a370,[1 2]


借鑑: 雨痕<GO學習筆記>

討論學習: 675020908

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