文章74
标签2
分类7

章节8_04_recover

一.recover

  • recover()表示恢复程序的panic(),让程序正常运行
  • recover()是和panic(v)一样都是builtin中函数,可以接收panic的信息,恢复程序的正常运行
// The recover built-in function allows a program to manage behavior of a
// panicking goroutine. Executing a call to recover inside a deferred
// function (but not any function called by it) stops the panicking sequence
// by restoring normal execution and retrieves the error value passed to the
// call of panic. If recover is called outside the deferred function it will
// not stop a panicking sequence. In this case, or when the goroutine is not
// panicking, or if the argument supplied to panic was nil, recover returns
// nil. Thus the return value from recover reports whether the goroutine is
// panicking.
func recover() interface{}
  • recover()一般用在defer内部,如果没有panic信息返回nil,如果有panic,recover会把panic状态取消
func main() {
    defer func() {
        if error:=recover();error!=nil{
            fmt.Println("出现了panic,使用reover获取信息:",error)
        }
    }()
    fmt.Println("11111111111")
    panic("出现panic")
    fmt.Println("22222222222")
}
  • 输出
11111111111
出现了panic,使用reover获取信息: 出现panic

二.函数调用过程中panic和recover()

  • recover()只能恢复当前函数级或当前函数调用函数中的panic(),恢复后调用当前级别函数结束,但是调用此函数的函数可以继续执行.
  • panic会一直向上传递,如果没有recover()则表示终止程序,但是碰见了recover(),recover()所在级别函数表示没有panic,panic就不会向上传递
func demo1(){
    fmt.Println("demo1上半部分")
    demo2()
    fmt.Println("demo1下半部分")
}
func demo2(){
    defer func() {
        recover()//此处进行恢复
    }()
    fmt.Println("demo2上半部分")
    demo3()
    fmt.Println("demo2下半部分")
}
func demo3(){
    fmt.Println("demo3上半部分")
    panic("在demo3出现了panic")
    fmt.Println("demo3下半部分")
}
func main() {
    fmt.Println("程序开始")
    demo1()
    fmt.Println("程序结束")
}

章节8_03_panic

一. panic

  • panic是builtin中函数
// The panic built-in function stops normal execution of the current
// goroutine. When a function F calls panic, normal execution of F stops
// immediately. Any functions whose execution was deferred by F are run in
// the usual way, and then F returns to its caller. To the caller G, the
// invocation of F then behaves like a call to panic, terminating G's
// execution and running any deferred functions. This continues until all
// functions in the executing goroutine have stopped, in reverse order. At
// that point, the program is terminated and the error condition is reported,
// including the value of the argument to panic. This termination sequence
// is called panicking and can be controlled by the built-in function
// recover.
func panic(v interface{})
  • panic有点类似与其他编程语言的throw,抛出异常.当执行到panic后终止剩余代码执行.并打印错误栈信息
func main() {
   fmt.Println("1")
   panic("panic执行了,哈哈")
   fmt.Println("2")
}
  • 执行结果
1
panic: panic执行了,哈哈

goroutine 1 [running]:
main.main()
    D:/gowork/c/main.go:7 +0x80
  • 注意panic不是立即停止程序(os.Exit(0)),defer还是执行的.
func main() {
   defer func(){
      fmt.Println("defer执行")
   }()
   fmt.Println("1")
   panic("panic执行了,哈哈")
   fmt.Println("2")
}

章节8_02_defer

一.defer使用

  • Go语言中defer可以完成延迟功能,当前函数执行完成后执行defer功能
  • defer最常用的就是关闭连接(数据库连接,文件等)可以打开连接后代码紧跟defer进行关闭,后面在执行其他功能

    • 在很多语言中要求必须按照顺序执行,也就是必须把关闭代码写在最后,但是经常会忘记关闭导致内存溢出,而Golang中defer很好的解决了这个问题.无论defer写到哪里都是最后执行
func main() {
   fmt.Println("打开连接")
   defer func(){
      fmt.Println("关闭连接")
   }()
   fmt.Println("进行操作")
   //输出:打开连接 进行操作 关闭连接
}

二.多个defer

  • 多重defer采用栈结构执行,先产生后执行
  • 在很多代码结构中都可能出现产生多个对象,而程序希望这些对象倒序关闭,多个defer正好可以解决这个问题
func main() {
   fmt.Println("打开连接A")
   defer func(){
      fmt.Println("关闭连接A")
   }()
   fmt.Println("打开连接B")
   defer func(){
      fmt.Println("关闭连接B")
   }()
   fmt.Println("进行操作")
   //输出:打开连接A 打开连接B 进行操作 关闭连接B 关闭连接A
}

三.defer和return结合

  • defer与return同时存在时,要把return理解成两条执行结合(不是原子指令),一个指令是给返回值赋值,另一个指令返回跳出函数
  • defer和return时整体执行顺序

    • 先给返回值赋值
    • 执行defer
    • 返回跳出函数
  • 没有定义返回值接收变量,执行defer时返回值已经赋值
func f() int{
    i:=0
    defer func(){
        i=i+2
    }()
    return i
}

func main() {
    fmt.Println(f())//输出:0
}
  • 声明接收返回值变量,执行defer时修改了返回值内容.

    • 由于return后面没有内容,就无法给返回值赋值,所以执行defer时返回值才有内容
func f() (i int){
    defer func(){
        i=i+2
    }()
    return
}
func main() {
    fmt.Println(f())//输出:2
}

章节8_01_错误

一. 错误

  • 在程序执行过程中出现的不正常情况称为错误
  • Go语言中使用builtin包下error接口作为错误类型,官方源码定义如下

    • 只包含了一个方法,方法返回值是string,表示错误信息
// The error built-in interface type is the conventional interface for
// representing an error condition, with the nil value representing no error.
type error interface {
    Error() string
}
  • Go语言中错误都作为方法/函数的返回值,因为Go语言认为使用其他语言类似try...catch这种方式会影响到程序结构
  • 在Go语言标准库的errors包中提供了error接口的实现结构体errorString,并重写了error接口的Error()方法.额外还提供了快速创建错误的函数
package errors

// New returns an error that formats as the given text.
func New(text string) error {
    return &errorString{text}
}

// errorString is a trivial implementation of error.
type errorString struct {
    s string
}

func (e *errorString) Error() string {
    return e.s
}
  • 如果错误信息由很多变量(小块)组成,可以借助fmt.Errorf("verb",...)完成错误信息格式化,因为底层还是errors.New()
// Errorf formats according to a format specifier and returns the string
// as a value that satisfies error.
func Errorf(format string, a ...interface{}) error {
    return errors.New(Sprintf(format, a...))
}

二.自定义错误

  • 使用Go语言标准库创建错误,并返回
func demo(i, k int) (d int, e error) {
    if k == 0 {
        e = errors.New("初始不能为0")
        d=0
        return
    }
    d = i / k
    return
}

func main() {
    result,error:=demo(6,0)
    fmt.Println(result,error)
}
  • 如果错误信息由多个内容组成,可以使用下面实现方式
func demo(i, k int) (d int, e error) {
    if k == 0 {
        e = fmt.Errorf("%s%d和%d", "除数不能是0,两个参数分别是:", i, k)
        d = 0
        return
    }
    d = i / k
    return
}

func main() {
    result, error := demo(6, 0)
    fmt.Println(result, error)
}

三.Go语言中错误处理方式

  • 可以忽略错误信息,使用占位符
func demo(i, k int) (d int, e error) {
    if k == 0 {
        e = fmt.Errorf("%s%d和%d", "除数不能是0,两个参数分别是:", i, k)
        d = 0
        return
    }
    d = i / k
    return
}

func main() {
    result, _ := demo(6, 0)
    fmt.Println(result)
}
  • 使用if处理错误,原则上每个错误都应该解决
func demo(i, k int) (d int, e error) {
    if k == 0 {
        e = fmt.Errorf("%s%d和%d", "除数不能是0,两个参数分别是:", i, k)
        d = 0
        return
    }
    d = i / k
    return
}

func main() {
    result, error := demo(6, 0)
    if error != nil {
        fmt.Println("发生错误", error)
        return
    }
    fmt.Println("程序执行成功,结果为:", result)
}

章节7_09_断言

一.断言

  • 只要实现了接口的全部方法认为这个类型属于接口类型,如果编写一个接口,这个接口中没有任何方法,这时认为所有类型都实现了这个接口.所以Go语言中interface{}代表任意类型
  • 如果interface{}作为方法参数就可以接收任意类型,但是在程序中有时有需要知道这个参数到底是什么类型,这个时候就需要使用断言
  • 断言使用时使用interface{}变量点括号,括号中判断是否属于的类型
i interface{}
i.(Type)
  • 断言的两大作用:

    • 判断是否是指定类型
    • 把interface{}转换为特定类型

二.代码示例

  • 断言可以有一个返回值,如果判断结果是指定类型返回变量值,如果不是指定类型报错
func demo(i interface{}){
    result:=i.(int)
    fmt.Println(result)
}

func main() {
    /*
    参数是456时,程序运行正常,输出:
        456
    参数是false时报错:
        panic: interface conversion: interface {} is bool, not int
     */
    demo(456)
}
  • 断言也可以有两个返回值,这时无论是否是指定类型都不报错.

    • 第一个参数:

      • 如果正确:返回值变量值
      • 如果错误:返回判断类型的默认值
    • 第二个参数:

      • 返回值为bool类型,true表示正确,false表示错误
func demo(i interface{}) {
    result, ok := i.(int)
    fmt.Println(result, ok)
}

func main() {
    /*
    参数是456时,程序运行正常,输出:
        456    true
    参数是字符串"abc"时程序运行正常,输出:
        0 false
     */
    demo("abc")
}

章节7_08_多态

一.多态

  • 多态:同一件事情由于条件不同产生的结果不同
  • 由于Go语言中结构体不能相互转换,所以没有结构体(父子结构体)的多态,只有基于接口的多态.这也符合Go语言对面向对象的诠释
  • 多态在代码层面最常见的一种方式是接口当作方法参数

二.代码示例

  • 结构体实现了接口的全部方法,就认为结构体属于接口类型,这是可以把结构体变量赋值给接口变量
  • 重写接口时接收者为Type*Type的区别

    • *Type可以调用*TypeType作为接收者的方法.所以只要接口中多个方法中至少出现一个使用*Type作为接收者进行重写的方法,就必须把结构体指针赋值给接口变量,否则编译报错
    • Type只能调用Type作为接收者的方法
type Live interface {
    run()
    eat()
}
type People struct {
    name string
}

func (p *People) run() {
    fmt.Println(p.name, "正在跑步")
}
func (p People) eat() {
    fmt.Println(p.name, "在吃饭")
}

func main() {
    //重写接口时
    var run Live = &People{"张三"}
    run.run()
    run.eat()
}
  • 既然接口可以接收实现接口所有方法的结构体变量,接口也就可以作为方法(函数)参数
type Live interface {
    run()
}
type People struct{}
type Animate struct{}

func (p *People) run() {
    fmt.Println("人在跑")
}
func (a *Animate) run() {
    fmt.Println("动物在跑")
}

func sport(live Live) {
    fmt.Println(live.run)
}

func main() {
    peo := &People{}
    peo.run() //输出:人在跑
    ani := &Animate{}
    ani.run() //输出:动物在跑
}

章节7_07_接口

一. 接口

  • 接口解释:接口是一组行为规范的定义.
  • 接口中只能有方法声明,方法只能有名称、参数、返回值,不能有方法体
  • 每个接口中可以有多个方法声明,结构体把接口中 所有 方法都重写后,结构体就属于接口类型
  • Go语言中接口和结构体之间的关系是传统面向对象中is-like-a的关系
  • 定义接口类型关键字是interface
type 接口名 interface{
  方法名(参数列表) 返回值列表
}
  • 接口可以继承接口,且Go语言推荐把接口中方法拆分成多个接口

二.代码示例

  • 接口中声明完方法,结构体重写接口中方法后,编译器认为结构体实现了接口

    • 重写的方法要求必须和接口中方法名称、方法参数(参数名称可以不同)、返回值列表完全相同
type People struct {
    name string
    age  int
}

type Live interface {
    run(run int)
}

func (p *People) run(run int) {
    fmt.Println(p.name, "正在跑步,跑了,", run, "米")
}

func main() {
    peo := People{"张三", 17}
    peo.run(100)
}
  • 如果接口中有多个方法声明,接口体必须重写接口中全部方法才任务结构体实现了接口
type People struct {
    name string
    age  int
}

type Live interface {
    run(run int)
    eat()
}

func (p *People) run(run int) {
    fmt.Println(p.name, "正在跑步,跑了,", run, "米")
}
func (p *People) eat() {
    fmt.Println(p.name, "正在吃饭")
}

func main() {
    peo := People{"张三", 17}
    peo.run(100)
}
  • 接口可以继承接口(组合),上面代码可以改写成下面代码
type People struct {
    name string
    age  int
}

type Live interface {
    run(run int)
    Eat
}

type Eat interface {
    eat()
}

func (p *People) run(run int) {
    fmt.Println(p.name, "正在跑步,跑了,", run, "米")
}
func (p *People) eat() {
    fmt.Println(p.name, "正在吃饭")
}

func main() {
    peo := People{"张三", 17}
    peo.run(100)
}

章节7_06_继承

一. 继承

  • 按照传统面向对象思想,继承就是把同一类事物提出共同点为父类,让子类可以复用父类的可访问性内容.
  • 继承有多种实现方式

    • 通过关键字继承,强耦合实现方式
    • 组合式继承,松耦合继承方式
  • 使用过Java或C#的应该知道尽量少用继承而是使用组合代替继承,可以使用高内聚,低耦合.Java之父之前在一次采访的时候也说过,如果给他一次机会重新做Java,他最希望修改的地方就是继承
  • Go语言中的继承是通过组合实现

二.匿名属性

  • 在Go语言中支持匿名属性(结构体中属性名字),但是每个最多只能存在匿名属性.编译器认为类型就是属性名,我们在使用时就把类型当作属性名进行使用
type People struct {
    string
    int
}

func main() {
    p:=People{"smallming",17}
    fmt.Println(p.string,p.int)
}

三.结构体之间的关系

  • 传统面向对象中类与类之间的关系

    • 继承:is-a,强耦合性,一般认为类与类之间具有强关系
    • 实现:like-a,接口和实现类之间的关系
    • 依赖:use-a,具有偶然性的、临时性的、非常弱的,但是B类的变化会影响到A,一般作为方法参数
    • 关联:has-a一种强依赖关系,比如我和我的朋友;这种关系比依赖更强、不存在依赖关系的偶然性、关系也不是临时性的,一般是长期性的,而且双方的关系一般是平等的、关联可以是单向、双向的
    • 聚合:has-a,整体与部分、拥有的关系
    • 组合:contains-a,他体现的是一种contains-a的关系,这种关系比聚合更强,也称为强聚合;他同样体现整体与部分间的关系,但此时整体与部分是不可分的,整体的生命周期结束也就意味着部分的生命周期结束
    • 组合>聚合>关联>依赖
  • Go语言中标准的组合关系
type People struct {
    name string
    age  int
}

type Teacher struct {
    peo       People
    classroom string //班级
}

func main() {
    teacher := Teacher{People{"smallming", 17}, "302教室"}
    //必须通过包含的变量名调用另一个结构体中内容
    fmt.Println(teacher.classroom, teacher.peo.age, teacher.peo.name)
}

四. 使用匿名属性完成Go语言中的继承

  • Go语言中的继承很好实现,把另一个结构体类型当作另一个结构体的属性,可以直接调用另一个结构体中的内容
  • 因为Go语言中结构体不能相互转换,所以不能把子结构体变量赋值给父结构体变量
type People struct {
    name string
    age  int
}

type Teacher struct {
    People
    classroom string //班级
}

func main() {
    teacher := Teacher{People{"smallming", 17}, "302教室"}
    fmt.Println(teacher.classroom, teacher.age, teacher.name)
}

章节7_05_封装

一. 封装

  • 封装主要体现在两个方面:封装数据、封装业务
  • Go语言中通过首字母大小控制访问权限.属性首字母小写对外提供访问方法是封装数据最常见的实现方式
  • 可以通过方法封装业务

    • 提出方法是封装
    • 控制结构体属性访问,对外提供访问方法也是封装
  • 在面向对象中封装的好处:

    • 安全性.结构体属性访问受到限制,必须按照特定访问渠道
    • 可复用性,封装的方法实现可复用性
    • 可读写,多段增加代码可读性

二.代码实现

  • Go语言同包任意位置可以访问全局内容,封装控制可以控制包外访问结构体中数据
type People struct {
    name string //姓名
    age  int    //体重.单位斤
}

func (p *People) SetName(name string) {
    p.name = name
}
func (p *People) GetName() string {
    return p.name
}

func (p *People) SetAge(age int) {
    p.age = age
}

func (p *People) GetAge() int {
    return p.age
}
  • 封装业务就是根据自己的需求提取代码,使用Go语言标准库中的函数过程就属性封装业务(代码)

章节7_04_Go语言中的面向对象

一.面向对象和面向过程

  • 面向过程编程代码只适用于当前情况,而面向对象编程更注重重用,同一套代码可以使用多样的情况
  • 面向过程编程(OPP)就是详细的按照顺序的把整个过程实现

    • 例如:学生从家里去上学需要出门-->找到OFO-->扫描开锁-->上车-->直行-->左拐-->找到”粥饼面”-->停车-->上锁-->开门--> 找到座位-->点山东煎饼和四川火锅-->上菜-->吃饭-->现金付钱-->出门-->找到OFO .....
  • 面向对象编程(OOP)一切皆对象,对象有自己的行为和特征,程序编写过程中把数据和业务进行封装,具有相同类型内容进行继承,同样代码实现多样效果等,实现高可用,高重用的程序.传统面向对象三大基本特征:封装,继承,多态

    • 例如:出门后步行或公交或开车或任意品牌共享单车只要能到吃饭的地方就行,吃饭的店任意能填饱肚子就可以,吃饭时什么菜都可以选不是只能山东煎饼和四川火锅,吃完饭付钱时除了现金支付还可以微信支付,支付宝,刷卡等.吃完饭只要能回家就行.

二.Go语言中的面向对象

  • 面向对象是一种思想,到目前为止还没有一个非常明确的定义,老程序员在不同时期对面向对象的理解是不同的.Go语言中对面向对象有着自己的理解

Although there is no universally accepted definition of object-oriented programming, for our purposes, an object is simply a value or variable that has methods , and a method is a function assiociated with a particular type.

  • 通过上面解释看出了Go语言开发者认为:面向对象就是特定类型(结构体)有这自己的方法,利用这个方法完成面向对象编程,并没有提封装、继承、多态.所有Go语言进行面向对象编程时,重点在于灵活使用方法.Go语言通过这样的设计降低了语言学习的压力.
  • Go语言有着自己对面向对象的理解,他也有着自己的封装、继承、多态

章节7_02_结构体指针

一.结构体指针

  • 由于结构体是值类型,在方法传递时希望传递结构体地址,可以使用时结构体指针完成
  • 可以结合new(T)函数创建结构体指针
    peo := new(People)
    //因为结构体本质是值类型,所以创建结构体指针时已经开辟了内存空间
    fmt.Println(peo == nil) //输出:false
    //由于结构体中属性并不是指针类型,所以可以直接调用
    peo.Name = "smallming"
    fmt.Println(peo)//输出:&{smallming 0}
    peo1:=peo
    peo1.Name="佳明哥"
    fmt.Println(peo1,peo)//输出:&{佳明哥 0} &{佳明哥 0}
  • 如果不想使用new(T)函数,可以直接声明结构体指针并赋值
    //声明结构体指针
    var peo *People
    //给结构体指针赋值
    peo = &People{"smallming", 17}
    /*
    上面代码使用短变量方式如下
    peo:= &People{"smallming", 17}
     */
    fmt.Println(peo)

二.判断

  • 结构体指针比较的是地址
  • (*结构体指针)取出地址中对应的值
    p1 := People{"smallming", 17}
    p2 := People{"smallming", 17}
    fmt.Printf("%p %p\n", &p1, &p2) //输出地址不同
    fmt.Println(p1 == p2)           //输出:true

    p3 := new(People)
    p3 = &People{"smallming", 17}
    //结构体变量不能和指针比较,使用*指针取出地址中值
    fmt.Println(p1 == *p3) //输出:true

    p4 := &People{"smallming", 17}
    //指针比较的是地址
    fmt.Println(p3 == p4) //输出:false

章节7_01_结构体

一.结构体

  • 结构体解释:将一个或多个变量组合到一起,形成新的类型.这个类型就是结构体
  • Go语言中的结构体和C++结构体有点类似,而Java或C#中类本质就是结构体
  • 结构体是值类型
  • 结构体定义语法

    • 通过语法可以看出,Go语言发明者明确认为结构体就是一种自定义类型
    type 结构体名称 struct{
      名称 类型//成员或属性
    }

二. 代码示例

  • 定义结构体

    • 结构体可以定义在函数内部或函数外部(与普通变量一样),定义位置影响到结构体的访问范围
    • 如果结构体定义在函数外面,结构体名称首字母是否大写影响到结构体是否能跨包访问
    • 如果结构体能跨包访问,属性首字母是否大写影响到属性是否跨包访问
type People struct {
    Name string
    Age  int
}
  • 声明结构体变量

    • 由于结构体是值类型,所以声明后就会开辟内存空间
    • 所有成员为类型对应的初始值
    var peo People
    fmt.Print(peo)//输出:{0 }
    fmt.Printf("%p",&peo)//会打印内存地址值
  • 可以直接给结构体多个属性赋值
    var peo People
    //按照结构体中属性的顺序进行赋值,可以省略属性名称
    peo = People{"smallming", 17}
    fmt.Println(peo)
    //明确指定给哪些属性赋值.可以都赋值,也可以只给其中一部分赋值
    peo = People{Age: 18, Name: "佳明哥"}
    fmt.Println(peo)
  • 也可以通过结构体变量名称获取到属性进行赋值或查看
    var peo People
    peo.Name="smallming"
    peo.Age=17
    fmt.Println(peo)
    fmt.Println(peo.Name)
    fmt.Println(peo.Age)

三.判断

  • 双等(==)判断结构体中内容是否相等
    p1 := People{"smallming", 17}
    p2 := People{"smallming", 17}
    fmt.Printf("%p %p\n", &p1, &p2) //输出地址不同
    fmt.Println(p1 == p2)           //输出:true

章节6_9_值传递和引用传递

一. 概述

  • 讨论值传递和引用传递时,其实就是看值类型变量和引用类型变量作为函数参数时,修改形参是否会影响到实参
  • 在Go语言中五个引用类型变量,其他都是值类型

    • slice
    • map
    • channel
    • interface
    • func()
  • 引用类型作为参数时,称为浅拷贝,形参改变,实参数跟随变化.因为传递的是地址,形参和实参都指向同一块地址
  • 值类型作为参数时,称为深拷贝,形参改变,实参不变,因为传递的是值的副本,形参会新开辟一块空间,与实参指向不同
  • 如果希望值类型数据在修改形参时实参跟随变化,可以把参数设置为指针类型

二.代码演示

  • 值类型作为参数代码演示
package main

import "fmt"

func demo(i int, s string) {
    i = 5
    s = "改变"
}

func main() {
    i := 1
    s := "原值"
    demo(i, s)
    fmt.Println(i, s) //输出:1 原值
}
  • 引用传递代码示例
package main

import "fmt"

func demo(arg []int) {
   arg[len(arg)-1] = 110
}

func main() {
   s := []int{1, 2, 3}
   demo(s)
   fmt.Println(s) //输出:[1 2 110]
}
  • 如果希望值类型实参跟随形参变化,可以把值类型指针作为参数
package main

import "fmt"

//行参指针类型
func demo(i *int, s string) {
   //需要在变量前面带有*表示指针变量
   *i = 5
   s = "改变"
}

func main() {
   i := 1
   s := "原值"
   //注意此处第一个参数是i的地址,前面&
   //s保留为值类型
   demo(&i, s)
   fmt.Println(i, s) //输出:5 原值
}

章节7_03_方法

一. 方法

  • 方法和函数语法比较像,区别是函数属于包,通过包调用函数,而方法属于结构体,通过结构体变量调用
  • 默认是函数,隶属于包,所以需要添加标识.告诉编译器这个方法属性哪个结构体

    • 调用方法时就把调用者赋值给接收者(下面的变量名就是接受者)
func (变量名 结构体类型) 方法名(参数列表) 返回值列表{
  //方法体
}
  • Go语言中已经有函数了,又添加了对方法的支持主要是保证Go语言是面向对象的.Go语言官方对面向对象的解释

    • 翻译如下:虽然面向对象没有统一的定义,但是对于我们来说对象仅仅是一个有着方法的值或变量,而方法就是一个属于特定类型的函数

  • 从上面的解释可以看出,官方给出可明确说明,方法类似于函数.方法归属于特定类型

二.代码示例

  • 定义一个People类型结构体,在对People结构体定义个run()方法
type People struct {
    Name string//姓名
    Weight    float64//体重.单位斤
}

func (p People) run(){
    fmt.Println(p.Name,"正在跑步")
}

func main() {
    peo:=People{"张三",17}
    peo.run()
}
  • 如果设定需求,在每次跑步后体重都减少0.1斤.上面代码就需要修改了.因为结构体是值类型,修改方法中结构体变量p的值,主函数中peo的值不会改变,因为传递的是值副本.所以修改方法中结构体类型为结构体指针类型就可以完成设定需求
type People struct {
    Name string//姓名
    Weight    float64//体重.单位斤
}

func (p *People) run(){
    fmt.Println(p.Name,"正在跑步,体重为:",p.Weight)//输出:张三 正在跑步,体重为: 17
    p.Weight-=0.1
}

func main() {
    peo:=&People{"张三",17}
    peo.run()
    fmt.Println(peo.Name,"跑完步后的体重是",peo.Weight)//输出:张三 跑完步后的体重是 16.9
}

章节6_08_闭包

一.闭包概述

  • 闭包不是Go语言独有的概念,在很多编程语言中都有闭包
  • 闭包就是解决局部变量不能被外部访问一种解决方案
  • 是把函数当作返回值的一种应用

二.代码演示

  • 总体思想为:在函数内部定义局部变量,把另一个函数当作返回值,局部变量对于返回值函数就相当于全局变量,所以多次调用返回值函数局部变量的值跟随变化
package main

import "fmt"

func main() {
    //res其实就是test1返回值函数,和之前匿名函数变量一个道理
    res := test1()
    fmt.Println(res()) //输出2
    fmt.Println(res()) //输出3
    fmt.Println(res()) //输出4
}

//注意此处,返回值类型是func int
func test1() func() int {
    i := 1
    return func() int {
        i = i + 1
        return i
    }
}
  • 如果重新调用test1()会重新声明及赋值局部变量i
package main

import "fmt"

func main() {
    f := test1()
    fmt.Println("f的地址", f) //输出匿名函数地址
    fmt.Println("f:", f()) //调用匿名函数输出2
    fmt.Println("f:", f()) //调用匿名函数输出3
    k := test1()
    fmt.Println("k的地址", k) //输出匿名函数地址,与f相等
    fmt.Println("k:", k()) //调用匿名函数输出2
    fmt.Println("f:", f()) //输出:4
    fmt.Println("k:", k()) //输出:3
}

func test1() func() int {
    i := 1
    return func() int {
        i++
        // 每调用一次test1()输出的地址不一样
        fmt.Println("i的地址:", &i)
        return i
    }
}

章节6_07_变量作用域

一.变量作用域

  • 变量声明位置决定了变量的可访问范围(哪里能调用到变量)
  • Go语言中变量的有效范围如下

    • 函数级别:变量声明在函数内部,只有在函数内部才能访问,称变量为局部变量
    • package 包级别,在当前包下都可以访问.称变量为全局变量.变量声明在函数外面
    • 应用级别,在整个应用下任何包内都可以访问.通过首字母大小写控制

二.局部变量

  • 局部变量一定是在函数内部
  • 在哪个{}内部声明,只能在哪个{}内部访问
func test1() {
    i := 2 //从此处开始到test1结束}任何位置都能调用i
    if i>=2{
        j:=3
        fmt.Println(i+j)//此处可以访问i
    }
    fmt.Println(i)
    //fmt.Println(j)//此处不能调用j,超出声明j时{}外
}
func test2() {
    fmt.Println(i) //此处无法调用test1()中的i
}

三.全局变量

  • 全局变量声明到函数外部,整个包都可以访问
  • 如果全局变量首字母大写,跨包也可以访问.
  • 声明全局变量时规范是
var (
    变量名
    变量名=值
)
  • 全局变量代码示例
var (
    name = "smallming"
    age  = 17
)

func demo1() {
      fmt.Println("名字:",name)
}

func demo2() {
      fmt.Println("年龄:",age)
}

章节6_06_包和包级别访问权限

一. 包概述

  • 包(package)是Go语言中组织单元.包是逻辑上的分组.而物理上的分组是不同的文件夹,文件夹和包一般是对应的
  • 把多个文件放入同一个文件夹中,这些文件就是在同一个包中.
  • 虽然允许源码文件的package和文件夹名不同但是最终编译后都会把文件的package编译成文件夹名称.所以为防止错误最好把文件的package和文件夹名称设置成相同的
  • 一个Go语言项目必须要有main包,其他自定义名称的包个数任意,根据自己的需求即可.
  • Go语言在寻找包时会从GOPATH/src 路径中寻找包,如果不存在去GOROOT/src(Go语言标准库源码所在文件夹)下找
  • 不同包下资源可以相互访问,在导入其他包后,可以访问包下首字母大写的内容
  • 同包下不同文件中全局资源可以随意访问

二.自定义包

  • 新建项目后在项目下新建src文件夹,在src文件夹中新建demo文件
  • 在demo文件中新建demo1.go和demo2.go文件
  • demo1.go文件源码如下
package demo//包为demo

import "fmt"

func demo1(){
    fmt.Println("执行demo1")
}
  • demo2.go文件源码如下
package demo//包为demo

import "fmt"

func Demo2()  {//函数名大写才能被其他包访问
    fmt.Println("执行demo2")
    demo1()//同包下内容任意访问
}
  • 在项目根目录下新建main.go,源码如下
package main

import "demo"

func main() {
    demo.Demo2()
}
  • 运行整个项目后,发现可以正常调用Demo2()函数
  • 整个程序目录结构如下

章节6_05_函数变量

一.函数变量

  • 在Go语言中函数也是一种类型,函数有多少种形式,函数变量就有多少种写法
    var a func()           //无参数无返回值
    var b func(int)        //有一个int类型参数
    var c func(int) string //有一个int类型参数和string类型返回值
    fmt.Println(a, b, c)   //输出:<nil> <nil> <nil>
  • 定义完函数变量后,可以使用匿名函数进行赋值.也可以使用已经定义好的函数进行赋值
  • 函数变量定义以后与普通函数调用语法相同,变量名就是普通函数声明的函数名
func main() {
    var a func()
    a = func() {
        fmt.Println("执行函数")
    }   //注意此处没有括号,有括号表示调用函数,变量a就表示接收函数返回值
    a() //调用函数

    /*
    也可以使用短变量方式定义函数变量
    等效于
    func b(s string){
        //...
    }
     */
    b := func(s string) {
        fmt.Println("执行第二个函数")
    }
    b("参数")

    //使用定义好的函数
    d := c
    d()
    //函数名称c也是一个变量
    c()
}

func c() {
    fmt.Println("c函数")
}
  • 函数类型变量是除了slice、map、channel、interface外第五种引用类型
func main() {
    var a func()
    a = b
    a()
    var c func()
    c = a
    c()
    fmt.Printf("%p %p", a, c)//输出地址相同
}

func b() {
    fmt.Println("b")
}

二. 函数作为参数或返回值

  • 变量可以作为函数的参数或返回值类型.而函数既然可以当做变量看待,函数变量也可以当做函数的参数或返回值
  • 函数作为参数时,类型写成对应的类型即可
func main() {
    a(func(s string) {
        fmt.Println(s)
    })
}

func a(b func(s string)) {
    fmt.Println("a执行")
    b("传递给s的内容")
}
  • 函数作为返回值
func main() {
    //此时result指向返回值函数.
    result := a()
    //调用函数,才能获取结果
    fmt.Println(result())
}

func a() func() int {
    return func() int {
        return 110
    }
}