面向对象
面向对象
结构体
package main
import "fmt"
//定义一个cat的结构体,将cat的属性信息放到cat结构体中进行管理
type Cat struct {
Name string
Age int
Color string
}
func main() {
//创建一个cat变量
var cat1 Cat
cat1.Name = "小白"
cat1.Age = 10
cat1.Color = "白色"
fmt.Println(cat1)
}
结构体的声明和使用
赋值的四种方式
p2 := Person{"mary",20}
fmt.Println(p2)
//方式3
var p3 *Person= new(Person)
//因为p3是一个指针,因此标准的字段赋值方式
(*p3).Name = "jack"
(*p3).Age = 10
fmt.Println(*p3)
//方式4
var p4 *Person = &Person{"marry",10}
fmt.Println(*p4)
如果结构体的字段类型是指针,slice和map的值都是nil,如果使用这样的字段,需要先make才能使用
package main
import "fmt"
type Person struct {
Name string
Age int
Score [5]float64
ptr *int
slice []int
map1 map[string]string
}
func main() {
//如果结构体的字段类型是指针,slice和map的值都是nil,如果使用这样的字段,需要先make才能使用
//定义一个结构体变量
var p1 Person
fmt.Println(p1)
// 不同结构体变量的字段是独立,互不影响的,一个结构体变量字段的更改
//不会影响另外一个,结构体是值类型
p1.map1 = make(map[string]string)
p1.map1["key1"] = "tom"
fmt.Println(p1)
}
package main
import "fmt"
type Person struct {
Name string
Age int
}
func main() {
var p1 Person
p1.Name = "小明"
p1.Age = 10
var p2 *Person = &p1
fmt.Println((*p2).Age)
fmt.Println(p2.Age)
p2.Name = "tom"
fmt.Printf("p2.Name=%v p1.Name=%v \n", p2.Name, p1.Name)
fmt.Printf("p2.Name=%v p1.Name=%v \n", (*p2).Name, p1.Name)
fmt.Printf("p1的地址=%p \n", &p1)
fmt.Printf("p2的地址=%p p2的值=%p\n", &p2,p2)
}
//10
10
p2.Name=tom p1.Name=tom
p2.Name=tom p1.Name=tom
p1的地址=0xc000004480
p2的地址=0xc000006028 p2的值=0xc000004480
p2里面的值指向p1的内存地址,共享一个地址
使用结构体注意事项
-
结构体内的所有字段在内存中是连续的
package main import "fmt" type Point struct { x int y int } type Rect struct { leftUp, rightDown Point } func main() { r1 := Rect{Point{1, 2}, Point{3, 4}} fmt.Printf("r1.leftUp.x 地址= %p,r1.leftUp.y 地址=%p,r1.rightDown.x 地址=%p,r1.rightDown.y 地址=%p", &r1.leftUp.x, &r1.leftUp.y, &r1.rightDown.x, &r1.rightDown.y) } //r1.leftUp.x 地址= 0xc00000e420,r1.leftUp.y 地址=0xc00000e428,r1.rightDown.x 地址=0xc00000e430,r1.rightDown.y 地址=0xc00000e438
-
结构体是用户单独定义的类型,和其他类型进行转换是需要有完全相同的字段
package main import "fmt" type A struct { Num int } type B struct { Num int } func main() { var a A var b B a = A(b) fmt.Println(a,b) }
-
结构体进行type重新定义,go认为是新的数据类型,但是可以相互强制转化
-
struct的每个字段上,可以协商一个tag,该tag可以通过反射机制获取,常见的使用场景就是序列化和反序列化
package main import "fmt" import "encoding/json" type Monsters struct { Name string `json:"name"` # 这个就是tag Age int `json:"age"` Skill string `json:"skill"` } func main() { //创建一个Monster变量 monster :=Monsters{"牛魔王",200,"火云拳"} //将monster变量序列化为 字符串的json jsonStr,err := json.Marshal(monster) if err != nil{ fmt.Println(err) } fmt.Println(string(jsonStr)) }
方法
go中的方法是作用在指定的数据类型上的,因此自定义类型都可以有方法,不仅仅是struct
type A struct {
Num int
}
func (a A) test(){
fmt.Println(a.Num)
}
-
func(a A)test(){}
表示A结构体有一个方法,方法名为test -
(a A)体现test方法是和A类型绑定的
package main
import "fmt"
type AA struct {
Num int
}
func (a AA) test(){
fmt.Println(a.Num)
}
func main() {
a :=AA{1}
a.test() //调用方法
}
- test方法和AA类型绑定
- test方法只能通过AA类型的变量来调用,而不能直接调用,也不能使用其他类型变量来调用
func(a A)test(){}
p表示哪个A变量调用,p就是他的副本,和传参类似
案例
给Person结构体添加是计算方法,计算1+...+1000结果
package main
import "fmt"
type Person struct {
Name string
}
func (person Person) sum() {
res := 0
for i := 1; i <= 1000; i++ {
res += i
}
fmt.Println(person.Name, "计算结果是", res)
}
func (person Person) speak() {
fmt.Println(person.Name, "是一个好人")
}
func main() {
person := Person{"tom"}
person.speak()
person.sum()
}
接收参数
func (person Person) sum(n int)
有返回值
func (person Person)getSum(n1 int, n2 int) int{
return n1+n2
}
func main() {
res :=person.getSum(10,20)
fmt.Println(res)
}
计算圆的面积
package main
import (
"fmt"
)
type Circle struct {
radius float64
}
func (c Circle) area() float64 {
return 3.14 * c.radius * c.radius
}
func main() {
c := Circle{4.0}
res := c.area()
fmt.Println(res)
}
-
结构体类型是值方法,在方法调用中,遵守值类型的传递机制,是值拷贝传递方式
-
修改结构体变量的值,可以通过指针的方式来处理, 这样就不会是值拷贝类型了
// //func (c Circle) area() float64 { // return 3.14 * c.radius * c.radius //} ////为了提高效率,我们方法和结构体的指针类型绑定 // //func (c *Circle) area2() float64 { // //因为c是指针,因此我们标准的访问其他字段方式是(*c).radius // //return 3.14 * (*c).radius * (*c).radius // return 3.14 * c.radius * c.radius //} //func main() { // c := Circle{4.0} // res := c.area() // fmt.Println(res) // //res2 := (&c).area2() // res2 := c.area2() //解释器在底层做了一个优化 // fmt.Println(res2) //}
-
go中的方法作用在指定的数据类型上,因此自定义类型,都可以有方法,不仅仅是struct,如int都可以有方法
-
方法的访问范围控制的规则,和函数一样.方法名首字母小写,只能在本包中去访问,首字母大写,可以在其他和本包中使用
-
如果一个变量实现string这个方法,那么fmt.println默认会调用这个变量的string进行输出
练习
-
编写结构体,编写一个方法,方法不需要参数,在方法中打印一个10*8的长方形
package main import "fmt" type MethodUtils struct { } func (mu MethodUtils) Print() { for i := 1; i <= 10; i++ { for j := 1; j <= 8;j++{ fmt.Print("*") } fmt.Println() } func main() { var mu MethodUtils mu.Print() }
-
编写一个方法,提供m和n两个参数,方法中打印一个m*n的矩形
func (mu MethodUtils) Print2(m int,n int) { for i := 1; i <= m; i++ { for j := 1; j <= n;j++{ fmt.Print("*") } fmt.Println() } }
方法和函数的区别
- 对于普通函数,接收者为值类型,不能将指针类型的数据直接传递,反之亦然
- 不管调用的形式如何,真正决定是只拷贝还是地址拷贝,看这个方法和哪个类型绑定
- 如果是和值类型,比如
(p Person)
,就是值拷贝,如果是(p *Person)
就是地址拷贝
package main
import "fmt"
type Student struct {
Name string
Gender string
Age int
Id int
Score float64
}
func (student *Student) say() string {
infoStr := fmt.Sprintf("学生的信息 name = [%v] gender = [%v] age = [%v] id = [%v] "+
"score = [%v]", student.Name, student.Gender, student.Age, student.Id, student.Score)
return infoStr
}
func main() {
stu1 := Student{"zc","男",11,1111,99.09}
fmt.Println(stu1.say())
}
工厂模式
go的结构体没有构造函数,通常可以使用工厂模式来解决这个问题
package main
type Student struct {
Name string...
}
func main() {
}
因为这里的Student 的字母S是大写的, 如果我们想在其他包创建Student 的实例引入model包后,就可以直接创建Student结构体的变量,如果首字母是小写,就不行了,这个可以用工厂模式来解决
#factory.go
package main
import (
"demo/model"
"fmt"
)
func main() {
stu :=model.NewStudent("tom",10.01)
fmt.Println(*stu)
fmt.Println(stu.GetScore())
}
#model/model.go
package model
type student struct {
Name string
Score float64
}
func NewStudent(n string,s float64) *student{
return &student{
Name: n,
Score: s,
}
}
//如果字段小写,可以写一个方法
func (s *student) GetScore() float64{
return s.score
}