golang 复合数据类型

数组

固定长度,不如slice灵活,go中很少直接使用数组。

数组是值传递,不能通过修改数组类型的参数来修改这个数组的值。

package main import "fmt" func main() { x := [3]int{1, 2, 3} func(arr [3]int) { arr[0] = 7 fmt.Println(arr) }(x) fmt.Println(x) } [7 2 3] [1 2 3] q := [...]int{1,2,3} 如果数组长度位置是省略号,则数组长度由实际插入决定

slice

  • 变长序列。写法是[]T,T代表类型。
  • 前边整理过的s[m:n] s[m:] s[:n] s[:]
  • 跟数组一样,切片包括一个执行第一个值的指针,所以复制是就是生成一个别名的指针,指向第一个值。
  • 切片反转
  • 不能通过== 判断两个切片是否相同,但是可以通过bytes.Equal函数判断两个字节型slice是否相容([]byte),但是对于其他类型的slice必须自己展开比较。
  • 判断为空;
len(s)==0
  • 用内置的make函数创建一个指定元素类型、长度和容量的slice。容量部分可以忽略,容量将等于长度。
make([]T,len) make([]T,len,cap) //类似 make([]T,cap)[:len]

这段看不懂 在底层,make创建了一个匿名的数组变量,然后返回一个slice;只有通过返回的slice才能用底层匿名的数组变量。在第一中语言中,slice是整个数组的view。在第二种语句中,slice只引用了底层数组的前len个元素,但是容量包含整个的数组。额外的元素是留给未来的增长用的。

append函数

用于向slice中追加元素

var runes []rune for _, r:=range "hello, 世界"{ runes = append(runes, r) } fmt.Printf("%q\n", runes) //"['h' 'e' 'l' 'l' 'o' ',' ' ' '世' '界']"

在循环中使用append构建一个由9个rune字符组成的slice。 – cap()返回的是数组切片分配的空间大小,可以有预留。

my := make([]int, 5, 10) cap(my)返回的是10,make生成5个初始化值的my切片。
  • copy(a,b)复制切片(有返回值,返回的是成功复制的元素个数),从b复制到a
  • appendInt函数分配时,容量大于切片的容量会重新分配内存,*2。容量够用就不会。
  • append可以追加多个元素甚至可以是一个切片。

slice内存技巧

旋转,反转,修改元素。 – 返回不含空字符串的列表nonempty函数

func nonempty(strings []string) []string { i := 0 for _, s := range strings { if s != "" { strings[i] = s i++ } } return strings[:i] }
  • 一个slice可以模拟一个stack。
stack = append(stack, v)
  • 要删除slice中的某个元素但是保存原有的元素顺序,可以通过内置的copy函数将后边的子slice向前移动一位完成。
func remove(slice []int, i int) []int { copy(slice[i:], slice[i+1:]) return slice[:len(slice)-1] } func main() { s := []int{5,6,7,8,9} fmt.Println(remove(s, 2)) //"[5 6 8 9]" }
  • 改写reverse函数,用数组指针代替slice
  • 给函数传递数组,传递的只是数组的拷贝,问不是他的指针
package main import "fmt" func reverse(s *[5]int) { for i, j := 0, len(s)-1; i < j ; i, j = i+1, j-1 { (*s)[i], (*s)[j] = (*s)[j], (*s)[i] } } func main() { s := [5]int{1,2,3,4,5} reverse(&s) fmt.Println(s) }
  • 编写一个rotate函数,通过一次循环完成旋转。
package main import "fmt" func rotate(s []int, x int) []int{ a := make([]int, 5, 10) for i := 0; i < len(s); i++ { a[i] = s[(i+x)%5] } return a } func main() { my := []int{1,2,3,4,5} fmt.Println(rotate(my, 5)) // fmt.Println(my) }
  • 消除[]string中相邻重复字符串
func StringNoRep(s []string) []string { for i := range s { if i == len(s)-1 { return s } if s[i] == s[i+1] { s = append(s[:i], s[i+1:]...) } } return s }

map

在go语言中,一个MAP就是一个哈希表的引用,map类型可以写成map[K]V,key和value。map中所有的key可以使同一类型,所有的value可以是同一类型,但是key和value可以是不同类型。 内置的make函数可以创建一个map:

ages := make(map[string]int) // mapping from strings to ints ages["alice"] = 31 ages["chalie"] = 34 ager := map[string]int{ "alice": 31, "charlie": 34 }
  • 内置delete函数可以删除元素。
delete(ages, "alice")
  • 因为map中的元素不是变量,所以不能进行取址操作。
  • 可以用range风格的for循环遍历map的key/value
  • map的迭代顺序是随机的,并且不同的哈希函数实现可能导致不同的遍历顺序。如果要按顺序遍历key/value对,必须显式地对key进行排序,可以使sort包的Strings函数符字符串slice进行排序。
for name, age := range ages{ fmt.Pringf("%s\t%d\n", name, age) }
  • 不能向一个nil的map中存数据,在向map中存数据前必须先创建map
  • 使用range遍历map数据类型时,两个变量分别对应map的第一个类型和第二个类型
package main import "fmt" func main() { ages := make(map[string]int) // mapping from strings to ints ages["alice"] = 31 ages["chalie"] = 34 for i,j := range ages { fmt.Println(i) fmt.Println(j) } } alice 31 chalie 34 Process finished with exit code 0

结构体

类似于类中的结构体,需要绑定到一个对象中。利用对象进行访问、复制、取址等工作。 – 参数顺序不同也是不同的结构体。

type Employee struct { ID int Name string Address string DoB time.Time Position string Salary int ManagerID int } var dilbert Employee

对成员取址,然后通过指针访问

position := &dilbert.Salary *position = "Senior " + *position // promoted, for outsourcing to Elbonia
  • S结构体中的成员不能是S类型的,但是可以是*S指针类型的,这样可以创建递归的数据结构,如链表和树结构。下边的代码是用数实现插入。
package main import "fmt" type tree struct { value int left, right *tree }//每个树的节点都是这个结构体递归出来的。 // Sort sorts values in place. func Sort(values []int) { var root *tree for _, v := range values { root = add(root, v) } appendValues(values[:0], root) } // appendValues appends the elements of t to values in order // and returns the resulting slice. func appendValues(values []int, t *tree) []int { if t != nil { values = appendValues(values, t.left) values = append(values, t.value) values = appendValues(values, t.right) } return values } func add(t *tree, value int) *tree { if t == nil { // Equivalent to return &tree{value: value}. t = new(tree) t.value = value return t } if value < t.value { t.left = add(t.left, value) } else { t.right = add(t.right, value) } return t } func main() { my := []int{1,2,5,8,4,2,5,8} Sort(my) fmt.Println(my) }

结构体面值

结构体值也可以用结构体面值表示,可以指定每个成员的值

type Point struct{x,y int} p := Point{1, 2}

通常用第二种写法,顺序和其他成员的值不用考虑

p := Point{X: 1}

结构体可以做参数和返回值,可以对结构体中的成员做缩放等处理。 因为函数的参数都是值拷贝,所以要对结构体进行修改就要传入指针。

如果结构体的所有成员都是可比较的,那么这个结构体也是可比较的。可比较的结构体类型可以用于map的key类型。

结构体嵌入和匿名成员

两个有相似之处的结构体可以嵌入 比如轮胎和圆和点

type Point struct { X, Y int } type Circle struct { Center Point Radius int } type Wheel struct { Circle Circle Spokes int }

但是访问会很繁琐比如 var w Wheel w.Circle.Center.X = 8 所以要有匿名成员

type Circle struct { Point Radius int } type Wheel struct { Circle Spokes int }

各有一个匿名成员。特性是:类型必须是命名的类型或者是指向一个命名的类型的指针。 Point类型嵌入到了Circle结构体,Circle类型嵌入到了Wheel结构体 这样访问就变成了 var w Wheel x.X = 8 这样它们的字面值并没有简短的表示匿名成员的语法,一下语法不存在。

w = Wheel{8, 8, 5, 20} // compile error: unknown fields w = Wheel{X: 8, Y: 8, Radius: 5, Spokes: 20} // compile error: unknown fields

可以用这样的语法

w = Wheel{Circle{Point{8, 8}, 5}, 20} w.X = 5 fmt.Printf("%#v\n", w)

副词,代表打印时和go语言一样的语法结构

JSON

JSON是对JavaScript中各种类型的值——字符串、数字、布尔值和对象——Unicode文本编码。它可以用有效可读的方式表示第三章的基础数据类型和本章的数组、slice、结构体和map等聚合数据类型。 将GO中的一个结构体的切片转换成JSON的过程叫做编组,通过json.Marsha完成。生成的是很长的字符串,没缩进很难阅读。另一个json.Marshallndent函数是格式化的输出。

data, err := json.MarshalIndent(movies, "", " ") if err != nil { log.Fatalf("JSON marshaling failed: %s", err) } fmt.Printf("%s\n", data)

发表回复

您的邮箱地址不会被公开。必填项已用 * 标注