文章目录
前言
《Head First Go》第六章
1. 切片
切片实际上是一个Go的数据结构,我们可以增加更多值。和数组对比,切片是由多个相同类型的元素组成,但是切片可以追加元素。
声明切片: 声明切片不会自动创建一个切片,我们需要调用make函数。和数组对比,声明切片不需要显式定义这个切片大小
var mySlice []string //声明一个切片,但是没有创建
创建切片: 使用make函数创建切片
mySlice = make([]string, 7)
声明出来之后,用法和数组一样,下标索引
短变量创建切片:
primes := make([]int, 7)
同样,对于数组来说,for…range和len函数也是可以用到切片上面的
package main
import "fmt"
func main() {
primes := make([]int, 7)
//或者使用_进行占位
for index, i :=range primes{
fmt.Print(index, i, " ");
}
}
2. 切片字面量
下面是一行的:
notes := []string{"do", "re", "mi", "fa", "so", "la", "ti"} //切片字面量赋值
多行的:
primes :=[]int{
1,
2,
3, //记得加逗号
}
3. 切片运算符
为什么有了数组还需要切片呢?因为切片就是在数组的基础上建立的,切片仅仅是数组中的一部分(或者所有)的元素的视图。
当使用make函数或者切片字面量创建一个切片的时候,底层的数组会自动创建出来(只有通过切片,才可以访问到)。但是我们也可以自己创建一个数组,然后基于数组通过切片运算符创建一个切片。
func main() {
notes := []string{"do", "re", "mi", "fa", "so", "la", "ti"} //切片字面量赋值
fmt.Printf("notes: %v\n", notes)
mySlice := notes[1:3]
fmt.Printf("mySlice: %v\n", mySlice) //mySlice: [re mi]
}
我们注意这种方法创建出来的切片只是包括了下标 1 到下标 2 的数据,也就是说 [i : j] ==> arr[i] … arr[j-1]
当然了我们也可以对 i 进行省略,比如上面的 notes[1:3] 可以使用 notes[ :3]
,结果就是从下标0到下标2
func main() {
notes := []string{"do", "re", "mi", "fa", "so", "la", "ti"} //切片字面量赋值
fmt.Printf("notes: %v\n", notes)
mySlice := notes[:3]
fmt.Printf("mySlice: %v\n", mySlice) //mySlice: [do re mi]
}
我们可以省略 i,那么自然也可以省略 j,mySlice := notes[1:]
表示从下标1一直到最后,下面是代码:
package main
import "fmt"
func main() {
notes := []string{"do", "re", "mi", "fa", "so", "la", "ti"} //切片字面量赋值
fmt.Printf("notes: %v\n", notes)
mySlice := notes[1:]
fmt.Printf("mySlice: %v\n", mySlice) //mySlice: [re mi fa so la ti]
}
4. 底层数组
切片其实就是底层数组的一个映射,切片并不会自己保存数据。那么当数组底层修改的时候,切面也会被修改。
package main
import "fmt"
func main() {
notes := []string{"do", "re", "mi", "fa", "so", "la", "ti"} //切片字面量赋值
fmt.Printf("notes: %v\n", notes) //notes: [do re mi fa so la ti]
slice1 := notes[0:3]
fmt.Printf("slice1: %v\n", slice1) //slice1: [do re mi]
notes[0] = "修改"
fmt.Printf("notes: %v\n", notes) //notes: [修改 re mi fa so la ti]
fmt.Printf("slice1: %v\n", slice1) //slice1: [修改 re mi]
}
同样的,如果有多个切片,那么数组的修改会反映给多个所有的切片。在创建切片的时候我们是使用了make方法来创建的,因为这样我们就可以不用关心底层数组是怎么样的了。
5. 使用 “append” 函数在切片上添加数据
package main
import "fmt"
func main() {
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的过程中,由于到达了底层数组的容量,那么就会创建一个新的数组,可能导致这两个切片引用的数组不一样。
package main
import "fmt"
func main() {
s1 := []string{"s1", "s1"}
s2 := append(s1, "s2", "s2")
s3 := append(s2, "s3", "s3")
s4 := append(s3, "s4", "s4")
//打印切片
fmt.Println(s1, s2, s3, s4) //[s1 s1] [s1 s1 s2 s2] [s1 s1 s2 s2 s3 s3] [s1 s1 s2 s2 s3 s3 s4 s4]
s4[0] = "XX"
fmt.Println(s1, s2, s3, s4) //[s1 s1] [s1 s1 s2 s2] [XX s1 s2 s2 s3 s3] [XX s1 s2 s2 s3 s3 s4 s4]
fmt.Printf("s1: %p, s2:%p, s3:%p, s4:%p", s1, s2, s3, s4) //s1: 0xc00004e3c0, s2:0xc000024080, s3:0xc000110000, s4:0xc000110000
}
可以看到的是s1和s2的地址不一样,而s3和s4的地址一样,所以在添加的过程中,底层数组进行了重新创建的过程。导致了s3和s4的地址不一样了,那么正确的做法应该就是:将append的返回值赋值给s1
6. 切片和零值
没有赋值的切片:
package main
import "fmt"
func main() {
floatSlice := make([]float64, 10) //[0 0 0 0 0 0 0 0 0 0]
boolSlice := make([]bool, 10) //[false false false false false false false false false false]
fmt.Println(floatSlice, boolSlice)
}
切片变量自己的 0 值:nil,一个没有赋值的切片变量为 nil
func main{
var initSlice []int
var stringSlice []string
fmt.Printf("initSlice: %#v, stringSlice: %#v", initSlice, stringSlice) //initSlice: []int(nil), stringSlice: []string(nil)
}
对于一个空的切片,使用len函数返回的结果是0,而使用append函数会追加值后返回新的切片,实际上这个切面是底层重新创建了的切片。
package main
import "fmt"
func main() {
var slice []string
fmt.Printf("旧的: %p\n", slice) //旧的: 0x0
if len(slice) == 0{
slice = append(slice, "a")
}
fmt.Printf("slice: %v\n", slice) //slice: [a]
fmt.Printf("新的: %p\n", slice) //新的: 0xc00004c250
}
7. 使用切片和 “append” 读取额外的文件行
我们使用切片来读取文件中的浮点数,这样就不需要提前设定好数值了。如果程序出错,我们返回 nil 和 error 即可。
package main
import (
"bufio"
"os"
"strconv"
)
func GetFLoats(fileName string) ([]float64, error) {
var numbers []float64
file, err := os.Open(fileName)
if err != nil{
return nil, err
}
scanner := bufio.NewScanner(file)
for scanner.Scan(){
number, err := strconv.ParseFloat(scanner.Text(), 64) //读取文件
if(err != nil){
return nil, err
}
numbers = append(numbers, number)
}
err = file.Close(); //关闭文件
if(err != nil){
return nil, err;
}
if(scanner.Err() != nil){
return numbers, scanner.Err();
}
return numbers, nil;
}
func main(){
}
8. 可变参数
用法:name...type
, 用法和数组一样
package main
import (
"fmt"
)
func manyArgs(numbers...int) int{
var sum int
for _, number := range numbers{
sum += number
}
return sum
}
func main(){
i := manyArgs(1, 2, 3)
fmt.Printf("i: %v\n", i) //i: 6
}
那么既然可变参数和切片都是多个同类型参数,能不能把切片作为参数传给可变参数呢?
可以看到传递切片会报错,那么有什么办法呢?Go语言提供了一个特殊的语法,只需要在可变参数后面加上...
就可以了
package main
import (
"bufio"
"fmt"
"os"
"strconv"
)
func manyArgs(numbers...int) int{
var sum int
for _, number := range numbers{
sum += number
}
return sum
}
func main(){
name := []int{1,2,3,4,5,6,7}
i := manyArgs(name...)
fmt.Printf("i: %v\n", i) //i: 28
}
如有错误,欢迎指出!!!!