go语言oop&接口&type&错误处理

文章目录[x]
  1. 1:oop
  2. 2:接口(interface)
  3. 2.1:空接口
  4. 2.2:接口断言
  5. 2.3:接口嵌套
  6. 3:type
  7. 4:异常处理
  8. 4.1: panic恐慌

go的基础部分也是不少的,这里我就小生觉得的部分记一下笔记,这个学的时候是暑假在家,无奈到底还是被遗忘曲线被打败了。现在有的也记不清了,刚好整理一下,自己再梳理梳理。

oop


先从oop说吧,我们都知道oop是面向对象编程的缩写,但是这里要说的是go并不是一个纯面向对象的编程语言。在go中的面向对象,结构体替换了类。Go并没有提供类class,但是它提供了结构体struct,方法method,可以在结构体上添加。提供了捆绑数据和方法的行为,这些数据和方法与类类似。其实很多东西看模板看很长时间也不知道究竟说啥,不如直接看代码理解的快

  1. 定义结构体和方法
package main

import (
"fmt"
)

//定义结构体
type Employee struct {
FirstName string
LastName string
TotalLeaves int
LeavesTaken int
}

//括号里是那个结构体,表示那个实例可以调用 这里也可以用指针表示 e *Employee类似这样也是可以的
func (e Employee) LeavesRemaining() {
fmt.Printf("%s %s has %d leaves remaining", e.FirstName, e.LastName, (e.TotalLeaves - e.LeavesTaken))
}

func main() {
e := Employee {
FirstName: "洛尘曦",
LastName: "yvam",
TotalLeaves: 30,
LeavesTaken: 20,
}
e.LeavesRemaining()

fmt.Prinfln()
fmt.Println("-----------分割线-----------")

//当然这里用指针的形式也是可以的
q := &Employee {
FirstName: "洛尘曦",
LastName: "yvam",
TotalLeaves: 30,
LeavesTaken: 20,
}
fmt.Printf("%T\n",q)
q.LeavesRemaining()
}

看一下运行的结果:

洛尘曦 yvam has 10 leaves remaining
-----------分割线-----------
*main.Employee
洛尘曦 yvam has 10 leaves remaining
Process finished with exit code 0

b.New()函数替代了构造函数

我们知道java或者C++都是有构造函数的,但是go语言可以用New函数代替,看下面的例子

package main

import (
"fmt"
)

//定义结构体
type Employee struct {
FirstName string
LastName string
TotalLeaves int
LeavesTaken int
}

//括号里是那个结构体,表示那个实例可以调用 这里也可以用指针表示 e *Employee类似这样也是可以的
func (e Employee) LeavesRemaining() {
fmt.Printf("%s %s has %d leaves remaining", e.FirstName, e.LastName, (e.TotalLeaves - e.LeavesTaken))
}

func main() {
var e Employee
e.LeavesRemaining()
}

看一下执行结果:

has 0 leaves remaining

 

代码更改后:

package main

import (
"fmt"
)

//定义结构体
type Employee struct {
FirstName string
LastName string
TotalLeaves int
LeavesTaken int
}

//括号里是那个结构体,表示那个实例可以调用 这里也可以用指针表示 e *Employee类似这样也是可以的
func (e Employee) LeavesRemaining() {
fmt.Printf("%s %s has %d leaves remaining", e.FirstName, e.LastName, (e.TotalLeaves - e.LeavesTaken))
}
func New(firstName string, lastName string, totalLeave int, leavesTaken int) Employee {
e := Employee {firstName, lastName, totalLeave, leavesTaken}
return e
}

func main() {
e := New("yvam", "洛尘曦", 30, 20)
e.LeavesRemaining()
}

看一下执行结果:

yvam 洛尘曦 has 10 leaves remaining

 

c.通过嵌入结构体实现组成-->模仿继承

package main

import (
"fmt"
)

//此处我们定义一个结构体
type author struct {
firstName string
lastName string
bio string
}

func (a author) fullName() string {
return fmt.Sprintf("%s %s", a.firstName, a.lastName)
}

//此处我们定义另一个结构体,不过不同的是,此处结构体中的字段是另一个结构体,需要注意的是,post可以访问author的所有属性和方法
type post struct {
title string
content string
author
}

func (p post) details() {
fmt.Println("Title: ", p.title)
fmt.Println("Content: ", p.content)
fmt.Println("Author: ", p.author.fullName())
fmt.Println("Bio: ", p.author.bio)
}

func main() {
author1 := author{
"Naveen",
"Ramanathan",
"Golang Enthusiast",
}
post1 := post{
"Inheritance in Go",
"Go supports composition instead of inheritance",
author1,
}
post1.details()
}

执行结果:

Title:  Inheritance in Go
Content:  Go supports composition instead of inheritance
Author:  Naveen Ramanathan
Bio:  Golang Enthusiast

 

c.多态性

我们都知道继承,封装,多态是面向对象语言的三大特性,例如在java中,当编译时类型和运行时类型不一致,就可能出现所谓的多态(Polymorphism)。类对象赋给父类变量 或 实现类对象赋给接口变量,该对象可以有多种形态,在运行时期会表现出 子类 或 实现类 特征(调用 子类 或 实现类 的覆盖方法)。Go中的多态性是在接口的帮助下实现的。类型接口的变量可以保存实现接口的任何值。接口的这个属性用于实现Go中的多态性。

看一个例子:

package main

import "fmt"
type Income interface {  
    calculate() int
    source() string
}

type FixedBilling struct {  
    projectName string
    biddedAmount int
}

type TimeAndMaterial struct {  
    projectName string
    noOfHours  int
    hourlyRate int
}


func (fb FixedBilling) calculate() int {
return fb.biddedAmount
}

func (fb FixedBilling) source() string {
return fb.projectName
}

func (tm TimeAndMaterial) calculate() int {
return tm.noOfHours * tm.hourlyRate
}

func (tm TimeAndMaterial) source() string {
return tm.projectName
}

func calculateNetIncome(ic []Income) {
var netincome int = 0
for _, income := range ic {
fmt.Printf("Income From %s = $%d\n", income.source(), income.calculate())
netincome += income.calculate()
}
fmt.Printf("Net income of organisation = $%d", netincome)
}func calculateNetIncome(ic []Income) {
var netincome int = 0
for _, income := range ic {
fmt.Printf("Income From %s = $%d\n", income.source(), income.calculate())
netincome += income.calculate()
}
fmt.Printf("Net income of organisation = $%d", netincome)
}

func main() {
project1 := FixedBilling{projectName: "Project 1", biddedAmount: 5000}
project2 := FixedBilling{projectName: "Project 2", biddedAmount: 10000}
project3 := TimeAndMaterial{projectName: "Project 3", noOfHours: 160, hourlyRate: 25}
incomeStreams := []Income{project1, project2, project3}
calculateNetIncome(incomeStreams)
}

执行结果就不贴了呢calculateNetIncome(ic []Income)这个函数便实现了多态

总结一下大概就是:

go语言通过接口模拟多态

就一个接口的实现
1.看成实现本身的类型,能够访问实现类中的属性和方法
2.看成是对应的接口类型,那就只能够访问接口中的方法

接口(interface)


先看一下接口的定义:面向对象世界中的接口的一般定义是“接口定义对象的行为”。它表示让指定对象应该做什么。实现这种行为的方法(实现细节)是针对对象的。比如java中的接口,接口的本质是契约,就是规范,就像人的法律一样,都需要去遵守。我们知道在java中抽象类可以有普通方法,但是接口必须定义的是一直不变的东西,比如常量、必须是抽象方法。在Go中,接口是一组方法签名。当类型为接口中的所有方法提供定义时,它被称为实现接口。它与OOP非常相似。接口指定了类型应该具有的方法,类型决定了如何实现这些方法。说这些定义的时候,感觉真是玄之又玄,所以直接看代码例子就会通透很多。

在GO语言中,接口实际上是一组方法签名,接口和类型的实现关系,是非侵入式。什么意思,比如在在java中的接口就是显式定义的。

1.当需要接口类型的对象时,可以使用任意实现类对象代替
2.接口对象不能访问实现类中的属性,当然也包括方法-->这一点很重要。。。

下面看一个例子:

package main

import "fmt"

func main() {
//1.创建Mouse类型
m1 := Mouse{"机械蛇"}
fmt.Println(m1.name)
//2.创建FlashDisk
f1 := FlashDisk{"金士顿"}
fmt.Println(f1.name)

testInterface(m1)
testInterface(f1)

var usb USB
usb= f1
usb.start()
usb.end()
//fmt.Println(usb.name) //不能访问实现类中的属性

f1.deleteData()
//usb.deleteData  //不能访问实现类中的方法

var arr [3]USB
arr[0] = m1
arr[1] = f1
fmt.Println(arr)
}

//1.定义接口
type USB interface {
start() //USB设备开始工作
end() //USB设备结束工作
}

//2.实现类
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 (f FlashDisk)start(){
fmt.Println(f.name,"准备开始工作,可以进行数据的存储。。")
}
func (f FlashDisk)end(){
fmt.Println(f.name,"可以弹出。。")
}

//3.测试方法
func testInterface(usb USB){ //usb = m1 usb = f1
usb.start()
usb.end()
}

func (f FlashDisk) deleteData(){
fmt.Println(f.name,"U盘删除数据。。")
}

执行结果:

机械蛇
金士顿
机械蛇 鼠标,准备就绪,可以开始工作了,点点点。。
机械蛇 结束工作,可以安全退出。。
金士顿 准备开始工作,可以进行数据的存储。。
金士顿 可以弹出。。
金士顿 准备开始工作,可以进行数据的存储。。
金士顿 可以弹出。。
金士顿 U盘删除数据。。
[{机械蛇} {金士顿} <nil>]

空接口


空接口不包含任何的方法,正因为如此,所有的类型都实现了空接口,因此空接口可以存储任意类型的数值。例如在fmt包下的Print系列函数接受的参数都是空接口:

func Print(a ...interface{}) (n int, err error)  //这里要说的是...三个点主要是用于函数有多个不定参数的情况,可以接受多个不确定数量的参数。比如在学数组的时候,darr := [...]int{1,2,3,4,5}这里的三个点表示根据后面的元素确定数组的大小(其实我们知道,在go语言中切片和数组,就是因为数组的大小一旦确定就是不可变的,因此引入slice,这样更灵活,用起来也更加自由)。

看一下下面的例子,其实例子里面写的很通俗易懂,并且还复习了一下map slice啥的

package main

import "fmt"

func main() {

var a1 A = Cat{"花猫"}
var a2 A = Person{"王二狗",30}
var a3 A = "haha"
var a4 A = 100
fmt.Println(a1)
fmt.Println(a2)
fmt.Println(a3)
fmt.Println(a4)
test1(a1)
test1(a2)
test1(3.14)
test1("Ruby")

test2(a3)
test2(1000)

//map,key字符串,value任意类型
map1 := make(map[string]interface{})
map1["name"] = "李小花"
map1["age"] = 30
map1["friend"] = Person{"Jerry",18}
fmt.Println(map1)

//切片,存储任意类型的数据
slice1 := make([]interface{},0,10)
slice1 = append(slice1,a1,a2,a3,a4,100,"abc")
fmt.Println(slice1)

test3(slice1)

}

func test3(slice2 []interface{}){
for i:=0;i<len(slice2);i++{
fmt.Printf("第%d个数据:%v\n",i+1,slice2[i])
}
}

//接口A是空接口,理解为代表了任意类型
func test1(a A){
fmt.Println(a)
}

func test2(a interface{}){
fmt.Println("--->",a)
}

//空接口
type A interface {

}
type Cat struct {
color string
}
type Person struct {
name string
age int
}

运行结果:

{花猫}
{王二狗 30}
haha
100
{花猫}
{王二狗 30}
3.14
Ruby
---> haha
---> 1000
map[age:30 friend:{Jerry 18} name:李小花]
[{花猫} {王二狗 30} haha 100 100 abc]
第1个数据:{花猫}
第2个数据:{王二狗 30}
第3个数据:haha
第4个数据:100
第5个数据:100
第6个数据:abc

接口断言


先来看一下什么是接口断言,因为空接口 interface{}没有定义任何函数,因此 Go 中所有类型都可以说是实现了空接口。当一个函数的形参是interface{},那么在函数中,需要对形参进行断言,从而得到它的真实类型。其实看完这一段话我自己也没明白啥是接口断言 ,所以还是直接看例子哈。

语法定义:

方式一:
1.instance := 接口对象.(实际类型) //不安全,会panic()
2.instance, ok := 接口对象.(实际类型) //安全

方式二:switch
switch instance := 接口对象.(type){
case 实际类型1:
....
case 实际类型2:
....
....
}

package main

import (
"math"
"fmt"
)

func main() {

var t1 Triangle = Triangle{3,4,5}
fmt.Println(t1.peri())
fmt.Println(t1.area())
fmt.Println(t1.a, t1.b,t1.c)

var c1 Circle = Circle{4}
fmt.Println(c1.peri())
fmt.Println(c1.area())
fmt.Println(c1.radius)

var s1 Shape
s1 = t1
fmt.Println(s1.peri())
fmt.Println(s1.area())

var s2 Shape
s2 = c1
fmt.Println(s2.peri())
fmt.Println(s2.area())

testShape(t1)
testShape(c1)
testShape(s1)

getType(t1)
getType(c1)
getType(s1)
//getType(100)

var t2 *Triangle = &Triangle{3,4,2}
fmt.Printf("t2:%T,%p,%p\n",t2,&t2,t2)
getType(t2)
getType2(t2)
getType2(t1)

}

func getType2 (s Shape){
switch ins := s.(type) {
case Triangle:
fmt.Println("三角形。。",ins.a,ins.b,ins.c)
case Circle:
fmt.Println("圆形。。",ins.radius)
case *Triangle:
fmt.Println("三角形结构体指针:",ins.a,ins.b,ins.c)
}
}
func getType(s Shape){
//断言
if ins, ok := s.(Triangle) ; ok{
fmt.Println("是三角形,三边是:",ins.a,ins.b,ins.c)
}else if ins, ok := s.(Circle); ok{
fmt.Println("是圆形,半径是:",ins.radius)
}else if ins, ok := s.(*Triangle) ;ok{
fmt.Printf("ins:%T,%p,%p\n",ins,&ins,ins)
fmt.Printf("s:%T,%p,%p\n",s,&s,s)
}else {
fmt.Println("我也不知道了。。。")

}
}

func testShape(s Shape){
fmt.Printf("周长:%.2f,面积:%.2f\n",s.peri(),s.area())
}
//1.定义一个接口
type Shape interface {
peri() float64 //形状的周长
area() float64 //形状的面积
}

//2.定义实现类:三角形
type Triangle struct {
//a float64
//b float64
//c float64
a, b, c float64
}

func (t Triangle) peri() float64 {
return t.a + t.b + t.c
}

func (t Triangle) area() float64 {
p := t.peri() / 2
s := math.Sqrt(p * (p-t.a)*(p-t.b)*(p-t.c))
return s
}

type Circle struct {
radius float64
}

func (c Circle) peri()float64 {
return c.radius * 2 * math.Pi
}
func (c Circle) area () float64{
return math.Pow(c.radius,2) * math.Pi
}

代码也比较简单易懂,就是定义了一个接口和两个实现类,然后。。。看一下运行结果

12
6
25.132741228718345
50.26548245743669
周长:12.00,面积:6.00
周长:25.13,面积:50.27
周长:12.00,面积:6.00
是三角形,三边是: 3 4 5
是圆形,半径是: 4
是三角形,三边是: 3 4 5
t2:*main.Triangle,0xc00012a020,0xc0001280a0
ins:*main.Triangle,0xc00012a028,0xc0001280a0
s:*main.Triangle,0xc000104210,0xc0001280a0
三角形结构体指针: 3 4 2
三角形。。 3 4 5

接口嵌套


关于接口嵌套,顾名思义就是接口嵌套接口,这里面需要注意的问题就是接口调方法的时候根据接口的嵌套层级不同,它只能调用本本级及其上一层的方法,还有就是下层能赋给上层,但是反之不能,因为层没有下层的方法。

package main

import "fmt"

func main() {
/*
接口的嵌套:
*/
var cat Cat = Cat{}
cat.test1()
cat.test2()
cat.test3()

fmt.Println("--------------")
var a1 A = cat
a1.test1()

fmt.Println("--------------")
var b1 B = cat
b1.test2()
fmt.Println("--------------")
var c1 C = cat
c1.test1()
c1.test2()
c1.test3()

fmt.Println("----------")
//var c2 C = a1
var a2 A = c1
a2.test1()
}

type A interface {
test1()
}

type B interface {
test2()
}

type C interface {
A
B
test3()
}

type Cat struct {
//如果想实现接口C,那不止要实现接口C的方法,还要实现接口A,B中的方法
}

func (c Cat) test1() {
fmt.Println("test1()....")
}

func (c Cat) test2() {
fmt.Println("test2()....")
}

func (c Cat) test3() {
fmt.Println("test3()....")
}

这个代码就不贴运行结果了呢

type


type是go语法里的重要而且常用的关键字,type绝不只是对应于C/C++中的typedef。搞清楚type的使用,就容易理解go语言中的核心概念struct、interface、函数等的使用。

定义接口

定义结构体

定义其他的新类型

package main

import "fmt"

type myint int
type mystr string

func main() {

var i1 myint
var i2 = 100
i1 = 100
fmt.Println(i1)
//i1 = i2 //cannot use i2 (type int) as type myint in assignment
fmt.Println(i1,i2)

var name mystr
name = "王二狗"
var s1 string
s1 = "李小花"
fmt.Println(name)
fmt.Println(s1)
name = s1 //cannot use s1 (type string) as type mystr in assignment
}

定义函数类型,返回值是一个函数。

package main

import (
"fmt"
"strconv"
)

func main() {

res1 := fun1()
fmt.Println(res1(10,20))
}

type my_fun func (int,int)(string)

//fun1()函数的返回值是my_func类型
func fun1 () my_fun{
fun := func(a,b int) string {
s := strconv.Itoa(a) + strconv.Itoa(b)
return s
}
return fun
}

类型起别名

type byte = uint8
type rune = int32

不能为不在一个包中的类型定义方法。

package main

import (
"fmt"
)

type Person struct {
name string
}

func (p Person) Show() {
fmt.Println("Person-->",p.name)
}

//类型别名
type People = Person

type Student struct {
// 嵌入两个结构
Person
People
}

func (p People) Show2(){
fmt.Println("People------>",p.name)
}

func main() {
//
var s Student

//s.name = "王二狗" //ambiguous selector s.name
s.People.name = "李小花"
s.Person.name = "王二狗"
//s.Show() //ambiguous selector s.Show
s.Person.Show()
s.People.Show2()
fmt.Printf("%T,%T\n",s.Person,s.People) //main.Person,main.Person

}

上面的一个例子,在通过s直接访问name的时候,或者s直接调用Show()方法,因为两个类型都有 name字段和Show() 方法,会发生歧义,证明People 的本质确实是Person 类型。

异常处理


错误指的是可能出现问题的地方出现了问题。比如打开一个文件时失败,这种情况在人们的意料之中 。

而异常指的是不应该出现问题的地方出现了问题。比如引用了空指针,这种情况在人们的意料之外。可见,错误是业务过程的一部分,而异常不是 。

通俗一点说,比如在你开车的时候,开着开着面前突然窜出来一条狗,这个属于异常,但是如果开着开着你的汽车发动机坏了,这个就是属于错误。这个不是我们能管的,需要到4s店或者厂家处理。

Go语言没有提供像JavaC#语言中的try...catch异常处理方式,而是通过函数返回值逐层往上抛。

package main

import (
"fmt"
"os"
)

func main() {
f, err := os.Open("/test.txt")
if err != nil {
fmt.Println(err)
return
}
//根据f进行文件的读或写
fmt.Println(f.Name(), "opened successfully")
}

看一下上边的代码,如果一个函数或方法返回一个错误,那么按照惯例,它必须是函数返回的最后一个值。因此,Open 函数返回的值是最后一个值。处理错误的惯用方法是将返回的错误与nil进行比较。nil值表示没有发生错误,而非nil值表示出现错误。在我们的例子中,我们检查错误是否为nil。如果它不是nil,我们只需打印错误并从主函数返回。

自定义错误:

package main

import (
"errors"
"fmt"
)

func main() {
/*
error:内置的数据类型,内置的接口
定义方法:Error() string

使用go语言提供好的包:
errors包下的函数:New(),创建一个error对象
fmt包下的Errorf()函数:
func Errorf(format string, a ...interface{}) error
*/
//1.创建一个error数据
err1 := errors.New("自己创建玩的。。")
fmt.Println(err1)
fmt.Printf("%T\n",err1) //*errors.errorString

//2.另一个创建error的方法
err2 := fmt.Errorf("错误的信息码:%d",100)
fmt.Println(err2)
fmt.Printf("%T\n",err2)

fmt.Println("-----------------")
err3 := checkAge(-30)
if err3 != nil{
fmt.Println(err3)
return
}
fmt.Println("程序。。。go on。。。")
}
//设计一个函数:验证年龄是否合法,如果为负数,就返回一个error
func checkAge(age int) error{
if age < 0{
//返回error对象
//return errors.New("年龄不合法")
err := fmt.Errorf("您给定的年龄是:%d,不合法",age)
return err
}
fmt.Println("年龄是:",age)
return nil
}
package main

import (
"net"
"fmt"
)

func main() {
addr,err := net.LookupHost("www.baidu.com")
fmt.Println(err)
if ins, ok := err.(*net.DNSError);ok{
if ins.Timeout(){
fmt.Println("操作超时。。")
}else if ins.Temporary(){
fmt.Println("临时性错误。。")
}else{
fmt.Println("通常错误。。")
}
}
fmt.Println(addr)
}

上面的请求域名,如果写一个无效的,那么既不是暂时,也不是超时,程序便会打印出来错误。

 panic恐慌

package main

import "fmt"

func main() {
/*
panic:词义"恐慌",
recover:"恢复"
go语言利用panic(),recover(),实现程序中的极特殊的异常的处理
panic(),让当前的程序进入恐慌,中断程序的执行
recover(),让程序恢复,必须在defer函数中执行
*/
defer func(){
if msg := recover();msg != nil{
fmt.Println(msg,"程序回复啦。。。")
}
}()
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("我是一个函数funA()....")
}

func funB(){//外围函数

fmt.Println("我是函数funB()...")
defer myprint("defer funB():1.....")

for i:= 1;i<=10;i++{
fmt.Println("i:",i)
if i == 5{
//让程序中断
panic("funB函数,恐慌了")
}
}//当外围函数的代码中发生了运行恐慌,只有其中所有的已经defer的函数全部都执行完毕后,该运行恐慌才会真正被扩展至调用处。
defer myprint("defer funB():2.....")
}

运行结果:

我是一个函数funA()....
我是函数funB()...
i: 1
i: 2
i: 3
i: 4
i: 5
defer funB():1.....
defer main:3.....
funB函数,恐慌了 程序回复啦。。。

这里面还涉及到了defer修饰函数前后的执行问题。

 

点赞

发表评论

昵称和uid可以选填一个,填邮箱必填(留言回复后将会发邮件给你)
tips:输入uid可以快速获得你的昵称和头像

Title - Artist
0:00