golang-數組和切片


數組

數組的定義

數組是具有固定長度並擁有零個或者多個相同數據類型元素的序列

定義一個數組的方法:
var 變量名[len] type

例子:
var a[5] int //3個整數的數組
var a[5]string //3個字符串的數組

像上面這種定義方法,我們是指定了數組的長度,但是還有如下定義方法:
var a=[...]int{1,2,3}
如果把數組的長度替換為...,那么數組的長度由初始化數組的元素個數決定

數組中的每個元素是通過索引來訪問,索引是從0開始
例如 數組var a[5]int 獲取第一個元素就是a[0],
獲取數組的長度是通過len(a)

這里需要知道:數組的長度也是數組類型的一部分,所以要知道[3]int和[4]int是不同的數組類型

默認情況下一個新數組中的元素初始值為元素類型的零值
如一個證書類型的數組,默認值就是0

初始化數組:

有一下幾種方法:
var a = [5] int{1,2,3,4,5}
var a = [5] int{1,2,3}
var a = [...]int{1,2,3,4}
var a = [5]string{1:"go",3:"python"}

關於數組的類型:
值類型

數組的遍歷

數組的遍歷方法:

var arr = [5]int{3, 4, 5, 6, 7}
for i, v := range arr {
  fmt.Println(i, "==", v)
}

  

當然如果不需要索引也可以:

var arr = [5]int{3, 4, 5, 6, 7}
for _, v := range arr {
  fmt.Println(v)
}

  

二維數組

var a[3][2]

其實二維數組可以通過excel表格理解,就是幾行幾列的問題,像上面的這個例子就是一個3行2列的二維數組。
關於二維數組的遍歷,創建一個二維數組並循環賦值,然后循環打印內容

package main

import (
	"fmt"
	"math/rand"
)

func main() {
	var arr = [5]int{3, 4, 5, 6, 7}
	for i, v := range arr {
		fmt.Println(i, "==", v)
	}

	//二維數組遍歷
	var arr2 [3][2]int
	for i := 0; i < 3; i++ {
		for j := 0; j < 2; j++ {
			arr2[i][j] = rand.Intn(10)
		}
	}
	//traversal
	fmt.Println("---------------------------")
	for i := 0; i < 3; i++ {
		for j := 0; j < 2; j++ {
			fmt.Println(arr2[i][j])
		}
	}

}

  

關於數組的比較

如果兩個數組的元素類型相同是可以相互比較的,例如數組a:= [2]int{1,2}和數組b:=[2]int{3,4}
因為同樣都是int類型,所以可以通過==來比較兩個數組,看兩邊的元素是否完全相同,使用!= 比較看兩邊的元素是否不同

通過下面的例子演示更加清晰:

package main

import (
    "fmt"
)

func main() {
    a := [2]int{1, 2}
    b := [...]int{1, 2}
    c := [2]int{3, 2}
    // d := [3]int{1, 2}

    fmt.Println(a == b, a == c, b == c)

    //fmt.Println(a == d)//這個會報錯,不能編譯
}

 

切片slice

定義

slice 表示一個擁有相同類型元素的可變長的序列

定義一個slice其實和定義一個數組非常類似
var 變量名 []type
var b = []int

和數組對比slice似乎就是一個沒有長度的數組

slice的初始化
var a[5] int //這是定義一個數組
var b []int = a[0,2]
var b []int = a[0:5]
var b []int = a[:]
var b []int = a[:3]

var b []int = []int{1,2,3,4}

同樣遍歷切片和數組是一模一樣的

通過把數組和slice對比我們其實可以發現,兩者其實非常類似,當然兩者也確實有着緊密的關系

slice的底層實現就是一個數組,通常我們會叫做slice的底層數組
slice具有三個屬性:指針,長度和容量,如下圖

指針指向數組的第一個可以從slice中訪問的元素,這個元素不一定是數組的第一個元素

長度是指slice中元素的個數,不能超過slice的容量
容量的大小是從slice的起始元素到底層數組的最后一個元素的個數
通過len和cap可以獲取slice的長度和容量
通過下面例子理解:
var s = [5]int{1, 2, 3, 4, 5}
var b = s[2:3]
var c = s[0:4]
現在問b的長度以及容量,c的長度以及容量
對比上面的定義其實很好明白
s 就好比slice的底層數組
而對於b這個slice來說他是從數組的第三個元素開始切片,切片的時候是左閉右開原則,也就是

arr[startIndex:endIndex],不包含endIndex,包含startIndex

所以b的長度是1
對於b的容量根據定義我們知道是從數組的第三個元素到數組的最后
所以b的容量是3

這樣我們也可以很容易得到c的長度是3,容量是5

slice創建

內置函數make可以創建一個具有指定元素類型、長度和容量的slice,其中容量參數可以省略,這樣默認slice的長度和容量就相等了

make([]type,len,cap)
make([]type,len)

現在說說關於:
make([]type,len)
make([]type,len,cap)

實make創建了一個無名數組並返回了它的一個slice;這個數組僅可以通過slice來訪問
第一個:make([]type,len)返回的slice引用了整個數組。
第二個:make([]type,len,cap)slice只引用了數組的前len個元素,但是它的容量是數組的長度
通過下圖理解切片的創建過程:

 

關於copy

該函數主要是切片(slice)的拷貝,不支持數組
將第二個slice里的元素拷貝到第一個slice里。如果加入的兩個數組切片不一樣大,就會按其中較小的那個數組切片的元素個數進行復制。

通過下面例子便於理解:package main

 import "fmt" func main() { var s1 []int = []int{1, 2, 3, 7, 8} var s2 []int = []int{4, 5, 6} copy(s2, s1) fmt.Printf("s2 is %v\n", s2) //s2 is [1 2 3]
 var s3 []int = []int{1, 2, 3, 7, 8} var s4 []int = []int{4, 5, 6} copy(s3, s4) fmt.Printf("s3 is %v\n", s3) //s3 is [4 5 6 7 8]
//
這次拷貝就是把s4中的前三個元素拷貝到s3中的前三個,把s3中的前三個進行了覆蓋
}

關於append

內置的函數append可以把元素追加到slice的后面

package main

import "fmt"

func main() {
    //通過下面例子理解,把“hello go”每個字符循環添加到一個slice中
    var runnes []rune
    for _, v := range "hello go" {
        runnes = append(runnes, v)
    }
    fmt.Printf("runnes is %v\n", runnes)

    //例子2直接在一個已經有元素的slice追加
    s1 := []int{1, 2, 3, 4, 5}
    s1 = append(s1, 6, 7)
    fmt.Printf("s1 is %#v\n", s1)

    //如果想要把另外一個slice也直接append到現在的slice中:
    a := []int{1, 2, 3}
    b := []int{4, 5}
    a = append(a, b...) //這里在b后面通過...其實就是把b中的元素給展開然后在append進a中
    fmt.Printf("a is %#v\n", a)
}

其實append函數對於理解slice的工作原理是非常重要的,下面是一個為[]int數組slice定義的一個方法:

 
package main

import "fmt"

func appendInt(x []int, y int) []int {
    var z []int
    zlen := len(x) + 1
    if zlen <= cap(x) { //slice有增長空間,則擴展slice
        z = x[:zlen]
    } else {
        //如果slice沒有空間,則為他分配一個新的底層數組,這里以一倍為例子
        zcap := zlen
        if zcap <= 2*len(x) {
            zcap = 2 * len(x)
        }
        z = make([]int, zlen, zcap)
        copy(z, x)
    }
    z[len(x)] = y
    return z
}
func main() {
    var x []int = []int{1, 2, 3, 4, 5}
    fmt.Printf("before len of x:%d\n", len(x))
    fmt.Printf("before cap of x:%d\n", cap(x))

    x = appendInt(x, 6)
    fmt.Printf("x is %#v\n", x)
    fmt.Printf("len of x:%d\n", len(x))
    fmt.Printf("cap of x:%d\n", cap(x))
}

從上面的這個方法可以看出:
每次appendInt的時候都會檢查slice是否有足夠的容量來存儲數組中的新元素,如果slice容量足夠,那么他會定義一個新的slice,注意這里仍然引用原始的底層數組,然后將新元素y復制到新的位置,並返回新的slice,這樣我們傳入的參數切片x和函數返回值切片z其實用的是相同的底層數組。
如果slice的容量不夠容納增長的元素,appendInt函數必須創建一個擁有足夠容量的新的底層數組來存儲新的元素,然后將元素從切片x復制到這個數組,再將新元素y追加到數組后面。這樣返回的切片z將和傳入的參數切片z引用不同的底層數組。

關於切片的比較

和數組不同的是,切片是無法比較的,因此不能通過==來比較兩個切片是否擁有相同的元素
slice唯一允許的比較操作是和nill比較,切片的零值是nill
這里需要注意的是:值為nill的slice的長度和容量都是零,但是這不是決定的,因為存在非nill的slice的長度和容量是零所以想要檢查一個slice是否為還是要使用len(s) == 0 而不是s == nill

package main

import (
    "fmt"
)

func main() {

    var s1 []int
    if s1 == nil {
        fmt.Println("s1==nil")
    } else {
        fmt.Println("s1!=nil")
    }

    var arr = [5]int{}

    s1 = arr[:]

    if s1 == nil {
        fmt.Println("s1==nil")
    } else {
        fmt.Println("s1!=nil")
    }
}

下面是整理的練習切片使用的例子

package main

import "fmt"

//slice來實現修改字符串
func changeStr(str string) {
    var runnes = []rune(str)
    runnes[0] = 'h'
    res := string(runnes)
    fmt.Println(res)
}

//slice來實現字符串反轉
func reverseStr(str string) {
    var runnes = []rune(str)
    var res string
    len := len(runnes)
    for i := len - 1; i >= 0; i-- {
        res += string(runnes[i])
    }
    fmt.Println(res)
}

func reverseStr2(str string) {
    var runnes = []rune(str)
    var res string
    len := len(runnes)
    //i,j分別代表字符串的開始和結尾,都向中間靠近替換
    for i, j := 0, len-1; i < j; i, j = i+1, j-1 {
        runnes[i], runnes[j] = runnes[j], runnes[i]
    }

    /*
        //下面這種寫法是錯誤的,j執行一次,i從0開始循環了j次
        for j := len - 1; j >= int(len/2); j-- {
            for i := 0; i <= j; i++ {
                runnes[i], runnes[j] = runnes[j], runnes[i]
            }
        }*/
    res = string(runnes)
    fmt.Println(res)
}

func main() {
    changeStr("Hello World")
    reverseStr("Hello World")
    reverseStr2("Hello World")
}

下面還有個切片刪除的例子:

package main

import "fmt"

func main() {
    index := 2
    var s = []int{12, 4, 5, 6} //s是數組
    s = append(s[:index], s[index+1:]...)
    fmt.Println(s)// [12 4 6]
}

 

GO當中的:string rune,byte

在Go當中的字符換string 底層是用byte數組存的,並且是不可改變的
當我們通過for key, value := range str這種方式循環一個字符串的時候,其實返回的每個value類型就是rune
而我們知道在go中雙引號引起來的是字符串string,在go中表示字符串有兩種方式:
一種是byte,代表utf-8字符串的單個字節的值;另外一個是rune,代表單個unicode字符串
關於rune官網中一段解釋:
rune is an alias for int32 and is equivalent to int32 in all ways. It is
used, by convention, to distinguish character values from integer values.

我們通過下面的代碼例子來理解一下:

var a = "我愛你go"
fmt.Println(len(a))

上面已經說了,字符串的底層是byte字節數組,所以我們通過len來計算長度的時候,其實就是獲取的該數組的長度,而一個中文字符是占3個字節,所以上面的結果是11
可能很多人第一眼看的時候,尤其初學者可能會覺得長度應該是5,其實,如果想要轉換成4只需要通過蝦米那方式就可以:

var a = "我愛你go"
fmt.Println(len([]rune(a)))

 

時間和日期類型

當前時間:now:= time.Now()

time.Now().Day()

time.Now().Minute()

time.Now().Month()

time.Now().Year()

time.Duration用來表示納秒

一些常用的時間常量

const (
Nanosecond Duration = 1
Microsecond =1000 * Nanosecond
Millisecond =1000 * Microsecond
Second =1000 * Millisecond
Minute =60 * Second
Hour =60 * Minute
)

注意:如果想要格式化時間的時候,要特別特別注意,只能通過如下方式格式化:
fmt.Println(time.Now().Format("2006-01-02 15:04:05"))
Format里面的時間是固定的,因為是go第一個程序的誕生時間,也不知道go的開發者怎么想的,估計是想讓所有學習go的人記住這個偉大的時刻吧

 

 

 

轉自:https://www.cnblogs.com/zhaof/p/8030562.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM