数组
【数组的概念】数组是同一种数据类型元素的集合。
【数组的声明与初始化】 声明之后,不可改变类型,不可改变长度。同类型&&同长度数组之间才可以相互赋值
// 数组初始化的几种方法
var num = [...]int{1, 2, 3} // [...]表示让编译器自己推断数组长数 var a = [9]int{1, 2, 3, 4, 5, 6, 7, 8, 9} var b [10]int = [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} c := [8]int{1, 2, 3, 4, 5, 6, 7, 8} // 初始化不赋值 var d = [9]int{} fmt.Println(d) // [0 0 0 0 0 0 0 0 0] // 数组元素的访问 fmt.Println(a[0]) // 1 // 数组的长度 fmt.Println(len(b)) // 10 // 数组的遍历 for k, v := range c { fmt.Println(k, v) }
【多维数组】
a := [3][4]int{ {0, 1, 2, 3} , /* 第一行索引为 0 */ {4, 5, 6, 7} , /* 第二行索引为 1 */ {8, 9, 10, 11}, /* 第三行索引为 2 */ } fmt.Println(a[1][2]) // 索引为1的第3个元素
数组是值类型,赋值会复制整个数组。因此改变副本的值,不会改变本身的值
package main import "fmt" func main() { var a = [9]int{1, 2, 3, 4, 5, 6, 7, 8, 9} var b [9]int b = a a[1] = 5 fmt.Println(a) // [1 5 3 4 5 6 7 8 9] fmt.Println(b) // [1 2 3 4 5 6 7 8 9] }
切片
【切片的概念】(Slice)是一个拥有相同类型元素的可变长度的序列
【切片的声明】
var a []string //声明一个字符串切片 var b = []int{} //声明一个整型切片并初始化 var c = []bool{false, true} //声明一个布尔切片并初始化 var d = []bool{false, true} //声明一个布尔切片并初始化
【切片的初始化】
//创建一个容量cap为8,长度len为5的零值切片,值为[0 0 0 0 0] var s1 []int = make([]int, 5, 8) //创建一个容量和长度都为8的零值切片,值为[0 0 0 0 0 0 0 0] var s2 []int = make([]int, 8) //也可以这样创建零值切片,参数从左到右依次是类型,Len,cap var s1 = make([]int, 5, 8) s2 := make([]int, 8) //这样创建的切片是满容的 var s []int = []int{1,2,3,4,5} // cap和len都为5 //创建空切片,他们值均为[] cap为0 len也为0 var s1 []int var s2 []int = []int{} var s3 []int = make([]int, 0)
切片之间直接赋值之后,修改其中一个切片的元素的值,另一个切片相对应元素的值会随之改变
切片之间用copy()赋值后,修改其中一个切片元素的值,另一个切片相对应元素的值不会发生改变
【切片的基础操作】
package main func main() { var s1 []int s2 := []int{14,15} for i := 0; i < 10; i++ { s1 = append(s1, i) } // 添加一个元素元素 s1 = append(s1, 10) // 添加多个元素元素 s1 = append(s1, 11, 12, 13) // 添加另一个切片的元素(后面加…) s1 = append(s1, s2...) // 删除索引最小的元素 _ = s1[1:] // 删除索引最大的元素 _ = s1[:1] // 删除 (非最大最小 && 索引为index)的元素 index := 4 _ = append(s1[:index], s1[index+1:]...) }
切片append之后赋值给一个新的变量,不能赋值给_,切片append追加之后如果超出了原切片的容量,会变成一个新的切片,不再和原切片共享同一个底层数组,原切片改变元素新切片对应元素不变。
切片的扩容要用append,不能用赋值的方法扩容,否则会触发panic中断程序
func main() { var s1 []int s1[0] = 1 // 此处会触发panic }
当比较短的切片扩容时,系统会多分配 100% 的空间
但切片长度超过1024时,扩容策略调整为多分配 25% 的空间
【切片之后再切片】
package main import ( "fmt" ) func main() { a := [5]int{1, 2, 3, 4, 5} s := a[1:3] // s := a[low:high] //s:[2 3] fmt.Printf("s:%v len(s):%v cap(s):%v\n", s, len(s), cap(s)) s2 := s[3:4] // 索引的上限是cap(s)而不是len(s) fmt.Printf("s2:%v len(s2):%v cap(s2):%v\n", s2, len(s2), cap(s2)) }
结果:
s:[2 3] len(s):2 cap(s):4 s2:[5] len(s2):1 cap(s2):1
【len()和cap】切片拥有自己的长度和容量,我们可以通过使用内置的len()
函数求长度,使用内置的cap()
函数求切片的容量。
【nil切片和空切片】
package main import "fmt" func main(){ // nil切片 var a []int fmt.Printf("a的指针地址是%p\n", a) if a == nil{ fmt.Println("a is nil") }else{ fmt.Println("a is not nil") } // 空切片 b:=make([]int,0) fmt.Printf("b的指针地址是%p\n", b) if b == nil{ fmt.Println("b is nil") }else{ fmt.Println("b is not nil") } }
结果:
a的指针地址是0x0 a is nil b的指针地址是0x5a6d68 b is not nil
【append 和 copy】append见↑切片的基础操作
package main import "fmt" func main(){ // 切片赋值了之后修改任意一个切片的元素,另一个也会变 a := []int{1, 2, 3, 4, 5} b := a fmt.Println(a) //[1 2 3 4 5] fmt.Println(b) //[1 2 3 4 5] b[0] = 1000 fmt.Println(a) //[1000 2 3 4 5] fmt.Println(b) //[1000 2 3 4 5] // copy()复制切片,值拷贝值,不赋值底层数组 d := []int{1, 2, 3, 4, 5} c := make([]int, 5, 5) copy(c, d) //使用copy()函数将切片d中的元素复制到切片c fmt.Println(d) //[1 2 3 4 5] fmt.Println(c) //[1 2 3 4 5] c[0] = 1000 fmt.Println(d) //[1 2 3 4 5] fmt.Println(c) //[1000 2 3 4 5] }
Map
【Map的概念】map是一种无序的基于key-value
的数据结构
【Map的定义】
var testMap map[string]int // 定义map fmt.Printf("%p", testMap) // 0x0 testMap["第一"] = 1 // 还未给Map分配内存,此处会报错
【Map的初始化】使用 make 来分配内存
testMap := make(map[string]int, 5)
【Map的无序】
package main import "fmt" func main() { testMap := make(map[string]int, 5) testMap["第一"] = 1 testMap["第二"] = 2 testMap["第三"] = 3 testMap["第四"] = 4 testMap["第五"] = 5 for k, v := range testMap { fmt.Printf("%v %v | ", k, v) } }
多执行几次,每次打印的顺序都不一样
第四 4 | 第五 5 | 第一 1 | 第二 2 | 第三 3 | 第三 3 | 第四 4 | 第五 5 | 第一 1 | 第二 2 | 第四 4 | 第五 5 | 第一 1 | 第二 2 | 第三 3 | 第一 1 | 第二 2 | 第三 3 | 第四 4 | 第五 5 |
【Map的基本操作】
package main import "fmt" func main() { // 初始化map testMap := make(map[string]int, 5) // 为map添加键值对 testMap["第一"] = 1 testMap["第二"] = 2 testMap["第三"] = 3 testMap["第四"] = 4 testMap["第五"] = 5 // 判断key是否存在 v, ok := testMap["第二"] if ok { fmt.Println("该键存在,值为", v) } // 遍历map for k, v := range testMap { fmt.Printf("%v %v | ", k, v) } // 删除键值对 delete(testMap, "第一")
// 获取map长度
len(map)
}
函数
【func的定义】函数是组织好的、可重复使用的、用于执行指定任务的代码块,Go语言中支持函数、匿名函数和闭包
【函数声明,函数的使用,函数多个返回值】
package main import "fmt" // 无返回值函数 func say() { fmt.Println("Hello 沙河") } // 单返回值函数 func add() int { return 1 } // 单返回值函数 func double() (int, string) { return 1, "hehe" } func main() { // 函数名:由字母、数字、下划线组成。但函数名的第一个字母不能是数字。在同一个包内,函数名也称不能重名(包的概念详见后文)。 // 参数 :参数由参数变量和参数变量的类型组成,多个参数之间使用,分隔。 // 返回值:返回值由返回值变量和其变量类型组成,也可以只写返回值的类型,多个返回值必须用()包裹,并用,分隔。 // 函数体:实现指定功能的代码块。 // func 函数名(参数)(返回值){ // 函数体 // } say() a := add() b, c := double() fmt.Println(a, b, c) }
【函数参数】
类型简写:多个同样类型的参数可以这么写
func intSum(x, y int) int { return x + y }
可变参数:参数数量不固定,通常作为函数的最后一个参数。函数的可变参数是通过切片来实现的。
func intSum2(x ...int) int { fmt.Println(x) //x是一个切片 sum := 0 for _, v := range x { sum = sum + v } return sum }
调用
ret4 := intSum2(10, 20, 30) fmt.Println(ret4) // 60
当 固定参数+可变参数 时
func intSum3(x int, y ...int) int { fmt.Println(x, y) sum := x for _, v := range y { sum = sum + v } return sum } 调用 ret8 := intSum3(100, 10, 20, 30) fmt.Println(ret8) // 160
参数为引用传递
package main import "fmt" // 参数为int类型的指针 func swap(x *int, y *int) { var temp int temp = *x // 将4赋值给了temp *x = *y // 将6赋值给了x *y = temp // 将4赋值给了y } func main() { var x int = 4 var y int = 6 swap(&x, &y) fmt.Println(x, y) }
【匿名函数】匿名函数就是没有函数名的函数
// 将匿名函数保存到变量 add := func(x, y int) { fmt.Println(x + y) } add(10, 20) // 通过变量调用匿名函数 //自执行函数:匿名函数定义完加()直接执行 func(x, y int) { fmt.Println(x + y) }(10, 20)
【函数的闭包】
package main import "fmt" // 返回值是一个函数 func adder() func(int) int { var x int return func(y int) int { x += y return x } } func main() { var f = adder() // f就是func(y int) int {}, f(10)就是 y = 10 作为参数代入,返回一个int fmt.Println(f(10)) //10 fmt.Println(f(20)) //30 fmt.Println(f(30)) //60 f1 := adder() fmt.Println(f1(40)) //40 fmt.Println(f1(50)) //90 }
进阶闭包函数
func adder2(x int) func(int) int { return func(y int) int { x += y return x } } func main() { var f = adder2(10) // x一直活在f的声明周期内 fmt.Println(f(10)) //20 fmt.Println(f(20)) //40 fmt.Println(f(30)) //70 f1 := adder2(20) fmt.Println(f1(40)) //60 fmt.Println(f1(50)) //110 }
【panic和recover】
package main import "fmt" func funcA() { fmt.Println("func A") } func funcB() { defer func() { err := recover() //如果程序出出现了panic错误,可以通过recover恢复过来 if err != nil { fmt.Println("recover in B") } }() panic("panic in B") // 此处模拟可能引发panic的语句 } func funcC() { fmt.Println("func C") }
func main() { funcA() funcB() funcC() }
结果
func A recover in B func C
recover()
必须搭配defer
使用。defer
一定要在可能引发panic
的语句之前定义。
指针
【指针概念】任何程序数据载入内存后,在内存都有他们的地址,这就是指针
【声明指针】
var a *int
【取指针地址】
ptr := &v // v的类型为T v:代表被取地址的变量,类型为T ptr:用于接收地址的变量,ptr的类型就为*T,称做T的指针类型。*代表指针。
func main() { a := 10 b := &a fmt.Printf("a:%d ptr:%p\n", a, &a) // a:10 ptr:0xc00001a078 fmt.Printf("b:%p type:%T\n", b, b) // b:0xc00001a078 type:*int fmt.Println(&b) // 0xc00000e018 }
【根据指针取值】
func main() { //指针取值 a := 10 b := &a // 取变量a的地址,将指针保存到b中 fmt.Printf("type of b:%T\n", b) c := *b // 指针取值(根据指针去内存取值) fmt.Printf("type of c:%T\n", c) fmt.Printf("value of c:%v\n", c) } 结果 type of b:*int type of c:int value of c:10
取地址操作符&
和取值操作符*
是一对互补操作符,&
取出地址,*
根据地址取出地址指向的值。
变量、指针地址、指针变量、取地址、取值的相互关系和特性如下:
对变量进行取地址(&)操作,可以获得这个变量的指针变量。
指针变量的值是指针地址。
对指针变量进行取值(*)操作,可以获得指针变量指向的原变量的值。
【空指针】一个变量只是声明了,没有分配内存地址,这个变量的指针就是空的
package main import "fmt" func main() { var v map[string]int // 声明了变量,但并未分配内存地址 //v["hehe"] = 1 fmt.Printf("%p", v) // 0x0 var b int fmt.Printf("%v",&b) // 0xc0000120f0 }
【new和make】二者都是用来做内存分配的。
new不大常用,func new(Type) *Type
Type表示类型,new函数只接受一个参数,这个参数是一个类型
*Type表示类型指针,new函数返回一个指向该类型内存地址的指针。并且该指针对应的值为该类型的零值
func main() { a := new(int) b := new(bool) fmt.Printf("%T\n", a) // *int fmt.Printf("%T\n", b) // *bool fmt.Println(*a) // 0 fmt.Println(*b) // false }
按照如下方式使用内置的new函数对a进行初始化之后就可以正常对其赋值了:
func main() { var a *int a = new(int) *a = 10 fmt.Println(*a) }
make只用于slice、map以及chan的内存创建,它返回的类型就是这三个类型本身,而不是他们的指针类型,因为这三种类型就是引用类型,所以就没有必要返回他们的指针了。
func main() { var b map[string]int b = make(map[string]int, 10) b["沙河娜扎"] = 100 fmt.Println(b) }
结构体
【结构体概念】Go语言提供了一种自定义数据类型,可以封装多个基本数据类型,这种数据类型叫结构体,英文名称struct
【结构体定义】
type person struct { name string city string age int8 }
【结构体实例化】
type person struct { name string city string age int8 } func main() { var p1 person p1.name = "沙河娜扎" p1.city = "北京" p1.age = 18 fmt.Printf("p1=%v\n", p1) //p1={沙河娜扎 北京 18} fmt.Printf("p1=%#v\n", p1) //p1=main.person{name:"沙河娜扎", city:"北京", age:18} }
或者
type person struct { name string age int } func main() { a := person { name: "heihei", age: 18, } fmt.Println(a) }
【结构体内存布局】结构体占用一块连续的内存。
type test struct { a int8 b int8 c int8 d int8 } n := test{ 1, 2, 3, 4, } fmt.Printf("n.a %p\n", &n.a) fmt.Printf("n.b %p\n", &n.b) fmt.Printf("n.c %p\n", &n.c) fmt.Printf("n.d %p\n", &n.d) 输出: n.a 0xc0000a0060 n.b 0xc0000a0061 n.c 0xc0000a0062 n.d 0xc0000a0063
【空结构体】空结构体是不占用空间的。
var v struct{} fmt.Println(unsafe.Sizeof(v)) // 0
【结构体继承和多态】
package main import "fmt" type Point struct { x int y int } func (p Point) show() { // point的show方法 fmt.Println("point.show") } type Circle struct { Point // 匿名内嵌结构体 Radius int } func (c Circle) show() { // circle的show方法 fmt.Println("Circle.show") } func main() { var c = Circle { Radius: 50, } fmt.Printf("%+v\n", c) fmt.Printf("%+v\n", c.Point) fmt.Printf("%d %d\n", c.x, c.y) // 继承了字段 fmt.Printf("%d %d\n", c.Point.x, c.Point.y) c.show() // 这里是circle的show方法。如果没有定义cricle的show方法,那么这里就继承了point的show方法
c.Point.show() // 这里是point的show方法,
}
结果:
{Point:{x:0 y:0} Radius:50} {x:0 y:0} 0 0 0 0 Circle.show point.show
先猜猜GO语言支不支持多态
package main import "fmt" type Point struct { x int y int } func (p Point) show() { // point的show方法 fmt.Println("point.show") p.test() // 当Point被嵌套在Circle中,这个test方法依然调用的是Point的test方法 } func (p Point) test() { // point的test方法 fmt.Println("this is Point test") } type Circle struct { Point // 匿名内嵌结构体 Radius int } func (c Circle) test() { // circle的test方法 fmt.Println("this is Circle test") } func main() { var c = Circle { Radius: 50, } c.show() }
如果GO语言支持多态,那么这个c.show()中调用的test方法应该是Circle中的test方法,因为子类重写了test方法,然而结果:
point.show this is Point test
子类的test方法并没有被调用,很明显GO语言不支持多态,那么如何来通过其他办法模拟多态呢?
GO语言通过接口来模拟多态
package main import "fmt" type Fruitable interface { eat() // 该接口有个eat方法,谁嵌套它,它就调用谁的eat() } type Fruit struct { Name string // 属性变量 Fruitable // 这是一个接口 } func (f Fruit) want() { fmt.Printf("I like ") f.eat() // 外结构体会自动继承匿名内嵌变量的方法 } type Apple struct {} func (a Apple) eat() { fmt.Println("eating apple") } type Banana struct {} func (b Banana) eat() { fmt.Println("eating banana") } func main() { var f1 = Fruit{"Apple", Apple{}} var f2 = Fruit{"Banana", Banana{}} f1.want() f2.want() } --------- I like eating apple I like eating banana
如果此时Fruit有自己的eat()方法,那么就失去模拟多态的效果了,调用的依然是Fruit的eat方法