Golang_6_方法

六、方法

方法是与 对象实例绑定 的特殊函数,是一个面向对象的概念

方法的函数定义语法的区别:方法有前置实力接收参数

构造函数

建议返回结构体指针类型(值拷贝开销较大)

func newPerson(name, city string, age int8) *Person {
   return &Person{
      name: name,
      city: city,
      age: age,
   }
}

调用构造函数

p := newPerson("Aris", "HB", 20)
fmt.Printf("%#v\n", p)
// &main.Person{name:"Aris", city:"HB", age:20}

方法 和 接受者

Go语言中的方法(Method)是一种作用于特定类型变量的函数。这种特定类型变量叫做接收者(Receiver)

接收者的概念就类似于其他语言中的this或者 self

func (接收者变量 接收者类型) 方法名(参数列表) (返回参数) {
    函数体
}
  • 接收者变量:接收者中的参数变量名在命名时,官方建议使用接收者类型名的第一个小写字母,而不是selfthis之类的命名。
    • 例如,Person类型的接收者变量应该命名为 pConnector类型的接收者变量应该命名为c等。
  • 接收者类型:接收者类型和参数类似,可以是指针类型和非指针类型。
  • 方法名、参数列表、返回参数:具体格式与函数定义相同。

例子:

func newPerson(name string, age int8) *Person {
   return &Person{
      name: name,
      age: age,
   }
}

func (p Person) Dream() {
	fmt.Printf("%s的梦想是学好Go!\n", p.name)
}

func main() {
   p := newPerson("Aris", 20)
   p.Dream()
   // Aris的梦想是学好Go!
}
指针类型的接受者

用于修改实际的成员变量

func (p *Person) SetAge(newAge int8) {
   p.age = newAge
}

调用:

func main() {
   p := newPerson("Aris", 20)
   fmt.Println(p.age) // 20
   p.SetAge(30)
   fmt.Println(p.age) // 30
}
值类型的接受者

当方法作用于值类型接收者时,Go语言会在代码运行时将接收者的值复制一份。

在值类型接收者的方法中可以获取接收者的成员值,但修改操作只是针对副本,无法修改接收者变量本身

func (p Person) SetAge2(newAge int8) {
	p.age = newAge
}
指针类型接受者使用场景
  • 需要修改接受者的值
  • 接受者是 拷贝代价比较大 的对象
  • 一致性,如果有某个方法使用了指针接收者,那么其他的方法也应该使用指针接收者。

任意类型添加方法

不仅仅是结构体,任何类型都可以拥有方法

例子,int类型添加方法

type MyInt int

func (m MyInt) SayHello() {
   fmt.Println("i am a \"int\"")
}

func main() {
   var m1 MyInt
   m1 = 10
   m1.SayHello() // i am a "int"
   	fmt.Printf("%#v  %T\n", m1, m1) 
    //10 main.MyInt
}

注意事项: 非本地类型不能定义方法,也就是说我们不能给别的包的类型定义方法。

结构体匿名字段

定义结构体时,不写字段名只写类型:

type Person2 struct {
   string
   int
}

使用:

func main() {
   p := Person2{
      "Aris",
      18,
   }
   fmt.Printf("%#v\n", p)
   //main.Person2{string:"Aris", int:18}
   fmt.Println(p.string, p.int)
   // Aris 18
}

注意事项:

  • 一个结构体中同种类型的匿名字段只能有一个,因为要保证字段名称的唯一性

嵌套结构体

type Address struct {
   Province   string
   City      string
}

type User struct {
   Name   string
   Gender string
   Address    Address
}

func main() {
   user := User{
      Name:  "Aris",
      Gender:    "男",
      Address: Address{
         Province:  "HB",
         City:     "XG",
      },
   }
   fmt.Printf("user=%#v\n", user)
   //user=main.User{Name:"Aris", Gender:"男", Address:main.Address{Province:"湖北", City:"孝感"}}
    // user=main.User{
	// 			Name:"Aris", 
	// 			Gender:"男", 
    // 			Address: main.Address{
	// 					Province:"HB", 
	// 					City:"XG"
	// 				}
	// 	}
}

“继承”

type Animal struct {
   name string
}

func (a *Animal) move() {
   fmt.Printf("%s会动!\n", a.name)
}

type Dog struct {
   Feet   int8
   *Animal // 嵌套匿名结构体实现继承
}

func (d *Dog) bark() {
   fmt.Printf("%s会汪汪!\n", d.name)
}

func main() {
   d := &Dog{
      Feet: 4,
      Animal: &Animal{ // 嵌套的是结构体指针
         name: "HuNiu",
      },
   }

   d.move() // HuNiu会动!
   d.bark() // HuNiu会汪汪!
}

结构体的字段可见性

结构体中:

  • 字段大写开头表示可公开访问
  • 小写表示私有(仅在定义当前结构体的包中可访问)。

结构体和JSON序列化(结构体和JSON互相转换)

package main

import (
   "encoding/json"
   "fmt"
)

//Student 学生
type Student struct {
   ID     int
   Gender string
   Name   string
}

//Class 班级
type Class struct {
   Title    string
   Students []*Student
}

func main()  {
   c := &Class{
      Title: "101",
      Students: make([]*Student, 0, 200),
      //Students: []*Student{},
   }

   for i := 0; i < 10; i++ {
      stu := &Student{
         Name: fmt.Sprintf("stu%02d", i),
         Gender: "男",
         ID: i,
      }
      // c班级 中追加成员
      c.Students = append(c.Students, stu)
   }

   // JSON序列化:结构体-->Json格式的字符串
   data, err := json.Marshal(c)
   if err != nil {
      fmt.Println("JSON marshal failed")
      return
   }
   fmt.Printf("JSON: %s\n", data)

   // JSON反序列化:JSON格式的字符串-->结构体
   str := `{"Title":"101","Students":[{"ID":0,"Gender":"男","Name":"stu00"},{"ID":1,"Gender":"男","Name":"stu01"},{"ID":2,"Gender":"男","Name":"stu02"},{"ID":3,"Gender":"男","Name":"stu03"},{"ID":4,"Gender":"男","Name":"stu04"},{"ID":5,"Gender":"男","Name":"stu05"},{"ID":6,"Gender":"男","Name":"stu06"},{"ID":7,"Gender":"男","Name":"stu07"},{"ID":8,"Gender":"男","Name":"stu08"},{"ID":9,"Gender":"男","Name":"stu09"}]}`
   c1 := &Class{}
   // 字符串str 反序列到结构体 c1
   err = json.Unmarshal([]byte(str), c1)
   if err != nil {
      fmt.Println("JSON unmarshal failed!")
      return
   }
   fmt.Printf("%#v\n", c1)
   // &main.Class{Title:"101", Students:[]*main.Student{(*main.Student)(0xc42007c6f0), (*main.Student)(0xc42007c720), (*main.Student)(0xc42007c750), (*main.Student)(0xc42007c780), (*main.Student)(0xc42007c7e0), (*main.Student)(0xc42007c810), (*main.Student)(0xc42007c840), (*main.Student)(0xc42007c870), (*main.Student)(0xc42007c8a0), (*main.Student)(0xc42007c8d0)}}

结构体标签

自定义序列化时的key

  • Tag是结构体的元信息,可以在运行的时候通过反射的机制读取出来。

  • Tag在结构体字段的后方定义,由一对反引号包裹起来

  • 结构体标签由一个或多个键值对组成

    • 键与值使用冒号分隔,值用双引号括起来。

    • 键值对之间使用一个空格分隔

    • 需要严格按照格式来

      结构体标签的解析代码的容错能力很差,一旦格式写错,编译和运行时都不会提示任何错误,通过反射也无法正确取值。

      例如不要在key和value之间添加空格。

package main

import (
	"encoding/json"
	"fmt"
)

type Student struct {
	// ID 通过指定 tag 实现 序列化该字段时使用的key
	ID		int		`json:"id_NewName"`
	Gender	string // JSON序列化默认用字段名作Key
	name	string // 私有不能被JSON包访问
}

func main() {
	s1 := Student{
		ID:		1,
		Gender:	"男",
		name:	"Aris",
	}

	data, err := json.Marshal(s1)
	if err != nil {
		fmt.Println("json marshal failed!")
		return
	}
	fmt.Printf("json str:%s\n", data) // json str:{"id":1,"Gender":"男"}
}

练习题

使用“面向对象”的思维方式编写一个学生信息管理系统。

  1. 学生有id、姓名、年龄、分数等信息
  2. 程序提供展示学生列表、添加学生、编辑学生信息、删除学生等功能
package main

import "fmt"

type Student struct {
	Id 		int
	Name	string
	Age		int
	Score	int
}

type Class struct {
	Title	string
	Student	[]*Student
}

// 展示学生列表
func (c *Class) ShowStudents() {
	fmt.Println(c)
}

// 添加学生
func (c *Class) AddStudent(s *Student) {
	c.Student = append(c.Student, s)
	fmt.Printf("%d-%s 添加成功!\n", s.Id, s.Name)
}

// 编辑学生信息
func (s *Student) EditStudent(sTemp Student) {

}

func main() {

}

分享: