Go语言基础(三):结构体与面向对象、包管理|青训营笔记

129 阅读5分钟

这是我参与「第三届青训营 -后端场」笔记创作活动的的第4篇笔记;主要学习了Go语言如何用面向对象的方式编程,还学习了Go如何做包管理.

struct 基本定义与使用

struct是go语言为我们提供的可以自定义的一种类型,该类型可以封装多个基本数据类型,可以用来存放一个事物的不同属性,声明和使用方式几乎和C语言一致,不愧是号称现代C语言。。。

tips: struct 传一个结构体参数是值传递,不像slice、map一样,变量名本身是一个指针,常用的办法是传一个指向该struct的指针。

package main
import (
	"fmt"
)

//type 关键字, 声明一中新的数据类型
type myint int

type Book struct {
	title string
	auth string
}

func set_book(book *Book) {
	// 居然不是-> 
	book.auth = "me"
}

func main() {
	var a myint = 10;
	fmt.Printf("a types = %T\n",a)

	var book1 Book
	book1.title = "Golang"
	book1.auth = "zhangsan"
	fmt.Println(book1)

	set_book(&book1)
	fmt.Println(book1)
}

类的表示与封装

Go在面向对象这块做了很多减法,没有public等权限,根据属性名的首字母大小来判断是否对外开放,没有构造函数、析构函数,所有事情都交给GC去处理;没有复杂的继承、多态等语法支持,利用组合的方式实现继承,利用接口的方式实现多态。

基本方法: 通过在func后加一个()来绑定该函数的所属对象(...好奇怪的语法)

package main
import (
	"fmt"
)


type Hero struct {
	Name string
	Ad int
	Level int
}

// this 是形参名
// GetName 方法不需要修改对象,所以单纯值拷贝就够了
func (this Hero) GetName() string {
	return this.Name
}

// set需要更改对象的属性,所以传入的是指针
func (this *Hero) SetName(new_name string) {
	this.Name = new_name
}

func (this Hero) Show() {
	fmt.Println(this)
}

func main() {
	// 创建一个对象
	myHero := Hero{Name:"zhangsan", Ad:1, Level:3 }
	myHero.Show();
	fmt.Println(myHero.GetName())
	myHero.SetName("lisi")
	myHero.Show()
}
// 好像是说不建议用this作为参数名,虽然不是关键字,但是产生误导??!!

tips: 和函数的命名方式同理,如果是外部调用的类名或者类中的属性名,需要大写,对外隐藏细节的话就要小写

继承

方式很简洁,这就是所谓的大道至简吗?

无需特定指名,只要实现了接口的所有函数就默认实现了该接口。。。好奇怪的机制

package main
import (
	"fmt"
)

type Human struct {
	Name string
	Sex string
}


func (this Human) Eat() {
	fmt.Println("Human is eating")
}

func (this Human) Walk() {
	fmt.Println("Human is walking")
}


// 父类继承子类
type Superman struct {
	Human // 只写一个类型,代表当前类继承了Human类
	level int
}

// 此时可以重载父类的方法
func (this Superman) Eat() {
	fmt.Println("Superman doesn't need to eat")
}


func main() {
	// 创建一个对象, Name: Sex: 这些可以省略
	h := Human{Name:"zhangsan", Sex:"男" }
	h.Eat();
	h.Walk()

	// 定义一个子类对象
	s := Superman{ Human{"super","男"}, 1}
	s.Eat()
	s.Walk()
}

多态/interface

使用interface 实现多态

不同于一般oop的向上转型,GO通过接口机制实现多态(很新奇啊)

package main
import (
	"fmt"
)

// 接口的本质是一个指针,有点类似于cpp的虚指针,指向着当前的类类型和函数表
type AnimalIF interface {
	Sleep()
	GetColor() string 
	GetType() string
}

// 具体的类,实现AnimalIF 接口
// 无需特定指名,只要实现了接口的所有函数就默认实现了该接口。。。好奇怪的机制
type Cat struct {
	color string 
}

func (this *Cat) Sleep() {
	fmt.Println("cat sleep")
}

func (this *Cat) GetColor() string {
	return this.color
}

func (this *Cat) GetType() string {
	return "Cat"
}

// Dog 实现AnimalIF 接口
type Dog struct {
	color string 
}
func (this *Dog) Sleep() {
	fmt.Println("dog sleep")
}

func (this *Dog) GetColor() string {
	return this.color
}

func (this *Dog) GetType() string {
	return "Dog"
}



func ShowAnimal(animal AnimalIF) {
	animal.Sleep()
	fmt.Println(animal.GetColor(),animal.GetType())
}


func main() {
	var animal AnimalIF // 定义一个所谓的原始对象
	animal = &Cat{"Green"} // animal 本质是一个指针

	animal.Sleep()
	fmt.Println(animal.GetColor(),animal.GetType())
	
	
	animal = &Dog{"Yellow"} // animal 本质是一个指针
	
	animal.Sleep()
	fmt.Println(animal.GetColor(),animal.GetType())

	fmt.Println("--------------")
	// 上述注释可以抽象出一个函数
	ShowAnimal(&Cat{"Red"})
}

interface空接口万能类型和类型断言机制

interface 可以成为通用万能类型(void* 这种) interface{} : 空接口

GO提供的基本的数据类型(包括struct)都实现了interface{}

所以可以用interface{} 这个类型的变量引用所有类型的数据

1.18版本 好像有更好的方式:知道了,1.18以后可以等效于std::any, 无论是指针还是值都可以使用interface{}.

package main

import(
	"fmt"
)

type Human struct {
	Name string
	Sex string
}

func myFunc(arg interface{}) {
	fmt.Println("---------------")
	fmt.Printf("arg.type = %T, arg.value = %v\n", arg,arg)

	// 只有interface{} 类型的变量才能这么用(断言机制)
	// 只有当ok 为true 的时候,value 的值才非空,且为该类型的字面量
	value, ok := arg.(string) 
	if !ok {
		fmt.Println("arg is not string,value = ",value)
	} else {
		fmt.Println("arg is string,value = ",value)
	}
}

func main() {
	myFunc(1)
	myFunc(3.14)
	myFunc("str")
	myFunc(Human{Name:"张三",Sex:"男"})
}

interface{} 的机制: 通过interface{} 的类型断言机制,在底层一个一个判断??

// 举例,对于interface{} 类型的变量arg
value,ok := arg.(string)
if ok {
   value == arg.value 
   type == string

}
else {
    value == nil
    type != string 
}

tips: 语法糖 如果实现了接收者是值类型的方法,会隐含地也实现了接收者是指针类型的方法。

变量的内置pair结构

反射的主要作用: 通过变量找到变量的concrete type(具体类型)

  • 变量在底层的结构

1.JPG

在接口的变量赋值的过程中,其对应的pair直接传递,保持不变的

  package main

import(
	"fmt"
)
func main() {
	var a string
	// 基本数据类型 都属于 static type
	// 此时a的结构应该是一个 pair<statictype:string, value:"abcd">
	a = "abcd"

	// pair<type:string, value:"abcd">
	var allType interface{}
	allType = a
	value,ok := allType.(string)
	fmt.Println(value)
}
package main

import(
	"fmt"
)

type Reader interface {
	ReadBook()
}

type Writer interface {
	WriterBook()
}

type Book struct {
}

func (this *Book) ReadBook() {
	fmt.Println("read")
}

func (this *Book) WriterBook() {
	fmt.Println("Write")
}



func main() {
	
	// 此时的 pair<type:Book, value:book{}>
	a := &Book{}
	// var a interface{} = &Book{} 
	fmt.Printf("type = %T\n",a)
	// 此时的 pair<type, value:>
	
	var r Reader
	// 此时的 pair<type:Book, value:book{}>
	r = a
	r.ReadBook()
	// r.WriterBook() // r 中没有WirterBook放法,所以就算类型正确也找不到该方法

	// 此时的 pair<type:Book, value:book{}> , pair 依旧没有变
	var w Writer = r.(Writer) //  w 和 r 具体的type一直相同,所以才能断言成功

	// w.ReadBook() // w 中没有ReadBook放法,所以就算类型正确也找不到该方法
	w.WriterBook()  
}