第六节 Go面向对象

 

go的结构体、函数、方法、接口、包、go mod

与其说是教程,更像是总结

一起学习:smiley:


Go面向对象概括

  • go语言仅支持封装,不支持继承多态

  • 只有struct

结构体和方法

创建结构体

定义一个树的子节点

//go
type treeNode struct{
	value int
	left,right *treeNode
}

//c
typedef struct item {
 long arrive;      
 int processtime;  
} Item;
 
typedef struct node{
    Item item;
    struct node * next;
} Node;

typedef struct queue{
    Node * front;  
    Node * rear;   
    int items;     
} Queue;

初始化结构体

// 这里的root是struct
var root treeNode
root = treeNode{value: 3}
//root.left是指针
root.left = &treeNode{}
root.right = &treeNode{5, nil, nil}

nodes := []treeNode{
		{value: 3},
		{},
		{6, nil, &root},
}

结构体是值类型

node1 := nodes//复制值,且是一个新的地址,不会影响到h1

深拷贝和浅拷贝

  • 深拷贝就是为新的对象分配了内存,再复制值
  • 浅拷贝就是引用类型

注意是否要修改(&)

type Emp struct {
	name string
	age  int8
	sex  byte
}

//深拷贝
h1 := Emp{"Li",20,1}
h2 : = h1 //深拷贝

//浅拷贝
h3 := &h1 //浅拷贝

构造函数

//工厂函数(构造函数)
//Go语言的局部变量不一定会随着函数结束被回收,看系统
//Go语言中允许在函数 中返回局部变量的地址
func createNode(value int) *treeNode {
	//在C++中,不能返回局部变量的地址
	return &treeNode{value: value}
}

函数与方法

//方法,treeNode结构体的(类)
//(node treeNode) 接收者 print() 类中的函数
//本质上是拷贝了一份node,再输出
func (node treeNode) print1() {
	fmt.Print(node.value, " ")
}

//第二种写法,普通函数
func print2(node treeNode) {
	fmt.Print(node.value)
}

匿名函数和匿名结构体

//匿名函数
res := func(a,b float64) float64{
	return math.Pow(a,b)
}(2,3)//调用

//匿名结构体
addr := struct{
	province, city string
}("广东省""广州市")

匿名字段

type User struct {
	string
	byte
	int8
	float64
}

func main() {
	user := User{"LiHua", 'm', 25, 177.5}
	p(user)
	p(user.string)
}

结构体嵌套

聚合关系:一个类作为另一个类的属性

继承关系:一个类作为另一个类的子类

聚合

var pl = fmt.Println

type Address struct {
   province, city string
}

type Person struct {
   name    string
   age     int
   // 聚合
   address *Address //因为要修改值,所以是引用类型
}

func main() {
   p := Person{}
   p.name = "LiHua"
   p.age = 20

   addr := Address{}
   addr.city = "广州市"
   addr.province = "广东省"

   p.address = &addr
   pl(p.name, " ", p.age, " ", p.address.province, " ", p.address.city)

   //匿名赋值
   p.address = &Address{
      province: "江西省",
      city:     "吉安市",
   }

   pl(p.name, " ", p.age, " ", p.address.province, " ", p.address.city)

   addr.city = "安徽省" //不会再改变,因为已经指向其他地方
   pl(p.name, " ", p.age, " ", p.address.province, " ", p.address.city)
}
LiHua   20   广东省   广州市
LiHua   20   江西省   吉安市
LiHua   20   江西省   吉安市 

“继承”

var pl = fmt.Println

type Person struct {
   name string
   age  int
   sex  string
}

type Student struct {
   Person
   schoolName string
}

func main() {
   p1 := Person{"LiHua", 25, "男"}
   s1 := Student{p1, "xxx学校"}
   pl(s1)

   s2 := Student{Person{"LiHua2", 30, "男"}, "XXX2学校"}
   pl(s2)

   s3 := Student{}
   s3.sex = "男"
   s3.schoolName = "xxxxxxx"
   s3.age = 15
   s3.name = "LiHua3"
   pl(s3)
}

方法的拷贝与指针

//传值,所以不会真正修改
//本质是拷贝了一份node,修改的是拷贝的那一份
func (node treeNode) setValue_nopoint(value int) {
	node.value = value
}

//传入的是指针,本质是修改指针,当然可以修改
//Go语言中没有C那么复杂的写法
func (node *treeNode) setValue_point(value int) {
	node.value = value
}
//中序便利,左->中->右
func (node *treeNode) traverse() {
	if node == nil {
		return
	}

	node.left.traverse()
	node.print1()
	node.right.traverse()
}
  • 函数(Function)是一段具有独立功能的代码,可以被反复多次的调用,从而实现代码复用
  • 方法(Method)是一个类的行为功能,只有该类的对象才能调用
  • 方法有接收者,而函数没有接收者

  • 函数不可以重名,方法可以重名(接收者不同)

  • 改变内容必须使用指针接收者
  • 结构过大也考虑使用指针接收者
  • —致性∶如有指针接收者,最好都是指针接收者

值接收者是Go语言特有的

指针和值均可接收指针和值

方法的继承

type Human struct {
   name, phone string
   age         int
}

//Human结构体的方法
func (h Human) saiHi() {
   fmt.Println(h.name, " ", h.phone)
}

type Student struct {
   Human
   string
}

type Emp struct {
   Human
   string
}

func main() {
   s1 := Student{Human{"LiHua", "123", 13}, "xxx"}
   e1 := Student{Human{"LiHua2", "456", 19}, "xxx"}
   s1.saiHi()
   e1.saiHi()
}
LiHua   123
LiHua2   456

案例-方法

package main

import "fmt"

type rect struct {
    width, height int
}

//私有
func (r *rect) area() int {
    return r.width * r.height
}

//私有
func (r rect) perim() int {
    return 2*r.width + 2*r.height
}

func main() {
    r := rect{width: 10, height: 5}

    fmt.Println("area: ", r.area())
    fmt.Println("perim:", r.perim())

    rp := &r
    fmt.Println("area: ", rp.area())
    fmt.Println("perim:", rp.perim())
}
area:  50
perim: 30
area:  50
perim: 30

接口

go语言不向java会强制要求实现接口中的方法

也不会明说某某方法要实现某某接口

判断是否实现了某某方法,就是看到有没有去实现接口中的方法,要是有实现那就实现了那个接口,不需要明示声明说实现某个接口(用实际行动说话)

package main

import (
    "fmt"
    "math"
)

//创建接口
type geometry interface {
    area() float64
    perim() float64
}

//结构体1
type rect struct {
    width, height float64
}
//结构体2
type circle struct {
    radius float64
}

//结构体1的方法,实现接口
func (r rect) area() float64 {
    return r.width * r.height
}
func (r rect) perim() float64 {
    return 2*r.width + 2*r.height
}

//结构体2的方法,实现接口
func (c circle) area() float64 {
    return math.Pi * c.radius * c.radius
}
func (c circle) perim() float64 {
    return 2 * math.Pi * c.radius
}

//geometry是接口
func measure(g geometry) {
    fmt.Println(g)
    //调用接口中的方法
    fmt.Println(g.area())
    fmt.Println(g.perim())
}

func main() {
    r := rect{width: 3, height: 4}
    c := circle{radius: 5}

    measure(r)
    measure(c)
}
{3 4}
12
14
{5}
78.53981633974483
31.41592653589793

duck typing

  • “duck typing“ 关注的不是对象的类型本身,而是它是如何使用的
  • ”duck typing“的编程语言,需要严格的显式声明,调用,实现某个接口,好处就是可以在编译阶段发现问题
  • 非”duck typing“的编程语言,不需要严格的显式声明,调用,实现某个接口,好处方便,但要在运行阶段才能看出问题

Go 折中了

结构体类型T不需要显式的声明实现了I接口。只要实现了I规定的所有,就自动实现了接口I

type ISayHello interface {
	SayHello() string
}

type Duck struct {
	name string
}

type Person struct {
	name string
}

//实现了接口ISayHello,隐式得得没有明说
func (d Duck) SayHello() string {
	return d.name + " hello!"
}

//实现了接口ISayHello,隐式得得没有明说
func (p Person) SayHello() string {
	return p.name + " hello!"
}

func main() {
	duck := Duck{"yaya"}
	person := Person{"LiHua"}
	fmt.Printf("%v %p\n", duck.SayHello(), &duck)
	fmt.Printf("%v %p\n", person.SayHello(), &person)
}
yaya hello! 0xc000108230
LiHua hello! 0xc000108240

空接口 interface{}

数据类型判断

whatAmI := func(i interface{}) {
        switch t := i.(type) {
        case bool:
            fmt.Println("I'm a bool")
        case int:
            fmt.Println("I'm an int")
        default:
            fmt.Printf("Don't know type %T\n", t)
        }
    }
whatAmI(true)
whatAmI(1)
whatAmI("hey")
I'm a bool
I'm an int
Don't know type string

或者结构体判断

//instance,ok := 接口对象.(实际类型)
func getType(s Shape){
	if instance,ok := s.(Rectangle);ok{
		...
	}else if instance,ok := s.(Triangle);ok{
		...
	}else if instance,ok := s.(Circle);ok{
		...
	}
}

嵌入 Embedding

接口嵌入接口

接口只能嵌入接口

这里的做为内嵌接口的含义实际上还是指的一个定义,而不是接口的一个实例,相当于合并了两个接口定义的函数,只有同时了Writer和 Reader 接口,是可以说是实现了WRer接口,即才可以作为WRer的实例

package main

import "fmt"

type Writer interface{
	Write()
}

type Reader interface{
	Read()
}

type WRer interface{
	Reader
	Writer
}

type names struct {
	name string
}

func (n names)Write()  {
	fmt.Println("write")
}

func (n names)Read()  {
	fmt.Println("read")
}

func la(w WRer){
	w.Read()
	w.Write()
}

func main() {
	name := names{name: "Lihua"}
	la(name)
}
read
write

结构体嵌入接口

type Printer interface {
	Print()
}

type CanonPrinter struct {
	Printname string
}

func (printer CanonPrinter) Print() {
	fmt.Println("this iscannoprinter printing now")
}

type PrintWorker struct {
	Printer
	name string
	age  int
}

//如果没有下面部分的实现,则这里只调用CanonPrinter实现的Print()方法。
func (printworker PrintWorker) Print() {
	fmt.Println("this is printing from PrintWorker")
	printworker.Printer.Print() // 这里 printworker 首先引用内部嵌入Printer接口的实例,然后调用Printer 接口实例的Print()方法
}

func main() {
	canon := CanonPrinter{Printname: "con1"}
	printworker := PrintWorker{Printer: canon, name: "ansendong", age: 34}
	printworker.Print()
}
this is printing from PrintWorker
this iscannoprinter printing now

结构体嵌入结构体

package main

import "fmt"

type base struct {
    num int
}

func (b base) describe() string {
    return fmt.Sprintf("base with num=%v", b.num)
}

type container struct {
    base
    str string
}

func main() {

    co := container{
        base: base{
            num: 1,
        },
        str: "some name",
    }

    fmt.Printf("co={num: %v, str: %v}\n", co.num, co.str)

    fmt.Println("also num:", co.base.num)

    fmt.Println("describe:", co.describe())
}
co={num: 1, str: some name}
also num: 1
describe: base with num=1

来填坑了

首字母大写表示public,首字母小写表示private

package tree

import "fmt"

type Node struct {
	Value       int
	Left, Right *Node
}

//大写,允许其他包调用
func (node Node) SetValue_nopoint(Value int) {
	node.Value = Value
}

Go语言的依赖管理

依赖,相当于使用别人的库

依赖管理的三个阶段 GOPATH -> GOVENDOR -> go mod

go mod

现在大部分都是用go mod

打开go mod和代理

go env -w GO111MODULE=on
go env -w GOPROXY=https://goproxy.cn,direct

使用go mod

创建go mod

go mod init <项目名称>

拉去第三方库

go get -u go.uber.org/zap
go get -u go.uber.org/zap@1.17.0 //指定版本,不指定就是最新版本

整理

go mod tidy

完整案例

简单使用Gin webapi框架

go get -u github.com/gin-gonic/gin
package main

import (
    "net/http"

    "github.com/gin-gonic/gin"
)

func main() {
    // 1.创建路由
   r := gin.Default()
   // 2.绑定路由规则,执行的函数
   // gin.Context,封装了request和response
   r.GET("/", func(c *gin.Context) {
      c.String(http.StatusOK, "hello World!")
   })
   // 3.监听端口,默认在8080
   // Run("里面不指定端口号默认为8080") 
   r.Run(":8000")
}