Go基础07:面向对象编程

概述

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

  • Golang 没有类(class),Go语言的结构体(struct)和其它编程语言的类(class)有同等的地位,你可以理解 Golang 是基于 struct来实现 OOP特性的。

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

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

  • Golang 面向对象(OOP)很优雅,OOP 本身就是语言类型系统(type system)的一部分,通过接口(interface)关联,耦合性低,也非常灵活。后面同学们会充分体会到这个特点。也就是说在 Golang 中面向接口编程是非常重要的特性。

结构体定义

package main

import "fmt"

// 定义 Person 结构体
type Person struct {

	// 定义属性
	Name   string
	Age    int
	Scores [5]float64
	ptr    *int              // 指针
	slice  []int             // 切片
	mp     map[string]string // Map
}

// 定义方法
func (p Person) test() {
	fmt.Println(p.Name)
}

func main() {

	a := 1

	// 创建一个 Person 结构体变量
	var p Person
	p.Name = "aa"
	p.Age = 18
	p.ptr = &a

	p.slice = make([]int, 10)
	p.slice[0] = 100

	p.mp = make(map[string]string)
	p.mp["key"] = "val"

	p.test() // aa

	fmt.Println(p)      // {aa 18 [0 0 0 0 0] 0xc0000ac058 [100 0 0 0 0 0 0 0 0 0] map[key:val]}
	fmt.Println(p.Name) // aa
	fmt.Println(p.Age)  // 18
}

实例化结构体

package main

import "fmt"

type Person struct {
	Name string
	Age  int
}

func main() {
	// 方式一
	var p1 Person
	p1.Name = "aa"
	p1.Age = 18
	fmt.Println(p1) // {aa 18}

	// 方式二
	p2 := Person{
		Name: "bb",
		Age:  20,
	}
	fmt.Println(p2) // {bb 20}

	// 方式三
	var p3 = new(Person)
	p3.Name = "cc"
	p3.Age = 22
	fmt.Println(*p3) // {cc 22}

	// 方式四
	var p4 = &Person{}
	p4.Name = "dd"
	p4.Age = 24
	fmt.Println(*p4) // {dd 24}
}

使用说明:

  1. 第三种和第四种方式返回的是结构体指针。
  2. 结构体指针访问字段的标准方式应该是:(*结构体指针).字段名,比如:(*p3).Name = "cc"
  3. 但 Go 做了一个简化,也支持 结构体指针.字段名,比如:p3.Name = "cc"。更加符合程序员使用的习惯,Go 编译器底层对 p3.Name 做了转化 (*p3).Name

使用细节 :

  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.Println(r1) // {\{1 2} {3 4}}
    	// r1有四个int,在内存中是连续分布的
    	fmt.Printf("%p,%p,%p,%p", &r1.leftUp.x, &r1.leftUp.y, &r1.rightDown.x, &r1.rightDown.y) 
        // 输出结果:0xc0000aa080,0xc0000aa088,0xc0000aa090,0xc0000aa098
    
    	r2 := Rect2{&Point{10, 20}, &Point{30, 40}}
    	fmt.Println(r2) // {\{10 20} {30 40}}
    	// r2有两个*Point类型,这两个*Point类型本身地址是连续的,但是他们指向的地址不一定是连续
    	fmt.Printf("%p,%p,%p,%p", &r2.leftUp.x, &r2.leftUp.y, &r2.rightDown.x, &r2.rightDown.y) 
        // 输出结果:0xc000014100,0xc000014108,0xc000014110,0xc000014118
    }
    
  2. 结构体是用户单独定义的类型,和其它类型进行转换时需要有完全相同的字段(名字、个数和类型)。

    package main
    
    import "fmt"
    
    type A struct {
    	Num int
    }
    
    type B struct {
    	Num int
    }
    
    type C struct {
    	Age int
    }
    
    func main() {
    	var a A
    	var b B
    
    	a = A(b)          // 结构体的字段一样,可以转换
    	fmt.Println(a, b) // {0} {0}
    
    	// var c C
    	// a = A(c) // 结构体的字段不一样,不可转换
    }
    
  3. 结构体进行 type 重新定义(相当于取别名),Golang 认为是新的数据类型,但是相互间可以强转。

    package main
    
    import "fmt"
    
    type Student struct {
    	Name string
    	Age  int
    }
    
    type Stu Student
    
    func main() {
    	var stu1 Student
    	var stu2 Stu
    
    	// stu2 = stu1 // error
    	stu2 = Stu(stu1) // ok
    
    	fmt.Println(stu2) // { 0}
    }
    
  4. struct 的每个字段上,可以写上一个 tag,该 tag 可以通过反射机制获取,常见的使用场景就是序列化和反序列化。

    package main
    
    import (
    	"encoding/json"
    	"fmt"
    )
    
    type Student struct {
    	Name string `json:"name11"`
    	Age  int    `json:"age22"`
    }
    
    func main() {
    	stu := Student{"aa", 18}
    	fmt.Println(stu) // {aa 18}
    
    	jsonStr, err := json.Marshal(stu)
    
    	if err != nil {
    		fmt.Println("json 处理错误", err)
    	}
    
    	fmt.Println(string(jsonStr)) // {"name11":"aa","age22":18}
    }
    
  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{"aa", 18}
    	fmt.Println(&stu) // Name=[aa] Age=[18]
    }
    
  6. 方法的访问范围控制的规则,和函数一样。方法名首字母小写,只能在本包访问,方法首字母大写,可以在本包和其它包访问。

方法和函数区别:

  1. 调用方式不一样。

    • 函数的调用方式: 函数名(实参列表)
    • 方法的调用方式: 变量.方法名(实参列表)
  2. 对于普通函数,接收者为值类型时,不能将指针类型的数据直接传递,反之亦然。

    package main
    
    import "fmt"
    
    type Person struct {
    	Name string
    }
    
    func test1(p Person) {
    	fmt.Println(p.Name)
    }
    
    func test2(p *Person) {
    	fmt.Println(p.Name)
    }
    
    func (p Person) test3() {
    	p.Name = "bb"
    	fmt.Println(p.Name)
    }
    
    func (p *Person) test4() {
    	p.Name = "cc"
    	fmt.Println(p.Name)
    }
    
    func main() {
    	p := Person{"aa"}
    	test1(p)  // aa
    	test2(&p) // aa
    
    	p.test3()           // bb
    	fmt.Println(p.Name) // aa
    
    	// 从形式上是传入地址,但是本质还是值拷贝
    	(&p).test3()        // bb
    	fmt.Println(p.Name) // aa
    
    	// 从形式上是传入值类型,但是本质还是地址拷贝
    	p.test4()           // cc
    	fmt.Println(p.Name) // cc
    
    	(&p).test4()        // cc
    	fmt.Println(p.Name) // cc
    }
    
    • 不管调用形式如何,真正决定是值拷贝还是地址拷贝,看这个方法是和哪个类型绑定.

    • 如果是和值类型,比如 (p Person),则是值拷贝,如果和指针类型,比如是 (p *Person) 则是地址拷贝。

工厂模式

D:\GoWork\src\model\student.go

package model

type student struct {
	Name  string
	score float64
}

// 结构体变量首字母小写,引入后不能直接使用,可以使用工厂模式解决
func NewStudent(n string, s float64) *student {
	return &student{
		Name:  n,
		score: s,
	}
}

// score 属性首字母小写,其他包不可以直接访问,需要提供一个方法
func (s *student) GetScore() float64 {
	return s.score
}

D:\GoWork\src\main\main.go

package main

import (
	"fmt"
	"model"
)

func main() {
	var stu = model.NewStudent("aa", 88.8)

	fmt.Println(*stu)                     // {aa 88.8}
	fmt.Println(stu.Name, stu.GetScore()) // aa 88.8
}

抽象

定义一个结构体时候,实际上就是把一类事物的共有的属性(字段)和行为(方法)提取出来,形成一个物理模型(结构体)。这种研究问题的方法称为抽象。

package main

import "fmt"

// Account 定义一个结构体
type Account struct {
	AccountNo string
	Pwd       string
	Balance   float64
}

// Deposit 存款
func (account *Account) Deposit(money float64, pwd string) {

	// 判断密码是否正确
	if pwd != account.Pwd {
		fmt.Println("你输入的密码不正确")
		return
	}

	// 判断存储余额是否正确
	if money <= 0 {
		fmt.Println("你输入的金额不正确")
		return
	}

	account.Balance += money
	fmt.Println("存款成功")
}

// Withdraw 取款
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("取款成功")
}

// Query 查询余额
func (account *Account) Query(pwd string) {

	// 判断密码是否正确
	if pwd != account.Pwd {
		fmt.Println("你输入的密码不正确")
		return
	}

	fmt.Printf("你的账号为:%v,余额为:%v\n", account.AccountNo, account.Balance)
}

func main() {
	// 实例化一个账户
	account1 := Account{
		AccountNo: "6666",
		Pwd:       "123456",
		Balance:   100.0,
	}

	account1.Query("123456")          // 你的账号为:6666,余额为:100
	account1.Deposit(200.0, "123456") // 存款成功
	account1.Query("123456")          // 你的账号为:6666,余额为:300
	account1.Withdraw(150, "123456")  // 取款成功
	account1.Query("123456")          // 你的账号为:6666,余额为:150

	// 再实例化一个账户
	account2 := Account{
		AccountNo: "8888",
		Pwd:       "123456",
		Balance:   200.0,
	}

	account2.Query("123456")          // 你的账号为:888,余额为:200
	account2.Deposit(300.0, "123456") // 存款成功
	account2.Query("123456")          // 你的账号为:888,余额为:500
	account2.Withdraw(250, "123456")  // 取款成功
	account2.Query("123456")          // 你的账号为:888,余额为:250
}

接口

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

import "fmt"

// Usb 声明一个接口
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) Start() {
	fmt.Println("计算机开始工作...")
}

func (c Computer) Stop() {
	fmt.Println("计算机停止工作...")
}

// 编写一个方法 Working 方法,接收一个 Usb 接口类型变量
// 只要是实现了 Usb接口(所谓实现 Usb 接口,就是指实现了 Usb 接口声明所有方法)
func (c Computer) Working(usb Usb) { // usb变量会根据传入的实参,来判断到底是 Phone,还是 Camera
	// 通过 Usb 接口变量来调用 Start 和 Stop 方法
	usb.Start()
	usb.Stop()
}

func main() {
	// 测试
	computer := Computer{}
	phone := Phone{}
	camera := Camera{}
	// 关键点
	computer.Working(phone)
	computer.Working(camera)
}

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

接口使用总结:

  1. 接口本身不能创建实例,但是可以指向一个实现了该接口的自定义类型的变量(实例)。
  2. 接口中所有的方法都没有方法体,即都是没有实现的方法。
  3. 在 Golang 中,一个自定义类型需要将某个接口的所有方法都实现,我们说这个自定义类型实现了该接口。
  4. 一个自定义类型只有实现了某个接口,才能将该自定义类型的实例(变量)赋给接口类型。
  5. 只要是自定义数据类型,就可以实现接口,不仅仅是结构体类型。
  6. 一个自定义类型可以实现多个接口。
  7. Golang 接口中不能有任何变量。
  8. 一个接口(比如A接口)可以继承多个别的接口(比如B,C接口),这时如果要实现A接口,也必须将B,C接口的方法也全部实现。
  9. interface 类型默认是一个指针(引用类型),如果没有对 interface 初始化就使用,那么会输出 nil。
  10. 空接口 interface{} 没有任何方法,所以所有类型都实现了空接口,即我们可以把任何一个变量赋给空接口。

接口最佳实现:

package main

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

// 1. 声明 Hero 结构体
type Hero struct {
	Name string
	Age  int
}

// 2. 声明一个 Hero 结构体切片类型
type HeroSlice []Hero

// 3. 实现 Interface 接口
func (hs HeroSlice) Len() int {
	return len(hs)
}

// Less 方法就是决定你使用什么标准进行排序
// 1. 按 Hero 的年龄从小到大排序!!
func (hs HeroSlice) Less(i, j int) bool {
	return hs[i].Age < hs[j].Age
	// 修改成对 Name排序
	// return hs[i].Name < hs[j].Name
}
func (hs HeroSlice) Swap(i, j int) {
	// 交换
	// temp := hs[i]
	// hs[i] = hs[j]
	// hs[j] = temp
	// 下面的一句话等价于三句话
	hs[i], hs[j] = hs[j], hs[i]
}

// 1. 声明 Student结构体
type Student struct {
	Name  string
	Age   int
	Score float64
}

// 将 Student 的切片,按 Score 从大到小排序!!

func main() {
	// 先定义一个数组/切片
	var intSlice = []int{0, -1, 10, 7, 90}
	// 要求对  intSlice切片进行排序
	// 1.冒泡排序...
	// 2.也可以使用系统提供的方法
	sort.Ints(intSlice)
	fmt.Println(intSlice)
	// 请大家对结构体切片进行排序
	// 1.冒泡排序...
	// 2.也可以使用系统提供的方法
	// 测试看看我们是否可以对结构体切片进行排序
	var heroes HeroSlice
	for i := 0; i < 10; i++ {
		hero := Hero{
			Name: fmt.Sprintf("英雄|%d", rand.Intn(100)),
			Age:  rand.Intn(100),
		}
		// 将  hero append到  heroes切片
		heroes = append(heroes, hero)
	}
	// 看看排序前的顺序
	for _, v := range heroes {
		fmt.Println(v)
	}
	// 调用  sort.Sort
	sort.Sort(heroes)
	fmt.Println("-----------排序后------------")
	// 看看排序后的顺序
	for _, v := range heroes {
		fmt.Println(v)
	}
	i := 10
	j := 20
	i, j = j, i
	fmt.Println("i=", i, "j=", j) // i=20 j = 10
}

三大特征

封装

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

封装的理解和好处:

  1. 隐藏实现细节。
  2. 提可以对数据进行验证,保证安全合理。

如何体现封装:

  1. 对结构体中的属性进行封装。
  2. 通过方法,包实现封装。

封装的实现步骤:

  1. 将结构体、字段(属性)的首字母小写(不能导出了,其它包不能使用,类似private)。
  2. 给结构体所在包提供一个工厂模式的函数,首字母大写。类似一个构造函数。
  3. 提供一个首字母大写的 Set方法(类似其它语言的public),用于对属性判断并赋值。

代码实现案例:

D:\GoWork\src\model\person.go

package model

type person struct {
	Name string
	age  int
	sal  float64
}

// 写一个工厂模式的函数,相当于构造函数
func NewPerson(name string) *person {
	return &person{
		Name: name,
	}
}

func (p *person) GetAge() int {
	return p.age
}

func (p *person) SetAge(age int) {
	p.age = age
}

func (p *person) GetSal() float64 {
	return p.sal
}

func (p *person) SetSal(sal float64) {
	p.sal = sal
}

D:\GoWork\src\main\main.go

package main

import (
	"fmt"
	"model"
)

func main() {
	p := model.NewPerson("aa")

	p.SetAge(18)
	p.SetSal(3000)

	fmt.Println(*p)                             // {aa 18 3000}
	fmt.Println(p.Name, p.GetAge(), p.GetSal()) // aa 18 3000
}

继承

  1. 继承可以解决代码复用,扩展性和维护性提高了。
  2. 当多个结构体存在相同的属性(字段)和方法时,可以从这些结构体中抽象出结构体,在该结构体中定义这些相同的属性和方法。
  3. 其它的结构体不需要重新定义这些属性(字段)和方法,只需嵌套一个匿名结构体即可。
package main

import "fmt"

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

func (stu *Student) ShowInfo() {
	fmt.Println(stu.Name, stu.Age, stu.Score)
}

func (stu *Student) SetScore(score int) {
	stu.Score = score
}

// Pupil 小学生结构体
type Pupil struct {
	Student // 嵌入了 Student 匿名结构体
}

func (p *Pupil) testing() {
	fmt.Println("小学生正在考试中...")
}

// Graduate 大学生结构体
type Graduate struct {
	Student // 嵌入了 Student 匿名结构体
}

func (g *Graduate) testing() {
	fmt.Println("大学生正在考试中...")
}

func main() {
	pupil := &Pupil{}
	pupil.Student.Name = "aa"
	pupil.Student.Age = 8
	pupil.testing() // 小学生正在考试中...
	pupil.Student.SetScore(60)
	pupil.Student.ShowInfo() // aa 8 60

	graduate := &Graduate{}
	graduate.Name = "bb"
	graduate.Student.Age = 28
	graduate.testing() // 大学生正在考试中...
	graduate.Student.SetScore(80)
	graduate.Student.ShowInfo() // bb 28 80
}

使用总结:

  1. 结构体可以使用嵌套匿名结构体所有的字段和方法,即:首字母大写或者小写的字段、方法都可以使用。
  2. 匿名结构体字段访问可以简化,graduate.Student.Name = "bb" => graduate.Name = "bb"
  3. 当结构体和匿名结构体有相同的字段或者方法时,编译器采用就近访问原则访问,如希望访问匿名结构体的字段和方法,可以通过匿名结构体名来区分。
  4. 结构体嵌入两个(或多个)匿名结构体,如两个匿名结构体有相同的字段和方法 (同时结构体本身没有同名的字段和方法),在访问时,就必须明确指定匿名结构体名字,否则编译报错。
  5. 如果一个 struct 嵌套了一个有名结构体,这种模式就是组合,如果是组合关系,那么在访问组合的结构体的字段或方法时,必须带上结构体的名字。

多重继承:

type Goods struct {
	Name string
	Price float64
}

type Brand struct {
	Name string
	Address string
}

type LV struct {
	Goods
	Brand
}
  • 如嵌入的匿名结构体有相同的字段名或者方法名,则在访问时,需要通过匿名结构体类型名来区分。
  • 为了保证代码的简洁性,建议大家尽量不使用多重继承。

多态

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

package main

import "fmt"

// Usb 声明一个接口
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("相机停止工作...")
}

type Computer struct {
	name string
}

func (c Computer) Start() {
	fmt.Println("计算机开始工作...")
}

func (c Computer) Stop() {
	fmt.Println("计算机停止工作...")
}

// 编写一个方法 Working 方法,接收一个 Usb 接口类型变量
// 只要是实现了 Usb接口(所谓实现 Usb 接口,就是指实现了 Usb 接口声明所有方法)
func (c Computer) Working(usb Usb) { // usb变量会根据传入的实参,来判断到底是 Phone,还是 Camera
	// 通过 Usb 接口变量来调用 Start 和 Stop 方法
	usb.Start()
	usb.Stop()
}

func main() {
	// 定义一个 Usb 接口数组,可以存放 Phone 和 Camera 的结构体变量
	// 这里就体现出多态数组
	var usbArr [3]Usb
	usbArr[0] = Phone{"vivo"}
	usbArr[1] = Phone{"小米"}
	usbArr[2] = Camera{"尼康"}
	fmt.Println(usbArr) // [{vivo} {小米} {尼康}]
}

类型断言

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

var x interface{}
var b float32 = 1.1
x = b // 空接口,可以接收任意类型

// 类型断言
y := x.(float32)
fmt.Printf("y 的类型是 %T 值是 %v", y, y) // y 的类型是 float32 值是 1.1

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

var x interface{}
var b float32 = 1.1
x = b // 空接口,可以接收任意类型

// 类型断言(带检测的)
if y, ok := x.(float32); ok {
    fmt.Printf("y 的类型是 %T 值是 %v", y, y) // y 的类型是 float32 值是 1.1
} else {
    fmt.Println("convert fail")
}

在前面的 Usb 接口案例做改进:给 Phone 结构体增加一个特有的方法 call(),当 Usb 接口接收的是 Phone 变量时,还需要调用 call()。

package main

import "fmt"

// Usb 声明一个接口
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 {
	name string
}

func (c Computer) Start() {
	fmt.Println("计算机开始工作...")
}

func (c Computer) Stop() {
	fmt.Println("计算机停止工作...")
}

// 编写一个方法 Working 方法,接收一个 Usb 接口类型变量
// 只要是实现了 Usb接口(所谓实现 Usb 接口,就是指实现了 Usb 接口声明所有方法)
func (c Computer) Working(usb Usb) { // usb变量会根据传入的实参,来判断到底是 Phone,还是 Camera
	// 通过 Usb 接口变量来调用 Start 和 Stop 方法
	usb.Start()
	// 如果 Usb 是指向 Phone 结构体变量,则还需要调用 Call 方法
	if phone, ok := usb.(Phone); ok {
		phone.Call()
	}
	usb.Stop()
}

func main() {
	// 定义一个 Usb 接口数组,可以存放 Phone 和 Camera 的结构体变量
	// 这里就体现出多态数组
	var usbArr [3]Usb
	usbArr[0] = Phone{"vivo"}
	usbArr[1] = Phone{"小米"}
	usbArr[2] = Camera{"尼康"}
	fmt.Println(usbArr) // [{vivo} {小米} {尼康}]

	// 遍历 usbArr
	var computer Computer
	for _, v := range usbArr {
		computer.Working(v)
		fmt.Println()
	}
	/*输出结果:
	手机开始工作...
	手机在打电话..
	手机停止工作...
	手机开始工作...
	手机在打电话..
	手机停止工作...
	相机开始工作...
	相机停止工作...*/
}

类型断言的最佳实践:

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

package main

import "fmt"

func TypeJudge(items ...interface{}) {
	for index, item := range items {
		switch item.(type) {
		case bool:
			fmt.Printf("第%v个参数是 bool 类型,值是 %v\n", index, item)
		case float32:
			fmt.Printf("第%v个参数是 float32 类型,值是 %v\n", index, item)
		case float64:
			fmt.Printf("第%v个参数是 float64 类型,值是 %v\n", index, item)
		case int, int32, int64:
			fmt.Printf("第%v个参数是 int,int32,int64 类型,值是 %v\n", index, item)
		case string:
			fmt.Printf("第%v个参数是 string 类型,值是 %v\n", index, item)
		default:
			fmt.Printf("第%v个参数是 不确定 类型,值是 %v\n", index, item)
		}
	}
}

func main() {
	var n1 float32 = 1.1
	var n2 float64 = 2.2
	var n3 int32 = 33
	var name string = "aa"
	address := "bb"
	n4 := 444

	TypeJudge(n1, n2, n3, name, address, n4)
	/*输出结果:
	第0个参数是 float32 类型,值是 1.1
	第1个参数是 float64 类型,值是 2.2
	第2个参数是 int,int32,int64 类型,值是 33
	第3个参数是 string 类型,值是 aa
	第4个参数是 string 类型,值是 bb
	第5个参数是 int,int32,int64 类型,值是 444*/
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值