Golang 快速上手 (3)


结构体

Golang 没有直接提供面向对象的概念,但是可以使用Go语法来实现面向对象编程的一些特性,例如:继承、多态、等特性。


定义一个结构体

type Animal struct {
    
    
	Name string
	Age  int
}

初始化结构体

默认初始化

	var dog Animal //定义一个结构体

使用键值对初始化

animal := Animal{
    
    
		Name: "animal",
		Age:  19,
	}

列表初始化

必须初始化结构体的所有字段。
初始值的填充顺序必须与字段在结构体中的声明顺序一致。
该方式不能和键值初始化方式混用。

	cat := Animal{
    
    
		"cat",
		12,
	}

部分初始化

	cat := Animal{
    
    
		Name: "cat",
	}


结构体的指针

new 关键字创建结构体指针

	pa := new(Animal)
	fmt.Printf("pa: %T\n", pa) //pa: *main.Animal

访问结构体成员

指针和对象都可以通过逗号访问结构体对象的成员,如果指针通过逗号访问成员编译器会默认解引用,(*pa),name 等价于 pa.name

	pa := new(Animal)
	pa.Name = "fish"
	pa.Age = 3

	(*pa).Name = "dog"
	(*pa).Age = 4
	fmt.Printf("pa: %v\n", *pa) //pa: {dog 4}

结构体的访问权限

结构体名称首字母大写,那么在其他包内才可以定义Person对象,小写则无法访问。

并且,结构体的属性首字母大写则其访问权限为public的,若为小写,则为private

type Person struct {
    
    
	Name string
	Age  int
}

func main() {
    
    
	p1 := Person{
    
    
		Name: "marry",
		Age:  30,
	}

	fmt.Printf("p1.Name: %v\n", p1.Name)
	fmt.Printf("p1.Age: %v\n", p1.Age)
}

编译器报错

type Person struct {
    
    
	name string
	age  int
}

func main() {
    
    
	p1 := Person{
    
    
		name: "marry",
		age:  30,
	}
	//p1.Name undefined (type Person has no field or method Name, but does have name)
	fmt.Printf("p1.Name: %v\n", p1.Name)
	fmt.Printf("p1.Age: %v\n", p1.Age)
}

方法首字母大写才能在其他包内访问


type Person struct {
    
    
	name string
	age  int
}

func (p Person) Run() {
    
    
	fmt.Println("Person Run")
}

func main() {
    
    
	p1 := Person{
    
    
		name: "marry",
		age:  30,
	}
}

对象的方法

Golang 没有直接提供面向对象的特性,也没有类对象的概念。但是,可以使用结构体来模拟这些特性。


定义结构体方法

Golang 中的方法,是一种特殊的函数,定义于struct之上(与struct关联、绑定),被称为struct的接受者(receiver)。

type Animal struct {
    
    
	Name string
	Age  int
}

func (a Animal) Eat() {
    
    
	fmt.Println("Animal Eat")
}

func (a Animal) Sleep() {
    
    
	fmt.Println("Animal Sleep")
}

func main() {
    
    

	a := Animal{
    
    
		Name: "dog",
		Age:  10,
	}

	a.Eat()
	a.Sleep()
}

方法接收者类型

//传递指针才能改变其值
func (per *Animal) Set1(age_ int, name_ string) {
    
    
	per.age = age_
	per.name = name_
}

//值传递 内部修改不会影响外部
func (per Animal) Set2() (int, string) {
    
    
	per.age = age_
	per.name = name_
}

方法注意点

  • 方法的receiver type可以是其他类型,如:type定义的类型别名、slice、 map、 channel、 func类型等

  • struct 结合它的方法就等价于面向对象中的类。只不过struct可以和它的方法分开,,并非一定要属于同一个文件,但必须属于同一个包。

  • 方法有两种接收类型: (T Type) 和(T *Type) , (T Type)是值传递,(T *Type)传递指针,内部修改会影响实参。

  • 方法就是函数,,Go中没有方法重载(overload)的说法,也就是说同一个类型中的所有方法名必须都唯一。

  • 如果receiver是一个指针类型, 则会自动解除引用。

  • 方法和type是分开的,意味着实例的行为(behavior)和数据存储(field)是分开的,但是它们通过receiver建立关联关系。



接口

go语言的接口,是一种新的类型定义,它把所有的具有共性的方法定义在一起,任何其他类型只要实现了这些方法就是实现了这个接口。
语法格式和方法非常类似。

定义一个接口

type Displayer interface {
    
    
	DisplayAudio(string)
	Displayvideo(string)
}

实现接口

type Displayer interface {
    
    
	DisplayAudio()
	DisplayVideo()
}
 
type MP4 struct {
    
    
}

func (mp4 MP4) DisplayAudio() {
    
    
	fmt.Println("DisplayAudio")
}

func (mp4 MP4) DisplayVideo() {
    
    
	fmt.Println("DisplayVideo")
}

func main() {
    
    

	var player Displayer = MP4{
    
    }

	player.DisplayAudio()
	player.DisplayVideo()
}

要注意,实现接口必须实现接口中的所有方法,不然无法将结构体赋值给接口类型变量


值类型receiver 指针类型receiver

和方法传参一样,必须传递指针才能在内部修改其值。。



接口和struct

一个结构体可以实现多个接口
多个结构体实现同一个接口(实现多态)


一个结构体实现多个接口

type Displayer interface {
    
    
	DisplayAudio()
	DisplayVideo()
}

type PlayGamer interface {
    
    
	PlayGame()
}

type MP4 struct {
    
    
	Volume int
}

func (mp4 MP4) DisplayAudio() {
    
    
	fmt.Println("DisplayAudio")
}

func (mp4 MP4) DisplayVideo() {
    
    
	fmt.Println("DisplayVideo")
}

func (mp4 MP4) PlayGame() {
    
    
	fmt.Println("PlayGame")
}

func main() {
    
    

	var player1 Displayer = MP4{
    
    
		Volume: 10,
	}
	var player2 PlayGamer = MP4{
    
    
		Volume: 10,
	}

	player1.DisplayAudio()
	player1.DisplayVideo()

	player2.PlayGame()
}

实现多态

多个结构体实现同一个接口 (多态)

package main

import "fmt"

type Per interface {
    
    
	eat()
	sleep()
}

type Dog struct {
    
    
}

type Cat struct {
    
    
}

func (d Dog) eat() {
    
    
	fmt.Println("Dog eat")
}

func (d Dog) sleep() {
    
    
	fmt.Println("Dog eat")
}

func (d Cat) eat() {
    
    
	fmt.Println("Cat eat")
}

func (d Cat) sleep() {
    
    
	fmt.Println("Cat eat")
}


type Person struct {
    
    
}

func (p Person) care(per Per) {
    
    
	per.eat()
	per.sleep()
}

//其实就是多态的使用
//OCP 开放扩展 关闭修改
func main() {
    
    

	dog := Dog{
    
    }
	cat := Cat{
    
    }

	person := Person{
    
    }
	person.care(dog)
	person.care(cat)
}

简单地实现一个多态的案例,传递Dog对象调用的就是Dog类的方法,传递Cat对象调用Cat类的方法。



继承

定义一个父类

type Animal struct {
    
    
	Name string
	Age  int
}

func (a Animal) Sleep() {
    
    
	fmt.Println("Animal Sleep")
}

func (a Animal) Eat() {
    
    
	fmt.Println("Animal Eat")
}

子类可以重定义父类的方法

type Dog struct {
    
    
	Animal
}
func (d Dog) Eat() {
    
    
	fmt.Println("Dog Eat")
}

func main() {
    
    
		d1 := Dog{
    
    
		Animal: Animal{
    
    
			Name: "Dog",
			Age:  3,
		},
	}
	d1.Eat() //Dog Eat
}


Golang 构造函数

golang没有构造函数的概念,但可以使用函数来模拟构造函数的的功能。

type Student struct {
    
    
	Name string
	Age  int
}

func NewStudent(name string, age int) (*Student, error) {
    
    

	if age > 99 || age < 0 {
    
    
		return nil, fmt.Errorf("age input error")
	}

	return &Student{
    
    Name: name, Age: age}, nil
}


Golang 包

在这里插入图片描述

包可以区分命令空间(一个文件夹中不能有两个同名文件),也可以更好的管理项目。go中创建一个包,一般是创建一个文件夹, 在该文件夹里面的go文件中,使用package关键字声明包名称,通常,文件夹名称和包名称相同。并且同一个文件下面只有一个包

包的注意点:

一个文件夹下只能有一个package

  • import后面的其实是GOPATH开始的相对目录路径,包括最后一段。但由于一个目录下只能有一个package,所以import 一个路径就等于是import了这个路径下的包。

需要注意,这里指的是“直接包含”的go文件。 如果有子目录,那么子目录的父目录是完全两个包。
比如实现了一个计算器package,名叫calc,位于calc目录下。但又想给别人一个使用范例,于是在calc下可以建个example子目录(calc/example/) ,这个子目录里有个example.go (calc/example/example.go) 。此时,example.go可以是main包,里面还可以有个main函数。

  • 一个package的文件不能在多个文件夹下

如果多个文件夹下有重名的package,它们其实是彼此无关的package。
如果一个go文件需要同时使用不同目录下的同名package,需要在import这些目录时为每个目录指定一个package的别名。|


包的导入方式

匿名导入

包一但导入就必须使用其内部函数,如未使用编译器报错,但如果有一个需求,需要调用 test_lib 的init函数,又不用调用test_lib内的函数就可以用 _ 匿名导入包。

import (
	"fmt"
	_ "test_lib"
)

重命名包:

如果包的名称太长,可以对其重命名使用。

import (
	"fmt"
	my_lib "test_lib"
)

func main() {
    
    
	my_lib.API()
}

展开包:

test_lib 包名前加逗号,那么调用test_lib 内的函数可以不加上包名,就像函数定义在当前包一样使用。

import (
	"fmt"
	. "test_lib"
)
func main() {
    
    
	API() // test_lib.API() 直接调用
}

不推荐使用,如果两个包都使用逗号展开包,如果两个包内存在同名函数就无法区分。


包管理工具 module

go modules 是golang 1.11新加的特性,用来管理模块中包的依赖关系。

常用指令

初始化模块

go mod init <项目模块名称>

依赖关系处理,根据go.mod文件

go mod tidy

将依赖包复制到项目下的vendor目录。
如果无法科学上网,可以使用这个命令,随后使用go build -mod=vendor编译

go mod vendor

显示依赖关系

go list -m all

显示详细依赖关系

go list -m -json all

下载依赖 ([path@version] 不是必写的)

go mod download [path@version]

在这里插入图片描述


猜你喜欢

转载自blog.csdn.net/juggte/article/details/125382130