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)关联,耦合性低,也非常灵活。在Golang中面向接口编程是非常重要的特性。
对上图的说明:
1、将一类事物的特性提取出来(比如猫类),形成一个新的数据类型,就是一个结构体。
2、通过这个结构体,我们可以创建多个变量(实例/对象)
3、事物可以猫类,也可以是Person,Fish或是某个工具类。。。
代码如下:
结构体和结构体变量(实例)的区别和联系
基本语法:
type结构体名称struct{field1typefield2type}举例:
typeCatstruct{NamestringAgeintColorstringHobbystring}字段/属性基本介绍
1、从概念或叫法上看:结构体字段=属性=field
2、字段是结构体的一个组成部分,一般是基本数据类型、数组,也可是引用类型。比如我们前面定义猫结构体的Namestring就是属性
4、不同结构体变量的字段是独立,互不影响,一个结构体变量字段的更改,不影响另外一个,结构体是值类型。
varpersonPersonfuncmain(){varpersonPersonperson.Name="bingle"person.Age=18fmt.Println("nameis",person.Name)fmt.Println("ageis",person.Age)}方式—2:{}
varpersonPerson=Person{}funcmain(){person:=Person{Name:"bingle",Age:18,}fmt.Println("nameis",person.Name)fmt.Println("ageis",person.Age)}方式—3:&varperson*Person=new(Person)funcmain(){varperson*Person=new(Person)//因为person是一个指针,因此标准的给字段赋值方式//(*person).Name="bingle"也可以这样写person.Name="bingle"//原因:go的设计者为了程序员使用方便,底层会对person.Name="bingle"进行处理//会给person加上取值运算(*person).Name="bingle"(*person).Name="bingle"person.Name="bingle"//(*person).Age=30person.Age=100fmt.Println(*person)fmt.Println("nameis",person.Name)fmt.Println("ageis",person.Age)}方式—4:{}
varperson*Person=&Person{}funcmain(){//下面的语句,也可以直接给字符赋值//varperson*Person=&Person{"bingle",60}varperson*Person=&Person{}//因为person是一个指针,因此标准的访问字段的方法//(*person).Name="bingle"//go的设计者为了程序员使用方便,也可以person.Name="bingle"//原因和上面一样,底层会对person.Name="bingle"进行处理,会加上(*person)(*person).Name="bingle"person.Name="bingle~~"(*person).Age=88person.Age=10fmt.Println("nameis",person.Name)fmt.Println("ageis",person.Age)}说明:
1、第3种和第4种方式返回的是结构体指针。
2、结构体指针访问字段的标准方式应该是:(*结构体指针).字段名,比如(*person).Name="bingle"
3、但go做了一个简化,也支持结构体指针.字段名,比如person.Name="bingle"。更加符合程序员使用的习惯,go编译器底层对person.Name做了转化(*person).Name。
我们来看一个思考题:
定义一个Person结构体
下面一段代码输出什么内容?
packagemainimport"fmt"typePersonstruct{NamestringAgeint}funcmain(){varperson1Personperson1.Age=18person1.Name="bingle1111"varperson2Person=person1fmt.Println(person2.Age)person2.Name="bingle2222"fmt.Printf("person2.Nameis%v,person1.Nameis%v",person2.Name,person1.Name)}输出结果:
变量总是存在内存中的,那么结构体变量在内存中是怎样存在的?
结构体在内存中的示意图:(和上面牛魔王、青牛精一个原理)
是值拷贝,内存空间是完全独立的
看下列代码,并分析原因:
funcmain(){varperson1Personperson1.Age=18person1.Name="bingle1111"varperson2*Person=&person1fmt.Println(*person2.Age)}不能这样写,会报错,原因是,.的运行优先级比*高
结构体使用注意事项和细节
1、结构体的所有字段在内存中是连续的
看下列代码:
packagemainimport"fmt"//结构体typePointstruct{xintyint}//结构体typeRectstruct{leftUp,rightDownPoint}funcmain(){r1:=Rect{Point{1,2},Point{3,4},}//r1有四个int,在内存中是连续分布//打印地址fmt.Printf("r1.leftUp.x地址=%pr1.leftUp.y地址=%pr1.rightDown.x地址=%pr1.rightDown.y地址=%p\n",&r1.leftUp.x,&r1.leftUp.y,&r1.rightDown.x,&r1.rightDown.y)}执行结果:
内存中的示意图:(int64是8个字节)
r2有两个*Point类型,这个两个*Point类型的本身地址也是连续的,但是他们指向的地址不一定是连续
2、结构体是用户单独定义的类型,和其它类型进行转换时需要有完全相同的字段(名字、个数和类型)
packagemainimport"fmt"typeAstruct{Numint}typeBstruct{Numint}funcmain(){varaAvarbBa=A(b)//可以转换,但是有要求,就是结构体的的字段要完全一样(包括:名字、个数和类型!)fmt.Println(a,b)}3、结构体进行type重新定义(相当于取别名),Golang认为是新的数据类型,但是相互间可以强转
packagemainimport"fmt"typeStudentstruct{NamestringAgeint}typeStuStudentfuncmain(){varstu1Studentvarstu2Stu//stu2=stu1//这样写是错误的,可以修改为stu2=Stu(stu1)stu2=Stu(stu1)fmt.Println(stu1,stu2)}4、struct的每个字段上,可以写上一个tag,该tag可以通过反射机制获取,常见的使用场景就是序列化和反序列化。
序列化的使用场景:
举例:
方法
对上面的总结
1、test方法和Person类型绑定
2、test方法只能通过Person类型的变量来调用,而不能直接调用,也不能使用其它类型变量来调用
3、func(thisPerson)test(){}...this表示哪个Person变量调用,这个this就是它的副本,这点和函数传参非常相似。
4、this这个名字,有程序员指定,不是固定,比如修改成person也是可以
方法快速入门
1、给Person结构体添加speak方法,输出xxx是一个好人
func(thisPerson)speak(){fmt.Println(this.Name,"是一个好人")}funcmain(){varpersonPersonperson.Name="bingle"person.speak()}2、给Person结构体添加jisuan方法,可以计算从1+..+1000的结果,说明方法体内可以函数一样,进行各种运算
func(thisPerson)jisuan(){res:=0fori:=1;i<=1000;i++{res+=i}fmt.Println(this.Name,"计算的结果是=",res)}3、给Person结构体jisuan2方法,该方法可以接收一个数n,计算从1+..+n的结果
func(thisPerson)jisuan2(nint){res:=0fori:=1;i<=n;i++{res+=i}fmt.Println(this.Name,"计算的结果是=",res)}5、方法的调用
funcmain(){varpersonPersonperson.Name="bingle"person.test()person.speak()person.jisuan()person.jisuan2(10)}方法的调用和传参机制原理方法的调用和传参机制和函数基本一样,不一样的地方是方法调用时,会将调用方法的变量,当做实参也传递给方法。下面,给Person结构体添加getSum(n1,n2)方法,计算两数的和,并返回结果:func(thisPerson)getSum(n1,n2int)int{returnn1+n2}funcmain(){varpersonPersonperson.Name="bingle"n1:=10n2:=20res:=person.getSum(n1,n2)fmt.Println("res=",res)}终端结果:
说明:
1、在通过一个变量去调用方法时,其调用机制和函数一样
2、不一样的地方时,变量调用方法时,该变量本身也会作为一个参数传递到方法(如果变量是值类型,则进行值拷贝,如果变量是引用类型,则进行地质拷贝)
func(receviertype)methodName(参数列表)(返回值列表){方法体return返回值}1、参数列表:表示方法输入
2、receviertype:表示这个方法和type这个类型进行绑定,或者说该方法作用于type类型
3、receivertype:type可以是结构体,也可以其它的自定义类型
4、receiver:就是type类型的一个变量(实例),比如:Person结构体的一个变量(实例)
5、返回值列表:表示返回的值,可以多个
6、方法主体:表示为了实现某一功能代码块
修改成如下:
3、Golang中的方法作用在指定的数据类型上的(即:和指定的数据类型绑定),因此自定义类型,都可以有方法,而不仅仅是struct,比如int,float32等都可以有方法(有点类似于C#中的扩展方法)
创建结构体变量时指定字段值
typeStustruct{NamestringAgeint}方式1:funcmain(){//方式1//在创建结构体变量时,就直接指定字段的值varstu1=Stu{"bingle1111",18}//stu1--->结构体数据空间stu2:=Stu{"bingle2222~",18}//在创建结构体变量时,把字段名和字段值写在一起,这种写法,就不依赖字段的定义顺序.varstu3=Stu{Name:"bingle3333",Age:18,}stu4:=Stu{Age:18,Name:"bingle4444",}fmt.Println(stu1,stu2,stu3,stu4)}