目录
结构体
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]