数组
数组定义
- 数组定义:var arr [4]int
切片
切片的底层
- 每一个切片引用了一个底层数组
- 切片本身不存储任何数据,都是底层数组存储,修改切片数据也是修改底层数组数据
- 当向切片添加数据时,没超过容量可以直接添加,超过容量会自动扩容(2倍增长)
- 切片一旦扩容,都要指向一个新的底层数组。
package main
import "fmt"
func main() {
s1 := []int{1, 2, 3}
fmt.Println(s1)
fmt.Printf("len:%d, cap:%d\n", len(s1), cap(s1))
fmt.Printf("%p\n", s1)
s1 = append(s1, 4, 5)
fmt.Println(s1)
fmt.Printf("len:%d, cap:%d\n", len(s1), cap(s1))
fmt.Printf("%p\n", s1)
s1 = append(s1, 6, 7, 8)
fmt.Println(s1)
fmt.Printf("len:%d, cap:%d\n", len(s1), cap(s1))
fmt.Printf("%p\n", s1)
}
直接再数组上创建切片
- 从已有的数组上创建切片,该切片的底层数组就是当前数组
- slice := arr[start:end) 左闭右开
- 长度是从start到end的数据量
- 容量是从start到数组的末尾 比如,s2的容量,就是7,3-10
- 如果切片的长度超过该数组长度,那么该切片会引用一个新的底层数组
- 改变切片的数据也会对应改变数组的数据
a := [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
s1 := a[:5] //1-5
s2 := a[3:8] //4-8
s3 := a[5:] // 6-10
s4 := a[:] // 1-10
fmt.Println("a:", a)
fmt.Println("s1:", s1)
fmt.Println("s2:", s2)
fmt.Println("s3:", s3)
fmt.Println("s4:", s4)
深拷贝和浅拷贝
- 深拷贝:拷贝的是数据背身 如:a = b
- 浅拷贝 拷贝的是数据地址 如:切片
- 拷贝元素函数,copy(s2,s3) s3的元素拷贝到s2,是按照下标一一对应拷贝的
Map
- map是一种引用类型
- 定义map的方法 三种,其中第一种声明为空 map[key]value
- map是无序的
var map1 map[int]string //只有声明,没有初始化 nil
var map2 = make(map[int]string) //make方法创建的map是非空
var map3 = map[string]int{"go": 98, "python": 87, "c": 88}
fmt.Println(map1)
fmt.Println(map2)
fmt.Println(map3)
fmt.Println(map1 == nil)
fmt.Println(map2 == nil)
fmt.Println(map3 == nil)
map的操作
- 获取数据,map[key]
- 添加数据, map[key] = value
- 如果key存在,获取数组,如果key不存在,获取的是value类型的零值
- v1, ok = map[key] 可以返回一个值判断key是否存在
- 删除数据,delete(map,key)
map的遍历
- for range 遍历: for k,v := range map k,v代表的是键和值,不像数组是下标和值
- 切片种存入map, 定义:slice := make([ ][key][value])
函数
可变参数
- 一个函数的类型可以确定,但是个数不确定的情况下使用
- 对于函数,可变参数相当于一个切片
高阶函数
- 高阶函数:接受一个函数作为参数的函数 比如oper
- 回到函数:作为另一个函数参数的函数 比如add
res3 := oper(3, 4, add)
fmt.Println(res3)
}
func add(x, y int) int {
return x + y
}
func oper(x, y int, fun func(int, int) int) int {
fmt.Println(x, y, fun)
res := fun(x, y)
return res
}
3 4 0x71e8c0
7
闭包
- 一个外层函数中,有内层函数,该内层函数会操作外层函数的局部变量,并且该外层函数的返回值就是内层函数。
- 这个内层函数和外层函数的局部变量,被称为闭包结构
# 注意这里的res1和res2
res1 := increment()
fmt.Println(res1)
v1 := res1()
fmt.Println(v1)
v2 := res1()
fmt.Println(v2)
res2 := increment()
v3 := res2()
fmt.Println(v3)
v4 := res2()
fmt.Println(v4)
}
func increment() func() int {
i := 0
res := func() int {
i += 1
return i
}
return res
}
0xf8eac0
1
2
1
2
指针数组 数组指针
package main
import "fmt"
func main() {
// 定义数组 要标明长度
arr1 := [4]int{1, 2, 3, 4}
// 定义指针 用var
var p1 *[4]int
p1 = &arr1
// p1存的是数组地址 *p1是拿到地址的数据
fmt.Println(p1)
fmt.Println(*p1)
//根据指针 操作数组 两种方法
(*p1)[0] = 200
fmt.Println(arr1)
p1[0] = 300
fmt.Println(arr1)
// 指针数组
a := 10
b := 20
c := 30
arr2 := [3]int{a, b, c}
//指针数组
arr3 := [3]*int{&a, &b, &c}
fmt.Println(arr2)
fmt.Println(arr3)
//在指针数组改变数值
*arr3[0] = 2000
fmt.Println(a)
//但是arr2没有改变,因为存取的时候arr2存取的是数值
fmt.Println(arr2)
}
函数返回指针
- 函数指针:一个指针 指向函数的指针
- 指针函数:该函数的返回值是指针
func main() {
// arr2接受的是arr1数组的指针,当函数结束时这个数组并没有被销毁
arr2 := fun3()
fmt.Println(arr2)
fmt.Println(&arr2)
arr2[0] = 10
fmt.Println(arr2)
arr3 := fun3()
fmt.Println(arr3)
fmt.Println(&arr3)
}
// 函数返回指针数组
func fun3() *[3]int {
arr1 := [3]int{1, 2, 3}
return &arr1
}
指针作为参数
func main() {
a := 10
fmt.Println(a)
fun3(&a)
fmt.Println(a)
}
func fun3(a *int) {
*a = 100
}
结构体
结构体的定义
package main
import "fmt"
type Person struct {
name string
sex string
age int
}
func main() {
//方法一
var p1 Person
p1.name = "王二狗"
p1.sex = "男"
p1.age = 10
fmt.Println(p1)
//方法二
p2 := Person{}
p2.name = "李二狗"
p2.sex = "男"
p2.age = 10
fmt.Println(p2)
//方法三
p3 := Person{name: "黄", sex: "男", age: 10}
fmt.Println(p3)
//方法四
p4 := Person{"黄", "男", 20}
fmt.Println(p4)
}
结构体指针
- 有两种定义方法
type Person struct {
name string
sex string
age int
}
func main() {
// 定义结构体指针
var p1 *Person
//使用var定义指针初始为nil
fmt.Println(p1)
fmt.Printf("%T", p1)
//使用new方法定义
//该方法定义不为空,定义的指针已经指向了新分配的内存空间,里面存储的值为0
p2 := new(Person)
fmt.Println(p2)
fmt.Printf("%T", p2)
}
go语言的面向对象
- 模拟继承性可以提升关系
- 也就是当访问B时,可以直接访问A的字段,而不需要B.A.name B.name即可
结构体的方法
- 子类继承匿名的"父类" 可以继承父类的方法,也可以重写
接口
- 定义简单接口
func main() {
m1 := Mouse{"罗技"}
d1 := FlashDisk{"闪迪"}
// 传入Mouse类
test(m1)
//传入Disk类
test(d1)
fmt.Println("接口测试:")
var u USB = m1
//传入接口类
test(u)
u.start()
u.end()
}
type USB interface {
start()
end()
}
type Mouse struct {
name string
}
type FlashDisk struct {
name string
}
func (m Mouse) start() {
fmt.Println(m.name, "鼠标启动")
}
func (m Mouse) end() {
fmt.Println(m.name, "鼠标结束")
}
func (d FlashDisk) start() {
fmt.Println(d.name, "U盘启动")
}
func (d FlashDisk) end() {
fmt.Println(d.name, "U盘结束")
}
func test(u USB) {
u.start()
u.end()
}
罗技 鼠标启动
罗技 鼠标结束
闪迪 U盘启动
闪迪 U盘结束
接口测试:
罗技 鼠标启动
罗技 鼠标结束
罗技 鼠标启动
罗技 鼠标结束
接口类型
- 接口本质,功能的定义和功能的实现分离开,降低耦合性
- 接口特性:非侵入式
- 多态:U盘可以是U盘设备也可以是设备,可以承担2个角色,能实现的功能范围不同
- go语言通过接口模拟多态
看成实现本身的类型,能够访问实现中的属性和方法
看成对应的接口类型,那就只能访问接口中的方法
空接口
- 学习了interface{}特殊变量,可以存储任意类型的数据
func main() {
var a1 A = Cat{color: "yellow"}
var a2 A = Person{name: "黄", age: 10}
var a3 A = 3
var a4 A = "11"
fmt.Println(a1)
fmt.Println(a2)
fmt.Println(a3)
fmt.Println(a4)
test(a1)
test(a2)
test(1)
test("a")
test2(412412312)
// interface{} 可以接受任意类型
//利用map 再value中存入任意数据
//但是map类型可以不用2个参数
map1 := make(map[int]interface{})
map1[0] = 1
map1[1] = "字符串"
map1[2] = Cat{color: "yellow"}
map1[3] = &Cat{color: "yellow"}
fmt.Println(map1)
fmt.Println("打印切片")
//切片至少要2个参数,这里的1是给他分配了一个空的内存空间 这个是我刚发现的 ,我给0就是初始时不分配存储空间
slice1 := make([]interface{}, 0)
slice1 = append(slice1, 1, 2, 3, "fwqf", Cat{color: "blue"})
fmt.Println(slice1)
}
func test(a A) {
fmt.Println("测试函数:", a)
}
func test2(a interface{}) {
fmt.Println("测试函数2", a)
}
type A interface {
}
type Cat struct {
color string
}
type Person struct {
name string
age int
}
{yellow}
{黄 10}
3
11
测试函数: {yellow}
测试函数: {黄 10}
测试函数: 1
测试函数: a
测试函数2 412412312
map[0:1 1:字符串 2:{yellow} 3:0xc000050270]
打印切片
[1 2 3 fwqf {blue}]
接口嵌套
func main() {
//不使用接口
var c Cat = Cat{name: "小花"}
c.test1()
c.test2()
c.test3()
fmt.Println("接口A调用:")
var a A = c
a.test1()
fmt.Println("接口B调用:")
var b B = c
b.test2()
fmt.Println("接口C调用:")
var d C = c
d.test1()
d.test2()
d.test3()
}
type A interface {
test1()
}
type B interface {
test2()
}
type C interface {
A
B
test3()
}
type Cat struct {
name string
}
func (c Cat) test1() {
fmt.Println("这是1方法")
}
func (c Cat) test2() {
fmt.Println("这是2方法")
}
func (c Cat) test3() {
fmt.Println("这是3方法")
}
这是1方法
这是2方法
这是3方法
接口A调用:
这是1方法
接口B调用:
这是2方法
接口C调用:
这是1方法
这是2方法
这是3方法
接口断言
- 定义断言有两种方法
- 如果想要接口USB接收Mouse和FlashDisk类,那么接口中的start(), end()方法都要一一单独实现才不会报错,也就是说func (m Mouse) start()不能省略
package main
import "fmt"
func main() {
m1 := Mouse{"罗技"}
d1 := FlashDisk{"闪迪"}
var u USB = m1
getType(m1)
getType(d1)
getType(u)
//getType(100) 这种类型会报错
fmt.Println("switch方法测试:")
//Switch方法测试
getType2(m1)
getType2(d1)
getType2(u)
}
type USB interface {
start()
end()
}
type Mouse struct {
name string
}
type FlashDisk struct {
name string
}
func (m Mouse) start() {
fmt.Println(m.name, "鼠标启动")
}
func (m Mouse) end() {
fmt.Println(m.name, "鼠标结束")
}
func (d FlashDisk) start() {
fmt.Println(d.name, "U盘启动")
}
func (d FlashDisk) end() {
fmt.Println(d.name, "U盘结束")
}
func test(u USB) {
u.start()
u.end()
}
func getType(u USB) {
//使用switch 语句进行断言
if ins, ok := u.(Mouse); ok {
fmt.Println(ins.name, "鼠标")
} else if ins, ok := u.(FlashDisk); ok {
fmt.Println(ins.name, "U盘")
}
}
func getType2(u USB) {
switch ins := u.(type) {
case Mouse:
fmt.Println(ins.name, "鼠标")
case FlashDisk:
fmt.Println(ins.name, "U盘")
}
}
罗技 鼠标
闪迪 U盘
罗技 鼠标
switch方法测试:
罗技 鼠标
闪迪 U盘
罗技 鼠标
type关键字
- 使用type,定义新类型
type myint int 定义新类型myint
虽然存储的都是int类型 但是2个类型不能相互转换 - 类型别名
type myint = int
这样就可以 相互转换,因为myint 和 int 本质上是同一个东西 - 非本地类型不能定义方法
意思就是你取函数别名的时候要在该函数的包内定义,但是如果你是定义函数新类型的话就可以
error
- Go的错误是一种类型,错误用内置的error表示,错误可以存储在变量中
package main
import (
"fmt"
"os"
)
//简单例子
func main() {
f, err := os.Open("test.txt")
if err != nil {
fmt.Println(err)
return
}
fmt.Println(f.Name(), "打开文件成功")
}
package main
import (
"fmt"
"os"
)
func main() {
f, err := os.Open("test.txt")
if err != nil {
fmt.Println(err)
return
}
fmt.Println(f.Name(), "打开文件成功")
}
open test.txt: The system cannot find the file specified.
自定义错误
创建错误变量的两种方式
package main
import (
"errors"
"fmt"
)
func main() {
//方法一
err1 := errors.New("自建错误1")
fmt.Println(err1)
fmt.Printf("%T", err1)
//方法二
fmt.Println()
fmt.Println("------------")
err2 := fmt.Errorf("自建错误2")
fmt.Println(err2)
fmt.Printf("%T", err2)
}
- 用error接口创建错误
package main
import (
"fmt"
"math"
)
func main() {
radius := -3.0
arr, err := circleArea(radius)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(arr)
}
// 定义一个结构体,表示错误类型
type areaError struct {
msg string
radius float64
}
// 实现error接口,就是实现Error()方法
func (e *areaError) Error() string {
return fmt.Sprintf("error:半径, %.2f, %s", e.radius, e.msg)
}
func circleArea(radius float64) (float64, error) {
if radius < 0 {
return 0, &areaError{"半径是非法的", radius}
}
return math.Pi * radius * radius, nil
}
error:半径, -3.00, 半径是非法的
- 该实例更为完成的阐述自定义error的使用,同时可以断言err得到更多的错误信息
package main
import "fmt"
func main() {
length, width := -3.7, -3.4
area, err := rectArea(length, width)
if err != nil {
fmt.Println(err)
//用断言获取更多错误信息
if err, ok := err.(*areaError); ok {
if err.lengthNegative() {
fmt.Println("长度错误", err.length)
}
if err.widthNegative(); ok {
fmt.Println("宽度错误", err.width)
}
}
return
}
fmt.Println(area)
}
type areaError struct {
msg string
length float64
width float64
}
func (e *areaError) Error() string {
return e.msg
}
func (e *areaError) lengthNegative() bool {
return e.length < 0
}
func (e *areaError) widthNegative() bool {
return e.width < 0
}
func rectArea(length, width float64) (float64, error) {
msg := ""
if length < 0 {
msg = "长度小于0"
}
if width < 0 {
if msg == "" {
msg = "宽度小于0"
} else {
msg = "长和宽都小于0"
}
}
if msg != "" {
return 0, &areaError{msg, length, width}
}
return length * width, nil
}
长和宽都小于0
长度错误 -3.7
宽度错误 -3.4
panic()和recover()
- panic()终止代码运行,但是运行了defer的语句会继续运行完
- recover()恢复代码的运行
go可以抛出一个panic的异常,然后在defer中通过recover捕获这个异常,然后正常处理
package main
import "fmt"
func main() {
funA()
defer myPrint("defer函数 main 3")
funB()
defer myPrint("defer函数 main 4")
fmt.Println("main over")
}
func myPrint(s string) {
fmt.Println(s)
}
func funA() {
fmt.Println("我是函数A")
}
func funB() {
fmt.Println("我是函数B")
defer myPrint("defer函数B 1")
for i := 0; i <= 5; i++ {
fmt.Println("i:", i)
}
defer myPrint("defer函数B 2")
}
正常执行带有defer的代码
我是函数A
我是函数B
i: 0
i: 1
i: 2
i: 3
i: 4
i: 5
defer函数B 2
defer函数B 1
main over
defer函数 main 4
defer函数 main 3
package main
import "fmt"
func main() {
funA()
defer myPrint("defer函数 main 3")
funB()
defer myPrint("defer函数 main 4")
fmt.Println("main over")
}
func myPrint(s string) {
fmt.Println(s)
}
func funA() {
fmt.Println("我是函数A")
}
func funB() {
fmt.Println("我是函数B")
defer myPrint("defer函数B 1")
for i := 0; i <= 5; i++ {
fmt.Println("i:", i)
if i == 3 {
//让程序中断
panic("函数B恐慌了")
}
}
defer myPrint("defer函数B 2")
}
我是函数A
我是函数B
i: 0
i: 1
i: 2
i: 3
defer函数B 1
defer函数 main 3
panic: 函数B恐慌了
- 加入recover()之后,主函数会恢复执行,但是剩下的for循环和defer函数B 2没有继续执行了。
package main
import (
"fmt"
)
func main() {
funA()
defer myPrint("defer函数 main 3")
funB()
defer myPrint("defer函数 main 4")
fmt.Println("main over")
}
func myPrint(s string) {
fmt.Println(s)
}
func funA() {
fmt.Println("我是函数A")
}
func funB() {
defer func() {
if msg := recover(); msg != nil {
fmt.Println("程序恢复啦")
}
}()
defer fmt.Println("我是函数B")
defer myPrint("defer函数B 1")
for i := 0; i <= 5; i++ {
fmt.Println("i:", i)
if i == 3 {
//让程序中断
panic("函数B恐慌了")
}
}
defer myPrint("defer函数B 2")
}
我是函数A
i: 0
i: 1
i: 2
i: 3
defer函数B 1
我是函数B
程序恢复啦
main over
defer函数 main 4
defer函数 main 3