这个是在B站上看边看视频边做的笔记,这一章是Glang面向对象编程
这一章内容较多,内容有Go语言的结构体是什么,怎么声明;Golang方法的调用和声明;go语言面向对象实例,go语言工厂模式;golang面向对象的三大特性:继承、封装、多态,面向对象编程应用实例,golang的接口介绍,类型断言
配套视频自己去B站里面搜【go语言】,最高的播放量就是
里面的注释我写的可能不太对,欢迎大佬们指出╰(°▽°)╯
(八)、面向对象编程1.看一个问题
2.使用现有技术解决
package mainfunc main() {/*张老太养了两只猫猫:一只名字叫小白,今年3岁,白色。还有一只叫小花,今年100岁,花色。请编写一个程序,当用户输入小猫的名字时,就显示该猫的名字,年龄,颜色。如果用户输入的小猫名错误,则显示张老太没有这只猫猫。*///1.使用变量处理// var cat1Name string = "小白"// var cat1Age int = 3// var cat1Color string = "白色"// var cat2Name string = "小花"// var cat2Age int = 100// var cat2Color string = "花色"//2.使用数组处理// var catNames [2]string = [...]string{"小白", "小花"}// var catAge [2]int = [...]int{3, 100}// var catColor [2]string = [...]string{"白色", "花色"}//3.使用map解决}
3.现有技术解决的缺点分析
- 使用变量或者数组来解决养猫的问题,不利于数据的管理和维护。因为名字,年龄,颜色都是属于一只猫,但是这里是分开保存。
- 如果我们希望对一只猫的属性(名字、年龄,颜色)进行操作(绑定方法), 也不好处理。
- 引出我们要讲解的技术–> 结构体。
4.Golang 语言面向对象编程说明
-
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 中面向接口编程是非常重要的特性。
一、结构体
对上图的说明
- 将一类事物的特性提取出来(比如猫类), 形成一个新的数据类型, 就是一个结构体。
- 通过这个结构体,我们可以创建多个变量(实例/对象)
- 事物可以猫类,也可以是Person , Fish 或是某个工具类。。。
package mainimport "fmt"type monster struct {Name string `json:"name"`Age int `json:"age"`Skill []string `json:"skill"`
}func main() {monster := monster{Name: "哈哈",Age: 100,}// monster.Skill = make([]string, 2, 6)monster.Skill = append(monster.Skill[:], "武器1")monster.Skill = append(monster.Skill[:], "武器2")monster.Skill = append(monster.Skill[:], "武器3")// monster.Skill[1] = "武器2"fmt.Println(monster)
}
输出
{哈哈 100 [武器1 武器2 武器3]}
1.快速入门
package mainimport "fmt"//定义一个Cat结构体,将Cat的各个字段/属性信息,放入到Cat结构体进行管理
type Cat struct {Name string //字符串模式是空Age int //int类型默认为0Color string //字符串模式是空Hobby string
}func main() {/*张老太养了两只猫猫:一只名字叫小白,今年3岁,白色。还有一只叫小花,今年100岁,花色。请编写一个程序,当用户输入小猫的名字时,就显示该猫的名字,年龄,颜色。如果用户输入的小猫名错误,则显示张老太没有这只猫猫。*///使用struct结构体来完成案例var cat1 Cat //创建一个cat的变量fmt.Println(cat1)//给结构体赋值cat1.Name = "小白"cat1.Age = 3cat1.Color = "白色"fmt.Println("猫的信息如下:")fmt.Println("Nmae =", cat1.Name)fmt.Println("Age =", cat1.Age)fmt.Println("Color =", cat1.Color)
}
通过上面的案例和讲解我们可以看出:
- 结构体和结构体变量(实例)的区别和联系
- 结构体是自定义的数据类型,代表一类事物.
- 结构体变量(实例)是具体的,实际的,代表一个具体变量
2.结构体变量(实例)在内存的布局(重要!)
3.如何声明结构体
type 结构体名称 struct {field1 typefield2 type
}
type Student struct {Name string //字段Age int //字段Score float32
}
4.字段/属性
package mainimport "fmt"//指针,slice,和map 的零值都是nil ,即还没有分配空间。
//如果需要使用这样的字段,需要先make,才能使用type Person struct {
Name string
Age int
Scores [5]float64
ptr *int //指针
slice []int //切片
map1 map[string]string //map
}func main() {
//定义结构体
var p1 Person
fmt.Println(p1) //{ 0 [0 0 0 0 0] <nil> [] map[]}if p1.ptr == nil {fmt.Println("ptr为空")
}if p1.slice == nil {fmt.Println("slice为空")
}if p1.map1 == nil {fmt.Println("map1为空")
}//使用slice,一定要make
p1.slice = make([]int, 10)
p1.slice[0] = 100
fmt.Println(p1.slice) //[100 0 0 0 0 0 0 0 0 0]//使用map,一定要make
p1.map1 = make(map[string]string)
p1.map1["key1"] = "1"
fmt.Println(p1.map1) //map[key1:1]fmt.Println(p1) //{ 0 [0 0 0 0 0] <nil> [100 0 0 0 0 0 0 0 0 0] map[key1:1]}}
package mainimport "fmt"type Monster struct {
Name string
Age int
}func main() {
//不同结构体变量的字段是独立,互不影响,一个结构体变量字段的更改,
//不影响另外一个,结构体是值类型
var monster1 Monster
monster1.Name = "牛魔王"
monster1.Age = 500monster2 := monster1 //这里因为是值拷贝 结构体是值类型,默认为拷贝
monster2.Name = "红孩儿" //不会影响monster1fmt.Println("monster1=", monster1) //monster1= {牛魔王 500}
fmt.Println("monster2=", monster2) //monster2= {红孩儿 500}
}
5.创建结构体变量和访问结构体字段
//方式1var p1 Personp1.Name = "tom"p1.Age = 23fmt.Println(p1)
//方式2p2 := Person{"mary", 20}fmt.Println(p2)
//方式3var p3 *Person = new(Person) //因为p3是一个指针,因此标准的给字段方式是赋值(*p3).Name = "smith" //标准写法(*p3).Age = 30fmt.Println(*p3)
//方式4//也可以直接给值//var person *Person = &Person{"jak", 30}var person *Person = &Person{}//因为person是一个指针,因此标准的访问字段的方法是//(*person).Name = "scott"(*person).Name = "scott" //标准写法person.Name = "scott" //简化写法(*person).Age = 16 //标准写法person.Age = 16 //简化写法fmt.Println(*person)
6.struct 类型的内存分配机制
- 看一个思考题
p2.Name = tom p1.Name = 小明
- 看下面代码,并分析原因
原因:
- 看下面代码,并分析原因
7.结构体使用注意事项和细节
package mainimport "fmt"//结构体
type Point struct { //声明一个结构体x int //声明类型y int
}type Rect struct { //声明一个结构体leftUp, reghtDown Point //把Point分别传入给leftUp和reghtDown
}type Rect2 struct {leftUp, reghtDown *Point //把Point的指针地分别传入给leftUp和reghtDown}func main() {//r1有4个int,在内存中是连续分布//打印地址r1 := Rect{Point{1, 2}, Point{3, 4}} //调用结构体Rect,并把Point{1, 2}传入给leftUp,Point{3, 4}出入给reghtDownfmt.Printf("r1.leftUp.x 地址=%p\nr1.leftUp.y 地址=%p\n", &r1.leftUp.x, &r1.leftUp.y)fmt.Printf("r1.reghtDown.x 地址=%p\nr1.reghtDown.y 地址=%p\n", &r1.reghtDown.x, &r1.reghtDown.y)fmt.Println()//r2有两个*Point类型, 这个两个*point类型的本身地址也是连续的,//但是他们指向的地址不一定是连续r2 := Rect2{&Point{1, 2}, &Point{3, 4}}//本身地址fmt.Printf("r2.leftUp 本身地址=%p\nr2.reghtDown 本身地址=%p\n", &r2.leftUp, &r2.reghtDown)//指向地址不一定连续,fmt.Printf("r2.leftUp 指向地址=%p\nr2.reghtDown 指向地址=%p\n", r2.leftUp, r2.reghtDown)
}
r1.leftUp.x 地址=0xc00000c240
r1.leftUp.y 地址=0xc00000c248
r1.reghtDown.x 地址=0xc00000c250
r1.reghtDown.y 地址=0xc00000c258r2.leftUp 本身地址=0xc00005a250
r2.reghtDown 本身地址=0xc00005a258
r2.leftUp 指向地址=0xc0000140c0
r2.reghtDown 指向地址=0xc0000140d0
package mainimport "fmt"type A struct {Num int
}type B struct {Num int
}//结构体是用户单独定义的类型,和其它类型进行转换时需要有完全相同的字段(名字、个数和类型)
func main() {var a A //a的类型是结构体Avar b B //b的类型是结构体Bfmt.Println(a, b) //{0} {0}a = A(b) //对b进行强制转换,转换为结构体A,之所以能进行强转,是因为结构体的类型字段是一致的(名字、格式、类型)fmt.Println(a)
}
- 结构体进行type 重新定义(相当于取别名),Golang 认为是新的数据类型,但是相互间可以强转
package mainimport ("encoding/json""fmt"
)type Monster struct {Name string `json:"name"` //`json:"name"`就是struct结构体的标签,使用json进行序列化时会输出标签Age int `json:"age"`Skill string `json:"skill"`
}func main() {//1.创建一个Monster变量monster := Monster{"牛魔王", 500, "野蛮冲撞"}//2.将monster变量序列化为json格式字符串//内置函数json.Marshal函数中使用反射jsonMonster, err := json.Marshal(monster)if err != nil {fmt.Println("转换错误", err)}fmt.Println("jsonStr", jsonMonster) //jsonStr [123 34 110 ......... 158 34 125]//需要转换为string类型fmt.Println("jsonStr", string(jsonMonster)) // jsonStr {"name":"牛魔王","age":500,"skill":"野蛮冲撞"}
}
二、方法
1.基本介绍
在某些情况下,我们要需要声明(定义)方法。
比如Person 结构体:除了有一些字段外( 年龄,姓名…)
Person 结构体还有一些行为比如:可以说话、跑步…,通过学习,还可以做算术题。
这时就要用方法才能完成。
Golang 中的方法是作用在指定的数据类型上的(即:和指定的数据类型绑定),因此自定义类型,都可以有方法,而不仅仅是struct。
2.方法的声明和调用
type A struct {Num int
}
func (a A) test() {fmt.Println(a.Num)
}
package mainimport "fmt"type Person struct {Name string
}//给Person类型绑定一个方法
func (p Person) test() { //把结构Person绑定给p,并调用test()函数,此时test()为方法fmt.Println("test() name =", p.Name) //由于绑定过Person结构体
}func main() {var p Person //声明p的类型p.test() //调用方法 test() name=p.Name = "tom" //tom赋值给p.Namep.test() //调用方法 test() name = tom}
3.方法快速入门
package mainimport "fmt"type Person struct {Name string
}func (p Person) speak() {fmt.Println(p.Name, "是一个好人")
}func main() {var p Person //必须先声明pp.Name = "tom"p.speak()
}
package mainimport "fmt"type Person struct {Name string
}//给Person 结构体添加jisuan 方法,可以计算从1+..+1000 的结果, 说明方法体内可以函数一样,进行各种运算
func (p Person) jisuan() {res := 0for i := 0; i <= 1000; i++ {res += i}fmt.Println(p.Name, "计算的结果是=", res)
}func main() {var p Person //必须先声明pp.Name = "tom"p.speak()p.jisuan() //tom 计算的结果是= 500500
}
type Person struct {Name string
}
//给Person 结构体jisuan2 方法,该方法可以接收一个数n,计算从1+..+n 的结果
func (p Person) jisuan2(n int) {res := 0for i := 0; i <= n; i++ {res += i}fmt.Println(p.Name, "计算的结果是=", res)
}
func main() {var p Person //必须先声明pp.Name = "tom"p.jisuan2(10) //tom 计算的结果是= 55
}
type Person struct {Name string
}//给Person 结构体添加getSum 方法,可以计算两个数的和,并返回结果
func (p Person) getSum(n1 int, n2 int) int {return n1 + n2
}func main() {var p Person //必须先声明pp.Name = "tom"res := p.getSum(10, 20)fmt.Println(res) //30
}
4.方法的调用和传参机制原理:(重要!)
package mainimport "fmt"//1) 声明一个结构体Circle, 字段为radius
//2) 声明一个方法area 和Circle 绑定,可以返回面积。
//3) 提示:画出area 执行过程+说明type Circle struct {radius float64
}func (c Circle) area() float64 {return 3.14 * c.radius * c.radius //圆面积=圆周率×半径×半径
}func main() {//创建一个Circle变量var c Circlec.radius = 4.0res := c.area()fmt.Println(res) //50.24
}
5.方法的声明(定义)
func (recevier type) methodName(参数列表) (返回值列表){方法体return 返回值
}
recevier typereceiver typereceiverreturn
6.方法的注意事项和细节
package mainimport "fmt"type Circle struct {radius float64
}//为了提高效率,通常我们方法和结构体的指针类型绑定
func (c *Circle) area2() float64 {//因为c是指针,因此我们标准的访问其字段的方式是(*c).redius//标准写法 return 3.14 * (*c).radius * (*c).radius //圆面积=圆周率×半径×半径return 3.14 * c.radius * c.radius
}func main() {//创建一个Circle变量var c Circlec.radius = 5.0res2 := (&c).area2() //标准写法fmt.Println(res2)//编译器底层做了优化(&c).area2() 等价c.area()//因为编译器会自动的给加上&cres1 := c.area2()fmt.Println(res1)}
package mainimport "fmt"/*
Golang 中的方法作用在指定的数据类型上的(即:和指定的数据类型绑定),因此自定义类型,
都可以有方法,而不仅仅是struct, 比如int , float32 等都可以有方法
*/type integer intfunc (i integer) print() {fmt.Println("i =", i)
}//编写一个方法, 可以改变i的值
func (i *integer) change() {*i = *i + 1
}func main() {var i integer = 10i.print() //i = 10i.change()fmt.Println("i =", i) //i = 11
}
package mainimport "fmt"type Student struct {Name stringAge int
}func (stu *Student) String() string { //赋值Student的指针给stustr := fmt.Sprintf("Name=[%v] Age=[%v]", (*stu).Name, (*stu).Age)return str
}func main() {//定义一个Student变量stu := Student{Name: "tom",Age: 20,}fmt.Println(stu) //不使用指针输出,使用默认方式输出 {tom 20}fmt.Println(&stu) //使用地址进行输出 Name=[tom] Age=[20]
}
7.方法的练习题
- 编写结构体(MethodUtils),编程一个方法,方法不需要参数,在方法中打印一个10*8 的矩形,在main 方法中调用该方法。
package mainimport "fmt"//编写结构体(MethodUtils),编程一个方法,方法不需要参数,在方法中打印一个10*8 的矩形,
//在main 方法中调用该方法。type MethodUtils struct {
}//给MethodUtils编写方法
func (mu MethodUtils) Print() {for j := 0; j < 10; j++ {for i := 0; i < 8; i++ {fmt.Printf("*")}fmt.Println()}}func main() {var mu MethodUtilsmu.Print()
}
- 编写一个方法,提供m 和n 两个参数,方法中打印一个m*n 的矩形
package mainimport "fmt"//编写结构体(MethodUtils),编程一个方法,方法不需要参数,在方法中打印一个10*8 的矩形,
//在main 方法中调用该方法。type MethodUtils struct {
}//给MethodUtils编写方法
func (mu MethodUtils) Print(n1 int, n2 int) {for j := 0; j < n1; j++ {for i := 0; i < n2; i++ {fmt.Printf("*")}fmt.Println()}}func main() {var mu MethodUtilsmu.Print(8, 10)
}
-
编写一个方法算该矩形的面积(可以接收长len,和宽width), 将其作为方法返回值。在main方法中调用该方法,接收返回的面积值并打印。
-
编写方法:判断一个数是奇数还是偶数
-
根据行、列、字符打印对应行数和列数的字符,比如:行:3,列:2,字符*,则打印相应的效果
-
定义小小计算器结构体(Calcuator),实现加减乘除四个功能
实现形式1:分四个方法完成
实现形式2:用一个方法搞定
练习题
8.方法和函数区别
package mainimport "fmt"type Person struct {Name string
}func test01(p Person) {fmt.Println(p.Name)
}func test02(p *Person) {fmt.Println(p.Name)
}func main() {p := Person{"tom"}test01(p)test02(&p)
}
(p Person)(p *Person)
三、面向对象编程应用实例
1.步骤
- 声明(定义)结构体,确定结构体名
- 编写结构体的字段
- 编写结构体的方法
2.案例1
package mainimport "fmt"/*
1) 编写一个Student 结构体,包含name、gender、age、id、score 字段,分别为string、string、int、int、float64 类型。
2) 结构体中声明一个say 方法,返回string 类型,方法返回信息中包含所有字段值。
3) 在main 方法中,创建Student 结构体实例(变量),并访问say 方法,并将调用结果打印输出。
*/type Student struct {name stringgender stringage intid intscore float64
}func (student *Student) say() string {infoStr := fmt.Sprintf("student信息 name=[%v] gender=[%v] age=[%v] id=[%v] score=[%v]",student.name, student.gender, student.age, student.id, student.score,)return infoStr
}func main() {//测试//创建一个Student实例变量var stu = Student{name: "tom",gender: "male",age: 18,id: 1000,score: 99.98,}fmt.Println(stu.say())
}
3.案例2
- 编写一个Dog 结构体,包含name、age、weight 字段
- 结构体中声明一个say 方法,返回string 类型,方法返回信息中包含所有字段值。
- 在main 方法中,创建Dog 结构体实例(变量),并访问say 方法,将调用结果打印输出。
4.案例3
-
编程创建一个Box 结构体,在其中声明三个字段表示一个立方体的长、宽和高,长宽高要从终端获取
-
声明一个方法获取立方体的体积。
-
创建一个Box 结构体变量,打印给定尺寸的立方体的体积
5.案例4
-
一个景区根据游人的年龄收取不同价格的门票,比如年龄大于等于18,收费20 元,其它情况门票免费.
-
请编写Visitor 结构体,根据年龄段决定能够购买的门票价格并输出
四、创建结构体变量时指定字段值
package mainimport "fmt"type Stu struct {Name stringAge int
}func main() {//方式1//在创建结构体变量时,就直接指定字段的值var stu1 = Stu{"小明", 19}stu2 := Stu{"小米", 20}//在创建结构体变量时,把字段名和字段值写在一起var stu3 = Stu{Name: "jack",Age: 18,}var stu4 = Stu{Name: "mary",Age: 21,}fmt.Println(stu1, stu2, stu3, stu4) //{小明 19} {小米 20} {jack 18} {mary 21}
}
package mainimport "fmt"type Stu struct {Name stringAge int
}func main() {//方式2 返回结构体的指针类型(!! !var stu5 = &Stu{"小王", 29}stu6 := &Stu{"小花", 28}//在创建结构体指针变量时,把字段名和字段值写在一起,这种写法,就不依赖字段的定义顺序.var stu7 = &Stu{Name: "小李",Age: 39,}stu8 := &Stu{ Name: "小李",Age: 39,}fmt.Println(stu5, stu6, stu7, stu8) //&{小王 29} &{小花 28} &{小李 39} &{小李 39}//&表示是地址符,使用*对原值进行取值fmt.Println(*stu5, *stu6, *stu7, *stu8) //{小王 29} {小花 28} {小李 39} {小李 39}}
五、工厂模式
1.说明
Golang 的结构体没有构造函数,通常可以使用工厂模式来解决这个问题。
package modeltype Student struct {Name string...
}
因为这里的Student 的首字母S 是大写的,如果我们想在其它包创建Student 的实例(比如main 包),引入model 包后,就可以直接创建Student 结构体的变量(实例)。
type student struct {....}
2.工厂模式案例
package modeltype Student struct {Name stringScore float64
}
package mainimport ("demo/28demo/02/model""fmt"
)func main() {//创建要给Student实例var stu = model.Student{Name: "小明",Score: 86.25,}fmt.Println(stu)
}
package modeltype student struct {Name stringScore float64
}//因为student结构体首字母是小写,因此是只能在mode1使用
//我们通过工厂模式来解决func NewStudent(n string, s float64) *student { //定义一个公开的方法,使用一个方法来接收。并把值返回return &student{Name: n,Score: s,}
}
package mainimport ("demo/28demo/02/model""fmt"
)func main() {//因为student结构体是首字母小写,我们可以通过工厂模式来解决var stu = model.NewStudent("tom", 88.8)fmt.Println(*stu)fmt.Printf("name=%v score=%v", stu.Name, stu.Score)
}
3.思考题
思考一下,如果model 包的student 的结构体的字段Score 改成score,我们还能正常访问吗?又应该如何解决这个问题呢?
package modeltype student struct {Name stringscore float64
}//如果score字段首字母小写,则,在其它包不可以直接方法,我们可以提供一个方法
func (s *student) GetScore() float64 {return s.score
}
package mainimport ("demo/28demo/02/model""fmt"
)func main() {//因为student结构体是首字母小写,我们可以通过工厂模式来解决var stu = model.NewStudent("tom", 88.8)fmt.Println(*stu)fmt.Printf("name=%v score=%v", stu.Name, stu.GetScore())
}
六、面向对象编程思想-抽象
1.抽象的介绍
我们在前面去定义一个结构体时候,实际上就是把一类事物的共有的属性(字段)和行为(方法)提取出来,形成一个物理模型(结构体)。这种研究问题的方法称为抽象。
package mainimport "fmt"//定义一个结构体
type Account struct {AccountNo stringPwd stringBalance float64
}//方法
//1.存款
func (account *Account) Deposite(useraccount string, pwd string, money float64) {// if useraccount != account.AccountNo {// fmt.Println("你输入的账号不正确")// return// }// //看下输入的密码是否正确// if pwd != account.Pwd {// fmt.Println("你输入的密码不正确")// return// }if money <= 0 {fmt.Println("你输入的金额不正确")return}account.Balance += moneyfmt.Println("存款成功")fmt.Printf("你的账号为=%v,你的余额为=%v\n", account.AccountNo, account.Balance)}//2.取款
func (account *Account) WithDraw(useraccount string, pwd string, money float64) {// if useraccount != account.AccountNo {// fmt.Println("你输入的账号不正确")// return// }// if pwd != account.Pwd {// fmt.Println("你输入的密码不正确")// return// }if money <= 0 || money > account.Balance { //取款小于等于0,你要取款的钱大于你存的钱fmt.Println("你输入的金额不正确")return}account.Balance -= moneyfmt.Println("取款成功")fmt.Printf("你的账号为=%v,你的余额为=%v\n", account.AccountNo, account.Balance)}//3.查询余额
func (account *Account) Query(useraccount string, pwd string) {// if useraccount != account.AccountNo {// fmt.Println("你输入的账号不正确")// return// }// if pwd != account.Pwd {// fmt.Println("你输入的密码不正确")// return// }fmt.Printf("你的账号为=%v,你的余额为=%v\n", account.AccountNo, account.Balance)
}func main() {account := Account{AccountNo: "icbc",Pwd: "123456",Balance: 100.0,}var user stringvar password stringvar input stringfmt.Println("请输入账号:")fmt.Scanln(&user) //用户输入fmt.Println("请输入密码:")fmt.Scanln(&password)if user != account.AccountNo || password != account.Pwd {fmt.Println("您输入的账号或密码有误")return} else {fmt.Printf("请输入您要办理的业务:\n1.存款\n2.取款\n3.查看余额\n")fmt.Scanln(&input)switch input {case "1", "存款", "1.存款":account.Deposite(user, password, 123)case "2", "取款", "2.取款":account.WithDraw(user, password, 12.66)case "3", "查看余额", "3.查看余额":account.Query(user, password)default:fmt.Println("输入有误,请重新输入")}fmt.Println()}
}
七、面向对象编程三大特性-封装
1.基本介绍
Golang 仍然有面向对象编程的继承,封装和多态的特性,只是实现的方式和其它OOP 语言不一样,下面我们进行详细的讲解Golang 的三大特性是如何实现的。
2.封装介绍
封装(encapsulation)就是把抽象出的字段和对字段的操作封装在一起,数据被保护在内部,程序的其它包只有通过被授权的操作(方法),才能对字段进行操作
3.封装的理解和好处
- 隐藏实现细节
- 提可以对数据进行验证,保证安全合理(Age)
4.如何体现封装
- 对结构体中的属性进行封装
- 通过方法,包实现封装
5.封装的实现步骤
func (var 结构体类型名) SetXxx(参数列表) (返回值列表) {//加入数据验证的业务逻辑var.字段= 参数
}
func (var 结构体类型名) GetXxx() {return var.age;
}
- 特别说明:在Golang 开发中并没有特别强调封装,这点并不像Java. 所以提醒学过java 的朋友,不用总是用java 的语法特性来看待Golang, Golang 本身对面向对象的特性做了简化的.
6.快速入门案例
package modelimport "fmt"type person struct {Name stringage int //其他包不能直接访问 年龄sal float64 //其他包不能直接访问 薪水
}//写一个工厂模式的函数,相当于构造函数
func NwePerson(name string) *person { //让小写的结构体可以被其他包引用return &person{Name: name,}
}//为了访问age和sal 我们编写一 对SetXxx的方法和Getxx的方法
func (p *person) SetAge(age int) { //使用指针赋值给结构体中的年龄 存入值if age > 0 && age < 150 {p.age = age} else {fmt.Println("年龄范围不正确")}
}func (p *person) GetAge() int { //Get查看这个值 ,直接返回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 { //Get查看sal这个值并直接返回给GetSal()return p.sal
}
package mainimport ("demo/29demo/model""fmt"
)func main() {p := model.NwePerson("小明") //声明model并把小明赋值给结构体person的namefmt.Println(*p) //使用*取原值进行输出p.SetAge(18) //set存入age值p.SetSal(5000)fmt.Println(*p)fmt.Printf("name=%v age=%v sal=%v", p.Name, p.GetAge(), p.GetSal()) //调用方法并输出
}
7.练习
package modelimport "fmt"type account struct {user string //长度在6-10之间pwd string //必须是6位balance float64 //必须>20
}//小写的结构体可以被其他包引用
func NewAccount(user string, pwd string, balance float64) *account {fmt.Println(len(user))if len(user) < 6 || len(user) > 10 {fmt.Println("输入的范围有误,长度必须在6-10之间")return nil}if len(pwd) != 6 {fmt.Println("输入的范围有误,长度必须是6位")return nil}if balance < 20 {fmt.Println("输入的范围有误,余额(必须>20)")return nil}return &account{user: user,pwd: pwd,balance: balance,}
}// func (a *account) SetUser(user string) {length := len(user)if 6 <= length && length <= 10 {a.user = user} else {fmt.Println("输入的范围有误,长度必须在6-10之间")}
}func (a *account) SetPwd(pwd string) {length := len(pwd)if length == 6 {a.pwd = pwd} else {fmt.Println("输入的范围有误,长度必须是6位")}
}func (a *account) SetBalance(balance float64) {if balance >= 20 {a.balance = balance} else {fmt.Println("输入的范围有误,余额(必须>20)")}
}func (a *account) GetUser() string {return a.user
}func (a *account) GetPwd() string {return a.pwd
}func (a *account) GetBalance() float64 {return a.balance
}
package mainimport ("demo/29demo/01/model""fmt"
)/*
1) 创建程序,在`model` 包中定义`Account`结构体:在`main` 函数中体会Golang 的封装性。
2) `Account` 结构体要求具有字段:账号(长度在6-10 之间)、余额(必须>20)、密码(必须是六
3) 通过SetXxx 的方法给Account 的字段赋值。
4) 在main 函数中测试
*/func main() {account := model.NewAccount("小红帽", "123456", 123)if account != nil {fmt.Println("创建成功", *account)} else {fmt.Println("创建失败")}account.SetUser("小明")fmt.Println(*account)fmt.Println(account.GetUser())
}
八、面向对象编程三大特性-继承
1.引出继承的必要性
一个小问题,看个学生考试系统的程序extends01.go,提出代码复用的问题
代码
package mainimport "fmt"//编写一个学生考试系统//小学生考试
type Pupil struct {Name stringAge intScore int
}//小学生考试
//显示学生信息
func (p *Pupil) ShowInfo() {fmt.Printf("学生姓名=%v 年龄=%v 成绩=%v\n", p.Name, p.Age, p.Score)
}//设置学生成绩
func (p *Pupil) SteScore(score int) {p.Score = score
}func (p *Pupil) Tesing() {fmt.Println("小学生正在考试中")
}//大学生考试
type Graduate struct {Name stringAge intScore int
}//显示学生信息
func (p *Graduate) ShowInfo() {fmt.Printf("学生姓名=%v 年龄=%v 成绩=%v\n", p.Name, p.Age, p.Score)
}//设置学生成绩
func (p *Graduate) SteScore(score int) {p.Score = score
}func (p *Graduate) Tesing() {fmt.Println("大学生正在考试中")
}func main() {//测试var pupil = &Pupil{Name: "tom",Age: 10,}pupil.Tesing()pupil.SteScore(89)pupil.ShowInfo()var graduate = &Graduate{Name: "mary",Age: 20,}graduate.Tesing()graduate.SteScore(76)graduate.ShowInfo()
}
对上面代码的小结
- Pupil 和Graduate 两个结构体的字段和方法几乎,但是我们却写了相同的代码, 代码复用性不强
- 出现代码冗余,而且代码不利于维护,同时也不利于功能的扩展。
- 解决方法-通过继承方式来解决
2.继承基本介绍和示意图
继承可以解决代码复用,让我们的编程更加靠近人类思维。
当多个结构体存在相同的属性(字段)和方法时,可以从这些结构体中抽象出结构体(比如刚才的Student),在该结构体中定义这些相同的属性和方法。
其它的结构体不需要重新定义这些属性(字段)和方法,只需嵌套一个Student 匿名结构体即可。[示意图]
在Golang 中,如果一个struct 嵌套了另一个匿名结构体,那么这个结构体可以直接访问匿名结构体的字段和方法,从而实现了继承特性。
3.嵌套匿名结构体的基本语法
type Goods struct {Name stringPrice int
}
type Book struct {Goods //这里就是嵌套匿名结构体GoodsWriter string
}
4.快速入门案例
package mainimport "fmt"type Student struct {Name stringAge intScore int
}///
//将Pupil和Graduate共有的方法绑定到*Student
func (stu *Student) ShowInfo() {fmt.Printf("学生姓名=%v 年龄=%v 成绩=%v\n", stu.Name, stu.Age, stu.Score)}
func (stu *Student) SteScore(score int) {stu.Score = score
}///
///type Pupil struct {Student //嵌入Student匿名结构体
}type Graduate struct {Student //嵌入Student匿名结构体
}//保留特有的方法
func (p *Pupil) Tesing() {fmt.Println("小学生正在考试中")
}func (p *Graduate) Tesing() {fmt.Println("大学生正在考试中")
}///func main() {//当我们对结构体嵌入了匿名结构体后使用方法会发生变化//方式1pupil := &Pupil{}pupil.Student.Name = "tom"pupil.Student.Age = 10pupil.Tesing()pupil.Student.SteScore(75)pupil.Student.ShowInfo()graduate := &Graduate{}graduate.Student.Name = "mary"graduate.Student.Age = 20graduate.Tesing()graduate.Student.SteScore(68)graduate.Student.ShowInfo()fmt.Println()//方式2......pupil.Name = "tom~"pupil.Age = 100pupil.Tesing()pupil.SteScore(750)pupil.ShowInfo()graduate.Name = "mary~"graduate.Age = 200graduate.Tesing()graduate.SteScore(680)graduate.ShowInfo()}
5.继承的深入讨论
-
结构体可以使用嵌套匿名结构体所有的字段和方法,即:首字母大写或者小写的字段、方法,都可以使用。【举例说明】
-
匿名结构体字段访问可以简化
package mainimport "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.age)
}type B struct {A
}func main() {var b Bb.A.Name = "tom"b.A.age = 19b.A.SayOk()b.A.hello()//上面写法可以简化b.Name = "smith"b.age = 20b.SayOk()b.hello()
}
b.Name
- 当结构体和匿名结构体有相同的字段或者方法时,编译器采用就近访问原则访问,如希望访问匿名结构体的字段和方法,可以通过匿名结构体名来区分【举例说明】
package mainimport "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.age)
}///
type B struct {AName string
}func (b *B) SayOk() {fmt.Println("B SayOk", b.Name)
}///func main() {var b Bb.Name = "jack" //使用就近原则,导入jackb.A.Name = "scott" //使用匿名结构体全路径b.age = 100b.SayOk() //使用就近原则b.A.SayOk() //使用匿名结构体全路径b.hello()
}
- 结构体嵌入两个(或多个)匿名结构体,如两个匿名结构体有相同的字段和方法(同时结构体本身没有同名的字段和方法),在访问时,就必须明确指定匿名结构体名字,否则编译报错。【举例说明】
package mainimport "fmt"type A struct {Name stringAge int
}type B struct {Name stringScore float64
}//C同时嵌入了2个不同的结构体,此时就必须明确指定匿名结构体名字
type C struct {AB
}func main() {var c C//如果c没有Name字段,而A和B有Name, 这时就必须通过指定置名结构体名字来区分//所以 c.Name 就会包编译错误, 这个规则对方法也是一 样的!c.A.Name = "tom"fmt.Println(c.A.Name)}
package mainimport "fmt"type A struct {Name stringAge int
}type D struct {a A //有名结构体 组合关系}func main() {如果D中是一个有名结构体,则访问有名结构体的字段时,就必须带上有名结构体的名字var d D//d.Name = "jack" //直接去d里面找Name,发现没有叫做Name的就直接报错,d.a.Name = "jack" //因为A是有名结构体,需要完整的路径}
package mainimport "fmt"type Goods struct {Name stringPrice float64
}type Brend struct {Name stringAddrees string
}type Tv1 struct {GoodsBrend
}type Tv2 struct { //使用指针类型*Goods*Brend
}func main() {//嵌套匿名结构体后,也可以在创建结构体变量(实例)时,直接指定各个匿名结构体字段的值tv1 := Tv1{Goods{"电视机001", 5999.09}, Brend{"海尔", "山东"}} //方式1fmt.Println(tv1)tv2 := Tv1{ //方式2Goods{Name: "电视机002",Price: 5000.09,},Brend{Name: "海尔",Addrees: "山东",},}fmt.Println(tv2)//使用指针tv3 := Tv2{&Goods{"电视机003", 6999.09}, &Brend{"创维", "河南"}}fmt.Println(*tv3.Goods, *tv3.Brend)tv4 := Tv2{&Goods{Name: "电视机004",Price: 4000.99,},&Brend{Name: "创维",Addrees: "河南",},}fmt.Println(*tv4.Goods, *tv4.Brend)
}
6.练习
结构体的匿名字段是基本数据类型,如何访问, 下面代码输出什么
package mainimport "fmt"type Monster struct {Name stringAge int
}type E struct {Monsterint //匿名字段时基本数据类型n int //如果有了1个int,后面int必须有名称
}func main() {var e Ee.Name = "牛魔王"e.Age = 500e.int = 20e.n = 10fmt.Println(e) //{{牛魔王 500} 20 10}
}
说明
- 如果一个结构体有 int 类型的匿名字段,就不能第二个。
- 如果需要有多个 int 的字段,则必须给 int 字段指定名
7.面向对象编程-多重继承
-
如一个 struct 嵌套了多个匿名结构体,那么该结构体可以直接访问嵌套的匿名结构体的字段和方 法,从而实现了多重继承。
-
案例
通过一个案例来说明多重继承使用
-
多重继承细节说明
-
如嵌入的匿名结构体有相同的字段名或者方法名,则在访问时,需要通过匿名结构体类型名来区分。【案例演示】
-
为了保证代码的简洁性,建议大家尽量不使用多重继承
-
九、接口(interface)
1.基本介绍
按顺序,我们应该讲解多态,但是在讲解多态前,我们需要讲解接口(interface),因为在Golang 中多态特性主要是通过接口来体现的。
2.接口快速入门
这样的设计需求在Golang 编程中也是会大量存在的,前面说过,一个程序就是一个世界,在现实世界存在的情况,在程序中也会出现。我们用程序来模拟一下前面的应用场景。
package mainimport "fmt"//声明一个接口
type Usb interface {//声明了2个没有实现的方法Start()Stop()
}//让手机Phone实现Usb接口的方法
type Phone struct {
}func (p Phone) Start() {fmt.Println("启动手机...")
}
func (p Phone) Stop() {fmt.Println("关闭手机...")
}//让相机Camera实现Usb接口的方法
type Camera struct {
}func (c Camera) Start() {fmt.Println("启动相机...")
}func (c Camera) Stop() {fmt.Println("关闭相机...")
}//计算机
type Computer struct{}//编写一个方法Working,接收一个usb接口类型变量
//只要是实现了Usb接口 (所谓实现Usb接口,就是指实现了Usb接口声明所有方法)
func (c Computer) Working(usb Usb) { //2.接收到phone//通过usb接口变量来调用start和stop方法usb.Start() //3.寻找关于phone的Start方法 输出:启动手机...usb.Stop() //4.寻找关于phone的Stop方法 输出:关闭手机...// //上面等同于下面这个// phone := Phone{}// phone.Start()
}func main() {//测试,先创建结构体变量computer := Computer{}phone := Phone{}camera := Camera{}computer.Working(phone) //1.传入phone参数fmt.Println()computer.Working(camera)
}
3.基本语法
interface 类型可以定义一组方法,但是这些不需要实现。并且interface 不能包含任何变量。到某个自定义类型(比如结构体Phone)要使用的时候,在根据具体情况把这些方法写出来(实现)。
implement
4.接口使用的应用场景
5.注意事项和细节
package mainimport "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 = stustu.Say()a.Say()
}
package mainimport "fmt"type AInterface interface { //定义一个接口Say()
}type BInterface interface { //定义一个接口Hello()
}type Monster struct { //定义struct结构体
}func (m Monster) Hello() { //定义Hello并调用Monster结构体fmt.Println("Monster Hello()")
}func (m Monster) Say() {fmt.Println("Monster Say()")
}func main() { //调用接口//Monster实现 了AInterface 和BInterfacevar monster Monstervar a AInterface = monstervar b BInterface = monstera.Say()b.Hello()
}
package maintype BInterface interface {test01()
}type CInterface interface {test02()
}type AInterface interface { //如果要实现A接口,必须把A接口所有的反法全部实现BInterfaceCInterfacetest03()
}type Stu struct {
}//如果需要实现AInterface,就需要将BInterface CInterface的方法都实现
func (stu Stu) test01() {} //缺一不可func (stu Stu) test02() {} //缺一不可func (stu Stu) test03() {} //缺一不可//如果需要实现AInterface,就需要将BInterface CInterface的方法都实现
func main() {var stu Stuvar a AInterface = stuvar b BInterface = stua.test01()b.test01()
}
package mainimport "fmt"type T interface{} //定义一个空接口type Stu struct { //定义一个结构体
}func main() {var nume float64 = 8.0//方式1var stu Stu //调用结构体var t1 T = stu //声明位空接口类型并调用结构体t1 = numefmt.Println(t1)//方式2var t2 interface{} = stu //定义一个空接口类型,并传给t2t2 = numefmt.Println(t2)
}
6.接口编程的最佳实践
package mainimport ("fmt""math/rand""sort"
)// 实现对Hero 结构体切片的排序: sort.Sort(data Interface)//1.声明Hero结构体
type Hero struct {Name stringAge int
}//2.声明一个Hero结构体切片类型
type HeroSlice []Hero //声明Hero切片//3.interface接口
func (hs HeroSlice) Len() int {return len(hs) //遍历数组的数量,并返回
}//Less方法就是决定你使用什么标准进行排序
//3.1.按Hero年龄进行从小到大进行排序
func (hs HeroSlice) Less(i, j int) bool {return hs[i].Age < hs[j].Age //判断大小,返回bool类型//修改对Name的排序//return hs[i].Name < hs[j].Name
}//3.2把值进行交换
func (hs HeroSlice) Swap(i, j int) {hs[i], hs[j] = hs[j], hs[i]
}func main() {//先定义一个数组/切片var intSlice = []int{0, -1, 10, 7, 90}//要求对interface进行排序fmt.Println(intSlice)sort.Ints(intSlice)fmt.Println(intSlice)//========================================//对结构体切片进行排序var heros HeroSlice//生成英雄herofor i := 0; i < 10; i++ {hero := Hero{ //调用Hero结构体,传入英雄和英雄的年龄Name: fmt.Sprintf("英雄%d", rand.Intn(100)), //使用rand.Intn生成0到100的伪随机数Age: rand.Intn(100), //使用rand.Intn生成0到100的伪随机数}heros = append(heros, hero) //使用append对切片进行动态追加,并赋值给heros}//看看排序前的顺序fmt.Println(heros)for _, v := range heros { //使用range对heros进行遍历fmt.Println(v)}fmt.Println("-----排序后-----")//调用sort.Sortsort.Sort(heros)//看看排序后的顺序for _, v := range heros {fmt.Println(v)}
}
//1.声明Student 结构体
type Student struct{Name stringAge intScore float64
}
//将Student 的切片,安Score 从大到小排序!!
7.实现接口vs 继承
package mainimport ("fmt"
)/
//猴子Monkey结构体
type Monkey struct {Name string
}func (ithis *Monkey) Climbing() { //猴子Monkey的方法fmt.Println(ithis.Name, "生来会爬树")
}
//
//小猴子LittleMonkey结构体
type LittleMonkey struct {Monkey //继承猴子Monkey
}
//
//声明鸟的能力BirdAble接口
type BirdAble interface {Flying() //飞翔Flying方法
}//让小猴子LittleMonkey实现鸟的能力BirdAble
func (ithis *LittleMonkey) Flying() {fmt.Println(ithis.Name, "通过学习,会飞翔了")
}
//
//声明鱼的能力FishAble接口
type FishAble interface {Swimming() //游泳Swimming方法
}//让小猴子LittleMonkey实现鱼的能力FishAble
func (ithis *LittleMonkey) Swimming() {fmt.Println(ithis.Name, "学会了游泳")
}
/func main() {//创建一个LittleMonkey实例monkey := LittleMonkey{Monkey{Name: "悟空",},}monkey.Climbing() //调用Monkey的方法monkey.Flying() //调用鸟的能力BirdAble接口,实现飞翔Flying方法monkey.Swimming() //调用鱼的能力BirdAble接口,实现游泳Swimming方法
}
-
实现接口可以看作是对继承的一种补充
-
接口和继承解决的解决的问题不同
继承的价值主要在于:解决代码的复用性和可维护性。
接口的价值主要在于:设计,设计好各种规范(方法),让其它自定义类型去实现这些方法。
PersonStudentBirdAbleLittleMonkey
- 接口在一定程度上实现代码解耦
十、面向对象编程三大特性-多态
1.基本介绍
变量(实例)具有多种形态。面向对象的第三大特征,在Go 语言,多态特征是通过接口实现的。可以按照统一的接口来调用不同的实现。这时接口变量就呈现不同的形态。
2.快速入门
在前面的Usb 接口案例,Usb usb ,既可以接收手机变量,又可以接收相机变量,就体现了Usb 接口多态特性。
package mainimport "fmt"//声明一个接口
type Usb interface {//声明了2个没有实现的方法Start()Stop()
}//让手机Phone实现Usb接口的方法
type Phone struct {
}func (p Phone) Start() {fmt.Println("启动手机...")
}
func (p Phone) Stop() {fmt.Println("关闭手机...")
}//让相机Camera实现Usb接口的方法
type Camera struct {
}func (c Camera) Start() {fmt.Println("启动相机...")
}func (c Camera) Stop() {fmt.Println("关闭相机...")
}//计算机
type Computer struct{}//编写一个方法Working,接收一个usb接口类型变量
//只要是实现了Usb接口 (所谓实现Usb接口,就是指实现了Usb接口声明所有方法)
func (c Computer) Working(usb Usb) { //2.接收到phone//通过usb接口变量来调用start和stop方法usb.Start() //3.寻找关于phone的Start方法 输出:启动手机...usb.Stop() //4.寻找关于phone的Stop方法 输出:关闭手机...// //上面等同于下面这个// phone := Phone{}// phone.Start()
}func main() {//测试,先创建结构体变量computer := Computer{}phone := Phone{}camera := Camera{}computer.Working(phone) //1.传入phone参数fmt.Println()computer.Working(camera)
}
3.接口体现多态的两种形式
package mainimport "fmt"///
//声明一个接口
type Usb interface {//声明了2个没有实现的方法Start()Stop()
}//////
//让手机Phone实现Usb接口的方法
type Phone struct {Name string
}func (p Phone) Start() {fmt.Println("启动手机...")
}
func (p Phone) Stop() {fmt.Println("关闭手机...")
}func (p Phone) Call() {fmt.Println("可以使用电话...")
}//////
//让相机Camera实现Usb接口的方法
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()usb.Stop()//如果usb是指向Phone结构体变量,则还需要调用Call方法//类型断言if phone, ok := usb.(Phone); ok { //判断usb的参数是否为phone,phone.Call() //当等于时就执行Call()方法,否则跳过}
}func main() {//定义一个Usb接口数组,可以存放Phone和Camear的结构体变量//这里就体现出多态数组var usbArr [3]Usb //声明一个数组,里面可以存放3个元素fmt.Println(usbArr) //[<nil> <nil> <nil>]//使用接口实现多个数据类型usbArr[0] = Phone{"vivo"}usbArr[1] = Phone{"小米"}usbArr[2] = Camera{"索尼"}fmt.Println(usbArr) //多态数组[{vivo} {小米} {索尼}]fmt.Println()var computer Computerfor _, v := range usbArr {computer.Working(v)fmt.Println()}
}
4.类型断言
1.基本介绍
类型断言,由于接口是一般类型,不知道具体类型,如果要转成具体类型,就需要使用类型断言,具体的如下:
package mainimport "fmt"func main() {//带检测的类型断言var x interface{}var b float32 = 1.1x = by, ok := x.(float64) //返回ok是否为ture或falseif ok { //等同于ok == turefmt.Println("convert succes")fmt.Printf("y的类型是%T 值是%v", y, y)} else {fmt.Println("convert fail")}fmt.Println("继续执行")
}
2.类型断言的最佳实践
package mainimport "fmt"///
//声明一个接口
type Usb interface {//声明了2个没有实现的方法Start()Stop()
}//////
//让手机Phone实现Usb接口的方法
type Phone struct {Name string
}func (p Phone) Start() {fmt.Println("启动手机...")
}
func (p Phone) Stop() {fmt.Println("关闭手机...")
}func (p Phone) Call() {fmt.Println("可以使用电话...")
}//////
//让相机Camera实现Usb接口的方法
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()usb.Stop()//如果usb是指向Phone结构体变量,则还需要调用Call方法//类型断言if phone, ok := usb.(Phone); ok { //判断usb的参数是否为phone,phone.Call() //当等于时就执行phone.Call()方法,否则跳过}
}func main() {//定义一个Usb接口数组,可以存放Phone和Camear的结构体变量//这里就体现出多态数组var usbArr [3]Usb //声明一个数组,里面可以存放3个元素fmt.Println(usbArr) //[<nil> <nil> <nil>]//使用接口实现多个数据类型usbArr[0] = Phone{"vivo"}usbArr[1] = Phone{"小米"}usbArr[2] = Camera{"索尼"}fmt.Println(usbArr) //多态数组[{vivo} {小米} {索尼}]fmt.Println()var computer Computerfor _, v := range usbArr {computer.Working(v)fmt.Println()}
}
package mainimport "fmt"//编写一个函数,可以判断输入的参数是什么类型
func TyeepJudge(items ...interface{}) { //定义一个...根据大小自动扩容的空接口for index, x := range items { //遍历items ,index为下标,x为值index++switch x.(type) { //固定写法,传入遍历后的值对类型进行判断case bool:fmt.Printf("第%v个参数是bool类型,值是%v\n", index, x)case float32:fmt.Printf("第%v个参数是float32类型,值是%v\n", index, x)case float64:fmt.Printf("第%v个参数是float64类型,值是%v\n", index, x)case int, int16, int32, int64:fmt.Printf("第%v个参数是 整数 类型,值是%v\n", index, x)case string:fmt.Printf("第%v个参数是string类型,值是%v\n", index, x)default:fmt.Printf("第%v个参数类型不确定,值是%v\n", index, x)}}
}func main() {var n1 float32 = 1.2var n2 float64 = 1.43var n3 int32 = 43var n4 string = "tom"n5 := "北京"n6 := 123TyeepJudge(n1, n2, n3, n4, n5, n6)
}
package mainimport "fmt"//1.自定义一个类型
type Student struct{}//编写一个函数,可以判断输入的参数是什么类型
func TyeepJudge(items ...interface{}) { //定义一个...根据大小自动扩容的空接口for index, x := range items { //遍历items ,index为下标,x为值index++switch x.(type) { //固定写法,传入遍历后的值对类型进行判断case bool:fmt.Printf("第%v个参数是bool类型,值是%v\n", index, x)case float32:fmt.Printf("第%v个参数是float32类型,值是%v\n", index, x)case float64:fmt.Printf("第%v个参数是float64类型,值是%v\n", index, x)case int, int16, int32, int64:fmt.Printf("第%v个参数是 整数 类型,值是%v\n", index, x)case string:fmt.Printf("第%v个参数是string类型,值是%v\n", index, x)case Student: //可以直接进行判断fmt.Printf("第%v个参数是Student类型,值是%v\n", index, x)case *Student: //可以直接进行判断fmt.Printf("第%v个参数是*Student类型,值是%v\n", index, x)default:fmt.Printf("第%v个参数类型不确定,值是%v\n", index, x)}}
}func main() {var n1 float32 = 1.2var n2 float64 = 1.43var n3 int32 = 43var n4 string = "tom"n5 := "北京"n6 := 123stu1 := Student{} //2.自定义类型stu2 := &Student{} //3.指针类型TyeepJudge(n1, n2, n3, n4, n5, n6, stu1, stu2) //4.传入参数
}
章节目录
【Golang第1~3章:基础】如何安装golang、第一个GO程序、golang的基础
【Golang第4章:函数】Golang包的引用,return语句、指针、匿名函数、闭包、go函数参数传递方式,golang获取当前时间
【Golang第5章:数组与切片】golang如何使用数组、数组的遍历和、使用细节和内存中的布局;golang如何使用切片,切片在内存中的布局
【Golang第6章:排序和查找】golang怎么排序,golang的顺序查找和二分查找,go语言中顺序查找二分查找介绍和案例
【Golang第7章:map】go语言中map的基本介绍,golang中map的使用案例,go语言中map的增删改查操作,go语言对map的值进行排序
【Golang第8章:面向对象编程】Go语言的结构体是什么,怎么声明;Golang方法的调用和声明;go语言面向对象实例,go语言工厂模式;golang面向对象的三大特性:继承、封装、多态
【Golang第9章:项目练习】go项目练习家庭收支记账软件项目、go项目练习客户管理系统项目
【Golang第10章:文件操作】GO语言的文件管理,go语言读文件和写文件、GO语言拷贝文件、GO语言判断文件是否存在、GO语言Json文件格式和解析
【Golang第11章:单元测试】GO语言单元测试
【Golang第12章:goroutine协程与channel管道】GO语言goroutine协程和channel管道的基本介绍、goroutine协