Go (三) 面向对象1

一、面向对象

1.1、初识golang面向对象

golang支持面向对象编程(OOP)。

golang没有类(class),go语言的结构体(struct)和其他编程语言的类(class)有同等的地位。简单理解golang是根据struct来实现面向对象编程(OOP)的。

golang有面向对象的继承封装多态的特性

golang面向对象很优雅,通过(interface)关联,耦合性低,很灵活。也就是说golang中面向接口编程是非常重要的特性。

二、面向对象-结构体(struct)结构体变量(值类型)

2.1、结构体(struct)结构体变量的快速入门(更多方式:详见2.4)

快速理解:可以把"结构体"理解为"","结构体变量"理解为"对象"

// 定义结构体变量,也叫做"对象"
type Cat struct {
	Name string
	Age int
	Color string
}

func main() {
	// 创建一个结构体变量"cat",类型为"Cat",是一个struct
	var cat1 Cat
	fmt.Println(cat1) // 返回的默认值:{ 0 } ,string默认值为"",int默认值为0,所以这里显示只有一个0

	// 给变量的"属性"赋值
	cat1.Color="yellow"
	cat1.Age=2
	cat1.Name="dandan"

	fmt.Println(cat1) // 返回值:{dandan 2 yellow} 分别对应Name,Age,Color的值
	fmt.Println(cat1.Name) // 返回值:dandan
	fmt.Println(cat1.Age) // 返回值:2
	fmt.Println(cat1.Color) // 返回值:yellow
	fmt.Printf("%T",cat1) // 类型:main.Cat
}

2.2、结构体变量在内存中的布局(重要)

1.声明一个结构体变量时,数据结构已经存在了,数据的值就是不同类型的默认值(string默认为空,int类型默认为0)

2.结构体变量值类型(相互数据不影响,如果想要相互数据影响的话,详见:2.5)

2.3、结构体(struct)声明

type 结构体名称 struct {

     field1 type

     field2 type

}

2.3.1、结构体(struct)声明例子

结构体名称首字母大写,那么结构体可以在其他包被使用

结构体(字段|属性)首字母大写,那么结构体字段可以在其他包被使用

不同结构体变量的字段是独立的,互不影响

type Cat struct {
   Name string  // (字段|属性)是string类型
   Age int  // (字段|属性)是int类型
   Color string  // (字段|属性)是string类型
}

2.3.2、创建一个结构体变量后,如果没有给字段赋值,那么会有一个默认值:

布尔类型的默认值是:false

string类型的默认值是:""

int类型的默认值是:0

指针,slice(切片)和map的默认值都是nil,即没有分配内存空间(在使用时,需要先make)

例子如下:

// 定义结构体变量,也叫做"对象"
type Test struct {
	par1 *int  // 指针
	slice1 []int  // 切片
	map1 map[string]string  // map
}

func main() {
	// 定义结构体变量
	var test Test
	fmt.Println(test.par1) // 默认值:<nil>
	fmt.Println(test.slice1) // 默认值:[]
	fmt.Println(test.map1)  // 默认值:map[]
	if test.par1 == nil {
		fmt.Println("test.par1 is nil")
	}
	if test.slice1 == nil {
		fmt.Println("test.slice1 is nil")
	}
	if test.map1 == nil {
		fmt.Println("test.map1 is nil")
	}
	// 依次输出:
	// test.par1 is nil
	// test.slice1 is nil
	// test.map1 is nil

	// 此时给"结构体变量"直接赋值,一定会报错。需要先给切片make,在使用
	test.slice1=make([]int,2)
	test.slice1[0]=111
	test.slice1[1]=222
	fmt.Println(test.slice1) // [111 222]

	//需要先给map make,在使用
	test.map1=make(map[string]string)
	test.map1["name"]="sudada"
	test.map1["age"]="18"
	fmt.Println(test.map1) // map[age:18 name:sudada]
}

2.4、结构体变量的声明和结构体变量字段使用

2.4.1、方式1(先声明结构体变量,然后赋值)

var test struct

// 定义结构体变量,也叫做"对象"
type Test struct {
	Name string
	Age int
}

func main() {
	var test Test
	test.Name="sudada"
	test.Age=18
	fmt.Println(test) // {sudada 18}
}

2.4.2、方式1的延伸(先声明结构体变量,然后赋值)

var test = struct{}

// 定义结构体变量,也叫做"对象"
type Test struct {
	Name string
	Age int
}

func main() {
	var test = Test{}
    // 简写 test := Test{}
	test.Name="sudada"
	test.Age=18
	fmt.Println(test) // {sudada 18}
}

2.4.3、方式2(声明结构体变量,同时赋值)

var test = struct{Name: "sudada",Age: 18,}

// 定义结构体变量,也叫做"对象"
type Test struct {
	Name string
	Age int
}

func main() {
	var test = Test{
		Name: "sudada",
		Age: 18,
	}
    // 或者简写
	test := Test{
		Name: "sudada",
		Age: 18,
	}
    // 或者这样写
    var test = Test{"sudada",18}
    test := Test{"sudada",18}

	fmt.Println(test) // {sudada 18}
}

2.4.4、方式3(结构体变量是一个指针时,然后赋值)

var test *struct = new(struct)

// 定义结构体变量,也叫做"对象"
type Test struct {
	Name string
	Age int
}

func main() {
	// 结构体变量是一个"指针"
	var test *Test = new(Test)  // 可以简写成:var test = new(Test)
	// 给"指针"类型的"结构体变量"字段赋值的方式(标准写法)
	(*test).Name="sudada"
	(*test).Age=18
	fmt.Println(*test) // {sudada 18}

	// go为了程序使用方便,针对"指针"类型的结构体变量,底层做了优化:test.Age=18 == (*test).Age=18
	test.Name="wang"
	test.Age=28
	fmt.Println(*test) // {wang 28}
}

2.4.5、方式3的延伸(结构体变量是一个指针时,然后赋值)

var test *struct = &struct{}

// 定义结构体变量,也叫做"对象"
type Test struct {
	Name string
	Age int
}

func main() {
	// 结构体变量是一个"指针"
	var test *Test = &Test{}  // 可以简写成:var test = &Test{}
	// 给"指针"类型的"结构体变量"字段赋值的方式(标准写法)
	(*test).Name="sudada"
	(*test).Age=18
	fmt.Println(*test) // {sudada 18}

	// go为了程序使用方便,针对"指针"类型的结构体变量,底层做了优化:test.Age=18 == (*test).Age=18
	test.Name="wang"
	test.Age=28
	fmt.Println(*test) // {wang 28}
}

2.4.6、方式4(结构体变量是一个指针同时赋值)

var test *struct = &Test{Name:"sudada", Age:28,}

// 定义结构体变量,也叫做"对象"
type Test struct {
	Name string
	Age int
}

func main() {
	// 结构体变量是一个"指针",声明时赋值
	var test *Test = &Test{
		Name:"sudada",
		Age:28,
	}
	fmt.Println(*test) // {sudada 28}

	// go为了程序使用方便,针对"指针"类型的结构体变量,底层做了优化:test.Age=18 == (*test).Age=18
	test.Name="wang"
	test.Age=28
	fmt.Println(*test) // {wang 28}
}

2.5、结构体变量指针赋值另外一个结构体变量时,内存分配机制

"test2指针"对应的"值"是"test1的指针地址"(那么修改test1或者test2时,2边的值都会跟着改变)

// 定义结构体变量,也叫做"对象"
type Test struct {
	Name string
	Age int
}

func main() {
	var test1 = Test{}
	test1.Name="sudada"
	test1.Age=18

	// 把test1的"指针"赋值给test2
	fmt.Printf("%p\n",&test1) // test1的指针是:0xc000008048
	var test2 *Test = &test1
	fmt.Printf("%p\n",&test2) // test2的指针是:0xc00006a028(test2的指针存放的值就是test1的指针)
	fmt.Printf("%p\n",test2) // test2的值(等于test1的指针)是:0xc000008048
	fmt.Println(*test2) // test2:{sudada 18}

	// 修改"指针"的值时,test1和test2对应的值都会改变
	test2.Name="wang"
	fmt.Println(test1)  // test1:{wang 18}
	fmt.Println(*test2)  // test2:{wang 18}
}

2.6、结构体struct 注意事项

2.6.1、结构体和其他类型进行转换时,需要有完全相同的字段(字段名字,字段个数和字段类型)

type A struct {
	Name string
	Age int
}

type B struct {
	Name string
	Age int
}

func main() {
	var a A
	var b B
	// 这样强制转换不会报错(A B结构体字段名称,字段个数和字段类型相同)
	a = A(b)
	fmt.Println(a,b)  // { 0} { 0}
}

2.6.2、结构体进行type重新定义(相当于取别名),goloang认为是新的数据,但是相互间可以强转

// 结构体1
type Student struct {
	Name string
	Age int
}
// 结构体2
type Stu Student

func main() {
	var stu1 Student
	var stu2 Stu
	// 这样强制转换不会报错(Student Stu结构体字段名称,字段个数和字段类型相同)
	stu1 = Student(stu2)
	fmt.Println(stu1,stu2)  // { 0} { 0}
}

2.6.3、结构体的每个字段都可以写一个tag,该tag可以通过反射机制获取

type student struct {
    Name string `json:"name"`
    Age int `json:"age"`
    Sex string `json:"sex"`
}

import (
	"encoding/json"
	"fmt"
)

// 结构体1
type student struct {
	Name string `json:"name"`
	Age int `json:"age"`
	Sex string `json:"sex"`
}

func main() {
	var stu1 = student{"sudada",18,"nan",}
	fmt.Println(stu1) // {sudada 18 nan}

	// 把结构体变量stu1序列化(这里传入"结构体字段"必须大写,如果不大写"结构体字段",那么json.Marshal读取不到值)
	jsonStr, err := json.Marshal(stu1)
	if err != nil {
		fmt.Println("报错: ",err)
	} else {
		fmt.Println(string(jsonStr)) // 默认返回的是bytes类型的切片,需要使用string做一下转换
		// 返回值(json格式):{"Name":"sudada","Age":18,"Sex":"nan"}

		// 结构体字段使用tag之后:
		// 返回值(json格式):{"name":"sudada","age":18,"sex":"nan"}
	}
}

三、面向对象-方法

3.1、什么是方法?

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

3.2、方法的声明和调用

快速理解方法:可以把"结构体"理解为"","结构体变量"理解为"对象",方法就是""里面定义的函数。

方法对象调用时:对象.方法(),会把对象本身作为实参传递给方法(默认是值传递,修改后不改变原来的值)

方法的声明:

func (name 结构体名称) 方法名 (实参 实参类型) (返回值 返回值类型){

       代码段

       return 返回值

}

方法的案例1:

// 定义"结构体类型Person"
type Person struct {
	Name string
	age int
}

// "结构体类型Person"的方法,名为"test"
// (person Person)表示:test方法,是给"结构体类型Person"绑定的
func (person Person) test()  {
	// 这里的person的值就等于sudada的值(值拷贝),但是修改person的值,不会影响sudada的值,例子如下:
	person.Name = "Jack"
	fmt.Println("test",person.Name) // 返回值:"Jack"
}

func main() {
	// 给"结构体变量aaa"赋值
	var sudada = Person {
		Name: "sudada",
		age: 18,
	}
	// 调用"结构体类型Person"的方法"test"
	sudada.test() // sudada调用test时会把sudada传入test方法内(谁调用就把谁传入),值拷贝
	fmt.Println("main",sudada.Name)  // 返回值:"sudada"
	// A.test() // 不能直接调用"结构体类型Person"的方法"test"
	// test() // 直接调用,会把"test"作为一个函数,而不是方法
}

3.3、方法的快速入门

3.3.1、案例1:给Person结构体添加speak方法(无参数无返回值)

// 定义"结构体类型Person"
type Person struct {
	Name string
	age int
}

// "结构体类型Person"的方法,名为"speak"
func (person Person) speak() {
	fmt.Println(person.Name, "is good man") // 返回值:sudada is good man
}

func main() {
	var p = Person{
		Name: "sudada",
		age: 18,
	}
	p.speak()
}

3.3.2、案例2:给Person结构体添加"jisuan"方法(把方法当做函数一样,接收参数返回值

// 定义"结构体类型Person"
type Person struct {
	Name string
	age int
}

// "结构体类型Person"的方法,名为"jisuan"
func (person Person) jisuan(num int) (sum int) {
	res:=0
	for i:=0;i<=num;i++{
		res+=i
	}
	return res
}

func main() {
	var p = Person{
		Name: "sudada",
		age: 18,
	}
	var res int
	res = p.jisuan(1000)
	fmt.Println("求和为",res) // 求和为 500500
}

3.4、方法的调用和传参机制

方法的调用和传参机制,和函数基本一样;

不一样的地方是:对象.方法(),会把对象本身作为实参传递给方法(默认是值传递,修改后不改变原来的值)

3.5、方法的注意事项(重要★★★★★

3.5.1、结构体类型是值类型,在方法调用中,使用的是值拷贝的传递方式。

3.5.2、为了提高效率,通常将方法结构体的指针类型绑定,例子:

// 定义"结构体类型Person"
type Person struct {
	Name string
	age int
}

// "结构体类型Person"的方法,名为"test"
// 这里的person作为一个指针类型
func (person *Person) test() {
	// 标准写法:(*person).Name = "Jack"
	// 因为编译器做了优化,可以简写如下:
	person.Name = "Jack"
	fmt.Println(person.Name) // 返回值:Jack
}

func main() {
	var p = Person{
		Name: "sudada",
		age: 18,
	}
	// 标准写法:(&p).test()
	// 因为编译器做了优化,可以简写如下:
	p.test()
	fmt.Println(p.Name)  // 返回值:Jack
}

3.5.3、glang中的方法是作用在指定的数据类型上的,即:方法和指定的数据类型绑定,因此自定义类型,都可以有方法。而不仅仅是struct。比如int,float等也可以有方法。

给int类型创建方法,例子:

type Integer int

func (i Integer)test()  {
	fmt.Println(i) // 返回值:10
}

// 绑定指针,修改值后,其他地方也会跟着修改
func (i *Integer)change()  {
	*i += 1
}

func main() {
	var i Integer = 10
	i.test()
	i.change()
	fmt.Println(i) // 返回值:11
}

3.5.4、方法名大写,可以在其他包使用,反之小写,只能在当前包使用。

3.5.5、如果一个类型实现了String()这个方法,那么调用fmt.Println时,默认会调用这个变量的String()方法进行输出。(输出日志时可以使用),案例:

type Student struct {
	Name string
	Age int
}

func (stu Student) String() string {
	msg := fmt.Sprintf("Name=[%v] Age=[%v]", stu.Name, stu.Age)
	return msg
}

func main() {
	var stu1 Student = Student{
		Name: "sudada",
		Age: 18,
	}
	// 调用fmt.Println方法
	fmt.Println(stu1) // 返回值:Name=[sudada] Age=[18]
}

3.6、方法和函数的区别(重要★★★★★

3.6.1、调用方式不一样

        函数调用:函数名(参数)

        方法调用:方法绑定的类型变量.方法名(参数)

3.6.2、普通函数:函数接收的参数类型为值类型时,不能将指针类型的数据直接传递。反之:函数接收的参数类型为指针类型时,不能将值类型的数据直接传递。例子如下:

package main

import "fmt"

type Student struct {
	Name string
}

// 函数接收的参数类型为值类型时,不能将指针类型的数据直接传递
func test1(szq Student)  {
	fmt.Println(szq.Name)
}
// 反之:函数接收的参数类型为指针类型时,不能将值类型的数据直接传递
func test2(szq *Student)  {
	fmt.Println(szq.Name)
}

func main() {
	var stu = Student{
		Name: "sudada",
	}
	test1(stu)  // 这里"实参的类"型,必须根据函数"形参接收的类型"去传递
	test2(&stu) // 这里"实参的类"型,必须根据函数"形参接收的类型"去传递
}

3.6.3、方法(如struct方法):方法的接收值为值类型时,可以用指针类型的变量调用方法,反之:方法的接收值为指针类型时,可以用值类型的变量调用方法。例子如下:

func (stu *Student) test1()  {}   指针传递
func (stu Student) test1()  {}   值传递

package main

import "fmt"

type Student struct {
	Name string
}

// 方法的接收值为指针类型时,可以用值类型的变量调用test1方法
func (stu *Student) test1()  {
	fmt.Println(stu.Name)
}

// 方法的接收值为值类型时,可以用指针类型的变量调用test2方法
func (stu Student) test2()  {
	stu.Name = "test2"
	fmt.Println(stu.Name)
}

func main() {
	var stu1 = Student{
		Name: "sudada",
	}
	var stu2 = Student{
		Name: "wang",
	}
	stu1.test1() // 方法接收的是"指针类型",但是"值类型的变量"也可以调用,返回值:sudada
	fmt.Println(stu1.Name) // 返回值:sudada

	(&stu2).test2() // 方法接收的是"值类型",但是"指针类型的变量"也可以调用(虽然使用了&stu2,但是本质上还是属于值传递,因为方法test2接收的类型是值类型),返回值:test2
	// (&stu2).test2() 等价于 stu2.test2()
	fmt.Println(stu2.Name)  // 返回值:wang(上述&stu2本质上还是属于值传递,因为方法test2接收的类型是值类型,所以变量stu2的值,并没有改变)
}

​编辑文章

四、面向对象-工厂模式

4.1、为什么会用到工厂模式?

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

4.1、怎么用工厂模式?

结构体struct的首字母小写,同时又需要在别的包内使用这个结构体时,需要用到工厂模式。

案例一(封装的一种模式)

// model.student.go文件
package model

type student struct {
	Name string
	age int
}

// "student结构体"首字母小写,只能当前文件内使用。
// 通过"工厂模式"可以将"student结构体"在其他包使用。
func NewStudent(name string, age int) *student  {
	return &student{
		Name: name,
		age: age,
	}
}

// 如果"age"字段首字母小写,那么其他包不可以直接访问"age"字段,可以提供一个方法获取"age"字段的值
func (n *student) GetAge() int {
	return n.age
}

// main.go文件
package main

import (
	"Study/go008/demo01/model"
	"fmt"
)

func main() {
	// stu 是一个指针,同时也是"student结构体"的变量(指针)
	stu := model.NewStudent("sudada",18)
	fmt.Println(*stu)  // 返回值:{sudada 18}
	fmt.Println(stu.Name)  // 返回值:sudada

	// fmt.Println(stu.age)  // "student结构体"的age字段是小写,这里不能使用
	// 所以这里给"student结构体"绑定一个"GetAge方法(首字母大写)
	fmt.Println(stu.GetAge())  // "返回值:18
}

五、面向对象-三大特性

面向对象思想-抽象

5.1、封装

5.1.1、什么是封装?

        封装就是把抽象出的字段,和对字段的操作封装在一起,将数据保护在内部,程序的其他包只有通过被授权的操作(方法)才能对字段进行操作。

5.1.2、怎么用封装?

1.对结构体中的属性进行封装;

2.通过方法,包,实现封装;

5.1.3、封装的实现步骤:

1.将结构体,字段,属性首字母小写

2.给结构体所在包提供一个工厂模式的函数首字母大写

3.提供一个首字母大写的Set方法,用于对属性判断并赋值

4.提供一个首字母大写的Get方法,用于获取属性的

5.1.4、封装的快速入门,案例一

// model/person.go 文件
package model

import "fmt"

type person struct {
	Name string
	age  int
	sal  float64
}

// 工厂模式,返回一个"struct结构体"对象
func NewPerson(name string) *person {
	return &person{
		Name: name,
	}
}

// 设置年龄的"方法"
func (p *person) SetAge(age int) {
	if age > 0 && age < 100 {
		p.age = age
	} else {
		fmt.Println("年龄范围不合法")
		// 给个默认值
		p.age = 18
	}
}
// 获取年龄的"方法"
func (p *person) GetAge() int {
	return p.age
}
// 设置薪资的"方法"
func (p *person) SetSal(sal float64) {
	if sal > 3000 && sal < 30000 {
		p.sal += sal
	} else {
		fmt.Println("薪资范围不对3000-30000")
	}
}
// 获取薪资的"方法"
func (p *person) GetSal() float64 {
	return p.sal
}


// main.go 文件
package main

import (
	"Study/go009/model"
	"fmt"
)

func main() {
	// person是一个"struct结构体"对象
	person := model.NewPerson("sudada")
	// 调用"SetAge"方法,设置年龄
	person.SetAge(22)
	// 调用"SetSal"方法,设置薪资
	person.SetSal(28000)

	// 调用"GetAge"方法,获取年龄
	person_age := person.GetAge()
	// 调用"GetSal"方法,获取薪资
	person_sal := person.GetSal()
	// Name是首字母大写的字段,这里可以直接调用
	fmt.Println(person.Name) // 返回值:sudada
	fmt.Println("Age", person_age) // 返回值:Age 22
	fmt.Println("Sal", person_sal) // 返回值:Age 28000
}

5.2、继承

5.2.1、什么是继承?

继承可以解决代码复用,当多个结构体存在相同的属性(字段)和方法时,可以从这些结构体中抽象出结构体在该结构体中定义这些相同的属性和方法

5.2.2、怎么用继承?

语法:

// "学生"结构体
type Student struct {
    Name string
    Age int
}

// "中学"结构体
type School struct {
    Student // 继承"结构体Student"
}

// "大学"结构体
type University struct {
    Student // 继承"结构体Student"
}

继承案例快速理解:

package main

import "fmt"

// "学生"结构体
type Student struct {
	Name string
	Age int
}
// "学生"结构体的方法""
func (stu *Student) score(num int) {
	fmt.Printf("学生:%v 的分数是:%v\n", stu.Name, num)
}

// "中学"结构体
type School struct {
	Student // 继承"结构体Student"
}
// "中学"结构体的方法"testing"
func (s *School) testing()  {
	fmt.Println("school")
}

// "大学"结构体
type University struct {
	Student // 继承"结构体Student"
}
// "大学"结构体的方法"testing"
func (s *University) testing()  {
	fmt.Println("university")
}

func main() {
	// 定义一个结构体变量"middleStu"
	middleStu := &School{}
	// 给结构体变量"middleStu"赋值(方式一)
	//middleStu.Student.Name = "sudada"
	//middleStu.Student.Age = 10
	// 给结构体变量"middleStu"赋值(方式二,简写)
	middleStu.Name = "sudada"
	middleStu.Age = 10
	middleStu.testing()  // 返回值:school
	fmt.Println(*middleStu)  // 返回值:{{sudada 10}}
	// 调用"继承的结构体Student的方法score"
	middleStu.score(80)  // 返回值:学生:sudada 的分数是:80

	// 定义一个结构体变量"UniStu"
	UniStu := &University{}
	// 给结构体变量"UniStu"赋值(方式一)
	//UniStu.Student.Name = "sudada"
	//UniStu.Student.Age = 20
	// 给结构体变量"UniStu"赋值(方式二,简写)
	UniStu.Name = "sudada"
	UniStu.Age = 20
	UniStu.testing()  // 返回值:university
	fmt.Println(*UniStu)  // 返回值:{{sudada 20}}
	// 调用"继承的结构体Student的方法score"
	middleStu.score(150)  // 返回值:学生:sudada 的分数是:150
}

5.2.3、继承的深入理解

1.结构体可以使用"父"结构体所有的字段和方法,即:首字母大写或者小写的字段,方法,都可以使用;

package main

import "fmt"

// "父"结构体
type Student struct {
	Name string
	age int
}
// "父"结构体的方法"score"
func (stu *Student) score(num int) {
	fmt.Printf("学生:%v 的分数是:%v\n", stu.Name, num)
}

// 结构体School 继承"父"结构体Student
type School struct {
	Student // 继承"结构体Student"
}

func main() {
	// 定义一个结构体变量"middleStu"
	middleStu := School{}
	// 结构体School使用"父"结构体Student的 字段
	middleStu.Name = "sudada"
	middleStu.age = 18
	fmt.Println(middleStu.Name) // 返回值:sudada
	fmt.Println(middleStu.age) // 返回值:18
	// 结构体School使用"父"结构体Student的 方法
	middleStu.score(80)  // 返回值:学生:sudada 的分数是:80
}

2."父"结构体字段可以简化;

package main

import "fmt"

// "父"结构体
type Student struct {
	Name string
	age int
}
// 结构体School 继承"父"结构体Student
type School struct {
	Student // 继承"结构体Student"
}

func main() {
	// 定义一个结构体变量"middleStu"
	middleStu := School{}
	// 变量赋值
	middleStu.Student.Name = "sudada"
	middleStu.Student.age = 18
	fmt.Println(middleStu.Student.Name) // 返回值:sudada
	fmt.Println(middleStu.Student.age) // 返回值:18
	// 变量赋值,可以简写为:
	middleStu.Name = "sudada"
	middleStu.age = 18
	fmt.Println(middleStu.Name) // 返回值:sudada
	fmt.Println(middleStu.age) // 返回值:18
}

3.当结构体和和"父"结构体相同的字段或者方法时,编译器采用就近访问的原则,如果希望访问"父"结构体的字段或方法,就指明"父"结构体的字段或方法来取值;

package main

import "fmt"

// "父"结构体
type Student struct {
	Name string
	age int
}
// 结构体Student的方法
func (st *Student) StudentSayName() {
	fmt.Println(st.Name)
}

// 结构体School 继承"父"结构体Student
type School struct {
	Student // 继承"结构体Student"
	Name string
}
// 结构体School的方法
func (sc *School) SchoolSayName() {
	fmt.Println(sc.Name)
}

func main() {
	// 定义一个结构体变量"middleStu"
	middleStu := School{}

	// 定义结构体School的"Name字段"的值为"sudada"
	middleStu.Name = "sudada"
	// 调用结构体School的方法"SchoolSayName"就能拿到School的Name字段的值
	middleStu.SchoolSayName()  // 返回值:"sudada"
	// 调用结构体Student的方法"StudentSayName"拿到Student的Name字段的值为空
	middleStu.StudentSayName()  // 返回值:"空"

	// 定义结构体Student的"Name字段"的值为"sudada"
	middleStu.Student.Name = "wangdalu"
	// 调用结构体School的方法"SchoolSayName"就能拿到School的Name字段的值
	middleStu.SchoolSayName()  // 返回值:"sudada"
	// 调用结构体Student的方法"StudentSayName"就能拿到Student的Name字段的值
	middleStu.StudentSayName()  // 返回值:"wangdalu"
}

4.结构体继承了多个"父"结构体,同时多个"父"结构体有相同的字段或方法,那么在访问时,就需要指定"父"结构体的名字,否则编译报错;

package main

// "父"结构体1(有Name字段)
type A struct {
	Name string
	Age int
}
// "父"结构体2(也有Name字段)
type B struct {
	Name string
}
// 结构体C,继承A,B结构体(没有Name字段)
type C struct {
	A
	B
}

func main() {
	c := C{}
	// 结构体变量c给Name字段赋值时,正确的写法(必须指定"父"结构体的名称)
	c.A.Name="sudada"
	c.B.Name="sudada"

	// 结构体变量c给Name字段赋值,错误的写法(没有指定"父"结构体的名称)
	//c.Name="sudada"
}

5."结构体B,字段aaa"的类型是一个"结构体A"时,这种模式就是组合(A和B组合)。组合模式下,访问"结构体A的字段或方法",必须带上"结构体B的字段名aaa";

package main

// 结构体A
type A struct {
	Name string
	Age int
}

// 结构体B
type B struct {
	// "字段aaa"的类型是结构体A
	aaa A
}

func main() {
	b := B{}
	// 组合模式下,想要访问"A结构体"的字段或方法,必须带上"结构体B的字段名aaa"
	b.aaa.Name="sudada"
	// 错误写法
	//b.Name="sudada"
}

6.结构体继承"父"结构体后,在创建结构体变量时,可以直接给"父"结构体的字段赋值;

// 继承结构体
import "fmt"

// 结构体A
type A struct {
	Name string
	Age int
}
// 结构体B(继承A结构体)
type B struct {
	A
}

func main() {
	// 结构体变量赋值,B结构体继承了A结构体,赋值方式: B{ A{Name:"sudada",Age:18} }
	b := B{
		A{
			Name: "sudada",
			Age: 18,
		},
	}
	fmt.Println(b.Name) // 返回值:sudada
	fmt.Println(b.Age) // 返回值:18

	// 错误写法(B结构体虽然继承了A结构体,但是B结构体没有字段Name和Age)
	//c := B{
	//	Name: "sudada",
	//	Age: 18,
	//}
}

// 继承的结构体是一个指针
import "fmt"

// 结构体A
type A struct {
	Name string
	Age int
}
// 结构体B(继承A结构体,是一个指针)
type B struct {
	*A
}

func main() {
	// 结构体变量赋值,B结构体继承了A结构体,赋值方式: B{ A{Name:"sudada",Age:18} }
	b := B{
		// 这里也要适配为指针
		&A{
			Name: "sudada",
			Age: 18,
		},
	}
	fmt.Println(b.Name) // 返回值:sudada
	fmt.Println(b.Age) // 返回值:18
	fmt.Println(b) // 返回值:{ 0xc000094030 } 是一个指针地址
}

7.结构体的匿名字段是基本数据类型时,如何使用

import "fmt"

type A struct {
	Name string
	Age int
}
// 结构体的匿名字段是基本数据类型时,如何使用
type B struct {
	A
	int
}

func main() {
	b := B{}
	b.Name="sudada"
	b.Age=18
	// 结构体变量给基本数据类型赋值,以及使用
	b.int=22
	fmt.Println(b)  // 返回值:{{sudada 18} 22}
}

5.2.4、多重继承(建议少用

什么是多重继承:一个结构体继承了多个"父"结构体,那么该结构体可以直接访问"父"结构体的字段和方法,实现了多重继承

案例:

package main

// "父"结构体1
type A struct {
	Name string
	Age int
}
// "父"结构体2
type B struct {
	Name string
}
// 结构体C,继承A,B结构体
type C struct {
	A
	B
}

5.3、多态

5.3.1、多态的基本介绍

golang中,多态特征是通过接口实现的。按照统一的接口调用不同的实现,这时接口变量就呈现不同的形态。

5.3.2、快速入门案例(复用了6.1.3的案例)

package main

import "fmt"

// 定义一个接口
type Usb interface {
	// 接口内定义2个方法
	Start()
	Stop()
}

// 定义结构体Phone
type Phone struct {
}
// 结构体Phone的Start方法
func (p Phone) Start() {
	fmt.Println("phone start")
}
// 结构体Phone的Stop方法
func (p Phone) Stop() {
	fmt.Println("phone Stop")
}

// 定义结构体Camera
type Camera struct {
}
// 结构体Camera的Start方法
func (c Camera) Start() {
	fmt.Println("camera start")
}
// 结构体Camera的Stop方法
func (c Camera) Stop() {
	fmt.Println("camera Stop")
}

// 定义结构体Computer
type Computer struct {
}
// 结构体Computer的Working方法,"接收参数"的类型为Usb(interface)
// Usb内有2个方法"Start"和"Stop":只要结构体也绑定了"Start"和"Stop"方法,那么这个结构体实现了"Usb接口"。
func (computer Computer) Working(usb Usb) {
	// 这里的usb就是一种"多态"。usb的值就是传递过来的"结构体变量",会去判断导致是camera还是phone。
	// 通过传递过来的"结构体变量"调用方法
	usb.Start()
	usb.Stop()
}

func main() {
	// 结构体变量
	computer := Computer{}
	camera := Camera{}
	phone := Phone{}

	// 调用方法
	computer.Working(phone)  // 返回值:phone start,phone stop
	computer.Working(camera)  // 返回值:camera start,camera stop
}

5.3.3、接口体现的"多态特性"

多态参数:上述入门案例,体现了多态参数(computer.Working方法,既可以接收phone变量,又可以接收camera变量,体现了Usb接口的多态)

多态数组,案例如下(在一个数组中,存放Phone结构体和Camera结构体,这两个结构体是两种不同类型)

package main

import "fmt"

// 定义一个接口
type Usb interface {
	// 接口内定义2个方法
	Start()
	Stop()
}

// 定义结构体Phone
type Phone struct {
	Name string
}
// 结构体Phone的Start方法
func (p Phone) Start() {
	fmt.Println("phone start")
}
// 结构体Phone的Stop方法
func (p Phone) Stop() {
	fmt.Println("phone Stop")
}

// 定义结构体Camera
type Camera struct {
	Name string
}
// 结构体Camera的Start方法
func (c Camera) Start() {
	fmt.Println("camera start")
}
// 结构体Camera的Stop方法
func (c Camera) Stop() {
	fmt.Println("camera Stop")
}

// 定义结构体Computer
type Computer struct {}
// 结构体Computer的Working方法,"接收参数"的类型为Usb(interface)
// Usb内有2个方法"Start"和"Stop":只要结构体也绑定了"Start"和"Stop"方法,那么这个结构体实现了"Usb接口"。
func (computer Computer) Working(usb Usb) {
	// 这里的usb就是一种"多态"。usb的值就是传递过来的"结构体变量",会去判断导致是camera还是phone。
	// 通过传递过来的"结构体变量"调用方法
	usb.Start()
	usb.Stop()
}

func main() {
	// 定义一个Usb接口数组,可以存放Phone和Camera的结构体变量
	// 可以体现出多态数组
	var usbArr [2]Usb
	usbArr[0] = Phone{
		Name: "iphone",
	}
	usbArr[1] = Camera{
		Name: "jianeng",
	}
	fmt.Println(usbArr)  // 返回值:[{iphone} {jianeng}]
}

5.3.4、类型断言(判断一个变量是否属于某个类型

1.什么是类型断言?

    由于接口是一般类型,不知道具体类型,如果要转成具体类型,需要使用类型断言(在进行类型断言时,如果类型不匹配,就会报错)

案例1:

package main

import "fmt"

type Point struct {
	Name string
}

func main() {
	var a interface{}
	var point Point = Point{Name: "x"}
	a = point
	// 如何将a赋值给b变量
	var b Point
	// b = a 这样赋值会报错
	b = a.(Point)  // 类型断言
	fmt.Println(b)
	// b = a.(Point)就是类型断言,表示判断变量a的值是否属于'Point结构体类型', 如果是则转成'Point结构体的变量'并赋值给b,否则报错。
}

案例2:

package main

import "fmt"

func main() {
	var x interface{}
	var b float32 = 1.1
	x = b
	fmt.Println(x)  // 返回值:1.1
	// 方式1:
	// y := x.(float32)
	// 方式2:
	y := x
	fmt.Println(y)  // 返回值:1.1
}

2.在类型断言时,附带检查机制(失败时提示错误信息,而不是panic)

package main

import "fmt"

func main() {
	var x interface{}
	var b float32 = 1.1
	x = b
	fmt.Println(x)  // 返回值:1.1

	// 类型断言检测机制(ok是一个bool类型)
	//y,ok := x.(float64)
	//if ok {
	//	fmt.Println("类型断言成功")
	//} else {
	//	fmt.Println("类型断言失败",y)
	//}

	// if判断-简洁版
	if y, ok := x.(float64); ok {
		fmt.Println("类型断言成功")
	} else {
		fmt.Println("类型断言失败", y)  // 类型断言失败 0
	}
}

5.3.5、类型断言的案例1

需求:Phone结构体有一个Call方法,遍历Usb数组,如果是phone变量则调用Call方法

package main

import (
	"fmt"
)

// 定义一个接口
type Usb interface {
	// 接口内定义2个方法
	Start()
	Stop()
}

// 定义结构体Phone
type Phone struct {
	Name string
}
// 结构体Phone的Start方法
func (p Phone) Start() {
	fmt.Println("phone start")
}
// 结构体Phone的Stop方法
func (p Phone) Stop() {
	fmt.Println("phone Stop")
}
// 结构体Phone的Call方法
func (p Phone) Call() {
	fmt.Println("phone Call")
}

// 定义结构体Camera
type Camera struct {
	Name string
}
// 结构体Camera的Start方法
func (c Camera) Start() {
	fmt.Println("camera start")
}
// 结构体Camera的Stop方法
func (c Camera) Stop() {
	fmt.Println("camera Stop")
}

// 定义结构体Computer
type Computer struct {}
// 结构体Computer的Working方法,"接收参数"的类型为Usb(interface)
// Usb内有2个方法"Start"和"Stop":只要结构体也绑定了"Start"和"Stop"方法,那么这个结构体实现了"Usb接口"。
func (computer Computer) Working(usb Usb) {
	// 通过参数usb(也就是Usb接口)去调用"Start"和"Stop"方法
	usb.Start()
	usb.Stop()
}

func main() {
	var usbArr [2]Usb
	usbArr[0] = Phone{"iphone"}
	usbArr[1] = Camera{"jianeng"}

	// Phone结构体有一个Call方法,遍历Usb数组,如果是phone变量则调用Call方法
	var computer Computer
	for _ ,v := range usbArr {
		// 使用类型断言,判断变量v的值是否属于"Phone结构体类型"如果是则调用"Phone结构体"的Call方法
		if p, ok := v.(Phone); ok{
			p.Call()  // 返回值:phone Call
		}
		computer.Working(v) // 返回值如下:
		//phone start
		//phone Stop
		//camera start
		//camera Stop
	}
}

5.3.6、类型断言的案例2

写一个函数,循环判断传入参数的类型

package main

import "fmt"

// 函数TypeJudge接收的参数是一个"空接口"(可以接收任意变量)
// 用来判断一个"实参"具体的类型
func TypeJudge(item ...interface{})  {
	for _,v := range item {
		// v.(type):这里的type是一个关键字,固定写法
		switch v.(type) {
		case bool:
			fmt.Println("这是一个,bool,类型,值为: ",v)
		case float64:
			fmt.Println("这是一个,float64,类型,值为: ",v)
		case int:
			fmt.Println("这是一个,int,类型,值为: ",v)
		case nil:
			fmt.Println("这是一个,nil,类型,值为: ",v)
		case string:
			fmt.Println("这是一个,string,类型,值为: ",v)
		case Student:
			fmt.Println("这是一个,Student,类型,值为: ",v)
		case *Student:
			fmt.Println("这是一个,*Student,类型,值为: ",v)
		default:
			fmt.Println("未知类型,类型,值为: ",v)
		}
	}
}

// 结构体Student
type Student struct {}

func main() {
	name := "sudada"
	TypeJudge(name)  // 返回值:这是一个,string,类型,值为:  sudada
	age := 18
	TypeJudge(age)  // 这是一个,int,类型,值为:  18
	num := 1.8
	TypeJudge(num)  // 这是一个,float64,类型,值为:  1.8
	status := true
	TypeJudge(status)  // 这是一个,bool,类型,值为:  true
	// 结构体变量类型1
	student1 := Student{}
	TypeJudge(student1)  // 这是一个,Student,类型,值为: {}
	// 结构体变量类型2
	student2 := &Student{}
	TypeJudge(student2)  // 这是一个,*Student,类型,值为: &{}
}

六、面向对象-接口(interface)非常重要★★★★★★

6.1、接口的介绍和快速入门

6.1.1、接口语法

type 接口名 interface {
    方法1(参数名 类型) (返回值名 返回值类型)
    方法2(参数名 类型) (返回值名 返回值类型)
}

6.1.2、接口介绍

        1.接口可以定义多个方法,但是不需要具体实现。(只有名称没有代码逻辑),高聚合,松耦合

        2.接口不能包含任何变量

        3.结构体在使用接口时,编写接口同名方法的具体实现逻辑。只要结构体包含了接口所有的方法,那么结构体就实现了这个接口。( 结构体的方法名接口的方法名一致时,结构体就实现了接口。案例如下:)

package main

import "fmt"

// 定义一个接口
type Usb interface {
	// 接口内定义2个方法
	Start()
	Stop()
}

// 定义结构体Phone
type Phone struct {
}
// 结构体Phone的Start方法
func (p Phone) Start() {
	fmt.Println("phone start")
}
// 结构体Phone的Stop方法
func (p Phone) Stop() {
	fmt.Println("phone Stop")
}

// 那么"结构体Phone"就实现了"接口Usb"

6.1.3、接口快速入门案例

package main

import "fmt"

// 定义一个接口
type Usb interface {
	// 接口内定义2个方法
	Start()
	Stop()
}

// 定义结构体Phone
type Phone struct {
}
// 结构体Phone的Start方法
func (p Phone) Start() {
	fmt.Println("phone start")
}
// 结构体Phone的Stop方法
func (p Phone) Stop() {
	fmt.Println("phone Stop")
}

// 定义结构体Camera
type Camera struct {
}
// 结构体Camera的Start方法
func (c Camera) Start() {
	fmt.Println("camera start")
}
// 结构体Camera的Stop方法
func (c Camera) Stop() {
	fmt.Println("camera Stop")
}

// 定义结构体Computer
type Computer struct {
}
// 结构体Computer的Working方法,"接收参数"的类型为Usb(interface)
// Usb内有2个方法"Start"和"Stop":只要结构体也绑定了"Start"和"Stop"方法,那么这个结构体实现了"Usb接口"。
func (computer Computer) Working(usb Usb) {
	// 这里的usb就是一种"多态"。usb的值就是传递过来的"结构体变量",会去判断导致是camera还是phone。
	// 通过传递过来的"结构体变量"调用方法
	usb.Start()
	usb.Stop()
}

func main() {
	// 结构体变量
	computer := Computer{}
	camera := Camera{}
	phone := Phone{}

	// 调用方法
	computer.Working(phone)  // 返回值:phone start,phone stop
	computer.Working(camera)  // 返回值:camera start,camera stop
}

6.2、接口的注意事项

6.2.1、接口本身不能创建实例,但是可以指向一个已经实现了该接口的变量。

package main

import "fmt"

// 接口A,有一个Say方法
type A interface {
	Say()
}

// 结构体Student实现了A接口
type Student struct {
	Name string
}
func (stu Student) Say()  {
	fmt.Println(stu.Name)
}

func main() {
	// Student结构体变量stu
	var stu = Student{Name: "sudada"}
	// 结构体Student只有实现了A接口,才可以这么赋值
	var a A = stu
	a.Say()  // 返回值:sudada
}

6.2.2、接口中所有的方法,只有名称,没有具体代码实现过程。

6.2.3、"结构体"或"自定义类型",需要将一个接口内的所有方法都实现,才能叫做实现了该接口。

6.2.4、只需要是"自定义的数据类型",都可以实现接口。不仅仅是"结构体类型"。

6.2.5、自定义类型可以实现多个接口(多个接口内可以有同名的方法)

package main

import "fmt"

// 接口A,有一个Say方法
type A interface {
	Say()
}
// 接口B,有一个Hello方法
type B interface {
	Hello()
}

// 结构体Student实现了A接口和B接口
type Student struct {
	Name string
}
func (stu Student) Say()  {
	fmt.Println("Say")
}
func (stu Student) Hello()  {
	fmt.Println("Hello")
}

func main() {
	var stu = Student{}
	// 调用A接口的方法Say
	stu.Say()  // 返回值:Say
	// 调用B接口的方法Hello
	stu.Hello()  // 返回值:Hello
}

6.2.6、Golang接口中不能有任何变量。

6.2.7、一个"接口A"如果继承了"B,C接口",那么要"实现A接口",就必须将B,C接口全部实现

package main

import "fmt"

// 接口B,有一个Hello方法
type B interface {
	Hello()
}
// 接口C,有一个Hai方法
type C interface {
	Hai()
}
// 接口A,有一个Say方法,同时继承B,C接口
type A interface {
	B
	C
	Say()
}


// 结构体Student实现了A接口(同时必须实现B,C接口)
type Student struct {
	Name string
}
func (stu Student) Say()  {
	fmt.Println("Say")
}
func (stu Student) Hai()  {
	fmt.Println("Hai")
}
func (stu Student) Hello()  {
	fmt.Println("Hello")
}

func main() {
	var stu = Student{}
	// 只有结构体Student将A接口(继承B,C接口)的所有方法都实现,才能这么使用
	var a A = stu
	a.Say()  // 返回值:Say
	a.Hai()  // 返回值:Hai
	a.Hello()  // 返回值:Hello
}

6.2.8、接口"interface"默认是一个指针(引用类型),如果没有对接口"interface"初始化就使用,默认会返回nil(详见6.1.3案例)

6.2.9、空接口"interface{}"没有任何的方法,所以所有类型都实现了空接口"interface{}"(可以把任何一个变量赋值给空接口"interface{}"),空接口"interface{}"也是一种类型。

import "fmt"

// 接口A
type A interface {
}
// 结构体Student
type Student struct {
	Name string
}

func main() {
	// "空接口"的使用方式1
	var stu = Student{}
	// 接口A没有任何方法(空接口),那么就可以把任意变量赋值给空接口
	var a A = stu
	fmt.Println(a)  // 返回值:{}

	// "空接口"的使用方式2
	// 变量test是一个"空接口"类型,赋值为"test"
	var test interface{} = "test"
	fmt.Println(test)  // 返回值:test
	
	// 变量test2是一个"空接口"类型,赋值为变量num
	var num int = 122
	var test2 interface{} = num
	fmt.Println(test2)  // 返回值:122
}

6.3、接口的案例(对"切片"的内容进行排序)

package main

import (
	"fmt"
	"math/rand"
	"sort"
)

// 定义结构体Hero
type Hero struct {
	Name string
	Age int
}
// 定义结构体切片HeroSlice
type HeroSlice []Hero

// 结构体Hero实现接口(绑定接口的方法)
// Len方法,返回结构体的长度
func (hs HeroSlice) Len() int {
	return len(hs)
}
// Less方法,决定使用怎样的方式进行排序
func (hs HeroSlice) Less(i,j int) bool {
	// 从小到大排序Age
	return hs[i].Age < hs[j].Age
	// 从大到小排序Age
	// return hs[i].Age > hs[j].Age
}
// Swap方法
func (hs HeroSlice) Swap(i,j int) {
	// a,b 两个变量交换值(a=b,b=a),方法1
	//temp := hs[i]
	//hs[i] = hs[j]
	//hs[j] = temp

	// a,b 两个变量交换值(a=b,b=a),方法2
	hs[i],hs[j] = hs[j],hs[i]
}

func main() {
	// 定义一个切片
	var test = []int{0,10,20,90,40}
	// 对切片排序,方式2(sort排序)
	sort.Ints(test)
	fmt.Println(test) // 返回值:[0 10 20 40 90]

	// "结构体切片"进行排序
	// 定义结构体变量heros
	var heros HeroSlice
	// 生成一些随机值h
	for i:=0;i<=10;i++{
		h := Hero{
			Name: fmt.Sprintf("英雄~%d",rand.Intn(100)),
			Age: rand.Intn(100),
		}
		// 将h的值append到结构体变量heros中去
		heros = append(heros, h)
	}
	fmt.Println(heros) // 返回值:[{英雄~13 25} {英雄~33 47} {英雄~64 31} {英雄~29 73} {英雄~67 2} {英雄~90 50} {英雄~41 48} {英雄~50 48} {英雄~18 5} {英雄~70 95} {英雄~88 33}]

	// 排序前heros的顺序
	for _,v := range heros {
		fmt.Println("-------排序前-------")
		fmt.Println(v)
	}

	// 调用sort.Sort进行排序
	sort.Sort(heros)
	fmt.Println("-------按照年龄排序后-------")
	for _,v := range heros {
		fmt.Println(v)
	}
}

七、接口和继承的比较

7.1、继承和接口解决的问题不同

1、继承主要解决代码的复用性和可维护性。

2、接口主要在于设计好各种规范(方法),让其他自定义类型去实现这些方法。

3、接口比继承更灵活,同时一定程度实现代码解耦。

7.2、简单的小案例说明

1、B结构体继承A结构体时,就自动继承了A结构体的字段和方法,并且可以直接使用。

2、B结构体需要扩展功能同时又不希望去修改继承关系时(不改变A结构体的字段和方法),则使用接口(实现接口,是对继承机制的一种补充)

package main

import "fmt"

// 结构体Monkey
type Monkey struct {
	Name string
}
// 结构体Monkey绑定的方法
func (m Monkey) pashu()  {
	fmt.Println(m.Name,"继承了爬树的技能...")
}
// 结构体LittleMonkey继承Monkey
type LittleMonkey struct {
	Monkey
}

// 接口
type Fly interface {
	fly()
}
type Swim interface {
	swimming()
}
// 结构体LittleMonkey实现接口Fly
func (m LittleMonkey) fly()  {
	fmt.Println(m.Name,"学会了飞行...")
}
// 结构体LittleMonkey实现接口Swim
func (m LittleMonkey) swimming()  {
	fmt.Println(m.Name,"学会了游泳...")
}

func main() {
	ddd := LittleMonkey{
		Monkey{Name: "悟空"},
	}
	ddd.pashu()  // 返回值:悟空 继承了爬树的技能...
	ddd.fly()  // 返回值:悟空 学会了飞行...
	ddd.swimming()  // 返回值:悟空 学会了游泳...
}

七、面向对象-例子

7.1、例子1:写一个方法,输出对象的所有信息

package main

import "fmt"

type Student struct {
	Name string
	gender string
	age int
	id int
	score float64
}

func (stu *Student)say() string {
	stuInfo := fmt.Sprintf(
		"%v\n%v\n%v\n%v\n%v",
		stu.Name,
		stu.gender,
		stu.age,
		stu.id,
		stu.score,
		)
	return stuInfo
}

func main() {
	var stu Student = Student{
		Name: "sudada",
		gender: "male",
		age: 18,
		id: 01,
		score: 100,
	}
	fmt.Println(stu.say())
	// sudada
	// male
	// 18
	// 1
	// 100
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值