zoukankan      html  css  js  c++  java
  • 面向对象编程

    一、结构体

    将一类事物的特性提取出来(比如猫类),形成一个新的数据类型,就是一个结构体。通过结构体,可以创建多个变量(实例/对象)。

    (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本身就是语言类型系统(typesystem)的一部分,通过接口(interface)关联,耦合性低,也非常灵活。

    package main
    
    import "fmt"
    
    type Cat struct {
    	Name  string
    	Age   int
    	Color string
    	Hobby string
    }
    
    func main() {
    	var cat Cat
    	cat.Name = "小白"
    	cat.Age = 3
    	cat.Color = "白色"
    	cat.Hobby = "吃鱼"
    	fmt.Println(cat)
    
    	fmt.Println(cat.Name + "的信息如下:")
    	fmt.Println(cat.Age)
    	fmt.Println(cat.Hobby)
    	fmt.Println(cat.Color)
    }
    

    1、结构体和结构体变量(实例)的区别和联系:

    结构体是自定义的数据类型,代表一类事物.
    结构体变量(实例)是具体的,实际的,代表一个具体变量

    2、声明结构体

    type 结构体名称 struct{

      field1 type

      field2 type

    }

    字段的类型可以为:基本类型、数组或引用类型
    在创建一个结构体变量后,如果没有给字段赋值,都对应一个零值(默认值),布尔类型是false,数值是0,字符串是""。
    数组类型的默认值和它的元素类型相关,比如score [3]int 则为[0, 0, 0]指针,slice,和map的零值都是nil,即还没有分配空间。

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

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

    package main
    
    import "fmt"
    
    type Person struct {
    	Name string
    	Age  int
    }
    
    func main() {
    	//1、直接声明
    	var p Person
    	fmt.Println(p)
    
    	//2、{}
    	var p2 Person = Person{}
    	fmt.Println(p2)
    
    	var p3 Person = Person{"mary", 43}
    	fmt.Println(p3)
    
    	//3、&
    	var person *Person = new(Person)
    	(*person).Name = "smith"
    	(*person).Age = 89
    	fmt.Println(*person)
    
    	//go编译器底层对person.Name做了转化(*person).Name
    	person.Name = "nancy"
    	person.Age = 12
    	fmt.Println(*person)
    
    	//4、{}
    	var person2 *Person = &Person{}
    	(*person2).Name = "scott"
    	(*person2).Age = 100
    	fmt.Println(*person2)
    
    	person2.Name = "zhuni"
    	person2.Age = 45
    	fmt.Println(*person2)
    }
    

    4、struct 类型的内存分配机制

    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).Name)
    	fmt.Println(p2.Age)
    	p2.Name = "tom~"
    	fmt.Printf("p2.Name = %v p1.Name = %v
    ", p2.Name, p1.Name)
    	fmt.Printf("p2.Name = %v p1.Name = %v
    ", (*p2).Name, p1.Name)
    
    	fmt.Printf("p1的地址%p
    ", &p1)
    	fmt.Printf("p2的地址%p p2的值%p
    ", &p2, p2)
    }
    

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

    package main
    
    import "fmt"
    
    type Point struct {
    	x int
    	y int
    }
    
    type Rect struct {
    	leftUp, rightDown Point
    }
    
    type Rect2 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)
    
    	r2 := Rect2{&Point{10, 20}, &Point{30, 40}}
    	fmt.Printf("r2.leftUp本身地址 = %p r2.rightDown本身地址 = %p
    ", &r2.leftUp, &r2.rightDown)
    	fmt.Printf("r2.leftUp指向地址 = %p r2.rightDown指向地址 = %p
    ", r2.leftUp, r2.rightDown)
    }
    

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

    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)
    }
    

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

    package main
    
    import "fmt"
    
    type Student struct {
    	Name string
    	Age  int
    }
    
    type Stu Student
    type integer int
    
    func main() {
    	var stu1 Student
    	var stu2 Stu
    
    	stu2 = Stu(stu1)
    	fmt.Println(stu1, stu2)
    
    	var i integer = 10
    	var j int = 20
    	j = int(i)
    	fmt.Println(i, j)
    }
    

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

    package main
    
    import (
    	"encoding/json"
    	"fmt"
    )
    
    type Monster struct {
    	Name  string `json:"name"`
    	Age   int    `json:"age"`
    	Skill string `json:"skill"`
    }
    
    func main() {
    	monster := Monster{"牛魔王", 89, "大犄角"}
    	jsonStr, err := json.Marshal(monster)
    	if err != nil {
    		fmt.Println("json处理错误 ", err)
    	}
    	fmt.Println("jsonStr", string(jsonStr))
    }
    

    5、创建结构体变量时指定字段值

    Golang 在创建结构体实例(变量)时,可以直接指定字段的值

    package main
    
    import "fmt"
    
    type Stu struct {
    	Name string
    	Age  int
    }
    
    func main() {
    	var stu1 = Stu{"小明", 19}
    	stu2 := Stu{"小黄", 40}
    
    	var stu3 = Stu{
    		Name: "jack",
    		Age:  20,
    	}
    
    	stu4 := Stu{
    		Age:  50,
    		Name: "mary",
    	}
    	fmt.Println(stu1, stu2, stu3, stu4)
    
    	var stu5 *Stu = &Stu{"小王", 29}
    	stu6 := &Stu{"小东", 35}
    	var stu7 = &Stu{
    		Name: "小李",
    		Age:  20,
    	}
    	stu8 := &Stu{
    		Age:  66,
    		Name: "张六",
    	}
    	fmt.Println(*stu5, *stu6, *stu7, *stu8)
    }
    

    二、方法

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

    1、方法的声明和调用

    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 Person struct {
    	Name string
    }
    
    func (p Person) test() {
    	fmt.Println("test() name=", p.Name)
    }
    
    func main() {
    	var p Person
    	p.Name = "tom"
    	p.test()
    }
    

    test方法和Person类型绑定

    test方法只能通过Person类型的变量来调用,而不能直接调用,也不能使用其它类型变量来调用
    func (p Person) test() {}... p表示哪个Person变量调用,这个p就是它的副本, 这点和函数传参非常相似。

    package main
    
    import "fmt"
    
    type Person struct {
    	Name string
    }
    
    func (p Person) speak() {
    	fmt.Println(p.Name, "是一个goodman~")
    }
    
    func (p Person) cal() {
    	res := 0
    	for i := 1; i <= 1000; i++ {
    		res += i
    	}
    	fmt.Println(p.Name, "计算结果是", res)
    }
    
    func (p Person) cal2(n int) {
    	res := 0
    	for i := 1; i <= n; i++ {
    		res += i
    	}
    	fmt.Println(p.Name, "计算结果是", res)
    }
    
    func (p Person) getSum(n1 int, n2 int) int {
    	return n1 + n2
    }
    
    func main() {
    	var p Person
    	p.speak()
    	p.cal()
    	p.cal2(100)
    	res := p.getSum(10, 20)
    	fmt.Println("res=", res)
    }
    

     在通过一个变量去调用方法时,其调用机制和函数一样。不一样的地方时,变量调用方法时,该变量本身也会作为一个参数传递到方法(如果变量是值类型,则进行值拷贝,如果变量是引用类型,则进行地址拷贝)。

    package main
    
    import "fmt"
    
    type Circle struct {
    	radius float64
    }
    
    func (c Circle) area() float64 {
    	return 3.14 * c.radius * c.radius
    }
    
    func main() {
    	var c Circle
    	c.radius = 4.0
    	res := c.area()
    	fmt.Println("面积是", res)
    }
    

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

      方法体

      return 返回值

    }

    参数列表:表示方法输入
    recevier type : 表示这个方法和type这个类型进行绑定,或者说该方法作用于type类型
    receiver type : type可以是结构体,也可以其它的自定义类型
    receiver : 就是type类型的一个变量(实例),比如 :Person结构体 的一个变量(实例)
    返回值列表:表示返回的值,可以多个
    方法主体:表示为了实现某一功能代码块
    return语句不是必须的。

    2、方法的注意事项

    (1)、结构体类型是值类型,在方法调用中,遵守值类型的传递机制,是值拷贝传递方式
    (2)、如希望在方法中,修改结构体变量的值,可以通过结构体指针的方式来处理

    package main
    
    import "fmt"
    
    type Circle struct {
    	radius float64
    }
    
    func (c *Circle) area2() float64 {
    	c.radius = 10
    	return 3.14 * c.radius * c.radius
    }
    
    func main() {
    	var c Circle
    	c.radius = 4.0
    	res := c.area2()
    	fmt.Println("面积是", res)
    	fmt.Println("radius=", c.radius)
    }
    

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

    package main
    
    import "fmt"
    
    type integer int
    
    func (i integer) print() {
    	fmt.Println("i=", i)
    }
    
    func (i *integer) change() {
    	*i = *i + 1
    }
    
    func main() {
    	var i integer = 10
    	i.print()
    	i.change()
    	fmt.Println("i=", i)
    }
    

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

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

    package main
    
    import "fmt"
    
    type Student struct {
    	Name string
    	Age  int
    }
    
    func (stu *Student) String() string {
    	str := fmt.Sprintf("Name=[%v] Age=[%v]", stu.Name, stu.Age)
    	return str
    }
    func main() {
    	stu := Student{
    		Name: "tom",
    		Age:  20,
    	}
    	fmt.Println(&stu)
    }
    

    3、方法和函数的区别

    (1)、调用方式不一样
    函数的调用方式: 函数名(实参列表)
    方法的调用方式: 变量.方法名(实参列表)
    (2)、对于普通函数,接收者为值类型时,不能将指针类型的数据直接传递,反之亦然
    (3)、对于方法(如struct的方法),接收者为值类型时,可以直接用指针类型的变量调用方法,反过来同样也可以

    三、工厂模式

    结构体的声明如下:

    package model

    type Student struct{

      Name string

    }

    这里的Student的首字母S是大写的,如果想在其它包创建Student的实例,引入model包后,就可以直接创建Student结构体的变量(实例)。如果首字母是小写的,就不可行了,需要工厂模式来解决。

    package model
    
    type student struct {
    	Name  string
    	Score float64
    }
    
    func NewStudent(n string, s float64) *student {
    	return &student{
    		Name:  n,
    		Score: s,
    	}
    }
    
    
    package main
    
    import (
    	"code/model"
    	"fmt"
    )
    
    func main() {
    	var stu = model.NewStudent("tom", 88.8)
    	fmt.Println(*stu)
    	fmt.Println("name=", stu.Name, " score=", stu.Score)
    }
    

    四、面向对象编程

    1、抽象

    把一类事物的共有的属性(字段)和行为(方法)提取 出来,形成一个物理模型(结构体)。

    package main
    
    import (
    	"fmt"
    )
    
    type Account struct {
    	AccountNo string
    	Pwd       string
    	Balance   float64
    }
    
    func (account *Account) Deposite(money float64, pwd string) {
    	if pwd != account.Pwd {
    		fmt.Println("输入的密码不正确")
    		return
    	}
    
    	if money <= 0 {
    		fmt.Println("输入的金额不正确")
    		return
    	}
    
    	account.Balance += money
    	fmt.Println("存款成功~~")
    }
    
    func (account *Account) WithDraw(money float64, pwd string) {
    	if pwd != account.Pwd {
    		fmt.Println("输入的密码不正确")
    		return
    	}
    
    	if money <= 0 || money > account.Balance {
    		fmt.Println("输入的金额不正确")
    		return
    	}
    
    	account.Balance -= money
    	fmt.Println("取款成功~~")
    }
    
    func (account *Account) Query(pwd string) {
    	if pwd != account.Pwd {
    		fmt.Println("输入的密码不正确")
    		return
    	}
    
    	fmt.Printf("账号%v的余额是%v
    ", account.AccountNo, account.Balance)
    }
    
    func main() {
    	account := Account{
    		AccountNo: "gs111111",
    		Pwd:       "666666",
    		Balance:   100.0,
    	}
    
    	account.Query("666666")
    	account.Deposite(200.0, "666666")
    	account.Query("666666")
    	account.WithDraw(150.0, "666666")
    	account.Query("666666")
    }
    

    2、封装

    Golang仍然有面向对象编程的继承,封装和多态的特性,只是实现的方式和其它OOP语言不一样。
    封装(encapsulation)就是把抽象出的字段和对字段的操作封装在一起,数据被保护在内部,程序的其它包只有通过被授权的操作(方法),才能对字段进行操作。
    封装的好处:隐藏实现细节;可以对数据进行验证,保证安全合理。
    如何实现封装:对结构体中的属性进行封装;通过方法,包实现封装。

    封装的实现步骤:

    (1)、将结构体、字段(属性)的首字母小写(不能导出了,其它包不能使用,类似private)
    (2)、给结构体所在包提供一个工厂模式的函数,首字母大写。类似一个构造函数
    (3)、提供一个首字母大写的Set方法(类似其它语言的public),用于对属性判断并赋值
    func (var 结构体类型名) SetXxx(参数列表) (返回值列表) {
      //加入数据验证的业务逻辑
      var.字段 = 参数
    }
    (4)、提供一个首字母大写的Get方法(类似其它语言的public),用于获取属性的值
    func (var 结构体类型名) GetXxx() {
      return var.age;
    }
    特别说明:在Golang开发中并没有特别强调封装,这点并不像Java。不用总是用java的语法特性来看待Golang, Golang本身对面向对象的特性做了简化的。

    model/person.go

    package model
    
    import "fmt"
    
    type person struct {
    	Name string
    	age  int
    	sal  float64
    }
    
    //工厂模式的函数,相当于构造函数
    func NewPerson(name string) *person {
    	return &person{
    		Name: name,
    	}
    }
    
    //为了访问age和sal,编写一对SexXxx的方法和GetXxx的方法
    func (p *person) SetAge(age int) {
    	if age > 0 && age < 150 {
    		p.age = age
    	} else {
    		fmt.Println("年龄范围不正确")
    	}
    }
    
    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("薪水范围不正确")
    	}
    }
    
    func (p *person) GetSal() float64 {
    	return p.sal
    }
    

     main/main.go

    package main
    
    import (
    	"fmt"
    	"model"
    )
    
    func main() {
    	p := model.NewPerson("smith")
    	p.SetAge(18)
    	p.SetSal(5000)
    	fmt.Println(*p)
    	fmt.Println(p.Name, " age=", p.GetAge(), " sal=", p.GetSal())
    }
    

    model/account.go

    package model
    
    import "fmt"
    
    type account struct {
    	accountNo string
    	pwd       string
    	balance   float64
    }
    
    func NewAccount(accountNo string, pwd string, balance float64) *account {
    	if len(accountNo) < 6 || len(accountNo) > 10 {
    		fmt.Println("账号的长度不对")
    		return nil
    	}
    
    	if len(pwd) != 6 {
    		fmt.Println("密码的长度不对")
    		return nil
    	}
    
    	if balance < 20 {
    		fmt.Println("余额数目不对")
    		return nil
    	}
    
    	return &account{
    		accountNo: accountNo,
    		pwd:       pwd,
    		balance:   balance,
    	}
    }
    
    func (account *account) Deposite(money float64, pwd string) {
    	if pwd != account.pwd {
    		fmt.Println("输入的密码不正确")
    		return
    	}
    
    	if money <= 0 {
    		fmt.Println("输入的金额不正确")
    		return
    	}
    
    	account.balance += money
    	fmt.Println("存款成功")
    }
    
    func (account *account) WithDraw(money float64, pwd string) {
    	if pwd != account.pwd {
    		fmt.Println("输入的密码不正确")
    		return
    	}
    
    	if money <= 0 || money > account.balance {
    		fmt.Println("输入的金额不正确")
    		return
    	}
    
    	account.balance -= money
    	fmt.Println("取款成功")
    }
    
    func (account *account) Query(pwd string) {
    	if pwd != account.pwd {
    		fmt.Println("输入的密码不正确")
    		return
    	}
    	fmt.Printf("账号%v的余额为%v
    ", account.accountNo, account.balance)
    }
    

     main/main.go

    package main
    
    import (
    	"fmt"
    	"model"
    )
    
    func main() {
    	account := model.NewAccount("test11", "999", 40)
    	if account != nil {
    		fmt.Println("创建成功 ", account)
    	} else {
    		fmt.Println("创建失败")
    	}
    }
    

    3、继承

    在Golang中,如果一个struct嵌套了另一个匿名结构体,那么这个结构体可以直接访问匿名结构体的字段和方法,从而实现了继承特性。

    package main
    
    import (
    	"fmt"
    )
    
    type Student struct {
    	Name  string
    	Age   int
    	Score int
    }
    
    func (stu *Student) ShowInfo() {
    	fmt.Printf("学生姓名=%v 年龄=%v 成绩=%v
    ", stu.Name, stu.Age, stu.Score)
    }
    
    func (stu *Student) SetScore(score int) {
    	stu.Score = score
    }
    
    type Pupil struct {
    	Student
    }
    
    func (p *Pupil) testing() {
    	fmt.Println("小学生正在考试中...")
    }
    
    type Graduate struct {
    	Student
    }
    
    func (p *Graduate) testing() {
    	fmt.Println("大学生正在考试中...")
    }
    
    func main() {
    	pupil := &Pupil{}
    	pupil.Student.Name = "tom"
    	pupil.Student.Age = 8
    	pupil.testing()
    	pupil.Student.SetScore(70)
    	pupil.Student.ShowInfo()
    
    	graduate := &Graduate{}
    	graduate.Student.Name = "mary"
    	graduate.Student.Age = 28
    	graduate.testing()
    	graduate.Student.SetScore(90)
    	graduate.Student.ShowInfo()
    }
    

    继承的好处:提高了代码的复用性、扩展性和可维护性。

    结构体可以使用嵌套匿名结构体所有的字段和方法,即:首字母大写或者小写的字段、方法, 都可以使用。

    匿名结构体字段访问可以简化。

    package main
    
    import "fmt"
    
    type A struct {
    	Name string
    	age  int
    }
    
    func (a *A) SayOk() {
    	fmt.Println("A SayOk", a.Name)
    }
    
    func (a *A) hello() {
    	fmt.Println("A hello", a.Name)
    }
    
    type B struct {
    	A
    }
    
    func main() {
    	var b B
    	b.A.Name = "tom"
    	b.A.age = 90
    
    	b.A.SayOk()
    	b.A.hello()
    
    	b.Name = "smith"
    	b.age = 20
    	b.SayOk()
    	b.hello()
    }
    

    当直接通过b访问字段或方法时,其执行流程如下:

    比如b.Name,编译器会先看b对应的类型有没有Name, 如果有,则直接调用B类型的Name字段,如果没有就去看B中嵌入的匿名结构体A有没有声明Name字段,如果有就调用,如果没有继续查找..如果都找不到就报错。
    当结构体和匿名结构体有相同的字段或者方法时,编译器采用就近访问原则访问,如希望访问匿名结构体的字段和方法,可以通过匿名结构体名来区分。

    结构体嵌入两个(或多个)匿名结构体,如两个匿名结构体有相同的字段和方法(同时结构体本身没有同名的字段和方法),在访问时,就必须明确指定匿名结构体名字,否则编译报错。
    如果一个struct嵌套了一个有名结构体,这种模式就是组合,如果是组合关系,那么在访问组合的结构体的字段或方法时,必须带上结构体的名字。

    嵌套匿名结构体后,也可以在创建结构体变量(实例)时,直接指定各个匿名结构体字段的值。

    package main
    
    import "fmt"
    
    type Goods struct {
    	Name  string
    	Price float64
    }
    
    type Brand struct {
    	Name    string
    	Address string
    }
    
    type TV struct {
    	Goods
    	Brand
    }
    
    type TV2 struct {
    	*Goods
    	*Brand
    }
    
    func main() {
    	tv := TV{Goods{"电视机001", 5000.99}, Brand{"海尔", "山东"}}
    
    	tv2 := TV{
    		Goods{
    			Price: 500.88,
    			Name:  "电视机002",
    		},
    		Brand{
    			Name:    "夏普",
    			Address: "北京",
    		},
    	}
    
    	fmt.Println("tv", tv)
    	fmt.Println("tv2", tv2)
    
    	tv3 := TV2{
    		&Goods{"电视机003", 700.88},
    		&Brand{"创维", "河南"},
    	}
    	tv4 := TV2{
    		&Goods{
    			Name:  "电视机004",
    			Price: 9000.00,
    		},
    		&Brand{
    			Name:    "长虹",
    			Address: "四川",
    		},
    	}
    
    	fmt.Println("tv3", *tv3.Goods, *tv3.Brand)
    	fmt.Println("tv4", *tv4.Goods, *tv4.Brand)
    }
    

    访问结构体的匿名字段是基本数据类型:

    package main
    
    import "fmt"
    
    type Monster struct {
    	Name string
    	Age  int
    }
    
    type E struct {
    	Monster
    	int
    	n int
    }
    
    func main() {
    	var e E
    	e.Name = "蚂蚁"
    	e.Age = 300
    	e.int = 20
    	e.n = 40
    	fmt.Println("e=", e)
    }
    

    如果一个结构体有int类型的匿名字段,就不能第二个。

    如果需要有多个int的字段,则必须给int字段指定名字。

    4、多重继承

    如一个struct嵌套了多个匿名结构体,那么该结构体可以直接访问嵌套的匿名结构体的字段和方法,从而实现了多重继承。

    package main
    
    import "fmt"
    
    type Goods struct {
    	Name  string
    	Price float64
    }
    
    type Brand struct {
    	Name    string
    	Address string
    }
    
    type TV struct {
    	Goods
    	Brand
    }
    
    func main() {
    	var tv TV = TV{Goods{Name: "冰箱", Price: 999.99,}, Brand{Name: "海尔", Address: "大连"}}
    	fmt.Println(tv.Goods.Name)
    	fmt.Println(tv.Price)
    }
    

    如果嵌入的匿名结构体有相同的字段名或者方法名,则在访问时,需要通过匿名结构体类型名来区分。

    五、接口

    在Golang中多态特性主要是通过接口来体现的。

    package main
    
    import "fmt"
    
    type Usb interface {
    	Start()
    	Stop()
    }
    
    type Phone struct {
    }
    
    func (p Phone) Start() {
    	fmt.Println("手机开始工作...")
    }
    
    func (p Phone) Stop() {
    	fmt.Println("手机停止工作...")
    }
    
    type Camera struct {
    }
    
    func (c Camera) Start() {
    	fmt.Println("相机开始工作...")
    }
    
    func (c Camera) Stop() {
    	fmt.Println("相机停止工作...")
    }
    
    type Computer struct {
    }
    
    func (c Computer) Working(usb Usb) {
    	usb.Start()
    	usb.Stop()
    }
    
    func main() {
    	computer := Computer{}
    	phone := Phone{}
    	camera := Camera{}
    
    	computer.Working(phone)
    	computer.Working(camera)
    }
    

     interface类型可以定义一组方法,但是这些不需要实现。并且interface不能包含任何变量。到某个自定义类型(比如结构体Phone)要使用的时候,在根据具体情况把这些方法写出来(实现)。

    type 接口名称 interface{

      method1(参数列表)返回值列表

      method2(参数列表)返回值列表

      ......

    }

    接口里的所有方法都没有方法体,即接口的方法都是没有实现的方法。接口体现了程序设计的多态和高内聚低偶合的思想。
    Golang中的接口,不需要显式的实现。只要一个变量,含有接口类型中的所有方法,那么这个变量就实现这个接口。因此,Golang中没有implement这样的关键字。

    1、接口使用的注意事项

    (1)、接口本身不能创建实例,但是可以指向一个实现了该接口的自定义类型的变量(实例)。

    package main
    
    import "fmt"
    
    type AInterface interface {
    	Say()
    }
    
    type Stu struct {
    	Name string
    }
    
    func (stu Stu) Say() {
    	fmt.Println("Stu Say()")
    }
    
    func main() {
    	var stu Stu
    	var a AInterface = stu
    	a.Say()
    }
    

    (2)、接口中所有的方法都没有方法体,即都是没有实现的方法。
    (3)、在Golang中,一个自定义类型需要将某个接口的所有方法都实现,我们说这个自定义类型实现了该接口。
    (4)、一个自定义类型只有实现了某个接口,才能将该自定义类型的实例(变量)赋给接口类型。
    (5)、只要是自定义数据类型,就可以实现接口,不仅仅是结构体类型。

    package main
    
    import "fmt"
    
    type AInterface interface {
    	Say()
    }
    
    type integer int
    
    func (i integer) Say() {
    	fmt.Println("integer Say i = ", i)
    }
    
    func main() {
    	var i integer = 10
    	var b AInterface = i
    	b.Say()
    }
    

    (6)、一个自定义类型可以实现多个接口。

    package main
    
    import "fmt"
    
    type AInterface interface {
    	Say()
    }
    
    type BInterface interface {
    	Hello()
    }
    
    type Monster struct {
    }
    
    func (m Monster) Hello() {
    	fmt.Println("Monster Hello()~~")
    }
    
    func (m Monster) Say() {
    	fmt.Println("Monster Say()~~")
    }
    
    func main() {
    	var monster Monster
    	var a AInterface = monster
    	var b BInterface = monster
    	a.Say()
    	b.Hello()
    }
    

    (7)、Golang接口中不能有任何变量。

    (8)、一个接口(比如 A 接口)可以继承多个别的接口(比如 B,C 接口),这时如果要实现 A 接口,也必 须将 B,C 接口的方法也全部实现。

    package main
    
    import "fmt"
    
    type BInterface interface {
    	test01()
    }
    
    type CInterface interface {
    	test02()
    }
    
    type AInterface interface {
    	BInterface
    	CInterface
    	test03()
    }
    
    type Stu struct {
    }
    
    func (stu Stu) test01() {
    	fmt.Println("test01()")
    }
    
    func (stu Stu) test02() {
    	fmt.Println("test02()")
    }
    
    func (stu Stu) test03() {
    	fmt.Println("test03()")
    }
    
    func main() {
    	var stu Stu
    	var a AInterface = stu
    	a.test01()
    }
    

    (9)、interface类型默认是一个指针(引用类型),如果没有对interface初始化就使用,那么会输出nil。

    (10)、空接口interface{} 没有任何方法,所以所有类型都实现了空接口, 即我们可以把任何一个变量赋给空接口。

    package main
    
    import "fmt"
    
    type Usb interface {
    	Say()
    }
    
    type Stu struct {
    }
    
    func (this *Stu) Say() {
    	fmt.Println("Say()")
    }
    
    func main() {
    	var stu Stu = Stu{}
    
    	var u Usb = &stu
    	u.Say()
    	fmt.Println("here", u)
    }
    

     实现对Hero结构体切片的排序: sort.Sort(data Interface)

    package main
    
    import (
    	"fmt"
    	"math/rand"
    	"sort"
    )
    
    type Hero struct {
    	Name string
    	Age  int
    }
    
    type HeroSlice []Hero
    
    func (hs HeroSlice) Len() int {
    	return len(hs)
    }
    
    func (hs HeroSlice) Less(i, j int) bool {
    	return hs[i].Age < hs[j].Age
    }
    
    func (hs HeroSlice) Swap(i, j int) {
    	hs[i], hs[j] = hs[j], hs[i]
    }
    
    type Student struct {
    	Name  string
    	Age   int
    	Score float64
    }
    
    func main() {
    	var intSlice = []int{0, -1, 10, 7, 90}
    	sort.Ints(intSlice)
    	fmt.Println(intSlice)
    
    	var heroes HeroSlice
    	for i := 0; i < 10; i++ {
    		hero := Hero{
    			Name: fmt.Sprintf("英雄|%d", rand.Intn(100)),
    			Age:  rand.Intn(100),
    		}
    		heroes = append(heroes, hero)
    	}
    
    	for k, v := range heroes {
    		fmt.Println(k, v)
    	}
    	sort.Sort(heroes)
    	fmt.Println("---------排序后---------")
    	for _, val := range heroes {
    		fmt.Println(val)
    	}
    }
    

    六、多态

    变量(实例)具有多种形态。面向对象的第三大特征,在Go语言,多态特征是通过接口实现的。可 以按照统一的接口来调用不同的实现。这时接口变量就呈现不同的形态。

    接口体现多态的两种形式:多态参数和多态数组

    1、多态参数

    package main
    
    import (
    	"fmt"
    )
    
    type Usb interface {
    	Start()
    	Stop()
    }
    
    type Phone struct {
    }
    
    func (p Phone) Start() {
    	fmt.Println("手机开始工作...")
    }
    
    func (p Phone) Stop() {
    	fmt.Println("手机停止工作...")
    }
    
    type Camera struct {
    }
    
    func (c Camera) Start() {
    	fmt.Println("相机开始工作...")
    }
    
    func (c Camera) Stop() {
    	fmt.Println("相机停止工作...")
    }
    
    type Computer struct {
    }
    
    func (c Computer) Working(usb Usb) {
    	usb.Start()
    	usb.Stop()
    }
    
    func main() {
    	computer := Computer{}
    	phone := Phone{}
    	camera := Camera{}
    
    	computer.Working(phone)
    	computer.Working(camera)
    }
    

     2、多态数组

    package main
    
    import (
    	"fmt"
    )
    
    type Usb interface {
    	Start()
    	Stop()
    }
    
    type Phone struct {
    	Name string
    }
    
    func (p Phone) Start() {
    	fmt.Println("手机开始工作...")
    }
    
    func (p Phone) Stop() {
    	fmt.Println("手机停止工作...")
    }
    
    type Camera struct {
    	Name string
    }
    
    func (c Camera) Start() {
    	fmt.Println("相机开始工作...")
    }
    
    func (c Camera) Stop() {
    	fmt.Println("相机停止工作...")
    }
    
    func main() {
    	var usbArr [3]Usb
    	usbArr[0] = Phone{"vivo"}
    	usbArr[1] = Phone{"小米"}
    	usbArr[2] = Camera{"尼康"}
    	fmt.Println(usbArr)
    }
    

    七、类型断言

    类型断言,由于接口是一般类型,不知道具体类型,如果要转成具体类型,就需要使用类型断言。

    package main
    
    import "fmt"
    
    type Point struct {
    	x int
    	y int
    }
    
    func main() {
    	var a interface{}
    	var point Point = Point{1, 2}
    	a = point
    	var b Point
    	//b=a.(Point)就是类型断言,表示判断a是否指向Point类型的变量,如果是就转成Point类型并赋给b变量,否则报错。
    	b = a.(Point)
    	fmt.Println(b)
    }
    

    在进行类型断言时,如果类型不匹配,就会报 panic, 因此进行类型断言时,要确保原来的空接口指向的就是断言的类型。

    package main
    
    import "fmt"
    
    func main() {
    	var x interface{}
    	var b float32 = 1.1
    	x = b
    
    	if y, ok := x.(float32); ok {
    		fmt.Println("convert success")
    		fmt.Printf("y的类型是%T 值是%v
    ", y, y)
    	} else {
    		fmt.Println("convert fail")
    	}
    	fmt.Printf("继续执行...")
    }
    

    类型断言最佳实践1:

    package main
    
    import (
    	"fmt"
    )
    
    type Usb interface {
    	Start()
    	Stop()
    }
    
    type Phone struct {
    	Name string
    }
    
    func (p Phone) Start() {
    	fmt.Println("手机开始工作...")
    }
    
    func (p Phone) Stop() {
    	fmt.Println("手机停止工作...")
    }
    
    func (p Phone) Call() {
    	fmt.Println("手机在打电话...")
    }
    
    type Camera struct {
    	Name string
    }
    
    func (c Camera) Start() {
    	fmt.Println("相机开始工作...")
    }
    
    func (c Camera) Stop() {
    	fmt.Println("相机停止工作...")
    }
    
    type Computer struct {
    }
    
    func (computer Computer) Working(usb Usb) {
    	usb.Start()
    
    	if phone, ok := usb.(Phone); ok {
    		phone.Call()
    	}
    
    	usb.Stop()
    }
    
    func main() {
    	var usbArr [3]Usb
    	usbArr[0] = Phone{"vivo"}
    	usbArr[1] = Phone{"小米"}
    	usbArr[2] = Camera{"尼康"}
    
    	var computer Computer
    	for _, v := range usbArr {
    		computer.Working(v)
    		fmt.Println()
    	}
    }
    

    类型断言最佳实践2:

    package main
    
    import "fmt"
    
    type Student struct {
    	Name  string
    	Age   int
    	Score int
    }
    
    //写一函数,循环判断传入参数的类型
    func TypeJudge(items ...interface{}) {
    	for index, x := range items {
    		switch x.(type) {
    		case bool:
    			fmt.Printf("第%v个参数是bool类型,值是%v
    ", index, x)
    		case float32:
    			fmt.Printf("第%v个参数是float32类型,值是%v
    ", index, x)
    		case float64:
    			fmt.Printf("第%v个参数是float64类型,值是%v
    ", index, x)
    		case int, int32, int64:
    			fmt.Printf("第%v个参数是整数类型,值是%v
    ", index, x)
    		case string:
    			fmt.Printf("第%v个参数是string类型,值是%v
    ", index, x)
    		case Student:
    			fmt.Printf("第%v个参数是Student类型,值是%v
    ", index, x)
    		case *Student:
    			fmt.Printf("第%v个参数是*Student类型,值是%v
    ", index, x)
    		default:
    			fmt.Printf("第%v个参数是类型不确定,值是%v
    ", index, x)
    		}
    	}
    }
    
    func main() {
    	var n1 float32 = 1.1
    	var n2 float64 = 2.3
    	var n3 int32 = 30
    	var name string = "tom"
    	address := "北京"
    	n4 := 300
    
    	stu := Student{Name: "nancy", Age: 45, Score: 90}
    
    	TypeJudge(n1, n2, n3, name, address, n4, stu, &stu)
    }
    
  • 相关阅读:
    PHP $_GET 获取 HTML表单(Form) 或url数据
    dedecms {dede:php}标签用法介绍
    php 连接mysql实例代码
    php 常量、变量用法详细介绍
    mysql出现too many connections错误提示
    支持中文字母数字、自定义字体php验证码程序
    我的LinqToSql学习笔记(1)
    使用Git新建项目 (命令行)
    使用SQL Server Profiler
    sqlserver2008 中使用 表值 参数
  • 原文地址:https://www.cnblogs.com/xidian2014/p/10586316.html
Copyright © 2011-2022 走看看