1.数组
数组长度固定,Go中很少直接使用,而slice长度可长可短使用的场合更多。
package main
import "fmt"
func main() {
var a [3]int //3个整数的数组
fmt.Println(a[0]) // 输出数组的第一个元素
fmt.Println(a[len(a)-1]) //输出数组的最后一个元素a[2]
for i , v := range a {
fmt.Println( i, v) //输出索引和元素
}
for _ , v := range a { //只输出元素
fmt.Println( v)
}
}
可使用数组字面量根据一组值来初始化一个数组:
import "fmt"
func main() {
var q [3]int = [3]int{1,2,3}
fmt.Println(q[2])
var r [3]int = [3]int{1,2}
fmt.Println(r[2]) // "0"
}
在数组字面量中,省略号出现在长度位置,代表数组的长度由初始化数组元素个数决定,上述数组可简化:
import "fmt"
func main() {
q := [...]int{1,2,3}
fmt.Printf("%T\n",q) // [3]int
}
- 数组的长度是数组类型的一部分,所以[3]int和[4]int是两种不同的数组类型。
- 数组常量的值在编译时就可以确定: ``` package main
import “fmt”
func main() { q := [3]int{1,2,3} q = [4]int{1,2,3,4} //编译错误,不可将[4]int赋值给[3]int
}
可以像下面这样,给出一组值,这一组值同样具有索引和索引对应的值:
package main
import “fmt”
func main() { type Currency int const ( USD Currency = iota EUR GBP RMB ) symbol := […]string{USD: “U”, EUR: “E”, GBP: “G”,RMB: “R”} fmt.Println(RMB,symbol[RMB]) // 3 R }
如果数组元素类型可毕竟,则数组也是可比较的:
package main
import “fmt”
func main() { a := [2]int{1, 2} b := […]int{1, 2} c := [2]int{1, 3} fmt.Println(a == b, a == c, b == c) // true false false d := [3]int{1, 2} fmt.Println(a == d) // 编译错误,无法比较 }
同上的另一个例子:
package main
import ( “fmt” “crypto/sha256” )
func main() { var c1 = sha256.Sum256([]byte(“x”)) c2 := sha256.Sum256([]byte(“X”)) fmt.Printf(“%x\n%x\n%t\n%T\n”, c1, c2 , c1 == c2, c1) }
其中:
- `%x`将数组里的字节按照十六进制的方法输出
- `%t`表示输出一个布尔值
- `%T`表示输出一个值的类型
可使用下面函数,传递给数组一个指针,让数组清零:
func zero(prt *[32]byte) { *prt = [32]byte{} }
# 2.slice
slice表示拥有相同类型的可变长度的序列,通常写成[]T,有三个属性:指针,长度和容量。
slice可以用来访问数组的部分或者全部的元素,这个数组称为**底层数组**。
指针指向slice中第一个可以从slice访问的元素而并不一定是第一个元素
长度指元素个数,不能超过其容量,可用len
容量指从slice起始元素到**底层数组**的最后一个元素间元素的个数,可用cap
一个底层数组可以对应多个slice,可引用任何位置且彼此间的元素可重叠:
package main
import "fmt"
func main() {
months := [...]string{1: "Jan" ,12: "Dec"}
fmt.Println(months[1]) // Jan
fmt.Println(months[12]) // Dec
}
此处没有设置索引为0的元素,这样的话它的值就是空字符串。
操作符s[i:j],创建了一个新的slice,引用了序列s中i到j-1的所有元素:
import “fmt”
func main() { months := […]string{1: “Jan” ,2: “Feb”,3: “Mar”,4: “Apr”,5: “May”, 6: “jun”,7: “jul”,8: “Aug”,9: “Sep”,10: “Oct”,11: “Nov”,12: “Dec”} Q2 := months[4:7] fmt.Println(Q2) // [Apr May jun] summer := months[6:9] fmt.Println(summer) // [jun jul Aug]
for _,s := range summer{
for _,q := range Q2{
if s == q {
fmt.Printf("两个中重叠的月份是 %s", s) // 两个中重叠的月份是 jun
}
}
} } ``` 在不超过容量cap的情况下,slice引用超出了被引用的长度时,则最终slice比原slice长
endlesssummer := summer[:5]
fmt.Println(endlesssummer) // [jun jul Aug Sep Oct]
slice包含了指向数组元素的指针,所以可以内部修改底层数组的元素,也就是说,创建一个数组的slice等于为数组创建了一个别名,下面演示可以进行反转函数:
package main
import "fmt"
func main() {
a := [...]int{0,1,2,3,4,5,6,7,8}
reverse(a[:])
fmt.Println(a) // [8 7 6 5 4 3 2 1 0]
}
func reverse(s []int) {
for i,j := 0 , len(s)-1; i < j ; i, j = i+1, j-1{
s[i], s[j] = s[j], s[i]
}
}
与数组的一些差异:
- 初始化slice时没有指定长度,这样区别的结果是,数组创建了固定长度的数组,而slice创建了指向数组的slice
- slice无法进行 == 比较操作,想比较的话要自己写函数:
func equal(x, y []string)bool{ if len(x) != len(y){ return false } for i := range x{ if x[i] != y[i]{ return false } } return true }append
内置函数可以追加到slice后面:
package main
import (
"fmt"
)
func main() {
var runs []rune
for _,v := range "HELLO, WORLD"{
runs = append(runs,v)
}
fmt.Printf("%q\n", runs)
}
若数组不够大时,则进行扩容,建立一个新的数组,将原来的复制进去,将新元素追加到后面,每次容量扩大一倍来较少内存分配的次数:
package main
import (
"fmt"
)
func main() {
var x, y []int
for i :=0; i < 10 ;i++{
y = appendInt(x,i)
fmt.Printf("%d cap=%d\t%v\n", i, cap(y), y)
x = y
}
}
func appendInt(x []int, y int)[] int {
var z []int
zlen := len(x) + 1
if zlen <= cap(x){
z = x[:zlen]
} else {
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
}
输出的结果为:
0 cap=1 [0]
1 cap=2 [0 1]
2 cap=4 [0 1 2]
3 cap=4 [0 1 2 3]
4 cap=8 [0 1 2 3 4]
5 cap=8 [0 1 2 3 4 5]
6 cap=8 [0 1 2 3 4 5 6]
7 cap=8 [0 1 2 3 4 5 6 7]
8 cap=16 [0 1 2 3 4 5 6 7 8]
9 cap=16 [0 1 2 3 4 5 6 7 8 9]
需要注意的是,只要可能改变slice的长度或容量,或使得slice指向不同的底层数组,都需要更新slice变量。虽然底层数组的元素是间接引用的,但slice的长度,指针和容量都不是。slice不是纯引用类型而是包括引用与显式两种数据类型。
内置的append可以同时给slice添加多个元素,甚至添加另一个slice里的所有元素:
func main() {
var x []int
x = append(x, 1)
x = append(x,2,3)
x = append(x,x...)
fmt.Println(x) // [1 2 3 1 2 3]
}
上式中省略号表示如何将一个slice转换为参数列表
可对append进行修改,匹配append的功能
func appendInt(x []int, y ...int)[] int {
var z []int
zlen := len(x) + len(y)
if zlen <= cap(x){
z = x[:zlen]
} else {
zcap := zlen
if zcap < 2*len(x){
zcap = 2*len(x)
}
z = make([]int, zlen, zcap)
copy(z[len(x):],y)
}
z[len(x)] = y
return z
}
slice就地修改
展示从给定的一个字符串列表中去除空字符串并返回一个新的slice:
package main
import "fmt"
func main() {
data := []string{"one"," ","three"," ","five"}
data = nonempty(data)
fmt.Printf("%q\n",data)
}
func nonempty(strings []string)[]string{
i :=0
for _,v := range strings{
if v != " "{
strings[i] = v
i++
}
}
return strings[:i]
}
slice可以实现栈,下面展示从slice中间移除一个元素并保留元素顺序:
package main
import "fmt"
func main() {
s := []int{0,1,2,3,4,5,6,7,8,9}
s = remove(s,4)
fmt.Println(s)
}
func remove(slice []int, i int)[]int {
copy(slice[i:],slice[i+1:])
return slice[:len(slice)-1]
}
3.Map
map的类型是map[K]V,其中K和V是字典的键和值对应的数据类型
键的类型K必须是可以通过操作符 == 来进行比较的数据类型,所以map可以检测一个键是否已经存在
创建map
- 内置函数make可以用来创建一个map:
ages := make(map[string]int) - 也可以使用map的字面量来创建一个带初始化键值对元素的字典:
ages := map[string]int{ "alice": 31, "charlie": 34, }这个等价于:
ages := make(map[string]int) ages["alice"] = 31 ages["charlie"] = 34删除元素
内置函数delete,根据键移除一个元素:
func main() {
// ages := make(map[string]int)
ages := map[string]int{
"alice": 31,
"charlie": 34,
}
fmt.Println(ages)
delete(ages, "alice")
fmt.Println(ages) // map[charlie:34]
}
即使键不在map中也是安全的,默认不存在的键,对应的值为0,同时支持快捷赋值方式:
package main
import "fmt"
func main() {
ages := map[string]int{
"alice": 31,
"charlie": 34,
}
fmt.Println(ages) // map[alice:31 charlie:34]
ages["bob"] = ages["bob"] + 1
fmt.Println(ages) // map[alice:31 charlie:34 bob:1]
ages["bob"]++
fmt.Println(ages) // map[alice:31 charlie:34 bob:2]
}
但是map元素不是一个变量,不能获取其地址,原因是map的增长可能会导致已有元素被重新散列到新的存储位置,这样的结果可能使地址无效。
遍历与输出
使用range即可,要说的一点是顺序是不固定的,:
package main
import "fmt"
func main() {
ages := map[string]int{
"alice": 31,
"charlie": 34,
}
for name,age := range ages{
fmt.Printf("%s\t%d\n", name, age)
}
}
如果要排序的话,加入键是string类型,可用sort包中的String函数进行键的排序:
func main() {
ages := map[string]int{
"alice": 31,
"charlie": 34,
"bob": 1,
"mike" : 34,
}
var names []string
for name := range ages{
names = append(names,name)
}
sort.Strings(names)
for _,name := range names{
fmt.Printf("%s\t%d\n", name, ages[name])
}
}
输出结果为:
alice 31
bob 1
charlie 34
mike 34
map类型的零值是nil,没有引用任何散列表:
import (
"fmt"
)
func main() {
var ages map[string]int
fmt.Println(ages == nil) // true
fmt.Println(len(ages) == 0) // true
}
但是向零值设置元素会导致错误:
package main
func main() {
var ages map[string]int
ages["aaa"] =21// 错误,未初始化map
}
由于一个键如果不在map中则值为0,想确认辨别一个不存在的元素或者恰好这个元素的值是0,第二个值是布尔值,报告该元素是否真的存在:
package main
import "fmt"
func main() {
ages := map[string]int{
"alice": 31,
"charlie": 34,
"bob": 0,
"mike" : 34,
}
_, ok := ages["Tom"]
if !ok{
fmt.Printf("不存在")
}
_, ok = ages["bob"]
if ok{
fmt.Printf("存在")
}
}
4.结构体
先定义一个叫Employee的结构体和一个结构体变量dilbert:
package main
import (
"time"
"fmt"
)
func main() {
type Employee struct {
ID int
Name string
Address string
DoB time.Time
Position string
Salary int
ManagerID int
}
// var dilbert Employee //与上一行一样
dilbert := Employee{}
dilbert.Salary = 5000 // 可访问结构体中的成员变量,进行赋值
fmt.Println(dilbert.Salary) // 5000
p := &dilbert.Position // 可获取成员变量的地址,通过指针访问
*p = "Bob" + *p
fmt.Println(*p) // Bob
var employeeOfTheMonth *Employee = &dilbert // 点号也可用于结构体指针上
// employeeOfTheMonth.Position += "(good worker)"
(*employeeOfTheMonth).Position += "(good worker)"//与上一行一样
fmt.Println(dilbert.Position) // Bob(good worker)
}
也可定义一个函数,通过给定ID返回一个指向Employee结构体的指针:
func ByID(id int) *Employee{}
id := dilbert.ID
ByID(id).Salary = 0
需要注意的几点:
- 相同类型的成员变量可写在一行
- 成员变量的顺序很重要,调换相同类型的成员或是声明在同一行成员换位置,就是在定义一个不同的结构体类型,一般只组合相关的成员变量
- 如果一个结构体的成员变量名称是首字母大写的,则这个变量是可导出的(这是Go最主要的访问控制机制)
一个二叉树实现插入排序的例子:
type tree struct {
value int
left,right *tree
}
func Sort(values [...]int){
var root *tree
for _,v := range values{
root = add(root, v)
}
appendValues(values[:0], root)
}
func appendValues(values []int, t *tree) []int{
if t != nil{
values = appendValues(values,t.left)
// values = appendValues(values,tree.value)
values = appendValues(values, t.right)
}
return values
}
func add(t *tree, value int) *tree{
if t == nil{
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
}
结构体字面量
结构体类型的值可以通过结构体字面量来设置:
- 对应直接设置,需要按照正确的顺序,我每个成员变量指定一个值。这样的弊端是必须记住每个成员变量的顺序,也使得未来结构体成员变量扩充或者重新排列的时候代码维护性差。适合于小结构体:
import "fmt"
func main() {
type Point struct {X,Y int}
p := Point{1,2}
fmt.Println(p.Y) // 2
}
- 指定成员变量和名称,初始化结构体变量:
func main() {
type Point struct {x,y int}
var _ = Point{x:1, y:2}
}
结构体类型的值可以作为参数传递给函数或者作为函数的返回值:
type Point struct {X,Y int}
func Scale(p Point, factor int) Point{
return Point{p.X * factor,p.Y * factor}
}
fmt.Println(Scale(Point{1, 2 }, 5)) // "{5, 10}"
为了效率,很多大型结构体通常使用结构体指针的方式直接传递给函数或从函数中返回:
func Bonus( e *Employee, Percent int) int {
return e.Salary * Percent/100
}
因此,结构体通常都通过指针的方式使用,因此可以使用一种简单的方式来创建、初始化一个struct类型的变量并获取它的地址:
pp := &Point{1, 2}
这个等价于
pp := new(Point)
*pp = Point{1, 2}
结构体比较
如果一个结构体中所有成员可比较,则两个结构体是可比较的。==表示按照顺序比较两个结构体变量的成员变量:
func main() {
type Point struct {x,y int}
pp := &Point{1, 2}
qq := &Point{2, 1}
fmt.Println(pp.x == qq.x && pp.y == qq.y) // false
fmt.Println(qq == pp) // false
}
结构体嵌套和匿名成员
将一个命名结构体当做另一个结构体类型的匿名成员使用。
举例:2D绘图程序关于图形的库,有(Spoke定义了车轮中条幅的数量):
type Circle struct {X, Y, Radius int}
type Wheel struct {X, Y, Radius, Spokes int}
创建一个Wheel类型的对象
func main() {
type Circle struct {X, Y, Radius int}
type Wheel struct {X, Y, Radius, Spokes int}
var w Wheel
w.X = 8
w.Y = 8
w.Radius = 5
w.Spokes = 20
}
如果所要支持的对象变多,意识到他们之间的相似性和重复性,重构相同的部分,但是重构之后显然访问wheel成员变量更麻烦了:
func main() {
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
w.circle.Center.Y = 8
w.circle.Radius = 5
w.Spokes = 20
}
在Go中,运行不带结构体成员,只需要指定类型即可。这种结构体成员称为匿名成员。这个结构体成员要满足的是它们的类型必须是一个命名类型或者指向命名类型的指针。可作出如下修改:
func main() {
type Point struct {X,Y int}
type Circle struct {
Point
Radius int
}
type Wheel struct {
Circle
Spokes int
}
var w Wheel
w.X = 8
w.Y = 8
w.Radius = 5
w.Spokes = 20
}
要注意的是,结构体字面量不能直接被如下初始化(下面两种都会编译错误):
w = Wheel{8, 8, 5, 20}
w = Wheel{X = 8, Y = 8, Radius = 5, Spokes = 20}
想初始化必须如下:
package main
import "fmt"
func main() {
type Point struct {X,Y int}
type Circle struct {
Point
Radius int
}
type Wheel struct {
Circle
Spokes int
}
//w = Wheel{Circle{Point{8, 8}, 5}, 20}
w := Wheel{
Circle:Circle{
Point:Point{X : 8, Y : 8},
Radius:5,},
Spokes : 20}
fmt.Printf("%#v\n", w)
w.X = 888
fmt.Printf("%#v\n", w)
}