Go语言学习(面向对象编程、错误和异常)

OOP思想

Go语言不是面向对象的语言,可以理解一些面向对象思想,只是通过一些方法模拟面向对象

物以类聚:随着事物的不断发展,需要学会归类。

语句多了:把一些功能性代码,或者一些完成相同类型作业的代码聚集到一起,函数就出现了;

变量多了:把相同类型,功能相近的变量放到一起,结构体就出现了;

方法多了、变量多了、结构体也多了,于是出现了类。

类:模板 -> 具体的对象

大型公司:

1个架构师CTO:拿到业务,分析需求(分类),架构软件(选型),写接口,面向对象解决问题

10个程序员:实现这些接口业务(CRUD) 面向过程解决问题

面向对象:可以解决很多复杂的问题,将现实中的问题拟人化(用人的思维去解决),适合多人协作;

面向过程:适合解决简单的问题,个人就可以完成。

面向对象思想核心:封装、继承、多态

继承

Go语言的继承

继承需要符合的关系是: is-a,父类更通用,子类更具体。

继承就是子类继承父类的特征和行为,使得子类具有父类的属性和方法,使得子类具有父类相同的行为。子类会具有父类的一般特性也会具有自身的特性。

Go语言的继承:结构体的嵌套
/*
    Go语言中结构体嵌套
    1、模拟继承 is - a
    type A struct {
        field
    }
    type B struct {
        A  // 匿名字段 B.A
        field
    }
    // 这样B是可以直接访问A的属性的
    2、聚合关系 has - a
    type C struct {
        field
    }
    type D struct {
        c C    // 聚合关系
        field
    }
    // D是无法直接访问C中的属性的,必须使用D.c.c_field
 */
package main

import "fmt"

// 定义一个父类
type Person struct {
    name string
    age  int
}

// 定义一个子类,Student拥有了父类所有的属性,还有自己的特性
type Student struct {
    Person // 匿名字段 实现了继承
    school string
}

func main() {
    // 1、创建父类对象
    p1 := Person{name: "张三", age: 18}
    fmt.Println(p1)              // {张三 18}
    fmt.Println(p1.name, p1.age) // 张三 18

    // 2、创建子类对象
    s1 := Student{Person: Person{name: "小明", age: 16}, school: "清华"}
    fmt.Println(s1)                            // {{小明 16} 清华}
    fmt.Println(s1.Person.name, s1.Person.age) // 小明 16

    // 3、创建子类对象
    var s2 Student
    s2.Person.name = "李四"
    s2.Person.age = 20
    s2.school = "北大"
    fmt.Println(s2) // {{李四 20} 北大}

    // 概念:提升字段,只有匿名字段才可以做到
    // Person在Student中是一个匿名字段,Person中的属性name age 就是提升字段
    // 所有的提升字段可以由子类对象直接访问
    var s3 Student
    s3.name = "王五"
    s3.age = 35
    s3.school = "川大"
    fmt.Println(s3)              // {{王五 35} 川大}
    fmt.Println(s3.name, s3.age) // 王五 35
}

匿名字段 + 提升字段:Go语言中的匿名字段

方法

方法不是函数,Go语言中同时拥有函数和方法,一定要将其他只有方法的语言区分开来。

方法:是某个类的动作,需要指定调用者,约定这个方法属于谁的。对象.方法()

函数:是一个特殊的类型,不需要指定调用者,定义了函数就可以直接使用函数名()调用

package main

import "fmt"

// 方法可以理解为函数多了一个调用者
// 方法可以重名,不同的调用者,调用的结果是不同的
type Dog struct {
    name string
    age  int
}
type Cat struct {
    name string
    age  int
}

// 方法定义 func 方法调用者 方法名()
// 1、方法可以重名,只需要调用者不同
// 2、如果调用者相同,则不能重名
func (dog Dog) eat() {
    fmt.Println("Dog eating...")
}
func (dog Dog) sleep() {
    fmt.Println("Dog sleeping...")
}
func (cat Cat) eat() {
    fmt.Println("Cat eating...")
}
func (cat Cat) sleep() {
    fmt.Println("Cat sleeping")
}
func main() {
    // 创建一个对象
    dog := Dog{
        name: "旺财",
        age:  5,
    }
    // 方法的调用,通过对应的结构体对象来调用
    dog.eat()   // Dog eating...
    dog.sleep() // Dog sleeping...

    cat := Cat{
        name: "喵喵",
        age:  1,
    }
    cat.eat()   // Cat eating...
    cat.sleep() // Cat sleeping 
}

方法的重写

需要结合继承来使用,子类可以访问父类属性,重写父类的方法,还可以增加自己的方法

package main

import "fmt"

// 方法重写 建立在父类和子类结构上的
type Animal struct {
    name string
    age  int
}

func (animal Animal) eat() {
    fmt.Println(animal.name, "正在吃...")
}
func (animal Animal) sleep() {
    fmt.Println(animal.name, "正在睡...")
}

// 子类
type Dog struct {
    Animal
    color string // 子类可以定义自己的属性
}

func (dog Dog) shout() {
    fmt.Println(dog.name, "汪汪汪...")
}

// 子类
type Cat struct {
    Animal
    color string // 子类可以定义自己的属性
}

// 子类重写父类的方法
func (cat Cat) eat() {
    fmt.Println(cat.name, "猫正在吃...")
}
func main() {
    // 定义一个子类
    dog := Dog{
        Animal{name: "旺财", age: 3},
        "灰色",
    }
    // 调用父类的方法
    dog.eat() // 旺财 正在吃...
    // 调用子类自己扩展的方法
    dog.shout() // 旺财 汪汪汪...
    // 定义一个子类
    cat := Cat{
        Animal{name: "喵喵", age: 1},
        "白色",
    }
    // 如果子类重写了父类的方法就调用子类重写的方法
    cat.eat() // 喵喵 猫正在吃...
    // 调用子类自己的属性
    fmt.Println(cat.color)

    // 子类可以调用父类的方法,而父类不可以调用子类自己扩展的方法
    a := Animal{"大黄", 3}
    a.eat() // 大黄 正在吃...
    a.sleep() // 大黄 正在睡...
}

多态

接口

接口:USB、typeC、插座

  1. Go语言提供了数据接口类型;

  1. 接口就是把一些共性的方法集合在一起定义;

  1. Go语言中子类隐式实现接口,不需要像Java一样显示声明A implements B;

  1. 如果A实现了B接口中所有的定义方法,就代表A实现了B接口;

  1. 接口时方法的定义集合,所以接口中不需要实现具体的方法内容。

package main

import "fmt"

// 接口的定义 interface  因为方法多了,要归类,形成方法集合
type USB interface {
    input()
    output()
}

// 因为属性多了,需要结构体
type Mouse struct {
    name string
}

// 结构体必须实现接口中全部方法才算实现了这个接口,否则不算
func (mouse Mouse) input() {
    fmt.Println(mouse.name, "鼠标输入")
}
func (mouse Mouse) output() {
    fmt.Println(mouse.name, "鼠标输出")
}

// 接口测试
func test(usb USB) {
    usb.input()
    usb.output()
}
func main() {
    m1 := Mouse{name: "罗技"}
    // 通过传入接口实现类来调用
    test(m1)

    k1 := KeyBoard{name: "雷蛇"}
    test(k1)

    // 定义高级类型
    var usb USB
    // k1向上转型 KeyBoard --> USB
    usb = k1
    fmt.Println(usb) // {雷蛇}
    // 接口是无法使用实现类的属性的
    // fmt.Println(usb.name)  type USB has no field or method name
}

type KeyBoard struct {
    name string
}

func (keyBoard KeyBoard) input() {
    fmt.Println(keyBoard.name, "键盘输入")
}
func (keyBoard KeyBoard) output() {
    fmt.Println(keyBoard.name, "键盘输出")
}

模拟多态

多态:一个事物拥有多种形态

package main

import "fmt"

// 定义接口
type Animal interface {
    eat()
    sleep()
}
type Dog struct {
    name string
}

func (dog Dog) eat() {
    fmt.Println(dog.name, "正在吃...")
}
func (dog Dog) sleep() {
    fmt.Println(dog.name, "正在睡...")
}
func test(animal Animal) {
    animal.eat()   // 旺财 正在吃...
    animal.sleep() // 旺财 正在睡...
}
func main() {
    // 多态 Dog有两重身份:1.Dog 2.Animal
    d1 := Dog{name: "旺财"}
    d1.eat()   // 旺财 正在吃...
    d1.sleep() // 旺财 正在睡...

    // Dog也可以是Animal
    test(d1)

    // 可以定义一个接口类型的变量
    var animal Animal
    // 所有实现类对象都可以赋给接口对象 模糊 --> 具体化
    // 这样才有意义
    animal = d1
    fmt.Printf("%T\n", animal)
    test(animal)
}

接口的实现类都拥有多态特性

空接口

不包含任何的方法。因此,所有结构体都默认实现了这个接口,表明空接口可以存储任何类型

Go语言中 interface{} == any 相当于 Java中的Object

package main

import "fmt"

// 定义空接口
type P interface {
}
type Teacher struct {
    name string
}
type Student struct {
    name string
}

func test(p P) {
    fmt.Println(p)
}

// 可以定义空接口的参数
// any is an alias for interface{} and is equivalent to interface{} in all ways.
// type any = interface{}
func test2(any interface{}) {

}

func main() {
    var t = Teacher{name: "小明"}
    var s = Student{name: "小李"}
    var a int = 1
    var str string = "GoGoGo"

    test(t)   // {小明}
    test(s)   // {小李}
    test(a)   // 1
    test(str) // GoGoGo

    // 空接口的使用
    // map
    map1 := make(map[string]interface{})
    map1["name"] = "小张"
    map1["age"] = 18
    fmt.Println(map1) // map[age:18 name:小张]

    // slice
    s1 := make([]any, 0, 10)
    s1 = append(s1, 1, "小李", false, t, s)
    fmt.Println(s1) // [1 小李 false {小明} {小李}]

    var arr [5]interface{}
    fmt.Println(arr) // [<nil> <nil> <nil> <nil> <nil>]
}

接口嵌套

package main

import "fmt"

type AA interface {
    test1()
}
type BB interface {
    test2()
}

// 接口嵌套
// 如果要实现CC这个接口,那么需要实现三个方法,这个对象就有三个接口可以转型
type CC interface {
    AA
    BB
    test3()
}
type SS struct {
}

func (ss SS) test1() {
    fmt.Println("test1")
}
func (ss SS) test2() {
    fmt.Println("test2")
}
func (ss SS) test3() {
    fmt.Println("test3")
}
func main() {
    // ss拥有四种形态:SS、AA、BB、CC
    var ss SS
    ss.test1() // test1
    ss.test2() // test2
    ss.test3() // test3

    // 向上转型之后只能调用自己对应的方法
    var a AA = ss
    a.test1()
    var b BB = ss
    b.test2()
    var c CC = ss
    c.test1()
    c.test2()
    c.test3()
}

接口断言

检查一个接口类型的变量是否符合预期值

比如:如果预期调用test3(),传递的是a、b对象,就需要判断这个对象是否是预期对象。

被断言的的对象必须是接口类型,否则会报错。

如果一个对象存在多种形态:接口判断

  • i.(T) 判断是否是预期接口

  • switch i.(type) 判断是否是预期的case结果

package main

import "fmt"

// 断言 t := i.(T) t: 就是i接口T类型的
// 语法 t, ok := i.(T)  ok 隐藏返回值,如果断言成功,ok就是true,否则就是false
func main() {
    assertsString("GoGoGo") // GoGoGo
    // 如果断言失败,就会报panic(恐慌)错误,程序就会停止执行
    // assertsString(123) // panic: interface conversion: interface {} is int, not string

    assertsInt(123)  // 是期望的结果: 123
    assertsInt("中国") // 不是期望的结果,无法执行预期操作
}
func assertsString(i interface{}) {
    s := i.(string)
    fmt.Println(s)
}
func assertsInt(i any) {
    r, ok := i.(int)
    if ok {
        fmt.Println("是期望的结果:", r)
    } else {
        fmt.Println("不是期望的结果,无法执行预期操作")
    }
}
package main

import "fmt"

type I interface {
}

func testAsserts(i any) {
    // switch i.(type) 接口断言
    switch i.(type) {
    case string:
        fmt.Println("变量为string类型")
    case int:
        fmt.Println("变量为int类型")
    case nil:
        fmt.Println("变量为nil类型")
    case interface{}:
        fmt.Println("变量为interface{}类型")
    case I:
        fmt.Println("变量为I类型")
    default:
        fmt.Println("未知类型")
    }

}
func main() {
    testAsserts("中国") // 变量为string类型
    testAsserts(1)    // 变量为int类型
    var i I           // 如果没有赋值,默认值为nil
    testAsserts(i)    // 变量为nil类型
    var i2 I = 2
    testAsserts(i2) // 变量为int类型

}

type 别名

type关键字的理解:

  1. type 定义一个类型

  • type User struct 定义结构体类型

  • type User interface 定义接口类型

  • type Diy (int、string、...) 自定义类型,全新的类型

  1. type 起别名

  • type xxx = 类型 将某个类型赋值给 xxx,相当于这个类型的别名

  • 别名只能在写代码的时候使用,增加代码的可阅读性

  • 别名在真实的编译过程中,还是原来的类型

package main

import "fmt"

// var 变量 type 类型(结构体、接口、别名...)
// type的别名用法,全局变量中
// 这是定义了一个新类型 MyInt,是int转换过来的,和int一样,但是不能和int发生操作,因为类型不同
type MyInt int

func main() {
    var a MyInt = 10
    var b int = 20
    // 如下代码报错:invalid operation: a + b (mismatched types int and MyInt)
    //fmt.Println(a + b)
    // 类型转换:type(value)
    fmt.Println(int(a) + b) // 30

    fmt.Printf("%T\n", a) // main.MyInt
    fmt.Printf("%T\n", b) // int

    // 给int起一个小名,但是他还是int  type any
    type diyint = int // 和int一样
    var c diyint = 30
    fmt.Println(b + c)    // 50
    fmt.Printf("%T\n", c) // int
}

错误和异常

错误:指的是程序中预期会发生的结果,预料之中;(例如,打开一个文件,发现文件被占用,可知的)

异常:不该出现问题的地方出现了问题,预料之外。(例如,调用一个对象,发现对象空指针,未知的)

错误是业务的一部分,而异常不是。

错误

错误在开发中是必须要考虑的问题。

  • 某些系统错误,文件被占用,网络延迟等;

  • 人为错误,用户在输入框会传递一些不正常的参数,sql注入

在实际的项目开发中,希望通过错误信息来快速定位问题,但不又不能让处理错误的代码些的冗余而啰嗦。

在Go语言中没有提供像Java、C#语言中的try...catch异常处理方式,而是通过函数返回值逐层往上抛。在代码中应该显式的检查错误,而非忽略错误。好处就是避免漏掉本应处理的错误,同时带来的弊端就是让代码变得繁琐。

Go语言中的错误也是一种类型,类似于int,float64等,使用error表示。因此错误值可以存储在变量中,通过函数返回、传递参数等。

package main

import (
    "fmt"
    "os"
)

func main() {
    // os 系统包,打开一个文件
    // func Open(name string) (*File, error)
    file, err := os.Open("a.txt")
    // 在开发中,需要考虑错误的类型 PathError
    // 1、文件不存在
    // 2、文件被占用
    // 3、文件易损耗
    // 可能出现的错误,需要解决
    if err != nil {
        fmt.Println(err) // open a.txt: The system cannot find the file specified.
        return
    }
    fmt.Println(file.Name())
}

错误的定义:

  • errors.New("xxx")

  • fmt.Errorf()

package main

import (
    "errors"
    "fmt"
)

// 设置年龄的函数,需要处理用户一些不合法的请求
// 返回值为error类型
func setAge(age int) error {
    if age < 0 {
        // 方式一
        return errors.New("输入的年龄不合法!")
    }
    // 返回程序正常的结果
    fmt.Println("年龄设置成功,age=", age)
    return nil
}
func main() {
    age_err := setAge(-1)
    if age_err != nil {
        fmt.Println(age_err)
    }
    // 错误是一个类型
    fmt.Printf("%T\n", age_err) // *errors.errorString

    // 方式二
    errInfo := fmt.Errorf("我是一个错误信息:%d\n", 500)
    if errInfo != nil {
        fmt.Println(errInfo) // 我是一个错误信息:500
    }
}

错误的类型

error是一个类型,一个接口,里面只有一个方法 Error()

type error interface {
    Error() string
}
package main

import (
    "fmt"
    "net"
)

func main() {
    addrs, err := net.LookupHost("www.baidu123456.com")
    if err != nil {
        fmt.Println(err)
    }
    // 如果要知道错误的具体类型,可以通过断言操作
    dnsErr, ok := err.(*net.DNSError)
    if ok {
        if dnsErr.Timeout() {
            fmt.Println("超时错误...")
        } else if dnsErr.Temporary() {
            fmt.Println("临时错误...")
        } else {
            fmt.Println("其它错误...")
        }
    }
    fmt.Println(addrs)
}

自定义错误

在项目开发中,更多的是使用自定义错误。

package main

import "fmt"

type ZgyError struct {
    msg  string
    code int
}

func (e *ZgyError) Error() string {
    // fmt.Sprintf 返回string
    return fmt.Sprintf("错误信息:%s, 错误代码:%d\n", e.msg, e.code)
}

func (e ZgyError) print() bool {
    fmt.Println("print hello, world")
    return true
}

// 测试
func test(i int) (int, error) {
    if i != 0 {
        return i, &ZgyError{msg: "非预期数据", code: 500}
    }
    // 正常返回
    return i, nil
}
func main() {
    i, err := test(1)
    // 处理错误的代码
    if err != nil {
        fmt.Println(err)
        zgy_err, ok := err.(*ZgyError)
        if ok {
            if zgy_err.print() {
                // 处理错误
            }
            fmt.Println(zgy_err.msg)
            fmt.Println(zgy_err.code)
        }
    }
    fmt.Println(i)
}

异常panic

恐慌painc,程序在运行的时候会发生panic。在一些预感到有异常的情况下,可以手动抛出panic。如果有panic发生,尽可能接受它,并处理。

package main

// panic
func main() {
    panic("程序 panic")
}

Java语言中使用try..catch机制处理异常,Go崇尚简洁,使用recover函数接收并处理panic函数抛出的恐慌异常信息。

package main

import "fmt"

// panic
// defer:延迟函数,函数倒序执行
func main() {
    defer fmt.Println("main--1")
    defer fmt.Println("main--2")
    fmt.Println("main--3")
    // 如果在函数内部使用recover接收处理了panic,那么此时程序会向下执行,否则不会继续执行
    testPanic(1)
    defer fmt.Println("main--4")
    fmt.Println("main--5")
}
func testPanic(num int) {
    // func recover() any
    // 接收恐慌抛出来的信息,并处理
    defer func() {
        if msg := recover(); msg != nil {
            fmt.Println("执行recover... panic msg: ", msg)
            // 处理逻辑
            fmt.Println("------程序已恢复-----")
        }
    }()

    defer fmt.Println("testPanic--1")
    defer fmt.Println("testPanic--2")
    fmt.Println("testPanic--3")
    // 如果在函数中一旦触发了panic,会终止panic后面要执行的代码
    // 如果panic前存放defer函数,正常按照逻辑倒序执行
    if num == 1 {
        panic("出现预定的异常--panic")
    }
    defer fmt.Println("testPanic--4")
    fmt.Println("testPanic--5")
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值