go的面向对象学习

文章目录

    • 面向对象编程(上)
      • 1.问题与解决思路
      • 2.结构体
        • 1》Golang语言面向对象编程说明
        • 2》结构体与结构体变量(实例/对象)的关系的示意图
        • 3》入门案例(using struct to solve the problem of cat growing)
      • 3.结构体的具体应用
      • 4.创建结构体变量和访问结构体字段
      • 5.struct类型的内存分配机制
      • 6.结构体的注意事项和使用细节
        • 1)结构体的所有字段在内存中是连续的
      • 7.方法
        • 1)基本介绍:
        • 2)方法的声明和调用
        • 3)对上面的代码总结和说明
        • 4)方法的快速入门
        • 6)方法的调用和传参机制原理(vital)
        • 7)方法的声明(定义)
        • 8)方法注意事项和细节讨论
        • 9)有关方法的课外习题
        • 10)方法和函数的区别
      • 8.面向对象编程的应用实例
    • 面向对象编程(下)
      • 1.创建结构体变量时指定字段值
      • 2.工厂模式
      • 3.vscode快捷键
      • 4.面向对象编程的三大特性
        • 1)基本介绍
        • 2)面向对象编程思想--抽象
        • 3)面向对象编程思想-封装
          • &1介绍
          • &封装的理解和好处
          • &如何实现封装
          • &案例入门
          • &练习:
        • 4)面向对象编程思想-继承
          • &1.为什么需要继承
          • &2继承的基本介绍和示意图
          • &3.嵌套匿名结构体的基本语法
          • &4.课堂练习
          • &5.多重继承
          • &6.多重继承细节说明
          • &4.课堂练习
          • &5.多重继承
          • &6.多重继承细节说明

面向对象编程(上)

1.问题与解决思路

问题:

张老太养了两只猫,一个名字叫小白,今年3岁,白色。还有一只叫小花,今年100岁,花色。请编写一个程序。当用户输入小猫的名字时,就显示该猫的名字,年龄,颜色。如果用户输入的小猫名错误,则显示张老太没有这只小猫

使用现有的技术来解决这个问题

1)单独的定义变量解决

代码演示

//1.使用变量的处理方式var cat1Name string = "小白"var cat1Age int = 3var cat1Color string = "白色"var cat2Name string = "小化"var cat2Age int = 100var cat2Color string = "花色"

2)使用数组解决

代码演示

//2.使用数组来解决var catNames [2]string = [...]string{"小白","小花"}var catAges [2]int = [...]int{3,100}var catColor [2]string = [...]string{"白色","花色"}//

现有的技术解决的缺点分析

  • 使用变量或者数组来解决养猫的问题,不利于数据的管理和维护。因为名字,年龄,颜色都是属于一只猫,但是这里分开保存
  • 如果我们希望对一只猫的属性(名字、年龄、颜色)绑定方法也不好处理
  • 引出讲解的知识点结构体

2.结构体

一个程序就是一个世界,有很多对象(变量)

1》Golang语言面向对象编程说明

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本身就是语言类型系统(type system)的一部分,通过接口(interface)关联,耦合性低,也非常灵活。后面学习就可以体会到了,也就是说在Golang中面向接口编程是非常重要的特性

2》结构体与结构体变量(实例/对象)的关系的示意图

在这里插入图片描述

简单来说就是类似与java的类和对象

对上图的说明

1)将一类事物的特性提取出来(比如猫类),形成一个新的数据类型,就是结构体

2)通过这个结构体,我们可以创建多个变量(实例对象)

3)事物可以是猫类,也可以是Person,Fish或是某个工具类

特征对象抽取图:

在这里插入图片描述

3》入门案例(using struct to solve the problem of cat growing)
package main
import ("fmt"
)
/*
张老太养了两只猫,一个名字叫小白,今年3岁,白色。
还有一只叫小花,今年100岁,花色。请编写一个程序。
当用户输入小猫的名字时,就显示该猫的名字,年龄,颜色。
如果用户输入的小猫名错误,则显示张老太没有这只小猫*/// func vars(){
// 	//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 catAges [2]int = [...]int{3,100}
// 	var catColor [2]string = [...]string{"白色","花色"}
// 	//
// }//定义一个cat结构体,将cat的各个字段/属性放入cat结构体进行管理
type Cat struct {Name stringAge intColor stringHobby string
}func main() {
//创建一个cat结构体变量
var cat1  Cat  //类似于var a int
cat1.Name = "小白"
cat1.Age = 3
cat1.Color = "白色"
cat1.Hobby = "吃<.))>>>"
fmt.Println("cat1=",cat1)fmt.Println("猫猫的信息如下:")
fmt.Println("name=",cat1.Name,"Age=",cat1.Age,"Color=",cat1.Color)
fmt.Println("cat1.Hobby",cat1.Hobby)
//输出结果如下:
/*cat1= {小白 3 白色}
猫猫的信息如下:
name= 小白 Age= 3 Color= 白色
*/
}

4》结构体和结构体变量(实例)的区别和联系

通过上面的案例和讲解我们可以看出

1)结构体是自定义的数据类型。代表一类事物

2)结构体变量(实例)是具体的,实际的,代表一个具体变量

5》结构体变量(实例)在内存中存在的布局

var cat1 Cat
cat1.Age = 3
cat1.Color = "白色"

画出示意图

在这里插入图片描述

3.结构体的具体应用

1)结构体的声明

type 结构体名称 struct {filed1 typefiled2 type
}//举例
//如果结构体的名称首字母为大大写,那就意味着该结构体可以被其他包使用
type Student struct {Name stringAge intSocre float32
}

2)字段/属性

基本介绍:

(1)从概念或叫法上看,结构体字段=属性=filed(集授课中,统一叫字段)

(2)字段是结构体的一一个组成部分,一般是基本数据类型、数组。比如我们前面定义猫结构体的Name string就是属性

3)字段和属性的细节说明

1、字段声明语法同变量,示例:字段名 字段类型

2、字段的类型可以为:基本类型、数组或引用类型

3、在创建一个结构体变量后,如果没有给字段赋值,都对应一个零值(默认值),规则同前面讲的一样

布尔类型 false,整型是0 ,字符是 " "

数组类型的默认值和它的元素类型相关,比如score [3]int的默认值是[0 0 0]

指针,slice和map的零值都是nil即没有分配空间(需要用make进行分配空间的操作)

package main
import ("fmt"
)
//指针,slice和map的零值都是nil即没有分配空间
//如果结构体的字段类型是这些,需要先make后才可以使用
type Person struct {Name stringAge intscores [5]float64ptr *int //指针slice []int//切片map1 map[string]string//切片
}func main() {//定义结构体变量var p1 Personfmt.Println(p1) //未赋值之前输出的是{ 0 [0 0 0 0 0] <nil> [] map[]}if p1.ptr == nil {fmt.Println("ok1")}if p1.slice == nil {fmt.Println("ok2")}if p1.map1 == nil {fmt.Println("ok3")}//使用slice,之前一定要makep1.slice = make([]int,10)p1.slice[0] = 100 //使用map也一定先要make操作p1.map1 = make(map[string]string)p1.map1["key1"] = "tom"fmt.Println(p1)
}

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

//不同结构体变量的字段是独立的,互不影响,// 一个结构体变量字段的更改,不影响另一个。结构体是值类型var monster1 Monstermonster1.Name = "牛魔王"monster1.Age = 500monster2 := monster1//默认结构体是值类型,值拷贝monster2.Name = "青牛精"fmt.Println("monster1=",monster1)//monster1= {牛魔王 500}fmt.Println("monster2=",monster2)//monster2= {青牛精 500}}

示意图

在这里插入图片描述

4.创建结构体变量和访问结构体字段

1)方式1-直接声明

案例演示:var person Person

2)方式2-{}

案例演示:var person Person = Person{}//方式2p2 := Person{"mary",20}// p2.Name = "Tom"// p2.Age = 18fmt.Println(p2)

3)方式3

案例:var person *Person = new (Person)
//方式3-8//案例:var person *Person = new (Person)var p3 *Person= new(Person)//因为p3是一个指针,因此标准的给字段赋值方式//(*p3).Name = "smith"也可以这样写 p3.Name = "smith"//原因是go的设计者为了程序员使用方便,在底层会对p3.Name = "smith"进行优化//底层会给p3加上取值运算 (*p3).Name = "smith"(*p3).Name = "smith"p3.Name = "jhon"(*p3).Age = 30fmt.Println(*p3)//{jhon 30}

4)方式4-{}

案例:var person *Person = &Person{}
//var person *Person = &Person{"jerry",60}写成这种形式也是对的//方式4-{}案例:var person *Person = &Person{}var person *Person = &Person{}//因为person是一个指针,因此标准的访问其字段的方法是//(*peron).Name = "scott"//go的设计者为了方便,也可以person.Name = "scott"//底层会进行优化操作(*person).Name = "scott"(*person).Age= 30fmt.Println(*person)

说明

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

5.struct类型的内存分配机制

看看结构体在内存中的存在形式

var p4 Personp4.Name= "小明"p4.Age = 23var p5 *Person = &p4//fmt.Println(*p5.Age)这种用法是错误的//需要将*号包起来,因为.的优先级比*的优先级更高fmt.Println((*p5).Age)//10fmt.Println(p5.Age) //10p5.Name = "tom~"fmt.Printf("p5.Name=%v p4.Name=%v \n",p5.Name,p4.Name)fmt.Printf("p5.Name=%v p4.Name=%v \n",(*p5).Name,p4.Name)/*输出结果如下所示p5.Name=tom~ p4.Name=tom~p5.Name=tom~ p4.Name=tom~*/
fmt.Printf("p4的地址是%p\n",&p4)//p4的地址是0xc042056420fmt.Printf("p5的地址是%p p5的值是%p\n",&p5,p5)//p5的地址是0xc04207a020 p5的值是0xc042056420

其内存图分析如下

在这里插入图片描述

6.结构体的注意事项和使用细节

1)结构体的所有字段在内存中是连续的
package main
import ("fmt"
)//结构体
type Point struct {x inty int
}
//结构体
type Rect struct {leftup,rightDown Point
}func main (){r1 := Rect{Point{1,2},Point{3,4}}//r1有4个int,在内存中连续分布//打印地址fmt.Printf("r1.leftup.x 地址=%p r1.leftup.y 地址=%p r1.rightDown.x 地址=%p r1.rightDown.y 地址=%p\n",&r1.leftup.x,&r1.leftup.y,&r1.rightDown.x,&r1.rightDown.y)//输出结果如下//r1.leftup.x 地址=0xc04200a2c0 r1.leftup.y 地址=0xc04200a2c8 // r1.rightDown.x 地址=0xc04200a2d0 r1.rightDown.y 地址=0xc04200a2d8//r2有两个*Point类型,这两个*Point类型的本身地址也是连续的//但是他们指向的地址不一定是连续的r2 := Rect2{&Point{10,20}, &Point{30,40}}fmt.Printf("r2.leftup本身地址=%p  r2.rightDown 本身地址=%p \n",&r2.leftup,&r2.rightDown)//输出结果为:r2.leftup本身地址=0xc0420421c0  r2.rightDown 本身地址=0xc0420421c8//他们指向的地址不一定是连续的 这个要看编译器或系统运行时而定fmt.Printf("r2.leftup指向地址=%p  r2.rightDown 指向地址=%p \n",r2.leftup,r2.rightDown)//r2.leftup指向地址=0xc0420120b0  r2.rightDown 指向地址=0xc0420120c0}

内存图

在这里插入图片描述

在这里插入图片描述

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

type A struct{Num int
}
type B struct{Num int
}
func main(){var a Avar b Bfmt.Println(a,b)//a=b两个不同类型的结构体不能相互赋值操作//进行强制转换a = A(b) //成功,前提是两个结构体的字段名称//,字段类型是一模一样的,其中一个不一样都不可以fmt.Println(a,b)
}

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

type Student struct{Name stringAge int
}
type Stu Studentfunc main(){var stu1 Studentvar stu2 Stu//stu2 = stu1 //正确吗?会报错,可以这样修改 stu2=Stu(stu1)stu2=Stu(stu1)fmt.Println(stu1,stu2)
}
type integer int
func main(){
var i integer = 10
var j int = 20
//j = i //正确吗? 也不可以的如果要就必须强制进行转换
j = int(i)
fmt.Println(i,j)
}

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

举例:

package main
import ("fmt""encoding/json"
)
type Monster struct {Name string `json:"name"`Age int `json:"age"`Skill string `json:"skill"`
}
func main(){
//1.创建一个Monster变量monster :=Monster{"牛魔王",500,"芭蕉扇"}//2.将monster变量序列化为json格式的字符串//json.Marshal函数中使用反射jsonStr, err:= json.Marshal(monster)if err !=nil{fmt.Println("json处理错误",err)}fmt.Println("jsonStr",string(jsonStr))//输出如下:jsonStr {"Name":"牛魔王","Age":500,"Skill":"芭蕉扇"}//在字段后面加上json序列化后://jsonStr {"name":"牛魔王","age":500,"skill":"芭蕉扇"}
}

序列化的应用场景:

在这里插入图片描述

7.方法

1)基本介绍:

在某些情况下,我们需要声明(定义)方法,比如Person结构体,除了有一些字段外(年龄,姓名…)Person结构体还有一些行为比如:可以说话、跑步。。通过学习,还可以做算术题,这时要使用方法才可以完成

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

2)方法的声明和调用
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
}//给A类型绑定一个方法
func (p Person) test() {p.Name = "jack"fmt.Println("test()",p.Name) //jack
}func main(){//定义一个Person实例var p Personp.Name = "小红"p.test() //调用方法fmt.Println("main()",p.Name) //小红,这里不会因为test()方法中的操作而改变,因为struct不是引用传递而是值传递还是小红
}
3)对上面的代码总结和说明
  1. test方法和Person类型绑定

  2. test方法只能通过Person类型的变量来调用,而不能直接调用,也不能使用其他类型变量来调用

    下面的使用方式都是错误的
    var dog Dog
    dog.test()
    test()
    
  3. func (p Person) test() {}…p表示哪个Person变量调用,这个p就是它的副本,这点和函数传参非常相似

  4. p这个·名字,是可以有程序员指定,不是固定的,比如修改成person也是可以的

4)方法的快速入门

(1)给Person结构体添加speak方法,输出xxx是一个好人

func (p Person) speak(){fmt.Printf("%v是一个好人\n",p.Name)
}

(2)给Person结构体添加jisuan方法,可以计算从1+…+1000的结果

func (p Person) jisuan() {var sum int = 0for i :=1;i <=1000;i++{sum += i}fmt.Printf("1+..+1000的结果是%v\n",sum)
}

(3)给Person结构体添加jisuan2方法,该方法可以接收一个数n,计算从1+…n的结果

func (p Person) jisuan2(n int) {var sum int = 0for i :=1;i <=n;i++{sum += i}fmt.Printf("1+..+%v的结果是%v\n",n,sum)
}

(4)给Person结构体添加getSum方法,可以计算两个数的和并返回结果。

func (p Person) getsum(n1 int,n2 int) int{return n1 + n2
}

(5)方法的调用

    p.speak()p.jisuan()p.jisuan2(10000)res := p.getsum(10,20)fmt.Println("res=",res)
6)方法的调用和传参机制原理(vital)

说明:方法的调用和传参机制和函数基本不一样,不一样的地方是方法调用时,会将调用方法的变量,当做实参也传递给方法。

案例1:画出前面getSum()方法的执行过程+说明

在这里插入图片描述

说明:

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

案例2:请编写一个程序,要求如下

  1. 声明一个结构体Circle,字段为radius
  2. 声明一个方法area和CIrcle绑定,可以返回面积
  3. 提示画出area执行过程+说明
package main
import ("fmt"
)
/*
案例2:请编写一个程序,要求如下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(){//创建一个结构体var c Circlec.radius = 4.0area := c.area()fmt.Printf("圆的面积为%v\n",area)
}

执行内存图:

在这里插入图片描述

其实这样的效率非常慢,会一对一进行值拷贝,用指针可以直接修改它就更快

7)方法的声明(定义)
func (recevier type) methodName (参数列表) (返回值列表){方法体return 返回值
}
  1. 参数列表,表示方法输入
  2. recevier type:表示这个方法和type这个类型进行绑定,或者说该方法作用于type类型
  3. recevier type:type可以是结构体,也可以是其他自定义类型
  4. recevier:就是type类型的一个变量(实例)比如:Person结构体的一个变量(实例)
  5. 参数列表:表示方法输入
  6. 返回值列表:表示返回的值,可以多个
  7. 方法主体。表示为了实现某一个功能代码块
  8. return语句不是必须的。
8)方法注意事项和细节讨论
  1. 结构体类型是值类型,在方法调用中,遵守值类型的传递机制,是值拷贝传递方式

    //为了提高效率,通常我们方法和结构体的指针类型进行绑定
    func (c *Circle) area2() float64 {//因为c是一个指针,因此我们标准访问其字段的方式是(*c)return 3.14 * (*c).radius * (*c).radius//(*c).radius等价为c.radius
    }func main(){//创建一个cicle变量var c Circlec.radius = 5.0//res2 := (&c).area2()//编译底层做了优化,(&c).area2()等价c.area2()//因为编译器会自动加上res2 := c.area2()fmt.Printf("圆的面积为%v\n",res2) //面积为78.5}
    
  2. 如果程序员希望在方法中,修改结构体变量的值,可以通过结构体指针的方式来处理

    func (c *Circle) area2() float64 {c.radius = 10//因为c是一个指针,因此我们标准访问其字段的方式是(*c)return 3.14 * (*c).radius * (*c).radius//(*c).radius等价为c.radius
    }func main(){//创建一个cicle变量var c Circlec.radius = 6.0//res2 := (&c).area2()//编译底层做了优化,(&c).area2()等价c.area2()//因为编译器会自动加上res2 := c.area2()fmt.Printf("圆的面积为%v\n",res2) //314fmt.Println("c.radius = ",c.radius) //10}
    
    //为了提高效率,通常我们方法和结构体的指针类型进行绑定
    func (c *Circle) area2() float64 {fmt.Printf("c是*cicle指向的地址=%p\n",c)c.radius = 10//因为c是一个指针,因此我们标准访问其字段的方式是(*c)return 3.14 * (*c).radius * (*c).radius//(*c).radius等价为c.radius
    }func main(){//创建一个结构体// var c Circle// c.radius = 4.0// area := c.area()// fmt.Printf("圆的面积为%v\n",area)//创建一个cicle变量var c Circlefmt.Printf("main的c结构体变量地址 =%p\n",&c)c.radius = 6.0//res2 := (&c).area2()//编译底层做了优化,(&c).area2()等价c.area2()//因为编译器会自动加上res2 := c.area2()fmt.Printf("圆的面积为%v\n",res2) //314fmt.Println("c.radius = ",c.radius) //10/*运行结果如下main的c结构体变量地址 =0xc0420120a0c是*cicle指向的地址=0xc0420120a0圆的面积为314c.radius =  10*/
    }
    

    在这里插入图片描述

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

    package main
    import ("fmt"
    )
    /*
    Golang中的方法作用在**指定的数据类型**上的
    (即:**和指定的数据类型绑定**)因此自定义类型,
    都可以有方法。而不仅仅是struct,比如int,float32等都可以有方法
    */
    type integer int
    func (i integer) print() {fmt.Println("i=",i)
    }
    //编写一个方法修改i的值
    func (i *integer) ch() {*i = *i +1
    }
    func main(){var i integer = 10i.print() //1 = 10i.ch()i.print() //i =11
    }
    
  4. 方法的访问范围控制的规则,和函数一样,方法名首字母小写,只能在本包访问,方法首字母大写,可以在本包和其他包访问

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

    //定义一个Student结构体
    type Student struct {Name stringAge int
    }
    //给Student实现方法String()
    func (stu *Student) String() string {str := fmt.Sprintf("Name=[%v] Age=[%v]",stu.Name,stu.Age)return str
    }
    func main(){//定义一个Student变量stu := Student{Name : "tom",Age : 20,}//如果实现了*Student类型的String()方法会自动调用String()要传入&stufmt.Println(&stu) 
    }
    
9)有关方法的课外习题
  1. 编写结构体(MethodUtils),编程一个方法,方法不需要参数,在方法中打印一个10 *8 的矩形,在main方法中调用该方法

    package main
    import ("fmt"
    )type MethodUtils struct{//字段...
    }
    //给MethodUtils编写方法
    func (m MethodUtils) print(){for i :=1; i<=10;i++{for j :=1;j<=8;j++{fmt.Print("* ")}fmt.Println()}
    }
    func main(){/*编写结构体(MethodUtils),编程一个方法,方法不需要参数,在方法中打印一个10 *8 的矩形,在main方法中调用该方法*/var m MethodUtilsm.print()
    }
    
  2. 编写一个方法,提供m和n两个参数,方法中打印m*n的矩形

    //编写一个方法,提供m和n两个参数,方法中打印m*n的矩形
    func (mu MethodUtils) print2(m int, n int){for i :=1; i<=m;i++{for j :=1;j<=n;j++{fmt.Print("* ")}fmt.Println()}
    } 
    func main(){/*编写结构体(MethodUtils),编程一个方法,方法不需要参数,在方法中打印一个10 *8 的矩形,在main方法中调用该方法*/var m MethodUtils// m.print()m.print2(3,4)
    }
    
  3. 编写一个方法算该矩形的面积(可以接收长len和宽width),将其作为方法返回值。在main方法中调用该方法,接收返回的面积值并打印

    func  (mu MethodUtils) area(length float32,width float32)float32{return length * width
    }
    func main(){res :=m.area(4.0,5.6)fmt.Println("该矩形的面积为:",res)
    }
    
  4. 编写方法,判断一个数是奇数还是偶数

    func (mu *MethodUtils) qi(m int) {if m %2==0{fmt.Printf("%v是偶数",m)}else{fmt.Printf("%v是奇数",m)}
    }
    
  5. 根据行、列、字符打印对应行数和列数的字符,比如:行:3,列:2,字符 * ,则打印相应的效果

    func (mu *MethodUtils) printe(n int, m int,key string) {for i :=1;i<= n;i++{for j :=1;j<=m;j++{fmt.Print(key)}fmt.Println()}
    }
    
  6. 定义小小计算器结构体(Calcuator),实现加减乘除四个功能

    实现形式1:分4个方法完成

    //实现形式1:
    type Calcuator struct{Num1 float64Num2 float64
    }
    //求和
    func (calcuator *Calcuator) getSum() float64{return calcuator.Num1 +calcuator.Num2
    }
    //求差
    func (calcuator *Calcuator) getJian() float64{return calcuator.Num1 -calcuator.Num2
    }
    //求积
    func (calcuator *Calcuator) getJi() float64{return calcuator.Num1 *calcuator.Num2
    }
    //求商
    func (calcuator *Calcuator) getShang() float64{return calcuator.Num1 /calcuator.Num2
    }
    func main(){n := Calcuator{24.0,8.0}he :=n.getSum()cha :=n.getJian()ji :=n.getJi()shangha :=n.getShang()fmt.Printf("和为%v 差为%v 积为%v 商为%v",he,cha,ji,shangha)
    }
    

    实现形式2:用一个方法搞定

    //实现形式2
    func (calcuator *Calcuator) getRes(operator byte) float64 {res :=0.0switch operator {case '+':res = calcuator.Num1 +  calcuator.Num2case '-':res = calcuator.Num1 -  calcuator.Num2case '*':res = calcuator.Num1 *  calcuator.Num2case '/':res = calcuator.Num1 / calcuator.Num2default:fmt.Println("运算符输入有误")}return res
    }
    func main(){
    s := Calcuator{32.0,8.0}res :=s.getRes('*')fmt.Println("运算的结果为:",res)
    }
    
    10)方法和函数的区别
    1. 调用方式不一样

      函数的调用方式 : 函数名(实参列表)

      方法的调用方式:变量.方法名(实参列表)

    2. 对于普通函数,接收者为值类型的时候,不能将指针类型的数据直接传递,反之亦然

      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)
      //  test01(&p)//不可以传地址
      test02(&p)}
    3. 对于方法如(struct的方法),接受者为值类型的时候,可以直接用指针类型的变量调用方法,反过来同样也可以

      //方法
      func (p Person) test03(){fmt.Print("test03=",p.Name)
      }func main(){p := Person{"tom"}p.test03() //可以
      (&p).test03()//指针传入地址也可以,从形式上是传入了地址但是本质上仍然是值拷贝
      }

      总结:

      1. 不管调用形式如何,真正决定是值拷贝还是地址拷贝,看这个方法是和哪个类型绑定
      2. 如果是和值类型,比如(p Person),则是值拷贝,如果是指针类型,是(p *Person)则是地址拷贝

8.面向对象编程的应用实例

1)步骤

  1. 声明(定义)结构体,确定结构体名
  2. 编写结构体的字段
  3. 编写结构体的方法

2)案例

  1. 编写一个Student结构体,包含name、gender、age、id、score字段,分别是string、string、int、int、float54类型

  2. 结构体中声明一个say()方法,返回string类型,方法返回信息包含所有字段值

  3. 在main1方法中,创建一个stsudent结构体实例(变量),并访问say方法,并将调用结构打印输出

    package main
    import ("fmt"
    )
    type Student struct{name stringgender stringage intid intscore float64}
    func (stu *Student) say() string{//使用fmt.Sprintf()函数将值转换为字符串inforstr :=fmt.Sprintf("student的信息如下 name = [%v] gender=[%v] age = [%v] id=[%v] score=[%v]",stu.name,stu.gender,stu.age,stu.id,stu.score)return inforstr
    }
    func main(){
    var stu = Student{"小红","男",34,1001,99.5}
    info :=stu.say()
    fmt.Println(info)
    }
    

3)案例2:盒子案例

  1. 编程创建一个Box结构体,在其中声明三个字段表示一个立方体的长、宽、高,长宽高要从终端获取

  2. 声明一个方法获取立方体的体积

  3. 创建一个Box结构体变量,打印给定尺寸的立方体的体积

    type Box struct{length float64width float64high  float64
    }
    func (b Box) getVolumn() float64{return b.length * b.width * b.high
    }func main(){
    //测试box
    var box Box
    box.length = 1.1
    box.width = 2.0
    box.high = 3.0
    Volum := box.getVolumn()
    fmt.Printf("这个黑子的体积是%.2f",Volum)
    }
    

3)案例3:景区门票案例

  1. 一个景区根据游人的年龄收取不同价格的民票,比如年龄大于18,收费20元,其他情况门票免费

  2. 请编写Visitor结构体,根据年龄段决定能够购买的门票价格并输出

    type Vistor struct{Name stringAge int
    }
    func (vistor Vistor) showPrice(){if vistor.Age >18{fmt.Println("门票价格为20元")}else{fmt.Println("免费")}}
    func main(){var vio Vistorfor {fmt.Println("请输入你的名字")fmt.Scanln(&vio.Name)if vio.Name == "n" {fmt.Println("退出程序")break}fmt.Println("请输入你的年龄")fmt.Scanln(&vio.Age)vio.showPrice()
    }
    }
    

面向对象编程(下)

1.创建结构体变量时指定字段值

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

2)创建结构体变量时指定字段值方式

  1. way1

    //在创建结构体变量时,就直接指定字段的值var stu1 = Stu{"tom",23}stu2 :=Stu{"晓明",20}//创建结构体变量时。把字段名和字段值写在一起var stu3 = Stu{ //这种写法更加稳健不依赖于字段的顺序Name : "jack",Age : 20,}//可以类型颠倒var stu3 = Stu{ //这种写法更加稳健不依赖于字段的顺序Age : 20,Name : "jack",}
    
  2. way2

    //方式2.返回结构体的指针类型(!!!)
    var stu5 *(Stu)= &Stu{"小王",29} //stu2--->地址 ---》结构体数据[xxxx,xxxx]stu6 :=&Stu{"小王",39}//在创建结构体指针变量是,把字段名和字段值写在一起,这种写法,就不依赖于字段的定义顺序var stu7 = &Stu{Name : "小李",Age : 49,}var stu8 = &Stu{Age :59,Name : "小李~",}fmt.Println(*stu5,*stu6,*stu7,*stu8)//
    }
    

2.工厂模式

1)说明

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

看一个需求

一个结构体的声明是这样的

package model
type Student struct{
Name string ....
}

因为这里的Student的首字母S是大写的,如果我们想在其它包创建Student的实例(比如main包),引入model包后,就可以直接创建Student结构体的变量(实例)

但是问题来了,如果首字母是小写的,比如是type student struct {,}就不行了,怎么办—》工厂模式来解决

2)工厂模式来解决问题

使用工程模式实现挎包创建结构体实例(变量)的案例

1》如果model包的结构体变量首字母大写,引入后,直接使用没有任何问题

package model
//定义一个结构体
type Student struct{Name stringScore float64
}
package main
import ("fmt""go_code/object2/factory/model"
)func main(){//创建一个Student的实例var stu = model.Student{Name : "Tom",Score : 78.9,}fmt.Println(stu)
}

2》如果model包的结构体变量首字母小写,引入后,不能直接使用,可以使用工厂模式解决

package model
//定义一个结构体
type student struct{Name stringScore float64
}
//因为student结构体首字母是小写,因此是只能在
//model包使用,通过工厂模式解决
func NewStudent(n string,s float64) *student{return &student{Name : n,Score : s,}
}在main包下的main,go中使用
package main
import ("fmt""go_code/object2/factory/model"
)func main(){//当student结构体首字母是小写的我们可以通过工厂模式解决var stu = model.NewStudent("tom~",88.8)fmt.Println(*stu)
}

问题:

思考一下,如果model包的Student的结构体字段Score改为score,我们还可以正常访问吗?当然不可以正常访问,我们要使用一个方法去访问

package model
//定义一个结构体
type student struct{Name stringscore float64
}//因为student结构体首字母是小写,因此是只能在
//model包使用,通过工厂模式解决
//返回这个结构体变量的指针
func NewStudent(n string,s float64) *student{return &student{Name : n,score : s,}
}//如果score字段首字母小写,则在其他包不可以访问,我们可以提供方法
//就是用结构体指针来访问结构体字段
func (s *student) GetScore() float64{return s.score //(底层会优化为//return (*s).score)
}package main
import ("fmt""go_code/object2/factory/model"
)func main(){//当student结构体首字母是小写的我们可以通过工厂模式解决var stu = model.NewStudent("tom~",88.8)fmt.Println(*stu)//注意了,这里我们使用getScore方法去访问stu,scorefmt.Printf("name=%v Score=%v",stu.Name,stu.GetScore())//fmt.Printf("name=%v score的值为=%v",stu.Name,(*stu).GetScore())
}

3.vscode快捷键

1)删除当前行 ctrl +shift +k [也可自定义]

2)向上/向下复制当前行Shfit + Alt + 下箭头/上箭头

3)补全代码 alt +/

4)添加注释和取消注释 ctrl + /

5)快速修复 alt + /

6)快速格式化代码 shift + alt +f

特别说明:

1)VSCODE的快捷键不要和输入法冲突,否则不会生效

2)一些快捷键需要安装Go插件后才能生效

4.面向对象编程的三大特性

1)基本介绍

Golang仍然有面向对象编程的继承,封装和多态的特性,只是实现的方式和其他的OOP语言不一样,下面就一一来讲解吧

2)面向对象编程思想–抽象

如何理解抽象?

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

在这里插入图片描述

代码实现:

package main
import ("fmt"
)
//定义一个结构体
type Account struct{AccountNo stringPwd stringBalance float64
}//方法
//1.存款
func (account *Account) Deposite(money float64,pwd string){//看看输入的密码是否正确if pwd !=account.Pwd{fmt.Println("你输入的密码不正确")return}//看看存款金额是否正确if money <= 0 {fmt.Println("你输入的金额不正确")return}account.Balance += moneyfmt.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 -= moneyfmt.Println("取款成功")
}//查询余额func (account *Account)Query(pwd string){//看看输入的密码是否正确if pwd !=account.Pwd{fmt.Println("你输入的密码不正确")return}fmt.Printf("你的账号为=%v 余额=%v \n",account.AccountNo,account.Balance)
}func main(){//测试一下account := Account{AccountNo : "gs111111",Pwd : "666666",Balance  : 1000.0,}for{//这里可以做的更加灵活,就是让用户通过控制台来输入命令//菜单account.Query("666666")account.Deposite(200,"666666")account.Query("666666")account.WithDraw(150.0,"666666")account.Query("666666")}}
3)面向对象编程思想-封装
&1介绍

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

&封装的理解和好处
  1. 隐藏实现细节

  2. 可以对数据进行验证,保证安全合理

    type Person struct{
    Age int
    }
    
&如何实现封装
  1. 对结构体中的属性进行封装(字段名小写)
  2. 通过方法,包 实现封装

4)封装的实现步骤

  1. 将结构体、字段(属性)的首字母小写(不能导出了,其他包不能使用,类似private)

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

  3. 提供一个首字母大写的Set方法(类似其他语言的public),用于对属性判断并赋值

    func (var 结构体类型名)SetXxx(参数列表)(返回值列表){//加入数据验证的业务逻辑var.字段 = 参数
    }
    
  4. 提供一个首字母大写的Get方法(类似其他语言的public),用于获取属性的值

    func (var 结构体类型名)GetXxx(){
    return var.字段
    }
    

    特别说明:在Golang开发中并没有特别强调封装,这点并不像java,所以学过java的朋友不能总是用java语法特征来看待Glang,Golang本身对向对象的特性做了简化。

&案例入门

有一个程序,不能随便查看人的年龄,工资等隐私,并对输入的年龄进行合理的验证.设计:model包(person go) main包(main.go调用Person结构体)

person.go
package model
import ("fmt"
)
type person struct{Name stringage int //字母小写,其他包不能访问sal float64
}//写一个工厂模式的函数,相当于构造函数
func NewPerson(name string) *person{return &person{Name : name,}
}
//为了访问age和sal我们编写一个SertXxx的方法和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.go进行调用
package main
import ("fmt""go_code/object2/encapsulate/model"
)func main (){p :=model.NewPerson("smith")p.SetAge(18)p.SetSal(5000.0)fmt.Println(*p)fmt.Println(p.Name,"age =",p.GetAge(),"sal = ",p.GetSal())
}
&练习:

创建一个程序在model包中定义一个Account结构体:在main函数中体会Golang封装性

  1. Account结构体要求具有字段:账号(长度在6-10之间)、余额(必须>20)、密码(必须是六位)
  2. 通过SetXxx的方法给Account的字段赋值
  3. 在main函数中测试
package model
import ("fmt"
)
//定义一个结构体
type account struct{accountNo stringpwd stringbalance 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,}
}//方法
//1.存款
func (ac *account) Deposite(money float64,pwd string){//看看输入的密码是否正确if pwd !=ac.pwd{fmt.Println("你输入的密码不正确")return}//看看存款金额是否正确if money <= 0 {fmt.Println("你输入的金额不正确")return}ac.balance += moneyfmt.Println("存款成功")
}
//取款
func (ac *account) WithDraw(money float64,pwd string){//看看输入的密码是否正确if pwd !=ac.pwd{fmt.Println("你输入的密码不正确")return}//看看存款金额是否正确if money <= 0 || money >ac.balance{fmt.Println("你输入的金额不正确")return}ac.balance -= moneyfmt.Println("取款成功")
}//查询余额
func (ac *account)Query(pwd string){//看看输入的密码是否正确if pwd !=ac.pwd{fmt.Println("你输入的密码不正确")return}fmt.Printf("你的账号为=%v 余额=%v \n",ac.accountNo,ac.balance)
}main。go中进行测试
package main
import ("fmt""go_code/object3/ex2/model"
)
func main(){account := model.NewAccount("jzh1111","999999",48)if account !=nil{fmt.Println("创建成功=",*account)}else{fmt.Println("创建失败")}
}
4)面向对象编程思想-继承
&1.为什么需要继承

当然是为了代码复用

走代码看看

package main
import ("fmt"
)
//编写一个学生考试系统
type Pupil struct {Name stringAge intScore int
}//显示成绩
func (p *Pupil) ShowInfo(){fmt.Printf("显示学生名字=%v 年龄=%v 成绩=%v",p.Name,p.Age,p.Score)
}
func (p *Pupil) SetScore(score int) {p.Score = score
}func (p *Pupil) testing() {fmt.Println("小学生正在考试")
}
//大学生,研究生。。
type Graduate struct {Name stringAge intScore int
}//显示成绩
func (p *Graduate) ShowInfo(){fmt.Printf("显示学生名字=%v 年龄=%v 成绩=%v",p.Name,p.Age,p.Score)
}
func (p *Graduate) SetScore(score int) {p.Score = score
}func (p *Graduate) testing() {fmt.Println("大学生正在考试")
}func main(){//测试var pupil = &Pupil{Name : "Tom",Age : 10,}pupil.testing()pupil.SetScore(90)pupil.ShowInfo()//测试var graduate = &Graduate{Name : "jhon",Age : 10,}graduate.testing()graduate.SetScore(90)graduate.ShowInfo()
}
//代码冗余 。。。高中生。。。

对上面代码做一个小结

  1. Pupil和Graduate两个结构体的字段和方法集合一样,但我们欠缺写了相同的代码,代码复用性不强
  2. 出现代码冗余,而且代码不利于维护,同时也不利于功能的扩展
  3. 解决方法:通过继承的方式解决
&2继承的基本介绍和示意图

继承可以解决代码复用,让我们的编程更加靠近人类思维

当多个结构体存在相同的属性(字段)和方法时,可以从这些结构体中抽象出结构体(比如刚才的Student),在该结构体中定义这些相同的属性和方法。

其他的结构体不需要重新定义这些属性(字段)和方法,只需嵌套一个Student匿名结构体即可

在这里插入图片描述

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

&3.嵌套匿名结构体的基本语法
type Goods struct{Name stringPrice int
}
type Book struct {Goods//这里就是嵌套匿名结构体Goods相当于加入了Name和Pirce这两个字段Writer string
}

&4.应用案例

我们对先前有关student的结构体进行改进操作减少其冗余性,代码实现

package main
import ("fmt"
)
//编写一个学生考试系统
type Student struct{Name stringAge intScore int
}
//将Pupil和Graduate共有的方法也绑定到Student
func (stu *Student) ShowInfo(){fmt.Printf("显示学生名字=%v 年龄=%v 成绩=%v",stu.Name,stu.Age,stu.Score)
}func (stu *Student)SetScore(score int) {stu.Score = score
}//给*Student增加一个方法,那么Pupil 和Graduate都可以使用该方法
func (stu *Student) GetSum(n1 int, n2 int) int{return n1 + n2
}//小学生
type Pupil struct {Student  //嵌入Student匿名结构体
}//这是Pupil结构体特有的方法,保留
func (p *Pupil) testing() {fmt.Println("小学生正在考试")
}
//大学生,研究生。。
type Graduate struct {Student  //嵌入Student匿名结构体
}
//保留特有的方法
func (p *Graduate) testing() {fmt.Println("大学生正在考试")
}func main(){//当我们对结构体嵌入了匿名结构体使用方法会发生变化pupil :=&Pupil{}pupil.Student.Name ="tom~"pupil.Student.Age = 8pupil.testing()pupil.Student.SetScore(70)pupil.Student.ShowInfo()fmt.Println("res=",pupil.Student.GetSum(1,2))//大学生进行操作graduate :=&Graduate{}graduate.Student.Name ="MARY"graduate.Student.Age = 28graduate.testing()graduate.Student.SetScore(90)graduate.Student.ShowInfo()fmt.Println("res=",graduate.Student.GetSum(10,79))}

&4.继承的深入讨论1

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

    package main
    import ("fmt"
    )type A struct {Name stringage 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 Bb.A.Name = "tom"b.A.age = 19b.A.SayOk()b.A.Hello()}
    
  2. 匿名结构体字段访问可以简化(如果B结构体中没有变量那么就会自动去寻找嵌入的结构体中的字段或方法)。

    var b Bb.A.Name = "tom"       b.Name = "tom"b.A.age = 19    ==>    b.age = 19b.A.SayOk()            b.SayOk()  b.A.Hello()            b.Hello()
    

    总结

    1. 当我们直接通过b访问字段或方法时,其执行流程如下比如b.Name

    ​ 2.编译器会先看b对应的类型有没有Name,如果有则直接调用B类型的 Name字段,如果没有继续查找,没有找到就报错

    ​ 3.如果没有就去看嵌入中的匿名结构体有没有声明Name字段

  3. 当结构体和匿名结构体有相同的字段或者方法时,编译器采用就近访问原则访问如希望访问匿名结构体的字段和方法,可以通过匿名结构体名来区分。

    package main
    import ("fmt"
    )type A struct {Name stringage 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 (b *B)SayOk() {fmt.Println("B SayOK",b.Name)
    }
    func main(){var b B
    //    b.A.Name = "tom"
    //    b.A.age = 19
    //    b.A.SayOk()
    //    b.A.hello()b.Name = "jack"b.age  = 100b.SayOk()  //B SayOK jackb.hello() // A Hello jack}
    
  4. 结构体嵌入两个(或多个)匿名结构体,如两个匿名结构体有相同额字段和方法(同时结构体本身没有同名的字段和方法),在访问时,就必须明确指定匿名结构体名字,否则编译报错

    package main
    import ("fmt"
    )type A struct {Name stringage int
    }
    type B struct {Name stringscore float64
    }
    type C struct {ABName string
    }
    func main(){var c C//如果 c 无Name字段,而A和B有Name,这时就必须指定匿名结构体名字来区分//c.Name = "tom"//会报错,因为不知道选择A里的Name还是B里面的Name//c.A.Name = "tom" //具体指定哪一个结构体中的Namec.Name = "tom" //成功是因为C有Name}
    //这个规则对于方法也是有效的
    
  5. 如果一个struct嵌套了一个有名的结构体,这种模式就是组合,如果是组合关系,那么在访问组合结构体的字段或方法时,必须带上结构体的名字

    type D struct {a A //有名结构体
    }
    func main(){var c Cvar d D
    //    d.Name= "jack"//报错,必须将结构体名字带上d.a.Name = "jack"
  6. 嵌套匿名结构体,也可以在创建结构体变量(实例)时,直接指定各个匿名结构体字段的值

    package main
    import ("fmt"
    )type A struct {Name stringage int
    }
    type B struct {Name stringscore float64
    }
    type C struct {ABName string
    }
    type D struct {a A //有名结构体
    }type Goods struct{Name stringPrice float64
    }type Brand struct{Name stringAddress string
    }
    type TV struct {GoodsBrand
    }
    type TV2 struct {  //也可将指针类型传入进去*Goods*Brand
    }
    func main(){var c C//如果 c 无Name字段,而A和B有Name,这时就必须指定匿名结构体名字来区分//c.Name = "tom"//会报错,因为不知道选择A里的Name还是B里面的Name//c.A.Name = "tom" //具体指定哪一个结构体中的Namec.Name = "tom" //成功是因为C有Namevar d D
    //    d.Name= "jack"//报错,必须将结构体名字带上d.a.Name = "jack"tv := TV{ Goods{"电视机001",5000.99},Brand{"海尔","山东"}, }tv2 := TV{Goods{Price : 6000.90,Name : "电视002",},Brand{Name : "长虹",Address :"北京",},}
    //创建之时要把地址传入进去tv3 := TV2{ &Goods{"电视机003",7000.99},&Brand{"创维","深圳"}, }fmt.Println("TV=",tv)fmt.Println("TV2=",tv2)fmt.Println("TV3=",*tv3.Goods,*tv3.Brand)tv4 := TV2{ &Goods{Name : "电视机004",Price : 9900.99,},&Brand{Name : "康佳",Address : "上海",}, }fmt.Println("TV4=",*tv4.Goods,*tv4.Brand)
    }
    
&4.课堂练习

结构体的匿名字段是基本数据类型,如何访问,下面的代码

type A struct {  Name stringAge int
}
type Stu struct {Aint
}
func  main(){
stu :=Stu{}
stu.Name = "tom"
stu.Age = 10
stu.int = 80
fmt.Println(stu)
}
//输出结果为
{{tom 10} 80}

说明:

  1. 如果一个结构体有int类型的匿名字段,就不能有第二个。
  2. 如果需要多个int类型字段,则必须给int指定名字
&5.多重继承

多重继承说明

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

&6.多重继承细节说明
  1. 如果嵌入的匿名结构体有相同的字段名或方法名,则在访问时,需要通过匿名结构体类型来区分
    main(){
    var c C

    var d D
    // d.Name= “jack”//报错,必须将结构体名字带上
    d.a.Name = “jack”

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

    package main
    import ("fmt"
    )type A struct {Name stringage int
    }
    type B struct {Name stringscore float64
    }
    type C struct {ABName string
    }
    type D struct {a A //有名结构体
    }type Goods struct{Name stringPrice float64
    }type Brand struct{Name stringAddress string
    }
    type TV struct {GoodsBrand
    }
    type TV2 struct {  //也可将指针类型传入进去*Goods*Brand
    }
    func main(){var c C//如果 c 无Name字段,而A和B有Name,这时就必须指定匿名结构体名字来区分//c.Name = "tom"//会报错,因为不知道选择A里的Name还是B里面的Name//c.A.Name = "tom" //具体指定哪一个结构体中的Namec.Name = "tom" //成功是因为C有Namevar d D
    //    d.Name= "jack"//报错,必须将结构体名字带上d.a.Name = "jack"tv := TV{ Goods{"电视机001",5000.99},Brand{"海尔","山东"}, }tv2 := TV{Goods{Price : 6000.90,Name : "电视002",},Brand{Name : "长虹",Address :"北京",},}
    //创建之时要把地址传入进去tv3 := TV2{ &Goods{"电视机003",7000.99},&Brand{"创维","深圳"}, }fmt.Println("TV=",tv)fmt.Println("TV2=",tv2)fmt.Println("TV3=",*tv3.Goods,*tv3.Brand)tv4 := TV2{ &Goods{Name : "电视机004",Price : 9900.99,},&Brand{Name : "康佳",Address : "上海",}, }fmt.Println("TV4=",*tv4.Goods,*tv4.Brand)
    }
    
&4.课堂练习

结构体的匿名字段是基本数据类型,如何访问,下面的代码

type A struct {  Name stringAge int
}
type Stu struct {Aint
}
func  main(){
stu :=Stu{}
stu.Name = "tom"
stu.Age = 10
stu.int = 80
fmt.Println(stu)
}
//输出结果为
{{tom 10} 80}

说明:

  1. 如果一个结构体有int类型的匿名字段,就不能有第二个。
  2. 如果需要多个int类型字段,则必须给int指定名字
&5.多重继承

多重继承说明

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

&6.多重继承细节说明
  1. 如果嵌入的匿名结构体有相同的字段名或方法名,则在访问时,需要通过匿名结构体类型来区分
  2. 为了保证代码的简洁性,尽量不要使用多重继承

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/100233.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

vue3 组件v-model绑定props里的值,修改组件的值要触发回调

很早之前就写了&#xff0c;一直没写篇博客记录下 <select v-model"typeVal" />const emit defineEmits([update:type]); const props defineProps({type: { type: String, default: }, });const typeVal computed({get() {return props.type;},set(value…

Docker-compose创建LNMP服务并运行Wordpress网站平台

一、部署过程 1.安装Docker #关闭防火墙 systemctl stop firewalld.service setenforce 0#安装依赖包 yum install -y yum-utils device-mapper-persistent-data lvm2 #设置阿里云镜像源 yum-config-manager --add-repo https://mirrors.aliyun.com/docker-ce/linux/centos/d…

17基于matlab卡尔曼滤波的行人跟踪算法,并给出算法估计误差结果,判断算法的跟踪精确性,程序已调通,可直接运行,基于MATLAB平台,可直接拍下。

17基于matlab卡尔曼滤波的行人跟踪算法&#xff0c;并给出算法估计误差结果&#xff0c;判断算法的跟踪精确性&#xff0c;程序已调通&#xff0c;可直接运行&#xff0c;基于MATLAB平台&#xff0c;可直接拍下。 17matlab卡尔曼滤波行人跟踪 (xiaohongshu.com)

【Redis学习1】Redis持久化机制详解

Redis持久化机制详解 一、Redis为什么需要持久化机制 Redis一般用作缓存&#xff0c;其数据存储在内存中&#xff0c;当Redis宕机后&#xff0c;内存中的数据将会丢失。因此使用缓存的时候&#xff0c;我们经常需要对内存中的数据进行持久化也就是将内存中的数据写入到硬盘中…

Jetpack:004-如何使用文本组件

文章目录 1. 概念介绍2. 使用方法2.1 通用参数2.2 专用参数 3. 示例代码4. 内容总结 我们在上一章回中介绍了Jetpack组件在布局中的对齐方式&#xff0c;本章回中主要介绍文 本组件的使用方法。闲话休提&#xff0c;让我们一起Talk Android Jetpack吧 1. 概念介绍 我们在本章…

基于ffmpeg给视频添加时间字幕

FFmpeg是一套可以用来记录、转换数字音频、视频&#xff0c;并能将其转化为流的开源计算机程序&#xff0c;我们可以基于ffmpeg对视频进行各种操作。本文主要介绍基于ffmpeg给视频添加字幕&#xff0c;字幕的内容为视频所播放的时间&#xff08;故需要安装ffmpeg&#xff0c;具…

ssti 前置学习

python venv环境 可以把它想象成一个容器&#xff0c;该容器供你用来存放你的Python脚本以及安装各种Python第三方模块&#xff0c;容器里的环境和本机是完全分开的 创建venv环境安装flask #apt install python3.10-venv #cd /opt #python3 -m venv flask1 #cd /opt 选…

吃透底层:从路由到前缀树

前言 今天学到关于路由相关文章&#xff0c;发现动态路由中有一个很常见的实现方式是前缀树&#xff0c;很感兴趣这个算法&#xff0c;故进行记录。 前缀树 Trie&#xff08;又被叫做字典树&#xff09;可以看作是一个确定有限状态自动机&#xff0c;尽管边上的符号一般是隐含…

Netty通信在中间件组件中的广泛使用-Dubbo3举例

Netty是一个高性能异步IO通信框架&#xff0c;封装了NIO&#xff0c;对各种bug做了很好的优化解决。所以很多中间件底层的通信都会使用Netty&#xff0c;比如说&#xff1a;Dubbo3&#xff0c;rocketmq&#xff0c;ElasticSearch等。 比方说&#xff0c;我们使用dubbo作为rpc跨…

基于SSM线上课程管理系统设计与实现

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;采用JSP技术开发 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#x…

C++构造函数

在本文中&#xff0c;您将学习C 中的构造函数。您将学习什么是构造函数&#xff0c;如何创建它以及C 中的构造函数类型。 构造函数是成员函数的一种特殊类型&#xff0c;它在创建对象时会自动对其进行初始化。编译器通过其名称和返回类型将给定的成员函数标识为构造函数。构造函…

各报文段格式集合

数据链路层-- MAC帧 前导码8B&#xff1a;数据链路层将封装好的MAC帧交付给物理层进行发送&#xff0c;物理层在发送MAC帧前&#xff0c;还要在前面添加8字节的前导码&#xff08;分为7字节的前同步码1字节的帧开始定界符&#xff09;MAC地址长度6B数据长度46&#xff5e;1500B…

爬取微博热榜并将其存储为csv文件

&#x1f64c;秋名山码民的主页 &#x1f602;oi退役选手&#xff0c;Java、大数据、单片机、IoT均有所涉猎&#xff0c;热爱技术&#xff0c;技术无罪 &#x1f389;欢迎关注&#x1f50e;点赞&#x1f44d;收藏⭐️留言&#x1f4dd; 获取源码&#xff0c;添加WX 目录 前言1.…

防止SQL注入攻击的综合解决方案

文章目录 摘要背景和危害性防御措施示例代码&#xff08;Java&#xff09;示例代码&#xff08;PHP&#xff09;示例MySQL命令示例代码&#xff08;Python&#xff09;示例代码&#xff08;C#&#xff0c;使用Entity Framework&#xff09; 进一步防御SQL注入攻击的措施使用ORM…

【Linux】Git使用

一、Git简介 Git 是一个开源的分布式版本控制系统&#xff0c;用于敏捷高效地处理很小或非常大的项目。 Git 是 Linus Torvalds 为了帮助管理 Linux 内核开发而开发的一个开放源码的版本控制软件。 Git 与常用的版本控制工具 CVS, Subversion 等不同&#xff0c;它采用了分布…

Kafka 简介之(学习之路)

正文 一、简介 1.1 概述 Kafka是最初由Linkedin公司开发&#xff0c;是一个分布式、分区的、多副本的、多订阅者&#xff0c;基于zookeeper协调的分布式日志系统&#xff08;也可以当做MQ系统&#xff09;&#xff0c;常见可以用于web/nginx日志、访问日志&#xff0c;消息服务…

原生JS-鼠标拖动

原生JS-鼠标拖动 通过鼠标的点击事件通过h5的属性 通过鼠标的点击事件 步骤&#xff1a; 1. 鼠标按下div。 2. 鼠标移动&#xff0c;div跟着移动 原生js&#xff0c;实现拖拽效果。1. 给被拖拽的div加上 onmousedown 鼠标【按下事件】。鼠标按下的时候&#xff0c;开始监听鼠标…

【Spring框架学习3】Spring Bean的作用域 及 生命周期

一、Spring Bean的作用域有哪些&#xff1f; Spring框架支持以下五种Bean的作用域&#xff1a; Singleton&#xff1a;这是默认的作用域&#xff0c;在每个Spring IoC容器中只有一个Bean的实例(IoC初始化后)。Spring 中的 bean 默认都是单例的&#xff0c;是对单例设计模式的…

cesium图标漂移分析与解决

漂移现象如下 什么是图标漂移&#xff1f; 随着视野改变&#xff0c;图标相对于地面发生了相对位置的变化 让人感觉到图标有飘忽不定的感觉 原因分析 图标是静止的&#xff0c;它的位置在世界坐标系中是绝对的、静止的。 漂移大部分的原因是&#xff1a; 透视关系发生了错…

新华三辅导笔记 2023/10/9-2023/10/13

新华三辅导笔记 一、需要用到的软件二、计算机网络概述1、计算机网络的定义和基本功能&#xff08;1&#xff09;什么是计算机网络&#xff08;2&#xff09;计算机网络的基本功能 2、&#xff08;1&#xff09;局域网、城域网和广域网&#xff08;范围划分&#xff09;&#x…