五、列表:数组

数组保存特定数量的元素,不能增长或者收缩。要声明保存数组的变量,需要在方括号([])中指定它保存的元素数量,后跟数组所保存的元素类型。

var myArray [4]string
var notes [7]string
notes[0]="do"
notes[1]="re"
notes[2]="mi"
fmt.Println(notes[0]) // do
fmt.Println(notes[1]) // re

创建数组时,它所包含的所有值都初始化为数组所保存类型的零值。

数组字面量

var notes [2]string=[2]string{"do","re"}
// 短变量声明
notes:=[2]string{"do","re"}
// 可以将数组字面量分散到多行上但是必须在代码中的每个换行符前使用逗号,如果数组字面量的最后一项后面跟着的是换行符,
// 需要在其后面跟一个逗号(使得以后向代码中添加更多的元素变得更容易。)
text:=[2]string{
    "hello",
    "world",
}

使用for…range安全遍历数组

在range格式中,提供一个变量,该变量(index)将保存每个元素的整数索引,另一个变量(value)将保存元素本身的值,以及要循环的数组。循化将为数组中的每一元素运行一次,将元素的索引赋值给第一个变量,将元素的值赋值给第二个变量。

for index,value := range myArray {
    fmt.Println(index,value)
} 

可以使用给空白标识符(_)忽略于for…range循环的值

读取文本文件

package main

import (
    "bufio"
    "fmt"
    "log"
    "os"
)

func main() {
    file,err:=os.Open("data.txt") // 打开数据文件进行读取,默认是在程序同目录下搜索数据文件
    if err!=nil {
        log.Fatal(err)
    }
    scanner:=bufio.NewScanner(file)
    for scanner.Scan() { // 从文件中读取一行,Scan方法将从文件中读取一行文本,如果读取数据成功则返回true,否则返回false
        fmt.Println(scanner.Text()) // 打印该行,Text方法将返回一个包含已读取数据的字符串
    }
    err=file.Close() // 关闭文件释放资源
    if(err!=nil) {
        log.Fatal(err)
    }
    if scanner.Err()!=nil {
        log.Fatal(scanner.Err())
    }
}

从os.Open会返回两个值:指向代表被打开文件的os.File值的指针,以及一个错误值。
一个读取文件程序,文件内容为:

71.8
56.2
89.5
//Package datafile allows reading data samples from files.
package datafile import(
    "bufio"
    "os"
    "strconv"
)

// GetFloats reads a float64 from each line of a file.
func GetFloat(fileName string) ([3]float,error) {
    var numbers [3]float64
    file,err:=os.Open(fileName)
    if err!=nil {
        return numbers,err
    }
    i:=0
    scanner:=bufio.NewScanner(file)
    for scanner.Scan() {
        numbers[i],err:=strconv.ParseFloat(scanner.Text(),64)
        if err!=nil {
            return numbers,err
        }
        i++
    }
    err=file.Close()
    if err!=nil {
        return numbers,err
    }
    if scanner.Err() !=nil {
        return numbers,scanner.Err()
    }
    return numbers,nil
}
六、追加的问题:切片

无法将更多的元素增加到一个数组中,而切片是一个可以通过增长来保存额外数据的集合类型。与数组相同的是,切片由多个相同类型的元素构成。不同的是,切片允许我们在结尾追加更多的元素。

var mySlice []string // 除了不指定大小,与声明一个数组变量的语法完全相同

不像数组变量,声明切片变量并不会自动创建一个切片。为此,你可以调用内建的make函数(其他的内建函数如len())。传递给make你想要创建的切片的类型(这个类型与你想要赋值的变量的类型相同)和需要创建的切片的长度。

var notes []string
notes=make([]string,7) // 创建7个字符串的切片
// 使用一个短变量声明的make会自动帮你推导出变量的类型
primes := make([]int,5)
// 内建的函数len对于切片也和数组有相同的效果。将一个切片的变量传入len,会返回一个整型的长度值
fmt.Println(len(notes)) // 7
// 使用切片字面量赋值
notes:=[]string{"do","me"}

每一个切片都构建于一个底层的数组之上。实际上是底层的数组存储了切片的数据;切片仅仅是数组中的一部分(或者所有)元素的视图。

underlyingArray:=[5]string{"a","b","c","d","e"}
slice1:=underlyingArray[0:3]
fmt.Println(slice1) // [a b c]

如果多个切片指向了同一个底层数组,数组的元素修改会反映给所有的切片。使用make和切片字面量来创建切片,而不是创建一个数组,再用一个切片在上面操作。使用了make和切片字面量,你就不用关心底层数组了。

使用“append”函数在切片上添加数据

Go提供一个内建的函数append来将一个或者多个值追加到切片的末尾。它返回一个与原切片元素完全相同的并且在尾部追加了新元素的新的更大的切片。

slice:=[] string("a","b"}
fmt. Println(slice, len(slice)) // [a b] 2
slice=append(slice,"c")
fmt. Println(slice, len(slice))  // [a b c] 3
slice=append(slice,"d","e")
fmt. Println (slice, len(slice))  // [a b c d e] 5

注意我们需要确保将append返回的值重新赋给传递给append的那个变量。这是为了避免append返回的切片中的一些不一致行为。切片的底层数组并不能增长大小。如果数组没有足够的空间来保存新的元素,所有的元素会被拷贝至一个新的更大的数组,并且切片会被更新为引用这个新的数组。但是由于这些场景都发生在append函数内部,无法知道返回的切片与传入append函数的切片是否具有相同的底层数组。如果你保留了两个切片,会导致一些非预期的错误。

例如我们有4个切片,后三个是通过append调用生成的。我们并没有遵循惯例将append函数的返回值赋给传入的变量。当我们给切片s4的一个元素赋值的时候,我们能看到s3中的体现。因为s4和s3碰巧都共享相同的底层数组。但是改变并没有在s2或者s1中体现,因为它们都有不同的底层数组。

s1:=[]string("s1","s1"}
s2:=append(s1,"s2","s2")
s3:=append(s2,"s3","s3")
s4:=apend(s3,"s4","s4")
fmt.Println(sl,s2,s3,s4)
s4[0]="XX" // 给第4个切片的一个元素赋值
fmt.Println(s1,s2,s3,84)
[sl sl] [sl sl s2 s2] [sl sl s2 s2 s3 s3] [sl sl s2 s2 s3 s3 s4 s4]
[sl sl] [sl sl s2 s2] [XX sl s2 s2 s3 s3] [XX sl s2 s2 s3 s3 s4 s4]
 “s1”和“s2”切片碰巧
 基于不同的底层数组,
 所以无法观察到变更!   "s3”切片与“s4”共享了一个相同的底层数组,所以“s4”的变化体现在这里!

所以我们调用append函数,惯例是将函数的返回值赋给你传入的那个切片变量。如果你只保存一个切片,你就无须考虑两个切片是否共享了同一个底层数组。

s1:=[]string("s1","s1"}
s1=append(s1,"s2","s2")
s1=append(s1,"s3","s3")
s1=append(s1,"s4","s4")
fmt.Println(s1) // [sl sl s2 s2 s3 s3 s4 s4]

修改文件读取程序

//Package datafile allows reading data samples from files.
package datafile import(
    "bufio"
    "os"
    "strconv"
)

// GetFloats reads a float64 from each line of a file.
func GetFloat(fileName string) ([3]float,error) {
    var numbers []float64
    file,err:=os.Open(fileName)
    if err!=nil {
        return numbers,err
    }
    //i:=0
    scanner:=bufio.NewScanner(file)
    for scanner.Scan() {
        number,err:=strconv.ParseFloat(scanner.Text(),64)
        if err!=nil {
            return numbers,err
        }
        numbers=append(numbers,number)
    }
    err=file.Close()
    if err!=nil {
        return numbers,err
    }
    if scanner.Err() !=nil {
        return numbers,scanner.Err()
    }
    return numbers,nil
}

出错时返回numbers是没有意义的,所以这种情况下返回nil。

从os.Args切片获取命令行参数

os包有一个包级别的变量os.Args,它是一个字符串的切片,代表了当前执行程序的命令行参数。

arguments:=os.Args[1:] // 通过os.Args获取一个不包含节元素的字符串切片

可变长参数函数

是否注意到Println和append可以有任意个数的参数?它们定义了一个可变长参数函数。一个可变长参数函数可以以多种参数个数来调用。为了让函数的参数可变长,在函数声明中的最后的(或者仅有的)参数类型前使用省略号(…)。

// 可变长参数函数的最后一个参数接收一个切片类型的变长参数,这个切片可以被函数当作普通切片来处理。
func myFunc(param1 int, param2  ...string) {
    
}
// strings变量保存了一个所有参数的切片
func severalStrings(strings...string) {
    fmt.Println(strings)
}
func main() {
    severalStrings("a","b") // [a b]
    severalStrings("a","b","c","d","e") // [a b c d e]
    severalStrings() // [] 如果没有参数,会收到一个空的切片
}

仅仅函数定义中的最后一个参数可以是可变长参数;你不能把它放到必需参数之前。

package main

inport "fmt"
// 这个average函数需要一个或者多个float64类型参数,而不是一个float64的切片类型
func average(numbers...float64)float64{
    var sum float64=0
    for_,number:=range numbers {
    sum+=number
}
    return sum/float64(len(numbers))
}

func main() {
    fmt.Println(average(100,50)) // 75
    fmt.Println(average(90.7,89.7,98.5,92.3)) // 92.8
}

向可变长参数函数传递一个切片,当我们调用一个可变长参数函数时,简单地在你传入的切片变量后增加省略号(…)即可正确调用变参数函数。

func severalInts(numbers.. int){
    fmt. Println(numbers)
}
func mix(nun int, flag bool, strings... string) {
    fmt. Println(num, flag, strings)
}

fumc main(){
    intSlice:=[]int{1,2,3}
    severalInts(intSlice...) // 使用int切片代替可变参数
    stringSlice:=[]string("a","b","c","d")
    mix(1,true,stringSlice...) // 使用string切片代替可变参数
}