go的结构体、函数、方法、接口、包、go mod
与其说是教程,更像是总结
一起学习
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")
}
PREVIOUSgo基础练习-上
NEXT第七节 Go错误处理与测试