go 语言面向对象编程上

结构体

package main
import (
	"fmt"
)
type cat struct{   //结构体定义
	name string
	age int
	color string
}
func main() {
	//
	t1 := cat{"小花",45,"黑色"}//实例化结构体
	t2 := cat{"小红",43,"黄色"}
	fmt.Println(t1.name,t2.name)//调用实例化的结果体
}
// 执行结果
// 小花 小红

  golang语言面向对象介绍

1)golang也支持面向对象编程(OOP),但是和传统的面向对象编程有区别,并不是纯粹的面向对象语言,所以说golang支持面向对象编程特性是比较准确的

2)golang没有类(class),go语言支持结构体(struct)和其他语言的类(class)有同等的地位,可以理解golang是基于struct 来实现OOP特性

3)golang 面向对象编程非常简洁,去掉了传统语言OOP语言的继承、方法重载、构造函数和 析构函数、隐藏的this指针等等

4)golang 仍然有面向对象编程的继承,封装和多态的特性,只是实现方式和其他OOP语言不一样,比如继承:golang 没有extends关键字,继承是通过匿名字段来实现的

5)golang 面向对象(OOP),OOP本身就是语言类型系统(type system的一部分),通过接口interface关联,耦合性低,也非常灵活,也就是说在golang中面向接口编程是非常重要的特性

结构体与结构体变量(示例与对象)的关系

image

对上图说明

1)将一类事务特征提取出来,形成一个新的数据类型,就是一个结构体

2)通过这个结构体可以创建多个变量

结构体和结构体变量的区别

1)结构体是自定义的数据类型,代表一类事物

2)结构体变量(实例)是具体方法,实际的,代码一个具体变量

结构体变量在内存中布局

image

结构体 字段属性

1)从概念或叫法上看:结构体字段=属性=field

2)字段是结构体的一个组成部分,一般是基本数据类型、数组、也可以是引用数据类型

3)在创建一个结构体变量后,如果没有给字段赋值,都对应于一个零值(默认值),规则同前面说的一样布尔类型是false;整型是0;字符串是空,数组类型的默认值和他的元素类型相关;例如sc[3]int则为[0,0,0];指针,slice ,map 的零值为nil,即没有分配空间

package main

import (
	"fmt"
	//"slices"
)

//如果结构体的字段是类型是:指针、slice、和map的零值都是nil,即没有分配空间
//如果需要使用这样字段需要先make
type Person struct{
	Name string
	Age  int 
	Scores [5]float64
	ptr *int 
	slice []int 
	map1 map[string]string 
}
func main(){

	var p1 Person
	fmt.Println(p1)
	if p1.ptr == nil{
		fmt.Println("为空")
	}
	p1.slice= make([]int,10)
	p1.slice[0]=67
	fmt.Println(p1)
	p1.map1 = make(map[string]string)
	p1.map1["你好"]="ty"
	fmt.Println(p1)
}
//执行结果
// PS D:\golang\goproject\src\src01\go_code\src> go run chapter09\deom02\main.go
// { 0 [0 0 0 0 0] <nil> [] map[]}
// 为空
// { 0 [0 0 0 0 0] <nil> [67 0 0 0 0 0 0 0 0 0] map[]}
// { 0 [0 0 0 0 0] <nil> [67 0 0 0 0 0 0 0 0 0] map[你好:ty]}

  

4)不同结构体变量的字段是独立的,互不影响,一个结构体变量字段更改不影响另一个,值类型

package main

import (
	"fmt"
	//"slices"
)

//如果结构体的字段是类型是:指针、slice、和map的零值都是nil,即没有分配空间
//如果需要使用这样字段需要先make
type Person struct{
	Name string
	Age  int 
	Scores [5]float64
	ptr *int 
	slice []int 
	map1 map[string]string 
}
type Person1 struct{
	Name string
	age int 
}
func main(){

	var p1 Person
	fmt.Println(p1)
	if p1.ptr == nil{
		fmt.Println("为空")
	}
	p1.slice= make([]int,10)
	p1.slice[0]=67
	fmt.Println(p1)
	p1.map1 = make(map[string]string)
	p1.map1["你好"]="ty"
	fmt.Println(p1)
	var c1 Person1
	c1.age= 10
	c1.Name= "红孩儿"
	fmt.Println(c1)
	c2 := c1
	fmt.Println(c2)
	c2.Name = "哪吒"
	fmt.Println(c2)

}
//执行结果
// PS D:\golang\goproject\src\src01\go_code\src> go run chapter09\deom02\main.go
// { 0 [0 0 0 0 0] <nil> [] map[]}
// 为空
// { 0 [0 0 0 0 0] <nil> [67 0 0 0 0 0 0 0 0 0] map[]}
// { 0 [0 0 0 0 0] <nil> [67 0 0 0 0 0 0 0 0 0] map[你好:ty]}
// {红孩儿 10}
// {红孩儿 10}
// {哪吒 10}

  内存分析

image

创建结构体变量和访问结构体字段

1)方式1 直接声明

var p1 Person

2) 方式2 {}

var p2 Person1 = Person1{}

3) 方式3

var p3 *Person1 = new(Person1)

4) 方式4

var p4 *Person1 = &Person1{}

说明 

(1)第3种和第4种方式返回的是 结构体指针

(2)结构体指针访问字段标准方式是(*p3结构体指针).字段 例如(*p3).Name="tom"

(3)   但go做了一个简化,也支持结构体.字段名字,比如p3.Name= "tom".更加符合程序员的习惯。go编译器底层会对p3.Name做转换(*p).Name

 示例

package main
import (
	"fmt"
)
type Person1 struct{
	Name string
	age int 
}
func main(){
	var p3 *Person1 = new(Person1)
	p3.Name= "cd"
	(*p3).age= 56
	fmt.Println(*p3)
}
//执行结果
// PS D:\golang\goproject\src\src01\go_code\src> go run chapter09\deom02\main.go
// {cd 56}

  内存分配机制

1图

image

 

package main

import (
	"fmt"
	//"slices"
)

//如果结构体的字段是类型是:指针、slice、和map的零值都是nil,即没有分配空间
//如果需要使用这样字段需要先make
type Person struct{
	Name string
	Age  int 
	Scores [5]float64
	ptr *int 
	slice []int 
	map1 map[string]string 
}
type Person1 struct{
	Name string
	age int 
}
func main(){
	var p3 *Person1 = new(Person1)
	p3.Name= "cd"
	(*p3).age= 56
	fmt.Println("p3赋值后",*p3)
	var p4 *Person1= p3
	p4.age= 98 //
	fmt.Println("p4 改值后p3",*p3)
	fmt.Println("p4的值",*p4)
}
//执行结果
// p3赋值后 {cd 56}
// p4 改值后p3 {cd 98}
// p4的值 {cd 98}

  内存分析图

image

结构体使用细节

1)结构体的所有字段在内存中是连续的

image

示例

package main
import (
	"fmt"
	//"slices"
)
type Person struct{
	x int
	y int 
}
type Rect struct{
	leftUP,rightDown Person
}
func main(){
	r1 :=Rect{Person{1,2},Person{3,5}}
	fmt.Printf("r1.leftUP.x 地址=%p r1.leftUP.y 地址=%p r1.rightDown.x 地址=%p r1.rightDown.y 地址=%p\n",
	&r1.leftUP.x,&r1.leftUP.y,&r1.rightDown.x,&r1.rightDown.y)
}
//执行结果
// PS D:\golang\goproject\src\src01\go_code\src> go run chapter09\deom03\main.go
// r1.leftUP.x 地址=0xc000016220 r1.leftUP.y 地址=0xc000016228 r1.rightDown.x 地址=0xc000016230 r1.rightDown.y 地址=0xc000016238

  分析图

image

 示例2

package main
import (
	"fmt"
	//"slices"
)
type Person struct{
	x int
	y int 
}
type Rect struct{
	leftUP,rightDown Person
}
type Rect1 struct{
	leftUP,rightDown *Person
}
func main(){
	r1 :=Rect{Person{1,2},Person{3,5}}
	fmt.Printf("r1.leftUP.x 地址=%p r1.leftUP.y 地址=%p r1.rightDown.x 地址=%p r1.rightDown.y 地址=%p\n",
	&r1.leftUP.x,&r1.leftUP.y,&r1.rightDown.x,&r1.rightDown.y)
	//r2 有两个*Point类型,这两个*Point类型的本身地址是连续的,但是他们指向的地址不一定是连续的,也有可能是连续的
	r2 :=Rect1{&Person{10,20},&Person{20,30}}
	fmt.Printf("r2.leftUP.地址=%p r2.rightDown 地址=%p\n",&r2.leftUP,&r2.rightDown)
	fmt.Printf("r2.leftUP.x 地址=%p r2.leftUP.y 地址=%p r2.rightDown.x 地址=%p r2.rightDown.y 地址=%p\n",
	&r2.leftUP.x,&r2.leftUP.y,&r2.rightDown.x,&r2.rightDown.y)

}
//执行结果
// PS D:\golang\goproject\src\src01\go_code\src> go run chapter09\deom03\main.go
// r1.leftUP.x 地址=0xc000016220 r1.leftUP.y 地址=0xc000016228 r1.rightDown.x 地址=0xc000016230 r1.rightDown.y 地址=0xc000016238
// r2.leftUP.地址=0xc000026070 r2.rightDown 地址=0xc000026078
// r2.leftUP.x 地址=0xc00000a0d0 r2.leftUP.y 地址=0xc00000a0d8 r2.rightDown.x 地址=0xc00000a0e0 r2.rightDown.y 地址=0xc00000a0e8

  2)结构体是用户单独定义的和其他类型进行转换时需要有完全相同的字段(名字、个数、类型)

package main
import (
	"fmt"
	//"slices"
)
type a struct{
	Num int
}
type b struct{
	Num int
}
func main(){
	var c1  a
	//fmt.Printf()
	d := b(c1)//强制转换
	fmt.Println(d)
}

  3) 结构体进行type重新定义(相当于取别名),golang认为是新的数据类型,但是可以互相之间转换

type C struct{
	Name string
	Age int 
}
type w C

  4) struct  每个字段上,可以写一个tag,该tag可以通过反射机制来获取,常见的使用场景就是序列号和反序列化

对代码说明:将struct变量进行json处理

问题:json 处理后的字段名也是首字母大写,这样如果我们是将json后的字符串返回给其他程序使用,比如jQuery,PHP等,那么可能他们不习惯这个命名方式

解决方案

1)将Monster 的字段首字母小写,这个行不通,会发现处理后,返回的是空字符串,因为json.Monster相当于在其他包里访问Monster结构体,首字母小写不能跨包访问

2)使用tag 标签解决

package main
import (
	"fmt"
	//"slices"
	"encoding/json"
)

type Monster struct{
	Name string `json:"name"`
	Age int `json:"age"`
	Skill string `json:"skill"`
}
func main(){
	monster:=Monster{"牛魔王",500,"水火棍"}
	//fmt.Println(monster)
//将monster序列化成json格式的字串
    jsonmonster,err := json.Marshal(monster)
	if err==nil{
		fmt.Println(string(jsonmonster))
	}else {
		fmt.Println(err)
	}

}
//执行结果
// PS D:\golang\goproject\src\src01\go_code\src> go run chapter09\deom04\main.go
// {"name":"牛魔王","age":500,"skill":"水火棍"}

  go 方法

基本介绍

在某些情况下,需要声明(定义方法)。比如person结构体,除了有一些字段外(年龄,姓名),Person结构体还有一些行为比如,说话,跑步,还可以做算术体,这是就要用方法才能完成

Golang 中的方法是作用在指定数据类型上(即:和指定的数据类型绑定),因此自定义类型,都可以有方法,而不仅仅是struct

定义方法

type A struct{
	Name int
}
func (a A)test(){// A 结构体有一个方法
	fmt.Println(a.Name)
}

 语法说明

1)func (a A)test(){}表示A结构体有一个方法,方法名字叫test

2)(a A)体现test方法和A类型结构体绑定的    

示例

package main
import (
	"fmt"
)
type Person struct{
	Num string
}
//给A类型绑定一份方法
func (p Person)test(){// A 结构体有一个方法
	fmt.Println("test()",p.Num)
}
func main(){
	w:=Person{"cx"}
	w.test()
}
//执行结果
// PS D:\golang\goproject\src\src01\go_code\src> go run chapter09\deom05\main.go
// test() cx

  总结

1)test 方法和Person结构体绑定

2)既然跟Person结构体绑定,那么只能通过Person结构体的实例来调用这个方法,不能直接调用,不能使用其他变量

3)func (p Person)test(){} p表示哪个Person 变量调用,这个p就是它的副本,这点跟函数传参类似

4)p 这个名字可以由程序员指定

方法快速入门

1)给person 结构体添加一个speak方法,输出XXX是好人

package main
import (
	"fmt"
)
type Person struct{
	Num string
}
func (p Person)speak(){
	fmt.Printf("%s你是好人",p.Num)
}

func main(){
	w:=Person{"cx"}
	// w.test()
	w.speak()
	// w.jisuan()
}
//执行结果
// PS D:\golang\goproject\src\src01\go_code\src> go run chapter09\deom05\main.go
// cx你是好人

2) 给person 结构体添加一个计算方法,可以输出1+.....+1000的结果

package main
import (
	"fmt"
)
type Person struct{
	Num string
}

func (p Person)jisuan(){
	var t int 
	for i:=1;i<=1000;i++{
		t+=i
	}
	fmt.Println("1+...+1000=",t)

}

func main(){
	w:=Person{"cx"}
	w.jisuan()
}
//执行结果
// PS D:\golang\goproject\src\src01\go_code\src> go run chapter09\deom05\main.go
// 1+...+1000= 500500

  3) 给Person 结构体添加一个jisuan1方法,该方法可以接受一个数n,计算从1+...n的结果

package main
import (
	"fmt"
)
type Person struct{
	Num string
}
func (P Person)jisuan1(n int){
	var t int 
	for i:=1;i<=n;i++{
		t +=i
	}
	fmt.Printf("1+...+%v=%v",n,t)
}
func main(){
	w:=Person{"cx"}
	w.jisuan1(60)
}
//执行结果
// PS D:\golang\goproject\src\src01\go_code\src> go run chapter09\deom05\main.go
// 1+...+60=1830

 4)给Person 结构体添加一个getSum方法。可以计算两个数的和,并返回结果

package main
import (
	"fmt"
)
type Person struct{
	Num string
}
func (P Person)getSum(a int,b int) int{
		return a+b
}
func main(){
	w:=Person{"cx"}
	fmt.Printf("45+89=%v\n",w.getSum(45,89))
}
//执行结果
// PS D:\golang\goproject\src\src01\go_code\src> go run chapter09\deom05\main.go
// 45+89=134

  方法调用和传参机制

说明:方法的调用和传参机制和函数基本一样,不一样的是方法调用时,会将调用方法的变量当做实参传递给方法,

流程图

image

说明:

1)在通过一个变量去调方法时,其调用机制和函数一样

2)不一样的地方时,变量调用方法时,该变量本身也会作为一个参数传递到方法中(如果变量是值类型,则进行值拷贝,如果是引用类型,则进行地址拷贝)注意方法不可以这样调用r.ares否则会输出地址值方法调用必须加()

package main
import (
	"fmt"
)
type Circle struct{
	radius float64
}
func (c Circle) ares() float64{
	//var t float64
	return c.radius*c.radius*3.14
}
func main(){

	r := Circle{85}
	fmt.Printf("半径是%v,的圆的面积%v\n",r.radius,r.ares())
//	fmt.Printf("半径是%v,的圆的面积%v\n",r.radius,r.ares) 不可以这样写,方法调用必须加(),否则就输出方法的地址值
}
//执行结果
// PS D:\golang\goproject\src\src01\go_code\src> go run chapter09\deom05\main.go
// 半径是85,的圆的面积22686.5

  内存分析

image

方法的声明定义

func (recevier type) methodName(参数列表) (返回值列表){

  方法体

  return 返回值

}

1)参数列表: 表示方法输入

2)recevier type : 表示这个方法和type 这个类型进行绑定,或者说该方法作用于type类型

3)recevier type: 可以是结构体,也可以是其他的自定义类型

4)recevier:就是type类型的一个变量(实例),比如,person结构体的一个变量

5)参数列表:表示方法输入

6)返回值列表:表示返回的值,可以多个

7)方法体:表示为了实现某一功能代码块

8)return 语句不是必须的

细节

1)结构体类型是值类型,在方法调用中,遵守值类型的传递机制,是值拷贝传递机制

2)如果希望在方法中,修改结构体变量,可以通过结构体指针的方式处理

package main
import (
	"fmt"
)

type Circle struct{
	radius float64
}
func (c Circle) ares() float64{
	return c.radius*c.radius*3.14
}
func (c *Circle) ares2() float64{
	//因为c是一个指针结构体实例,因此标准的访问其字段的方式是.(*c).radius
	return (*c).radius*(*c).radius*3.14
}
func main(){
	r := Circle{85}
	fmt.Printf("ares1()方法的输出半径是%v,的圆的面积%v\n",r.radius,r.ares())//	fmt.Printf("半径是%v,的圆的面积%v\n",r.radius,r.ares) 不可以这样写,方法调用必须加(),否则就输出方法的地址值
	t := Circle{76}
	fmt.Printf("ares2()方法的输出半径是%v,的圆的面积%v\n",t.radius, (&t).ares2())

}
//执行结果
// PS D:\golang\goproject\src\src01\go_code\src> go run chapter09\deom05\main.go
// 半径是85,的圆的面积22686.5
// 半径是76,的圆的面积18136.64

  go 语言作者做了优化

package main
import (
	"fmt"
)
type Person struct{
	Num string
}
type Circle struct{
	radius float64
}
func (c Circle) ares() float64{
	return c.radius*c.radius*3.14
}
func (c *Circle) ares2() float64{
	//因为c是一个指针结构体实例,因此标准的访问其字段的方式是.(*c).radius
	return (*c).radius*(*c).radius*3.14
    
}
func main(){
	r := Circle{85}
	fmt.Printf("ares1()方法的输出半径是%v,的圆的面积%v\n",r.radius,r.ares())//	fmt.Printf("半径是%v,的圆的面积%v\n",r.radius,r.ares) 不可以这样写,方法调用必须加(),否则就输出方法的地址值
	t := Circle{76}
	// fmt.Printf("ares2()方法的输出半径是%v,的圆的面积%v\n",t.radius, (&t).ares2())
	//编译器底层做了优化(&t).ares2()等价于t.ares2()
	//因为编译器底层添加了&
	fmt.Printf("ares2()方法的输出半径是%v,的圆的面积%v。。。。。\n",t.radius, t.ares2())

}
//执行结果
// PS D:\golang\goproject\src\src01\go_code\src> go run chapter09\deom05\main.go
// ares1()方法的输出半径是85,的圆的面积22686.5
// ares2()方法的输出半径是76,的圆的面积18136.64。。。。。  

  分析图

image

3)Golang 中的方法作用在指定的数据类型上(即:和指定的数据类型做绑定),因此自定义数据类型都可以有方法,而不仅仅是struct ,比如int、float32等都可以有方法

package main
import (
	"fmt"
)
//
type integer int 
func (i integer) print(){
	fmt.Println("i=",i)
}

//给A类型绑定一份方法

func main(){
	var i integer =120
	i.print()
}
//执行结果
// PS D:\golang\goproject\src\src01\go_code\src> go run chapter09\deom05\main.go
// i= 120。

4)方法的访问范围控制规则,和函数一样。方法名首字母小写,只能在本包访问,方法首字母大写,可以在本包和其他包访问

5)如果一个类型实现了String()这个方法,那么fmt.Println默认会调用这个变量的String()进行输出

package main
import (
	"fmt"
)
//
type integer int 
func (i integer) print(){
	fmt.Println("i=",i)
}

//给A类型绑定一份方法

type Monster struct{
	Name string
	age int 
}
func (stu *Monster) String() string{
	str := fmt.Sprintf("Name=[%v],Age=[%v]",(*stu).Name,(*stu).age)
	return str
}
func main(){
	var i integer =120
	i.print()
	var t Monster = Monster{"cx",67}
	//如果实现了*Monster类型的String 方法,就会自动调用
	fmt.Println(&t)//
}
//执行结果
// PS D:\golang\goproject\src\src01\go_code\src> go run chapter09\deom05\main.go
// i= 120
// Name=[cx],Age=[67]

  示例

package main
import (
	"fmt"
)
type Monster int 
func (m Monster)test1(){
	for i:=0; i<10; i++{
		for x:=0;x<8;x++{
			fmt.Printf("* ")
		}
		fmt.Println()
	}
}
func (d Monster)test2(m int,n int){
	for i:=0;i<m;i++{
		for x:=0;x<n;x++{
			fmt.Printf("* ")
		}
		fmt.Println()
	}
}
func (c Monster)test3(m int,n int) int{
	return m*n
}
func main(){
	var e Monster// 实例化类型
	fmt.Println("调用test1方法打印")
	e.test1()
	fmt.Println("调用test2方法打印")
	e.test2(10,7)
	fmt.Println("调用test3方法计算")
	fmt.Printf("长5宽4的面积是=%v\n",e.test3(5,4))

}
// 执行结果
// PS D:\golang\goproject\src\src01\go_code\src> go run chapter09\deom07\main.go
// 调用test1方法打印
// * * * * * * * * 
// * * * * * * * *
// * * * * * * * *
// * * * * * * * *
// * * * * * * * *
// * * * * * * * *
// * * * * * * * *
// * * * * * * * *
// * * * * * * * *
// * * * * * * * *
// 调用test2方法打印
// * * * * * * *
// * * * * * * *
// * * * * * * *
// * * * * * * *
// * * * * * * *
// * * * * * * *
// * * * * * * *
// * * * * * * *
// * * * * * * *
// * * * * * * *
// 调用test3方法计算
// 长5宽4的面积是=20

  打印二维数组反转

package main
import (
	"fmt"
)
type Monster int 
func (m Monster)test1(){
	for i:=0; i<10; i++{
		for x:=0;x<8;x++{
			fmt.Printf("* ")
		}
		fmt.Println()
	}
}
func (d Monster)test2(m int,n int){
	for i:=0;i<m;i++{
		for x:=0;x<n;x++{
			fmt.Printf("* ")
		}
		fmt.Println()
	}
}
func (c Monster)test3(m int,n int) int{
	return m*n
}
func (t Monster)test4(s [3][3]int) {
	for i:=0;i<3;i++{
		for x:=i;x<3;x++{
			s[i][x],s[x][i]=s[x][i],s[i][x]
			//fmt.Println(s[i][x],s[x][i])
		}
	}
	fmt.Println(s)
	
}
func main(){
	var e Monster
	fmt.Printf("长5宽4的面积是=%v\n",e.test3(5,4))
	t := [3][3]int{{1,2,3},{4,5,6},{7,8,9}}
	fmt.Println(t)
	e.test4(t)

}
// 执行结果
// PS D:\golang\goproject\src\src01\go_code\src> go run chapter09\deom07\main.go
// 长5宽4的面积是=20
// [[1 2 3] [4 5 6] [7 8 9]]
// [[1 4 7] [2 5 8] [3 6 9]]

  方法函数区别

核心区别

函数:独立存在  
方法:绑定在某个类型上的函数

语法上的区别

函数
func add(a, b int) int {
	return a + b
}
特点:
没有“归属”
是全局函数
方法(Method) type Circle struct { radius float64 } func (c Circle) Area() float64 { return c.radius * c.radius * 3.14 } 方法 方法 = 函数 + 接收者(绑定类型) 等价理解: func Area(c Circle) float64 只是 Go 提供了“绑定写法”

  调用方法

函数

add(1, 2)

方法

c := Circle{10}
c.Area()

更像“对象调用能力”

设计意义(非常重要)

Go 设计方法是为了:
让“类型拥有行为”
如
type User struct{}
加方法:
func (u User) Login()
func (u User) Logout()
就变成:
User 这个类型有“行为能力”

  方法的两种类型

1️⃣ 值接收者
func (c Circle) Area() float64
特点:
会复制 struct
2️⃣ 指针接收者
func (c *Circle) Grow() {
	c.radius++
}
特点:
可以修改原对象
避免拷贝

  一个关键区别

方法可以绑定类
type MyInt int

func (m MyInt) Print() {
	fmt.Println(m)
}
👉 普通函数做不到这一点

  什么时候用函数 vs 方法

用函数
不依赖某个类型
工具类逻辑
例如:
math.Sqrt()
用方法
操作某个结构体数据
属于某个类型的行为
例如:
user.Login()
order.CalculatePrice()

  标准回答

在 Go 中,函数是独立定义的代码块,而方法是绑定在某个类型上的函数,通过接收者与类型关联。本质上方法就是带有接收者参数的函数,使类型具备行为能力。

  关键

在 Go 中:
没有“类(class)”
但通过:
struct + method
实现了类似:
面向对象

  深层点

方法的存在是为了支持:
接口(interface)
例如:
type Animal interface {
	Speak()
}
只要类型有方法:
func (d Dog) Speak()
就自动实现接口。

  总结

对比函数方法
是否绑定类型
是否有接收者
是否属于某个类型
调用方式 add() obj.Method()

 

可以把 Go 理解成:

函数 → 行为  
struct → 数据  
method → 数据 + 行为(核心)

  补充调用方法

1)对于普通函数,接受者为值类型时,不能将指针类型的数据直接传递,反之亦然

2)对于方法(如struct的方法),接受者为值类型时,也可以直接用指针类型的变量调用,反之亦然

package main
import (
	"fmt"
)

func (p Person) test5 (){
	p.Name= "jack"
	fmt.Println("test5()=" ,p.Name)
}

func main(){
	// var e Monster
	// fmt.Printf("长5宽4的面积是=%v\n",e.test3(5,4))
	// t := [3][3]int{{1,2,3},{4,5,6},{7,8,9}}
	// fmt.Println(t)
	// e.test4(t)
	r := Person{"tom"}
	r.test5()
	fmt.Println("man()name=",r.Name)
	(&r).test5()//从形式上是地址,本质是值拷贝

}

  结论

1)不管调用形式如何,真正决定是值拷贝还是地址拷贝,看这个方法绑定是和那种类型绑定

2)如果是地址值类型,例如(p Person),则是值拷贝,如何和指针类型,例如(p *Person)则是地址拷贝

面向对象编程应用实例

步骤

1)声明(定义)结构体,确定结构体

2)编写结构体的字段

3)编写结构体的方法

示例

package main
import (
	"fmt"
	//"strconv"
)
type Student struct{
	Name string
	gender string
	age int 
	id int
	score float64
}
func (s Student)say() string{
	//var t string
	// strconv.Itoa(s.age) int 转字符串 strconv.FormatFloat(s.score, 'f', -1, 64) flost54 转字符串
//	t:=s.Name+" "+s.gender+" "+ strconv.Itoa(s.age)+" "+  strconv.Itoa(s.id) +" "+strconv.FormatFloat(s.score, 'f', -1, 64)
	//方式2
	t := fmt.Sprintf("Student 的信息 name=[%v],gender=[%v],age=[%v],id=[%v],score=[%v]",
	s.Name,s.gender,s.age,s.id,s.score)
	return t
}

func main(){

	t:=Student{"西钊","男",25,1,98.5}
    fmt.Println(t.say())
}
// 执行结果
// PS D:\golang\goproject\src\src01\go_code\src> go run chapter09\deom07\main.go
// Student 的信息 name=[西钊],gender=[男],age=[25],id=[1],score=[98.5]

  测试2

package main

import (
	"fmt"
	//"internal/syscall/windows"
	//"strconv"
)
type Dog struct{
	Name string
	Age int 
	Weighi string
}
func (t Dog) say()  {
//	d := fmt.Sprintf("Dog的信息 Name=[%v],Age=[%v],Weighi=[%v]",t.Name,t.Age,t.Weighi)
	fmt.Printf("Dog的信息 Name=[%v],Age=[%v],Weighi=[%v]\n",t.Name,t.Age,t.Weighi)
}
func main(){
	t:=Dog{"小黑",5,"骨头"}
	t.say()

}
// 执行结果
// PS D:\golang\goproject\src\src01\go_code\src> go run chapter09\deom10\main.go
// Dog的信息 Name=[小黑],Age=[5],Weighi=[骨头]

  测试3

package main

import (
	"fmt"
)

type Box struct{
	c float64
	k float64
	g float64
}
func (b Box) test(){
	fmt.Println("输入长")
	fmt.Scanln(&b.c)
	fmt.Println("输入宽")
	fmt.Scanln(&b.k)
	fmt.Println("输入高")
	fmt.Scanln(&b.g)
	fmt.Println("体积是",b.c*b.g*b.k)
}
func main(){
	// t:=Dog{"小黑",5,"骨头"}
	// t.say()
	var F Box
	F.test()

}
// 执行结果
// PS D:\golang\goproject\src\src01\go_code\src> go run chapter09\deom10\main.go
// 输入长
// 98
// 输入宽
// 56
// 输入高
// 45
// 体积是 246960

  示例

package main

import (
	"fmt"
	//"internal/syscall/windows"
	//"strconv"
)

type Visitor struct{
	Name string
	age int
}
func(v *Visitor)test(){
	for {
		fmt.Println("请输入名字")
		fmt.Scanln(&v.Name)
		if v.Name== "n"{
			break
		}
		fmt.Println("请输入年龄")
		fmt.Scanln(&v.age)
		if v.age > 18{
			fmt.Printf("%v的年龄是%v,门票是20元\n",v.Name,v.age)
		}else{
			fmt.Printf("%v的年龄是%v,门票免费\n",v.Name,v.age)
		}
		

	}
} 
func main(){

	var T Visitor
	T.test()

}
// 执行结果
// PS D:\golang\goproject\src\src01\go_code\src> go run chapter09\deom10\main.go
// 请输入名字
// 你好
// 请输入年龄
// 45
// 你好的年龄是45,门票是20元
// 请输入名字
// 小光
// 请输入年龄
// 6
// 小光的年龄是6,门票免费
// 请输入名字
// n

  创建结果体变量时指定字段的值,可以指定字段的值

方式1 在main

package main

import (
	"fmt"
	//"internal/syscall/windows"
	//"strconv"
)
type Stu struct{
	Name string
	Age int
}
func main(){
	//在创建结构体变量时,就直接指定字段的值
	var stu = Stu{"晨",8}
	te := Stu{"小明",6}
	//方式2在创建结构体变量时,把字段和字段值写在一起,这种方式更灵活,不依赖字段的定义顺序
	te1 := Stu{Name: "小明",Age: 6}

}

  方式2

package main

import (
	"fmt"
	//"internal/syscall/windows"
	//"strconv"
)
type Stu struct{
	Name string
	Age int
}
func main(){
	//在创建结构体变量时,就直接指定字段的值
	var stu = Stu{"晨",8}
	te := Stu{"小明",6}
	//在创建结构体变量时,把字段和字段值写在一起,这种方式更灵活,不依赖字段的定义顺序
	te1 := Stu{Name: "小明",Age: 6}
	fmt.Println(stu,te,te1)
	//方式2 返回指针 stu2---存储地址----》结构体实例化空间的内存地址值
	var str2 = &Stu{"小王",29}
	str6 :=&Stu{Age: 9,Name: "杨逍"}
	fmt.Println(*str2,*str6) 


}
// 执行结果
// PS D:\golang\goproject\src\src01\go_code\src> go run chapter09\deom11\main.go
// {晨 8} {小明 6} {小明 6}
// {小王 29} {杨逍 9}

  工厂模式

Golang 的结构体中没有构造函数,通常可以使用工厂模式来解决这个问题

需求

一个结构体的声明是这样的

package model

type Student struct {

Name string

}

因为这里的Student 的首字母S是大写的,如果想在其他包创建Student的实例(比如main包),引入model包后。就可以直接创建Student结构体的变量(实例)。如果首字母小写的,例如type student struct{...}就不行,这时候工厂模式解决

工厂模式实现跨包创建结构体实例(变量)的实例

1)model 包的结构体变量首字母大写,引入后,直接使用,没有问题

image

运行结果

PS D:\golang\goproject\src\src01\go_code\src> go run chapter09\deom12\main\main.go
{晨曦 90}

2) 如果model包的结构体变量的首字母小写,引入后,不可以直接使用,解决方案工厂模式

package main
import (
	"fmt"
	"src01/go_code/src/chapter09/deom12/model"//引包
)
func main(){
	//实例化student结构体
	// var stu = model.Student{Name: "晨曦",Score: 90}
	var stu = model.NewStudent("tom~",88.8)
	fmt.Println(stu.Name,stu.Score)

}
// 执行结果
// PS D:\golang\goproject\src\src01\go_code\src> go run chapter09\deom12\main\main.go
// tom~ 88.8


----------------
package model
//定义结构体
type student struct{
	Name string
	Score float64
}
//因为student结构体首字母是小写的,因此只能在model包使用
//通过工厂模式解决,接收两个参数返回一个结构体地址值
func NewStudent(n string,s float64) *student{
	return &student{
		Name: n,
		Score: s,
	}
}

  补充

image

如果student 内的Score 变量是小写则不能在main包里导出会报stu.score undefined (type *model.student has no field or method score)如果要获取他的值可以通过函数调用获取

package main
import (
	"fmt"
	"src01/go_code/src/chapter09/deom12/model"//引包
)
func main(){
	//实例化student结构体
	// var stu = model.Student{Name: "晨曦",Score: 90}
	var stu = model.NewStudent("tom~",88.8)
	//fmt.Println(stu.Name,stu.score) 因为结构体变量score首字母小写不可以直接导出
	//所以在model包里定义一个函数将他导出
	fmt.Println(stu.Name,model.Newget(stu))

}
// 执行结果
// PS D:\golang\goproject\src\src01\go_code\src> go run chapter09\deom12\main\main.go
// tom~ 88.8
---------------------------------------------------
package model
//定义结构体
type student struct{
	Name string
	score float64
}
//因为student结构体首字母是小写的,因此只能在model包使用
//通过工厂模式解决,接收两个参数返回一个结构体地址值
func NewStudent(n string,s float64) *student{
	return &student{
		Name: n,
		score: s,
	}
}
func Newget(n *student) float64{
	return n.score
}

  补充图

image

   方式进阶

 

package main
import (
	"fmt"
	"src01/go_code/src/chapter09/deom12/model"//引包
)
func main(){
	//实例化student结构体
	// var stu = model.Student{Name: "晨曦",Score: 90}
	var stu = model.NewStudent("tom~",88.8)
	//fmt.Println(stu.Name,stu.score) 因为结构体变量score首字母小写不可以直接导出
	//所以在model包里定义一个函数将他导出
	fmt.Println(stu.Name,model.Newget(stu))
	//使用方法调
	fmt.Println(stu.Test())

}
// 执行结果
// PS D:\golang\goproject\src\src01\go_code\src> go run chapter09\deom12\main\main.go
// tom~ 88.8
// 88.8
-------------------------------------------------------------------
package model
//定义结构体
type student struct{
	Name string
	score float64
}
//因为student结构体首字母是小写的,因此只能在model包使用
//通过工厂模式解决,接收两个参数返回一个结构体地址值
func NewStudent(n string,s float64) *student{
	return &student{
		Name: n,
		score: s,
	}
}
func Newget(n *student) float64{ 
	return n.score
}
func (t *student)Test() float64{
	return t.score
}

  补充图

image

 

 

 

 

 

 

 

 

 

 

 

 

posted @ 2026-03-17 20:53  烟雨楼台,行云流水  阅读(1)  评论(0)    收藏  举报